diff --git a/refilc/assets/images/btn_kreten_login.png b/refilc/assets/images/btn_kreten_login.png new file mode 100644 index 0000000..0af9566 Binary files /dev/null and b/refilc/assets/images/btn_kreten_login.png differ diff --git a/refilc/assets/images/showcase1.png b/refilc/assets/images/showcase1.png index e4b6535..e9465f4 100644 Binary files a/refilc/assets/images/showcase1.png and b/refilc/assets/images/showcase1.png differ diff --git a/refilc/assets/images/showcase2.png b/refilc/assets/images/showcase2.png index 0416828..e1bc254 100644 Binary files a/refilc/assets/images/showcase2.png and b/refilc/assets/images/showcase2.png differ diff --git a/refilc/assets/images/showcase3.png b/refilc/assets/images/showcase3.png index e90c17f..6d18a00 100644 Binary files a/refilc/assets/images/showcase3.png and b/refilc/assets/images/showcase3.png differ diff --git a/refilc/assets/images/showcase4.png b/refilc/assets/images/showcase4.png index 23e953f..873c834 100644 Binary files a/refilc/assets/images/showcase4.png and b/refilc/assets/images/showcase4.png differ diff --git a/refilc/lib/api/login.dart b/refilc/lib/api/login.dart index 0977ce8..6db0229 100644 --- a/refilc/lib/api/login.dart +++ b/refilc/lib/api/login.dart @@ -1,5 +1,6 @@ // ignore_for_file: avoid_print, use_build_context_synchronously +import 'package:flutter/foundation.dart'; import 'package:refilc/utils/jwt.dart'; import 'package:refilc_kreta_api/models/school.dart'; import 'package:refilc_kreta_api/providers/absence_provider.dart'; @@ -66,6 +67,7 @@ Future loginAPI({ address: '1117 Budapest, Gábor Dénes utca 4.', ), role: Role.parent, + refreshToken: '', ); if (onLogin != null) onLogin(user); @@ -107,7 +109,9 @@ Future loginAPI({ default: // normal login from here Provider.of(context, listen: false).userAgent = - Provider.of(context, listen: false).config.userAgent; + Provider.of(context, listen: false) + .config + .userAgent; Map headers = { "content-type": "application/x-www-form-urlencoded", @@ -127,6 +131,7 @@ Future loginAPI({ password: password, instituteCode: instituteCode, )); + if (res != null) { if (res.containsKey("error")) { if (res["error"] == "invalid_grant") { @@ -148,6 +153,7 @@ Future loginAPI({ name: student.name, student: student, role: JwtUtils.getRoleFromJWT(res["access_token"])!, + refreshToken: '', ); if (onLogin != null) onLogin(user); @@ -157,7 +163,8 @@ Future loginAPI({ .store .storeUser(user); Provider.of(context, listen: false).addUser(user); - Provider.of(context, listen: false).setUser(user.id); + Provider.of(context, listen: false) + .setUser(user.id); // Get user data try { @@ -167,7 +174,8 @@ Future loginAPI({ .fetch(week: Week.current()), Provider.of(context, listen: false).fetch(), Provider.of(context, listen: false).fetch(), - Provider.of(context, listen: false).fetchAll(), + Provider.of(context, listen: false) + .fetchAll(), Provider.of(context, listen: false) .fetchAllRecipients(), Provider.of(context, listen: false).fetch(), @@ -195,3 +203,112 @@ Future loginAPI({ return LoginState.failed; } + +// new login api +Future newLoginAPI({ + required String code, + required BuildContext context, + void Function(User)? onLogin, + void Function()? onSuccess, +}) async { + // actual login (token grant) logic + Provider.of(context, listen: false).userAgent = + Provider.of(context, listen: false).config.userAgent; + + Map headers = { + "content-type": "application/x-www-form-urlencoded; charset=UTF-8", + "accept": "*/*", + "user-agent": "eKretaStudent/264745 CFNetwork/1494.0.7 Darwin/23.4.0", + }; + + Map? res = await Provider.of(context, listen: false) + .postAPI(KretaAPI.login, headers: headers, body: { + "code": code, + "code_verifier": "DSpuqj_HhDX4wzQIbtn8lr8NLE5wEi1iVLMtMK0jY6c", + "redirect_uri": + "https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect", + "client_id": KretaAPI.clientId, + "grant_type": "authorization_code", + }); + + if (res != null) { + if (kDebugMode) { + print(res); + } + + if (res.containsKey("error")) { + if (res["error"] == "invalid_grant") { + print("ERROR: invalid_grant"); + return; + } + } else { + if (res.containsKey("access_token")) { + try { + Provider.of(context, listen: false).accessToken = + res["access_token"]; + Provider.of(context, listen: false).refreshToken = + res["refresh_token"]; + + String instituteCode = + JwtUtils.getInstituteFromJWT(res["access_token"])!; + String username = JwtUtils.getUsernameFromJWT(res["access_token"])!; + Role role = JwtUtils.getRoleFromJWT(res["access_token"])!; + + Map? studentJson = + await Provider.of(context, listen: false) + .getAPI(KretaAPI.student(instituteCode)); + Student student = Student.fromJson(studentJson!); + + var user = User( + username: username, + password: '', + instituteCode: instituteCode, + name: student.name, + student: student, + role: role, + refreshToken: res["refresh_token"], + ); + + if (onLogin != null) onLogin(user); + + // Store User in the database + await Provider.of(context, listen: false) + .store + .storeUser(user); + Provider.of(context, listen: false).addUser(user); + Provider.of(context, listen: false).setUser(user.id); + + // Get user data + try { + await Future.wait([ + Provider.of(context, listen: false).fetch(), + Provider.of(context, listen: false) + .fetch(week: Week.current()), + Provider.of(context, listen: false).fetch(), + Provider.of(context, listen: false).fetch(), + Provider.of(context, listen: false).fetchAll(), + Provider.of(context, listen: false) + .fetchAllRecipients(), + Provider.of(context, listen: false).fetch(), + Provider.of(context, listen: false).fetch(), + Provider.of(context, listen: false).fetch(), + ]); + } catch (error) { + print("WARNING: failed to fetch user data: $error"); + } + + if (onSuccess != null) onSuccess(); + + return LoginState.success; + } catch (error) { + print("ERROR: loginAPI: $error"); + // maybe check debug mode + // ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("ERROR: $error"))); + return LoginState.failed; + } + } + } + } + + return LoginState.failed; +} diff --git a/refilc/lib/app.dart b/refilc/lib/app.dart index f38352b..7a94f25 100644 --- a/refilc/lib/app.dart +++ b/refilc/lib/app.dart @@ -31,6 +31,7 @@ import 'package:provider/provider.dart'; import 'package:refilc_mobile_ui/common/system_chrome.dart' as mobile; import 'package:refilc_mobile_ui/screens/login/login_route.dart' as mobile; import 'package:refilc_mobile_ui/screens/login/login_screen.dart' as mobile; +// import 'package:refilc_mobile_ui/screens/login/kreten_login.dart' as mobileTest; import 'package:refilc_mobile_ui/screens/navigation/navigation_screen.dart' as mobile; import 'package:refilc_mobile_ui/screens/settings/settings_route.dart' @@ -80,7 +81,8 @@ class App extends StatelessWidget { CorePalette? corePalette; final status = StatusProvider(); - final kreta = KretaClient(user: user, settings: settings, status: status); + final kreta = KretaClient( + user: user, settings: settings, database: database, status: status); final timetable = TimetableProvider(user: user, database: database, kreta: kreta); final premium = PlusProvider(settings: settings); diff --git a/refilc/lib/database/init.dart b/refilc/lib/database/init.dart index 84d4444..cde1c14 100644 --- a/refilc/lib/database/init.dart +++ b/refilc/lib/database/init.dart @@ -66,6 +66,7 @@ const usersDB = DatabaseStruct("users", { "institute_code": String, "student": String, "role": int, "nickname": String, "picture": String, // premium only (it's now plus btw) "grade_streak": int, + "refresh_token": String, }); const userDataDB = DatabaseStruct("user_data", { "id": String, "grades": String, "timetable": String, "exams": String, @@ -138,7 +139,8 @@ Future initDB(DatabaseProvider database) async { "role": 0, "nickname": "", "picture": "", - "grade_streak": 0 + "grade_streak": 0, + "refresh_token": "", }, ); await migrateDB(db, struct: userDataDB, defaultValues: { diff --git a/refilc/lib/helpers/notification_helper.dart b/refilc/lib/helpers/notification_helper.dart index 1959866..f99ebfe 100644 --- a/refilc/lib/helpers/notification_helper.dart +++ b/refilc/lib/helpers/notification_helper.dart @@ -71,9 +71,11 @@ class NotificationsHelper { // Refresh kreta login for current user final status = StatusProvider(); KretaClient kretaClientForUser = KretaClient( - user: userProviderForUser, - settings: settingsProvider, - status: status); + user: userProviderForUser, + settings: settingsProvider, + database: database, + status: status, + ); await kretaClientForUser.refreshLogin(); // Process notifications for current user @@ -95,7 +97,7 @@ class NotificationsHelper { /* -ezt a kódot nagyon szépen megírta az AI, picit szerkesztgettem is rajta +ezt a kódot nagyon szépen megírta az AI, picit szerkesztgettem is rajta //pearoo what did you do - zypherift nem lesz tőle használhatatlan az app, de kikommenteltem, mert még a végén kima bántani fog Future liveNotification(UserProvider currentuserProvider, KretaClient currentKretaClient) async { diff --git a/refilc/lib/models/user.dart b/refilc/lib/models/user.dart index 83d15d3..1b76ec2 100644 --- a/refilc/lib/models/user.dart +++ b/refilc/lib/models/user.dart @@ -17,6 +17,8 @@ class User { String nickname; String picture; int gradeStreak; + // new login method + String refreshToken; String get displayName => nickname != '' ? nickname : name; bool get hasStreak => gradeStreak > 0; @@ -32,6 +34,7 @@ class User { this.nickname = "", this.picture = "", this.gradeStreak = 0, + required this.refreshToken, }) { if (id != null) { this.id = id; @@ -61,6 +64,7 @@ class User { nickname: map["nickname"] ?? "", picture: map["picture"] ?? "", gradeStreak: map["grade_streak"] ?? 0, + refreshToken: map["refresh_token"] ?? "", ); } @@ -75,6 +79,8 @@ class User { "role": role.index, "nickname": nickname, "picture": picture, + "grade_streak": gradeStreak, + "refresh_token": refreshToken, }; } diff --git a/refilc/lib/utils/jwt.dart b/refilc/lib/utils/jwt.dart index 2e0898b..d933693 100644 --- a/refilc/lib/utils/jwt.dart +++ b/refilc/lib/utils/jwt.dart @@ -39,4 +39,16 @@ class JwtUtils { } return null; } + + static String? getInstituteFromJWT(String jwt) { + var jwtData = decodeJwt(jwt); + + return jwtData?["kreta:institute_code"]; + } + + static String? getUsernameFromJWT(String jwt) { + var jwtData = decodeJwt(jwt); + + return jwtData?["kreta:user_name"]; + } } diff --git a/refilc/pubspec.yaml b/refilc/pubspec.yaml index 895c0a9..fa73090 100644 --- a/refilc/pubspec.yaml +++ b/refilc/pubspec.yaml @@ -3,7 +3,7 @@ description: "Egy nem hivatalos e-KRÉTA kliens, diákoktól diákoknak." homepage: https://refilc.hu publish_to: "none" -version: 5.0.3+269 +version: 5.0.4+272 environment: sdk: ">=3.3.2 <=3.4.3" diff --git a/refilc_kreta_api/lib/client/api.dart b/refilc_kreta_api/lib/client/api.dart index aea3b85..d689d64 100644 --- a/refilc_kreta_api/lib/client/api.dart +++ b/refilc_kreta_api/lib/client/api.dart @@ -5,7 +5,7 @@ class KretaAPI { static const login = BaseKreta.kretaIdp + KretaApiEndpoints.token; static const logout = BaseKreta.kretaIdp + KretaApiEndpoints.revoke; static const nonce = BaseKreta.kretaIdp + KretaApiEndpoints.nonce; - static const clientId = "kreta-ellenorzo-mobile-android"; + static const clientId = "kreta-ellenorzo-student-mobile-ios"; // ELLENORZO API static String notes(String iss) => diff --git a/refilc_kreta_api/lib/client/client.dart b/refilc_kreta_api/lib/client/client.dart index b075ec1..a198731 100644 --- a/refilc_kreta_api/lib/client/client.dart +++ b/refilc_kreta_api/lib/client/client.dart @@ -1,15 +1,16 @@ -// ignore_for_file: avoid_print +// ignore_for_file: avoid_print, use_build_context_synchronously import 'dart:convert'; import 'dart:io'; -import 'package:refilc/api/login.dart'; -import 'package:refilc/api/nonce.dart'; +// import 'package:refilc/api/login.dart'; +// import 'package:refilc/api/nonce.dart'; +import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/api/providers/status_provider.dart'; import 'package:refilc/models/settings.dart'; import 'package:refilc/models/user.dart'; -import 'package:refilc/utils/jwt.dart'; +// import 'package:refilc/utils/jwt.dart'; import 'package:refilc_kreta_api/client/api.dart'; import 'package:http/http.dart' as http; import 'package:http/io_client.dart' as http; @@ -24,6 +25,7 @@ class KretaClient { late final SettingsProvider _settings; late final UserProvider _user; + late final DatabaseProvider _database; late final StatusProvider _status; bool _loginRefreshing = false; @@ -32,9 +34,11 @@ class KretaClient { this.accessToken, required SettingsProvider settings, required UserProvider user, + required DatabaseProvider database, required StatusProvider status, }) : _settings = settings, _user = user, + _database = database, _status = status, userAgent = settings.config.userAgent { var ioclient = HttpClient(); @@ -212,7 +216,7 @@ class KretaClient { res = await request.send(); if (res.statusCode == 401) { - headerMap.remove("authorization"); + headerMap.remove("authorization"); await refreshLogin(); } else { break; @@ -232,65 +236,74 @@ class KretaClient { } } - Future refreshLogin() async { - if (_loginRefreshing) return; + Future refreshLogin() async { + if (_loginRefreshing) return null; _loginRefreshing = true; User? loginUser = _user.user; - if (loginUser == null) return; + if (loginUser == null) return null; Map headers = { - "content-type": "application/x-www-form-urlencoded", + "content-type": "application/x-www-form-urlencoded; charset=UTF-8", + "accept": "*/*", + "user-agent": "eKretaStudent/264745 CFNetwork/1494.0.7 Darwin/23.4.0", }; - String nonceStr = await getAPI(KretaAPI.nonce, json: false); - Nonce nonce = - getNonce(nonceStr, loginUser.username, loginUser.instituteCode); - headers.addAll(nonce.header()); - if (_settings.presentationMode) { print("DEBUG: refreshLogin: ${loginUser.id}"); } else { print("DEBUG: refreshLogin: ${loginUser.id} ${loginUser.name}"); } - Map? loginRes = await postAPI( - KretaAPI.login, - headers: headers, - body: User.loginBody( - username: loginUser.username, - password: loginUser.password, - instituteCode: loginUser.instituteCode, - ), - ); + refreshToken ??= loginUser.refreshToken; - if (loginRes != null) { - if (loginRes.containsKey("access_token")) { - accessToken = loginRes["access_token"]; - } - if (loginRes.containsKey("refresh_token")) { - refreshToken = loginRes["refresh_token"]; - } - - // Update role - loginUser.role = - JwtUtils.getRoleFromJWT(accessToken ?? "") ?? Role.student; - } + print("REFRESH TOKEN BELOW"); + print(refreshToken); if (refreshToken != null) { - Map? refreshRes = await postAPI(KretaAPI.login, + print("REFRESHING LOGIN"); + Map? res = await postAPI(KretaAPI.login, headers: headers, body: User.refreshBody( - refreshToken: refreshToken!, - instituteCode: loginUser.instituteCode)); - if (refreshRes != null) { - if (refreshRes.containsKey("id_token")) { - idToken = refreshRes["id_token"]; + refreshToken: loginUser.refreshToken, + instituteCode: loginUser.instituteCode, + )); + print("REFRESH RESPONSE BELOW"); + print(res); + if (res != null) { + if (res.containsKey("error")) { + // remove user if refresh token expired + if (res["error"] == "invalid_grant") { + // remove user from app + _user.removeUser(loginUser.id); + await _database.store.removeUser(loginUser.id); + + // return error + return "refresh_token_expired"; + } } + + if (res.containsKey("access_token")) { + accessToken = res["access_token"]; + } + if (res.containsKey("refresh_token")) { + refreshToken = res["refresh_token"]; + loginUser.refreshToken = res["refresh_token"]; + _database.store.storeUser(loginUser); + _user.refresh(); + } + if (res.containsKey("id_token")) { + idToken = res["id_token"]; + } + _loginRefreshing = false; + } else { + _loginRefreshing = false; } + } else { + _loginRefreshing = false; } - _loginRefreshing = false; + return null; } Future logout() async { diff --git a/refilc_mobile_ui/lib/screens/login/kreten_login.dart b/refilc_mobile_ui/lib/screens/login/kreten_login.dart index 2d0e28a..8396f8c 100644 --- a/refilc_mobile_ui/lib/screens/login/kreten_login.dart +++ b/refilc_mobile_ui/lib/screens/login/kreten_login.dart @@ -1,12 +1,13 @@ -import 'package:flutter/foundation.dart'; +// ignore_for_file: use_build_context_synchronously + import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:refilc_kreta_api/client/api.dart'; -import 'package:refilc_kreta_api/client/client.dart'; import 'package:webview_flutter/webview_flutter.dart'; class KretenLoginScreen extends StatefulWidget { - const KretenLoginScreen({super.key}); + const KretenLoginScreen({super.key, required this.onLogin}); + + // final String selectedSchool; + final void Function(String code) onLogin; @override State createState() => _KretenLoginScreenState(); @@ -29,112 +30,29 @@ class _KretenLoginScreenState extends State { currentUrl = url; }); + // final String instituteCode = widget.selectedSchool; + if (!url.startsWith( + 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) { + return; + } + List requiredThings = url .replaceAll( 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=', '') .replaceAll( - '&scope=openid email offline_access kreta-ellenorzo-webapi.public kreta-eugyintezes-webapi.public kreta-fileservice-webapi.public kreta-mobile-global-webapi.public kreta-dkt-webapi.public kreta-ier-webapi.public&state=refilc_student_mobile&session_state=', + '&scope=openid%20email%20offline_access%20kreta-ellenorzo-webapi.public%20kreta-eugyintezes-webapi.public%20kreta-fileservice-webapi.public%20kreta-mobile-global-webapi.public%20kreta-dkt-webapi.public%20kreta-ier-webapi.public&state=refilc_student_mobile&session_state=', ':') .split(':'); String code = requiredThings[0]; // String sessionState = requiredThings[1]; - debugPrint('url: $url'); - - // actual login (token grant) logic - Map headers = { - "content-type": "application/x-www-form-urlencoded", - "accept": "*/*", - "user-agent": - "eKretaStudent/264745 CFNetwork/1494.0.7 Darwin/23.4.0", - "code_verifier": "THDUSddKOOndwCkqBtVHvRjh2LK0V2kMyLP2QirqVWQ", - }; - - Map? res = await Provider.of(context, listen: false) - .postAPI(KretaAPI.login, headers: headers, body: { - "code": code, - "redirect_uri": - "https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect", - "client_id": "kreta-ellenorzo-student-mobile-ios", - "grant_type": "authorization_code", - }); - if (res != null) { - if (kDebugMode) { - print(res); - } - - // if (res.containsKey("error")) { - // if (res["error"] == "invalid_grant") { - // print("ERROR: invalid_grant"); - // return; - // } - // } else { - // if (res.containsKey("access_token")) { - // try { - // Provider.of(context, listen: false).accessToken = - // res["access_token"]; - // Map? studentJson = - // await Provider.of(context, listen: false) - // .getAPI(KretaAPI.student(instituteCode)); - // Student student = Student.fromJson(studentJson!); - // var user = User( - // username: username, - // password: password, - // instituteCode: instituteCode, - // name: student.name, - // student: student, - // role: JwtUtils.getRoleFromJWT(res["access_token"])!, - // ); - - // if (onLogin != null) onLogin(user); - - // // Store User in the database - // await Provider.of(context, listen: false) - // .store - // .storeUser(user); - // Provider.of(context, listen: false) - // .addUser(user); - // Provider.of(context, listen: false) - // .setUser(user.id); - - // // Get user data - // try { - // await Future.wait([ - // Provider.of(context, listen: false) - // .fetch(), - // Provider.of(context, listen: false) - // .fetch(week: Week.current()), - // Provider.of(context, listen: false).fetch(), - // Provider.of(context, listen: false) - // .fetch(), - // Provider.of(context, listen: false) - // .fetchAll(), - // Provider.of(context, listen: false) - // .fetchAllRecipients(), - // Provider.of(context, listen: false).fetch(), - // Provider.of(context, listen: false) - // .fetch(), - // Provider.of(context, listen: false) - // .fetch(), - // ]); - // } catch (error) { - // print("WARNING: failed to fetch user data: $error"); - // } - - // if (onSuccess != null) onSuccess(); - - // return LoginState.success; - // } catch (error) { - // print("ERROR: loginAPI: $error"); - // // maybe check debug mode - // // ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("ERROR: $error"))); - // return LoginState.failed; - // } - // } - // } - } + widget.onLogin(code); + // Future.delayed(const Duration(milliseconds: 500), () { + // Navigator.of(context).pop(); + // }); + // Navigator.of(context).pop(); }, onProgress: (progress) { setState(() { @@ -149,7 +67,7 @@ class _KretenLoginScreenState extends State { )) ..loadRequest( Uri.parse( - 'https://idp.e-kreta.hu/connect/authorize?prompt=login&nonce=refilc&response_type=code&code_challenge_method=S256&scope=openid%20email%20offline_access%20kreta-ellenorzo-webapi.public%20kreta-eugyintezes-webapi.public%20kreta-fileservice-webapi.public%20kreta-mobile-global-webapi.public%20kreta-dkt-webapi.public%20kreta-ier-webapi.public&code_challenge=Oj_aVMRJHYsv00mrtGJY72NJa7HY54lVnU2Cb4CWbWw&redirect_uri=https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect&client_id=kreta-ellenorzo-student-mobile-ios&state=refilc_student_mobile'), + 'https://idp.e-kreta.hu/connect/authorize?prompt=login&nonce=wylCrqT4oN6PPgQn2yQB0euKei9nJeZ6_ffJ-VpSKZU&response_type=code&code_challenge_method=S256&scope=openid%20email%20offline_access%20kreta-ellenorzo-webapi.public%20kreta-eugyintezes-webapi.public%20kreta-fileservice-webapi.public%20kreta-mobile-global-webapi.public%20kreta-dkt-webapi.public%20kreta-ier-webapi.public&code_challenge=HByZRRnPGb-Ko_wTI7ibIba1HQ6lor0ws4bcgReuYSQ&redirect_uri=https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect&client_id=kreta-ellenorzo-student-mobile-ios&state=refilc_student_mobile'), // &institute_code=${widget.selectedSchool} ); } diff --git a/refilc_mobile_ui/lib/screens/login/login_screen.dart b/refilc_mobile_ui/lib/screens/login/login_screen.dart index 572f3dc..e574253 100644 --- a/refilc_mobile_ui/lib/screens/login/login_screen.dart +++ b/refilc_mobile_ui/lib/screens/login/login_screen.dart @@ -5,13 +5,13 @@ import 'package:refilc/api/login.dart'; import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc_mobile_ui/common/custom_snack_bar.dart'; import 'package:refilc_mobile_ui/common/system_chrome.dart'; -import 'package:refilc_mobile_ui/screens/login/login_button.dart'; -import 'package:refilc_mobile_ui/screens/login/login_input.dart'; import 'package:refilc_mobile_ui/screens/login/school_input/school_input.dart'; import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'login_screen.i18n.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:refilc_mobile_ui/screens/login/kreten_login.dart'; //new library for new web login class LoginScreen extends StatefulWidget { const LoginScreen({super.key, this.back = false}); @@ -27,7 +27,8 @@ class LoginScreenState extends State { final passwordController = TextEditingController(); final schoolController = SchoolInputController(); final _scrollController = ScrollController(); - + // new controllers + final codeController = TextEditingController(); LoginState _loginState = LoginState.normal; bool showBack = false; @@ -75,223 +76,230 @@ class LoginScreenState extends State { @override Widget build(BuildContext context) { + precacheImage(const AssetImage('assets/images/showcase1.png'), context); + precacheImage(const AssetImage('assets/images/showcase2.png'), context); + precacheImage(const AssetImage('assets/images/showcase3.png'), context); + precacheImage(const AssetImage('assets/images/showcase4.png'), context); + // bool selected = false; + return Scaffold( body: Container( - decoration: BoxDecoration(color: AppColors.of(context).loginBackground), + decoration: const BoxDecoration(color: Color(0xFFDAE4F7)), child: SingleChildScrollView( physics: const ClampingScrollPhysics(), controller: _scrollController, child: Container( - decoration: - BoxDecoration(color: AppColors.of(context).loginBackground), + decoration: const BoxDecoration(color: Color(0xFFDAE4F7)), width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: SafeArea( child: Column( - mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - alignment: Alignment.topLeft, - padding: const EdgeInsets.only(left: 16.0, top: 12.0), - child: ClipOval( - child: Material( - type: MaterialType.transparency, - child: showBack - ? BackButton( - color: AppColors.of(context).loginPrimary) - : const SizedBox(height: 48.0), - ), - ), - ), - // app icon Padding( - padding: EdgeInsets.zero, - child: Image.asset( - 'assets/icons/ic_rounded.png', - width: 50.0, - ), - ), - - // texts - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10.0, - vertical: 12.0, - ), - child: Text( - 'reFilc', - style: TextStyle( - color: AppColors.of(context).loginPrimary, - fontSize: 28.0, - fontWeight: FontWeight.bold, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10.0, - ), - child: Text( - 'login_w_kreten'.i18n, - textAlign: TextAlign.center, - style: TextStyle( - color: AppColors.of(context).loginPrimary, - fontSize: 18.0, - fontWeight: FontWeight.w500, - height: 1.2, - ), - ), - ), - - const Spacer( - flex: 2, - ), - - // inputs - Padding( - padding: const EdgeInsets.only( - left: 22.0, - right: 22.0, - top: 0.0, - ), - child: AutofillGroup( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + padding: const EdgeInsets.only(left: 24, top: 20), + child: Row( children: [ - // username - Padding( - padding: const EdgeInsets.only(bottom: 6.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - "username".i18n, - maxLines: 1, - style: TextStyle( - color: AppColors.of(context).loginPrimary, - fontWeight: FontWeight.w500, - fontSize: 12.0, - ), - ), - ), - Expanded( - child: Text( - "usernameHint".i18n, - maxLines: 1, - textAlign: TextAlign.right, - style: TextStyle( - color: - AppColors.of(context).loginSecondary, - fontWeight: FontWeight.w500, - fontSize: 12.0, - ), - ), - ), - ], - ), + Image.asset( + 'assets/icons/ic_rounded.png', + width: 30.0, ), - Padding( - padding: const EdgeInsets.only(bottom: 12.0), - child: LoginInput( - style: LoginInputStyle.username, - controller: usernameController, - ), + const SizedBox(width: 8), + const Text( + 'reFilc', + style: TextStyle( + color: Color(0xFF050B15), + fontSize: 18.0, + fontWeight: FontWeight.bold, + fontFamily: 'Montserrat'), ), - - // password - Padding( - padding: const EdgeInsets.only(bottom: 6.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - "password".i18n, - maxLines: 1, - style: TextStyle( - color: AppColors.of(context).loginPrimary, - fontWeight: FontWeight.w500, - fontSize: 12.0, - ), - ), - ), - Expanded( - child: Text( - "passwordHint".i18n, - maxLines: 1, - textAlign: TextAlign.right, - style: TextStyle( - color: - AppColors.of(context).loginSecondary, - fontWeight: FontWeight.w500, - fontSize: 12.0, - ), - ), - ), - ], - ), + Material( + type: MaterialType.transparency, + child: showBack + ? BackButton(color: AppColors.of(context).text) + : const SizedBox(height: 48.0), ), - Padding( - padding: const EdgeInsets.only(bottom: 12.0), - child: LoginInput( - style: LoginInputStyle.password, - controller: passwordController, - ), - ), - - // school - Padding( - padding: const EdgeInsets.only(bottom: 6.0), - child: Text( - "school".i18n, - maxLines: 1, - style: TextStyle( - color: AppColors.of(context).loginPrimary, - fontWeight: FontWeight.w500, - fontSize: 12.0, - ), - ), - ), - SchoolInput( - scroll: _scrollController, - controller: schoolController, + ], + )), + Stack( + alignment: Alignment.bottomCenter, + children: [ + Column( + //login buttons and ui starts here + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + const SizedBox(height: 21), + CarouselSlider( + options: CarouselOptions( + height: MediaQuery.of(context).size.height, + viewportFraction: 1, + autoPlay: true, + autoPlayInterval: const Duration(seconds: 6), + pauseAutoPlayOnTouch: true), + items: [1, 2, 3, 4].map((i) { + return Builder( + builder: (BuildContext context) { + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: + const EdgeInsets.only(left: 24), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + Text( + "welcome_title_$i".i18n, + style: const TextStyle( + color: Color(0xFF050B15), + fontSize: 19, + fontFamily: 'Montserrat', + fontWeight: FontWeight.w700, + height: 1.3), + ), + const SizedBox( + height: 14.375), //meth + Padding( + padding: const EdgeInsets.only( + right: 20), + child: Text( + "welcome_text_$i".i18n, + style: const TextStyle( + color: Color(0xFF050B15), + fontFamily: 'FigTree', + fontWeight: + FontWeight.w500, + fontSize: 17, + height: 1.3), + ), + ), + ], + )), + const SizedBox(height: 15.625), + Padding( + padding: const EdgeInsets.only( + left: 16, right: 16), + child: Image.asset( + 'assets/images/showcase$i.png')) + ], + ); + }, + ); + }).toList(), ), ], ), - ), - ), - - // login button - Padding( - padding: const EdgeInsets.only( - top: 35.0, - left: 22.0, - right: 22.0, - ), - child: Visibility( - visible: _loginState != LoginState.inProgress, - replacement: const Padding( - padding: EdgeInsets.symmetric(vertical: 6.0), - child: CircularProgressIndicator( - valueColor: - AlwaysStoppedAnimation(Colors.white), + Container( + height: 300, + width: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Color(0x00DAE4F7), Color(0xFFDAE4F7)], + stops: [0, 0.12], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Padding( + padding: EdgeInsets.only( + top: 50, + bottom: MediaQuery.of(context).viewInsets.bottom), + child: Column( + children: [ + SizedBox( + height: 48, + width: double.infinity, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16), + child: GestureDetector( + onTap: () { + final NavigatorState navigator = + Navigator.of(context); + navigator + .push( + MaterialPageRoute( + builder: (context) => + KretenLoginScreen( + onLogin: (String code) { + codeController.text = code; + navigator.pop(); + }, + ), + ), + ) + .then((value) { + if (codeController.text != "") { + _NewLoginAPI(context: context); + } + }); + }, + child: Container( + width: + MediaQuery.of(context).size.width * + 0.75, + height: 50.0, + decoration: BoxDecoration( + // image: const DecorationImage( + // image: + // AssetImage('assets/images/btn_kreten_login.png'), + // fit: BoxFit.scaleDown, + // ), + borderRadius: + BorderRadius.circular(12.0), + color: const Color(0xFF0097C1), + ), + padding: const EdgeInsets.only( + top: 5.0, + left: 5.0, + right: 5.0, + bottom: 5.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/images/btn_kreten_login.png', + ), + const SizedBox( + width: 10.0, + ), + Container( + width: 1.0, + height: 30.0, + color: Colors.white, + ), + const SizedBox( + width: 10.0, + ), + Text( + 'login_w_kreta_acc'.i18n, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 15.0, + ), + ), + ], + )), + ), + ), + ), + const SizedBox(height: 8), + ], + ), ), ), - child: LoginButton( - child: Text("login".i18n, - maxLines: 1, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20.0, - )), - onPressed: () => _loginAPI(context: context), - ), - ), + ], ), - // error messages if (_loginState == LoginState.missingFields || _loginState == LoginState.invalidGrant || _loginState == LoginState.failed) @@ -312,8 +320,6 @@ class LoginScreenState extends State { textAlign: TextAlign.center, ), ), - const SizedBox(height: 22.0), - // privacy policy GestureDetector( onTap: () => PrivacyView.show(context), @@ -326,10 +332,6 @@ class LoginScreenState extends State { ), ), ), - - const Spacer( - flex: 1, - ), ], ), ), @@ -339,24 +341,71 @@ class LoginScreenState extends State { ); } - void _loginAPI({required BuildContext context}) { - String username = usernameController.text; - String password = passwordController.text; + // void _loginAPI({required BuildContext context}) { + // String username = usernameController.text; + // String password = passwordController.text; - tempUsername = username; + // tempUsername = username; - if (username == "" || - password == "" || - schoolController.selectedSchool == null) { - return setState(() => _loginState = LoginState.missingFields); + // if (username == "" || + // password == "" || + // schoolController.selectedSchool == null) { + // return setState(() => _loginState = LoginState.missingFields); + // } + + // // ignore: no_leading_underscores_for_local_identifiers + // void _callAPI() { + // loginAPI( + // username: username, + // password: password, + // instituteCode: schoolController.selectedSchool!.instituteCode, + // context: context, + // onLogin: (user) { + // ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + // context: context, + // brightness: Brightness.light, + // content: Text("welcome".i18n.fill([user.name]), + // overflow: TextOverflow.ellipsis), + // )); + // }, + // onSuccess: () { + // ScaffoldMessenger.of(context).hideCurrentSnackBar(); + // setSystemChrome(context); + // Navigator.of(context).pushReplacementNamed("login_to_navigation"); + // }).then( + // (res) => setState(() { + // // if (res == LoginState.invalidGrant && + // // tempUsername.replaceAll(username, '').length <= 3) { + // // tempUsername = username + ' '; + // // Timer( + // // const Duration(milliseconds: 500), + // // () => _loginAPI(context: context), + // // ); + // // // _loginAPI(context: context); + // // } else { + // _loginState = res; + // // } + // }), + // ); + // } + + // setState(() => _loginState = LoginState.inProgress); + // _callAPI(); + // } + + // new login api + // ignore: non_constant_identifier_names + void _NewLoginAPI({required BuildContext context}) { + String code = codeController.text; + + if (code == "") { + return setState(() => _loginState = LoginState.failed); } // ignore: no_leading_underscores_for_local_identifiers void _callAPI() { - loginAPI( - username: username, - password: password, - instituteCode: schoolController.selectedSchool!.instituteCode, + newLoginAPI( + code: code, context: context, onLogin: (user) { ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( diff --git a/refilc_mobile_ui/lib/screens/login/login_screen.i18n.dart b/refilc_mobile_ui/lib/screens/login/login_screen.i18n.dart index b9ef3b2..f8a07f8 100644 --- a/refilc_mobile_ui/lib/screens/login/login_screen.i18n.dart +++ b/refilc_mobile_ui/lib/screens/login/login_screen.i18n.dart @@ -33,6 +33,7 @@ extension Localization on String { "welcome_title_4": "Take as many notes as you want.", "welcome_text_4": "You can also organise your notes by lesson in the built-in notebook, so you can find everything in one app.", + "login_w_kreta_acc": "Log in with\ne-KRÉTA account", }, "hu_hu": { "username": "Felhasználónév", @@ -64,6 +65,7 @@ extension Localization on String { "welcome_title_4": "Füzetelj annyit, amennyit csak szeretnél.", "welcome_text_4": "A beépített jegyzetfüzetbe órák szerint is rendezheted a jegyzeteidet, így mindent megtalálsz egy appban.", + "login_w_kreta_acc": "Belépés e-KRÉTA\nfiókkal", }, "de_de": { "username": "Benutzername", @@ -95,6 +97,7 @@ extension Localization on String { "welcome_title_4": "Take as many notes as you want.", "welcome_text_4": "You can also organise your notes by lesson in the built-in notebook, so you can find everything in one app.", + "login_w_kreta_acc": "Mit e-KRÉTA-Konto\nanmelden", }, }; diff --git a/refilc_mobile_ui/lib/screens/login/login_screen_new.dart b/refilc_mobile_ui/lib/screens/login/login_screen_new.dart deleted file mode 100644 index d2b3fd0..0000000 --- a/refilc_mobile_ui/lib/screens/login/login_screen_new.dart +++ /dev/null @@ -1,618 +0,0 @@ -// // import 'dart:async'; - -// import 'package:refilc/api/client.dart'; -// import 'package:refilc/api/login.dart'; -// import 'package:refilc/theme/colors/colors.dart'; -// import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart'; -// import 'package:refilc_mobile_ui/common/custom_snack_bar.dart'; -// import 'package:refilc_mobile_ui/common/system_chrome.dart'; -// import 'package:refilc_mobile_ui/common/widgets/absence/absence_display.dart'; -// import 'package:refilc_mobile_ui/screens/login/login_button.dart'; -// import 'package:refilc_mobile_ui/screens/login/login_input.dart'; -// import 'package:refilc_mobile_ui/screens/login/school_input/school_input.dart'; -// import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter/services.dart'; -// import 'login_screen.i18n.dart'; -// import 'package:carousel_slider/carousel_slider.dart'; -// import 'package:flutter_svg/flutter_svg.dart'; - -// class LoginScreen extends StatefulWidget { -// const LoginScreen({super.key, this.back = false}); - -// final bool back; - -// @override -// LoginScreenState createState() => LoginScreenState(); -// } - -// class LoginScreenState extends State { -// final usernameController = TextEditingController(); -// final passwordController = TextEditingController(); -// final schoolController = SchoolInputController(); -// final _scrollController = ScrollController(); - -// LoginState _loginState = LoginState.normal; -// bool showBack = false; - -// // Scaffold Gradient background -// // final LinearGradient _backgroundGradient = const LinearGradient( -// // colors: [ -// // Color.fromARGB(255, 61, 122, 244), -// // Color.fromARGB(255, 23, 77, 185), -// // Color.fromARGB(255, 7, 42, 112), -// // ], -// // begin: Alignment(-0.8, -1.0), -// // end: Alignment(0.8, 1.0), -// // stops: [-1.0, 0.0, 1.0], -// // ); - -// late String tempUsername = ''; - -// @override -// void initState() { -// super.initState(); -// showBack = widget.back; - -// SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( -// statusBarColor: Colors.transparent, -// statusBarIconBrightness: Brightness.light, -// systemNavigationBarColor: Colors.white, -// systemNavigationBarIconBrightness: Brightness.dark, -// )); - -// FilcAPI.getSchools().then((schools) { -// if (schools != null) { -// schoolController.update(() { -// schoolController.schools = schools; -// }); -// } else { -// ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( -// content: Text("schools_error".i18n, -// style: const TextStyle(color: Colors.white)), -// backgroundColor: AppColors.of(context).red, -// context: context, -// )); -// } -// }); -// } - -// @override -// Widget build(BuildContext context) { -// precacheImage(const AssetImage('assets/images/showcase1.png'), context); -// precacheImage(const AssetImage('assets/images/showcase2.png'), context); -// precacheImage(const AssetImage('assets/images/showcase3.png'), context); -// precacheImage(const AssetImage('assets/images/showcase4.png'), context); -// bool selected = false; - -// return Scaffold( -// body: Container( -// decoration: const BoxDecoration(color: Color(0xFFDAE4F7)), -// child: SingleChildScrollView( -// physics: const ClampingScrollPhysics(), -// controller: _scrollController, -// child: Container( -// decoration: const BoxDecoration(color: Color(0xFFDAE4F7)), -// width: MediaQuery.of(context).size.width, -// height: MediaQuery.of(context).size.height, -// child: SafeArea( -// child: Column( -// children: [ -// // app icon -// Padding( -// padding: const EdgeInsets.only(left: 24, top: 20), -// child: Row( -// children: [ -// Image.asset( -// 'assets/icons/ic_rounded.png', -// width: 30.0, -// ), -// const SizedBox(width: 8), -// const Text( -// 'reFilc', -// style: TextStyle( -// color: Color(0xFF050B15), -// fontSize: 18.0, -// fontWeight: FontWeight.bold, -// fontFamily: 'Montserrat'), -// ), -// Material( -// type: MaterialType.transparency, -// child: showBack -// ? BackButton(color: AppColors.of(context).text) -// : const SizedBox(height: 48.0), -// ), -// ], -// )), -// Stack( -// alignment: Alignment.bottomCenter, -// children: [ -// Column( -// //login buttons and ui starts here -// mainAxisAlignment: MainAxisAlignment.end, -// crossAxisAlignment: CrossAxisAlignment.end, -// children: [ -// const SizedBox(height: 21), -// CarouselSlider( -// options: CarouselOptions( -// height: MediaQuery.of(context).size.height, -// viewportFraction: 1, -// autoPlay: true, -// autoPlayInterval: const Duration(seconds: 6), -// pauseAutoPlayOnTouch: true), -// items: [1, 2, 3, 4].map((i) { -// return Builder( -// builder: (BuildContext context) { -// return Column( -// crossAxisAlignment: -// CrossAxisAlignment.start, -// mainAxisAlignment: MainAxisAlignment.start, -// children: [ -// Padding( -// padding: -// const EdgeInsets.only(left: 24), -// child: Column( -// crossAxisAlignment: -// CrossAxisAlignment.start, -// mainAxisAlignment: -// MainAxisAlignment.start, -// children: [ -// Text( -// "welcome_title_$i".i18n, -// style: const TextStyle( -// color: Color(0xFF050B15), -// fontSize: 19, -// fontFamily: 'Montserrat', -// fontWeight: FontWeight.w700, -// height: 1.3), -// ), -// const SizedBox( -// height: 14.375), //meth -// Padding( -// padding: const EdgeInsets.only( -// right: 20), -// child: Text( -// "welcome_text_$i".i18n, -// style: const TextStyle( -// color: Color(0xFF050B15), -// fontFamily: 'FigTree', -// fontWeight: -// FontWeight.w500, -// fontSize: 17, -// height: 1.3), -// ), -// ), -// ], -// )), -// const SizedBox(height: 15.625), -// Padding( -// padding: const EdgeInsets.only( -// left: 16, right: 16), -// child: Image.asset( -// 'assets/images/showcase$i.png')) -// ], -// ); -// }, -// ); -// }).toList(), -// ), -// ], -// ), -// Container( -// height: 300, -// width: double.infinity, -// decoration: const BoxDecoration( -// gradient: LinearGradient( -// colors: [Color(0x00DAE4F7), Color(0xFFDAE4F7)], -// stops: [0, 0.12], -// begin: Alignment.topCenter, -// end: Alignment.bottomCenter, -// ), -// ), -// child: Padding( -// padding: EdgeInsets.only(top: 50, bottom: MediaQuery.of(context).viewInsets.bottom), -// child: Column( -// children: [ -// SizedBox( -// height: 48, -// width: double.infinity, -// child: Padding( -// padding: const EdgeInsets.symmetric( -// horizontal: 16), -// child: FilledButton( -// style: ButtonStyle( -// shape: WidgetStateProperty.all< -// RoundedRectangleBorder>( -// const RoundedRectangleBorder( -// borderRadius: BorderRadius.all( -// Radius.circular(12)), -// ))), -// onPressed: () { -// showModalBottomSheet( -// backgroundColor: Colors.transparent, -// context: context, -// builder: (BuildContext context) { -// return Container( -// height: MediaQuery.of(context) -// .size -// .height * -// 0.5 + MediaQuery.of(context).viewInsets.bottom, -// decoration: const BoxDecoration( -// color: Color(0xFFDAE4F7), -// borderRadius: BorderRadius.only( -// topRight: -// Radius.circular(24.0), -// topLeft: -// Radius.circular(24.0), -// ), -// ), -// child: Column( -// crossAxisAlignment: -// CrossAxisAlignment.center, -// mainAxisAlignment: -// MainAxisAlignment.start, -// children: [ -// Padding( -// padding: -// const EdgeInsets.only( -// top: 18), -// child: Container( -// decoration: -// const BoxDecoration( -// color: -// Color(0xFFB9C8E5), -// borderRadius: -// BorderRadius.only( -// topRight: -// Radius.circular( -// 2.0), -// topLeft: -// Radius.circular( -// 2.0), -// ), -// ), -// width: 40, -// height: 4, -// ), -// ), -// Container( -// width: double.infinity, -// child: AutofillGroup( -// child: Column( -// crossAxisAlignment: -// CrossAxisAlignment -// .end, -// children: [ -// // username -// Padding( -// padding: -// const EdgeInsets -// .only( -// bottom: -// 6.0), -// child: Row( -// mainAxisAlignment: -// MainAxisAlignment -// .spaceBetween, -// children: [ -// Expanded( -// child: Text( -// "username" -// .i18n, -// maxLines: -// 1, -// style: -// TextStyle( -// color: AppColors.of(context) -// .loginPrimary, -// fontWeight: -// FontWeight.w500, -// fontSize: -// 12.0, -// ), -// ), -// ), -// Expanded( -// child: Text( -// "usernameHint" -// .i18n, -// maxLines: -// 1, -// textAlign: -// TextAlign -// .right, -// style: -// TextStyle( -// color: AppColors.of(context) -// .loginSecondary, -// fontWeight: -// FontWeight.w500, -// fontSize: -// 12.0, -// ), -// ), -// ), -// ], -// ), -// ), -// Padding( -// padding: -// const EdgeInsets -// .only( -// bottom: -// 12.0), -// child: LoginInput( -// style: -// LoginInputStyle -// .username, -// controller: -// usernameController, -// ), -// ), - -// // password -// Padding( -// padding: -// const EdgeInsets -// .only( -// bottom: -// 6.0), -// child: Row( -// mainAxisAlignment: -// MainAxisAlignment -// .spaceBetween, -// children: [ -// Expanded( -// child: Text( -// "password" -// .i18n, -// maxLines: -// 1, -// style: -// TextStyle( -// color: AppColors.of(context) -// .loginPrimary, -// fontWeight: -// FontWeight.w500, -// fontSize: -// 12.0, -// ), -// ), -// ), -// Expanded( -// child: Text( -// "passwordHint" -// .i18n, -// maxLines: -// 1, -// textAlign: -// TextAlign -// .right, -// style: -// TextStyle( -// color: AppColors.of(context) -// .loginSecondary, -// fontWeight: -// FontWeight.w500, -// fontSize: -// 12.0, -// ), -// ), -// ), -// ], -// ), -// ), -// Padding( -// padding: -// const EdgeInsets -// .only( -// bottom: -// 12.0), -// child: LoginInput( -// style: -// LoginInputStyle -// .password, -// controller: -// passwordController, -// ), -// ), - -// // school -// Padding( -// padding: -// const EdgeInsets -// .only( -// bottom: -// 6.0), -// child: Text( -// "school".i18n, -// maxLines: 1, -// style: -// TextStyle( -// color: AppColors.of( -// context) -// .loginPrimary, -// fontWeight: -// FontWeight -// .w500, -// fontSize: -// 12.0, -// ), -// ), -// ), -// SchoolInput( -// scroll: -// _scrollController, -// controller: -// schoolController, -// ), -// ], -// ), -// ), -// ), -// const Padding( -// padding: EdgeInsets.only( -// left: 22.0, -// right: 22.0, -// top: 0.0, -// ), -// ), -// Padding( -// padding: -// const EdgeInsets.only( -// top: 35.0, -// left: 22.0, -// right: 22.0, -// ), -// child: Visibility( -// visible: _loginState != -// LoginState -// .inProgress, -// replacement: -// const Padding( -// padding: EdgeInsets -// .symmetric( -// vertical: -// 6.0), -// child: -// CircularProgressIndicator( -// valueColor: -// AlwaysStoppedAnimation< -// Color>( -// Colors -// .white), -// ), -// ), -// child: LoginButton( -// child: Text( -// "login".i18n, -// maxLines: 1, -// style: -// const TextStyle( -// fontWeight: -// FontWeight -// .bold, -// fontSize: 20.0, -// )), -// onPressed: () => -// _loginAPI( -// context: -// context), -// ), -// ), -// ), -// ]), -// ); -// }, -// ); -// }, -// child: Text( -// "login".i18n, -// style: const TextStyle( -// fontFamily: 'Montserrat', -// fontSize: 20, -// fontWeight: FontWeight.w700), -// )), -// ), -// ), -// const SizedBox(height: 8), -// ], -// ), -// ), -// ), -// ], -// ), - -// if (_loginState == LoginState.missingFields || -// _loginState == LoginState.invalidGrant || -// _loginState == LoginState.failed) -// Padding( -// padding: const EdgeInsets.only( -// top: 8.0, left: 12.0, right: 12.0), -// child: Text( -// [ -// "missing_fields", -// "invalid_grant", -// "error" -// ][_loginState.index] -// .i18n, -// style: const TextStyle( -// color: Colors.red, -// fontWeight: FontWeight.w500, -// ), -// textAlign: TextAlign.center, -// ), -// ), -// // privacy policy -// GestureDetector( -// onTap: () => PrivacyView.show(context), -// child: Text( -// 'privacy'.i18n, -// style: TextStyle( -// color: AppColors.of(context).loginSecondary, -// fontWeight: FontWeight.w500, -// fontSize: 14.0, -// ), -// ), -// ), -// ], -// ), -// ), -// ), -// ), -// ), -// ); -// } - -// void _loginAPI({required BuildContext context}) { -// String username = usernameController.text; -// String password = passwordController.text; - -// tempUsername = username; - -// if (username == "" || -// password == "" || -// schoolController.selectedSchool == null) { -// return setState(() => _loginState = LoginState.missingFields); -// } - -// // ignore: no_leading_underscores_for_local_identifiers -// void _callAPI() { -// loginAPI( -// username: username, -// password: password, -// instituteCode: schoolController.selectedSchool!.instituteCode, -// context: context, -// onLogin: (user) { -// ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( -// context: context, -// brightness: Brightness.light, -// content: Text("welcome".i18n.fill([user.name]), -// overflow: TextOverflow.ellipsis), -// )); -// }, -// onSuccess: () { -// ScaffoldMessenger.of(context).hideCurrentSnackBar(); -// setSystemChrome(context); -// Navigator.of(context).pushReplacementNamed("login_to_navigation"); -// }).then( -// (res) => setState(() { -// // if (res == LoginState.invalidGrant && -// // tempUsername.replaceAll(username, '').length <= 3) { -// // tempUsername = username + ' '; -// // Timer( -// // const Duration(milliseconds: 500), -// // () => _loginAPI(context: context), -// // ); -// // // _loginAPI(context: context); -// // } else { -// _loginState = res; -// // } -// }), -// ); -// } - -// setState(() => _loginState = LoginState.inProgress); -// _callAPI(); -// } -// } diff --git a/refilc_mobile_ui/lib/screens/login/login_screen_old.dart b/refilc_mobile_ui/lib/screens/login/login_screen_old.dart new file mode 100644 index 0000000..aaeb615 --- /dev/null +++ b/refilc_mobile_ui/lib/screens/login/login_screen_old.dart @@ -0,0 +1,513 @@ +// import 'dart:async'; + +import 'package:refilc/api/client.dart'; +import 'package:refilc/api/login.dart'; +import 'package:refilc/theme/colors/colors.dart'; +import 'package:refilc_mobile_ui/common/custom_snack_bar.dart'; +import 'package:refilc_mobile_ui/common/system_chrome.dart'; +// import 'package:refilc_mobile_ui/screens/login/kreten_login.dart'; +import 'package:refilc_mobile_ui/screens/login/login_button.dart'; +import 'package:refilc_mobile_ui/screens/login/login_input.dart'; +import 'package:refilc_mobile_ui/screens/login/school_input/school_input.dart'; +import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'login_screen.i18n.dart'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key, this.back = false}); + + final bool back; + + @override + LoginScreenState createState() => LoginScreenState(); +} + +class LoginScreenState extends State { + final usernameController = TextEditingController(); + final passwordController = TextEditingController(); + final schoolController = SchoolInputController(); + final _scrollController = ScrollController(); + + // new controllers + final codeController = TextEditingController(); + + LoginState _loginState = LoginState.normal; + bool showBack = false; + + // Scaffold Gradient background + // final LinearGradient _backgroundGradient = const LinearGradient( + // colors: [ + // Color.fromARGB(255, 61, 122, 244), + // Color.fromARGB(255, 23, 77, 185), + // Color.fromARGB(255, 7, 42, 112), + // ], + // begin: Alignment(-0.8, -1.0), + // end: Alignment(0.8, 1.0), + // stops: [-1.0, 0.0, 1.0], + // ); + + late String tempUsername = ''; + + @override + void initState() { + super.initState(); + showBack = widget.back; + + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarIconBrightness: Brightness.light, + systemNavigationBarColor: Colors.white, + systemNavigationBarIconBrightness: Brightness.dark, + )); + + FilcAPI.getSchools().then((schools) { + if (schools != null) { + schoolController.update(() { + schoolController.schools = schools; + }); + } else { + ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + content: Text("schools_error".i18n, + style: const TextStyle(color: Colors.white)), + backgroundColor: AppColors.of(context).red, + context: context, + )); + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration(color: AppColors.of(context).loginBackground), + child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + controller: _scrollController, + child: Container( + decoration: + BoxDecoration(color: AppColors.of(context).loginBackground), + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + alignment: Alignment.topLeft, + padding: const EdgeInsets.only(left: 16.0, top: 12.0), + child: ClipOval( + child: Material( + type: MaterialType.transparency, + child: showBack + ? BackButton( + color: AppColors.of(context).loginPrimary) + : const SizedBox(height: 48.0), + ), + ), + ), + + // app icon + Padding( + padding: EdgeInsets.zero, + child: Image.asset( + 'assets/icons/ic_rounded.png', + width: 50.0, + ), + ), + + // texts + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10.0, + vertical: 12.0, + ), + child: Text( + 'reFilc', + style: TextStyle( + color: AppColors.of(context).loginPrimary, + fontSize: 28.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10.0, + ), + child: Text( + 'login_w_kreten'.i18n, + textAlign: TextAlign.center, + style: TextStyle( + color: AppColors.of(context).loginPrimary, + fontSize: 18.0, + fontWeight: FontWeight.w500, + height: 1.2, + ), + ), + ), + + const Spacer( + flex: 2, + ), + + // kreten login button + // GestureDetector( + // onTap: () { + // final NavigatorState navigator = Navigator.of(context); + // navigator + // .push( + // MaterialPageRoute( + // builder: (context) => KretenLoginScreen( + // onLogin: (String code) { + // codeController.text = code; + // navigator.pop(); + // }, + // ), + // ), + // ) + // .then((value) { + // if (codeController.text != "") { + // _NewLoginAPI(context: context); + // } + // }); + // }, + // child: Container( + // width: MediaQuery.of(context).size.width * 0.75, + // height: 50.0, + // decoration: BoxDecoration( + // // image: const DecorationImage( + // // image: + // // AssetImage('assets/images/btn_kreten_login.png'), + // // fit: BoxFit.scaleDown, + // // ), + // borderRadius: BorderRadius.circular(12.0), + // color: const Color(0xFF0097C1), + // ), + // padding: const EdgeInsets.only( + // top: 5.0, left: 5.0, right: 5.0, bottom: 5.0), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Image.asset( + // 'assets/images/btn_kreten_login.png', + // ), + // const SizedBox( + // width: 10.0, + // ), + // Container( + // width: 1.0, + // height: 30.0, + // color: Colors.white, + // ), + // const SizedBox( + // width: 10.0, + // ), + // Text( + // 'login_w_kreta_acc'.i18n, + // textAlign: TextAlign.center, + // style: const TextStyle( + // color: Colors.white, + // fontWeight: FontWeight.bold, + // fontSize: 15.0, + // ), + // ), + // ], + // )), + // ), + + // const Spacer( + // flex: 1, + // ), + + // inputs + Padding( + padding: const EdgeInsets.only( + left: 22.0, + right: 22.0, + top: 0.0, + ), + child: AutofillGroup( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // username + Padding( + padding: const EdgeInsets.only(bottom: 6.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + "username".i18n, + maxLines: 1, + style: TextStyle( + color: AppColors.of(context).loginPrimary, + fontWeight: FontWeight.w500, + fontSize: 12.0, + ), + ), + ), + Expanded( + child: Text( + "usernameHint".i18n, + maxLines: 1, + textAlign: TextAlign.right, + style: TextStyle( + color: + AppColors.of(context).loginSecondary, + fontWeight: FontWeight.w500, + fontSize: 12.0, + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: LoginInput( + style: LoginInputStyle.username, + controller: usernameController, + ), + ), + + // password + Padding( + padding: const EdgeInsets.only(bottom: 6.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + "password".i18n, + maxLines: 1, + style: TextStyle( + color: AppColors.of(context).loginPrimary, + fontWeight: FontWeight.w500, + fontSize: 12.0, + ), + ), + ), + Expanded( + child: Text( + "passwordHint".i18n, + maxLines: 1, + textAlign: TextAlign.right, + style: TextStyle( + color: + AppColors.of(context).loginSecondary, + fontWeight: FontWeight.w500, + fontSize: 12.0, + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: LoginInput( + style: LoginInputStyle.password, + controller: passwordController, + ), + ), + + // school + Padding( + padding: const EdgeInsets.only(bottom: 6.0), + child: Text( + "school".i18n, + maxLines: 1, + style: TextStyle( + color: AppColors.of(context).loginPrimary, + fontWeight: FontWeight.w500, + fontSize: 12.0, + ), + ), + ), + SchoolInput( + scroll: _scrollController, + controller: schoolController, + ), + ], + ), + ), + ), + + // login button + Padding( + padding: const EdgeInsets.only( + top: 35.0, + left: 22.0, + right: 22.0, + ), + child: Visibility( + visible: _loginState != LoginState.inProgress, + replacement: const Padding( + padding: EdgeInsets.symmetric(vertical: 6.0), + child: CircularProgressIndicator( + valueColor: + AlwaysStoppedAnimation(Colors.white), + ), + ), + child: LoginButton( + child: Text("login".i18n, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.0, + )), + onPressed: () => _loginAPI(context: context), + ), + ), + ), + + // error messages + if (_loginState == LoginState.missingFields || + _loginState == LoginState.invalidGrant || + _loginState == LoginState.failed) + Padding( + padding: const EdgeInsets.only( + top: 8.0, left: 12.0, right: 12.0), + child: Text( + [ + "missing_fields", + "invalid_grant", + "error" + ][_loginState.index] + .i18n, + style: const TextStyle( + color: Colors.red, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 22.0), + + // privacy policy + GestureDetector( + onTap: () => PrivacyView.show(context), + child: Text( + 'privacy'.i18n, + style: TextStyle( + color: AppColors.of(context).loginSecondary, + fontWeight: FontWeight.w500, + fontSize: 14.0, + ), + ), + ), + + const Spacer( + flex: 1, + ), + ], + ), + ), + ), + ), + ), + ); + } + + // new login api + // ignore: non_constant_identifier_names, unused_element + void _NewLoginAPI({required BuildContext context}) { + String code = codeController.text; + + if (code == "") { + return setState(() => _loginState = LoginState.failed); + } + + // ignore: no_leading_underscores_for_local_identifiers + void _callAPI() { + newLoginAPI( + code: code, + context: context, + onLogin: (user) { + ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + context: context, + brightness: Brightness.light, + content: Text("welcome".i18n.fill([user.name]), + overflow: TextOverflow.ellipsis), + )); + }, + onSuccess: () { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + setSystemChrome(context); + Navigator.of(context).pushReplacementNamed("login_to_navigation"); + }).then( + (res) => setState(() { + // if (res == LoginState.invalidGrant && + // tempUsername.replaceAll(username, '').length <= 3) { + // tempUsername = username + ' '; + // Timer( + // const Duration(milliseconds: 500), + // () => _loginAPI(context: context), + // ); + // // _loginAPI(context: context); + // } else { + _loginState = res; + // } + }), + ); + } + + setState(() => _loginState = LoginState.inProgress); + _callAPI(); + } + + void _loginAPI({required BuildContext context}) { + String username = usernameController.text; + String password = passwordController.text; + + tempUsername = username; + + if (username == "" || + password == "" || + schoolController.selectedSchool == null) { + return setState(() => _loginState = LoginState.missingFields); + } + + // ignore: no_leading_underscores_for_local_identifiers + void _callAPI() { + loginAPI( + username: username, + password: password, + instituteCode: schoolController.selectedSchool!.instituteCode, + context: context, + onLogin: (user) { + ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + context: context, + brightness: Brightness.light, + content: Text("welcome".i18n.fill([user.name]), + overflow: TextOverflow.ellipsis), + )); + }, + onSuccess: () { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + setSystemChrome(context); + Navigator.of(context).pushReplacementNamed("login_to_navigation"); + }).then( + (res) => setState(() { + // if (res == LoginState.invalidGrant && + // tempUsername.replaceAll(username, '').length <= 3) { + // tempUsername = username + ' '; + // Timer( + // const Duration(milliseconds: 500), + // () => _loginAPI(context: context), + // ); + // // _loginAPI(context: context); + // } else { + _loginState = res; + // } + }), + ); + } + + setState(() => _loginState = LoginState.inProgress); + _callAPI(); + } +} diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart index b2c8f6e..cb6b2bb 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart @@ -30,6 +30,7 @@ import 'package:refilc_mobile_ui/common/profile_image/profile_image.dart'; import 'package:refilc_mobile_ui/common/soon_alert/soon_alert.dart'; // import 'package:refilc_mobile_ui/common/soon_alert/soon_alert.dart'; import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart'; +import 'package:refilc_mobile_ui/common/system_chrome.dart'; // import 'package:refilc_mobile_ui/common/system_chrome.dart'; import 'package:refilc_mobile_ui/common/widgets/update/updates_view.dart'; import 'package:refilc_mobile_ui/screens/news/news_screen.dart'; @@ -105,9 +106,11 @@ class SettingsScreenState extends State Provider.of(context, listen: false).restore(), Provider.of(context, listen: false).restore(), Provider.of(context, listen: false).restore(), - Provider.of(context, listen: false).refreshLogin(), ]); + Future refresh() => + Provider.of(context, listen: false).refreshLogin(); + void buildAccountTiles() { accountTiles = []; user.getUsers().forEach((account) { @@ -143,8 +146,58 @@ class SettingsScreenState extends State //? ColorUtils.stringToColor(account.name) //: Theme.of(context).colorScheme.secondary, ), - onTap: () { + onTap: () async { user.setUser(account.id); + + // check if refresh token is still valid + String? err = await refresh(); + if (err != null) { + showDialog( + context: context, + builder: (_) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0)), + title: Text('oopsie'.i18n), + content: Text('session_expired'.i18n), + actions: [ + ActionButton( + label: "Ok", + onTap: () async { + String? userId = user.id; + if (userId == null) return; + + // delete user + user.removeUser(userId); + await Provider.of(context, + listen: false) + .store + .removeUser(userId); + + // if no users, show login, else login with back button + if (user.getUsers().isNotEmpty) { + user.setUser(user.getUsers().first.id); + restore().then( + (_) => user.setUser(user.getUsers().first.id)); + + Navigator.of(context).pop(); + Navigator.of(context) + .pushNamed("login_back") + .then((value) { + setSystemChrome(context); + }); + } else { + Navigator.of(context).pop(); + Navigator.of(context) + .pushNamedAndRemoveUntil("login", (_) => false); + } + }) + ], + ), + ); + return; + } + + // switch user restore().then((_) => user.setUser(account.id)); Navigator.of(context).pop(); }, @@ -749,6 +802,72 @@ class SettingsScreenState extends State // plus subscribe inline const PlusSettingsInline(), + // const SizedBox( + // height: 16.0, + // ), + + // Panel( + // hasShadow: false, + // padding: const EdgeInsets.only(left: 24.0, right: 24.0), + // title: Padding( + // padding: const EdgeInsets.only(left: 24.0), + // child: Text('account_link'.i18n), + // ), + // isTransparent: true, + // child: Column( + // children: [ + // // QwID account linking + // PanelButton( + // onPressed: () { + // launchUrl( + // Uri.parse( + // 'https://qwid.qwit.dev/oauth2/authorize?client_id=refilc&response_type=code&scope=*'), + // mode: LaunchMode.externalApplication, + // ); + // }, + // title: Text("QwID fiók-összekapcsolás".i18n), + // leading: Icon( + // FeatherIcons.link, + // size: 22.0, + // color: AppColors.of(context).text.withOpacity(0.95), + // ), + // trailing: GestureDetector( + // onTap: () { + // showDialog( + // context: context, + // builder: (BuildContext context) { + // return AlertDialog( + // title: const Text("QwID?!"), + // content: const Text( + // "A QwID egy olyan fiók, mellyel az összes QwIT szolgáltatásba beléphetsz és minden adatod egy helyen kezelheted. \"Miért jó ez nekem?\" A QwID fiókba való bejelentkezéssel rengeteg új funkcióhoz férhetsz hozzá, ami sajnos korábban lehetetlen volt egy szimpla e-KRÉTA fiókkal. Fiókhoz kötve megoszthatsz bármilyen adatot a barátaiddal, vagy ha szeretnéd nyilvánosságra is hozhatod jegyeid, reFilc témáid, és még rengeteg dolgot. A QwID fiók abban is segít, hogy egyszerűbben kezelhesd előfizetéseid, valamint fiókodnak köszönhetően rengeteg ajándékot kaphatsz reFilc+ előfizetésed mellé egyéb QwIT és reFilc szolgáltatásokban. \"Miért QwID?\" A név a reFilc mögött álló fejlesztői csapat, a QwIT nevéből, valamint az angol Identity szó rövidítéséből ered. \"Egyéb hasznos tudnivalók?\" A QwID fiókodat bármikor törölheted, ha úgy érzed, hogy nem szeretnéd tovább használni. Bővebb információt az adatkezelésről és az általános feltételekről megtalálsz a regisztrációs oldalon. Fiókod kezeléséhez látogass el a qwid.qwit.dev weboldalra.", + // ), + // actions: [ + // TextButton( + // onPressed: () { + // Navigator.of(context).pop(); + // }, + // child: const Text("Szuper!"), + // ), + // ], + // ); + // }, + // ); + // }, + // child: Icon( + // FeatherIcons.helpCircle, + // size: 20.0, + // color: AppColors.of(context).text.withOpacity(0.95), + // ), + // ), + // borderRadius: const BorderRadius.vertical( + // top: Radius.circular(12.0), + // bottom: Radius.circular(4.0), + // ), + // ), + // ], + // ), + // ), + // settings submenus const SizedBox( height: 16.0, @@ -839,6 +958,18 @@ class SettingsScreenState extends State ), ], ), + // const SplittedPanel( + // padding: EdgeInsets.only(top: 8.0), + // cardPadding: EdgeInsets.all(4.0), + // children: [ + // MenuOtherSettings( + // borderRadius: BorderRadius.vertical( + // top: Radius.circular(12.0), + // bottom: Radius.circular(12.0), + // ), + // ), + // ], + // ), ], ), ), @@ -846,8 +977,8 @@ class SettingsScreenState extends State // // icon gallery (debug mode) if (kDebugMode) Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 24.0), + padding: const EdgeInsets.only( + bottom: 16.0, left: 24.0, right: 24.0), child: Panel( title: const Text("Debug"), child: Column( @@ -870,6 +1001,43 @@ class SettingsScreenState extends State ), ), + // other secion + SplittedPanel( + title: Text("other".i18n), + cardPadding: const EdgeInsets.all(4.0), + children: [ + PanelButton( + leading: Icon( + FeatherIcons.map, + size: 22.0, + color: AppColors.of(context).text.withOpacity(0.95), + ), + title: Text("stickermap".i18n), + onPressed: () => launchUrl( + Uri.parse("https://stickermap.refilc.hu"), + mode: LaunchMode.inAppBrowserView, + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), + bottom: Radius.circular(4.0), + ), + ), + PanelButton( + leading: Icon( + FeatherIcons.mail, + size: 22.0, + color: AppColors.of(context).text.withOpacity(0.95), + ), + title: Text("news".i18n), + onPressed: () => _openNews(context), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(4.0), + bottom: Radius.circular(12.0), + ), + ), + ], + ), + // // extra settings // Padding( // padding: @@ -902,19 +1070,6 @@ class SettingsScreenState extends State title: Text("about".i18n), cardPadding: const EdgeInsets.all(4.0), children: [ - PanelButton( - leading: Icon( - FeatherIcons.mail, - size: 22.0, - color: AppColors.of(context).text.withOpacity(0.95), - ), - title: Text("news".i18n), - onPressed: () => _openNews(context), - borderRadius: const BorderRadius.vertical( - top: Radius.circular(12.0), - bottom: Radius.circular(4.0), - ), - ), PanelButton( leading: Icon( FeatherIcons.lock, @@ -927,7 +1082,7 @@ class SettingsScreenState extends State // mode: LaunchMode.inAppWebView), onPressed: () => _openPrivacy(context), borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), + top: Radius.circular(12.0), bottom: Radius.circular(4.0), ), ), diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart index 855cc43..2c4387c 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart @@ -129,6 +129,8 @@ extension SettingsLocalization on String { "grade_exporting": "Grade Exporting", "custom": "Custom", "feedback": "Feedback", + "other": "Other", + "stickermap": "Sticker Map", }, "hu_hu": { "heads_up": "Figyelem!", @@ -256,6 +258,8 @@ extension SettingsLocalization on String { "grade_exporting": "Jegy exportálás", "custom": "Egyedi", "feedback": "Visszajelzés", + "other": "Egyéb", + "stickermap": "Matrica térkép", }, "de_de": { "heads_up": "Achtung!", @@ -383,6 +387,8 @@ extension SettingsLocalization on String { "grade_exporting": "Noten exportieren", "custom": "Benutzerdefiniert", "feedback": "Feedback", + "other": "Sonstiges", + "stickermap": "Sticker Map", }, }; diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart b/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart index f3aee86..ce76ee7 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart @@ -7,6 +7,8 @@ extension SettingsLocalization on String { "general": "General", "personalization": "Personalization", "extras": "Extras", + "other": "Other", + "stickermap": "reFilc Stickermap", "surprise_grades": "Surprise Grades", "cancel": "Cancel", "done": "Done", @@ -33,6 +35,8 @@ extension SettingsLocalization on String { "general": "Általános", "personalization": "Személyre szabás", "extras": "Extrák", + "other": "Egyéb", + "stickermap": "reFilc Matricatérkép", "surprise_grades": "Meglepetés jegyek", "cancel": "Mégse", "done": "Kész", @@ -59,6 +63,8 @@ extension SettingsLocalization on String { "general": "Allgemeine", "personalization": "Personalisierung", "extras": "Extras", + "other": "Andere", + "stickermap": "reFilc Aufkleberkarte", "surprise_grades": "Überraschende Noten", "cancel": "Abbrechen", "done": "Bereit", diff --git a/refilc_plus b/refilc_plus index 3207cdc..26cd3fc 160000 --- a/refilc_plus +++ b/refilc_plus @@ -1 +1 @@ -Subproject commit 3207cdcf6d7e6cd0d2f17851b3597597c14ea4ff +Subproject commit 26cd3fc163d72ddb849edfeb7fdb7b64c7df44bc