Merge pull request #128 from refilc/dev

dev to master
This commit is contained in:
Márton Kiss 2024-08-16 01:06:26 +02:00 committed by GitHub
commit 2c1bde9398
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1191 additions and 1005 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 KiB

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 KiB

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 204 KiB

View File

@ -1,5 +1,6 @@
// ignore_for_file: avoid_print, use_build_context_synchronously // ignore_for_file: avoid_print, use_build_context_synchronously
import 'package:flutter/foundation.dart';
import 'package:refilc/utils/jwt.dart'; import 'package:refilc/utils/jwt.dart';
import 'package:refilc_kreta_api/models/school.dart'; import 'package:refilc_kreta_api/models/school.dart';
import 'package:refilc_kreta_api/providers/absence_provider.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.', address: '1117 Budapest, Gábor Dénes utca 4.',
), ),
role: Role.parent, role: Role.parent,
refreshToken: '',
); );
if (onLogin != null) onLogin(user); if (onLogin != null) onLogin(user);
@ -107,7 +109,9 @@ Future loginAPI({
default: default:
// normal login from here // normal login from here
Provider.of<KretaClient>(context, listen: false).userAgent = Provider.of<KretaClient>(context, listen: false).userAgent =
Provider.of<SettingsProvider>(context, listen: false).config.userAgent; Provider.of<SettingsProvider>(context, listen: false)
.config
.userAgent;
Map<String, String> headers = { Map<String, String> headers = {
"content-type": "application/x-www-form-urlencoded", "content-type": "application/x-www-form-urlencoded",
@ -127,6 +131,7 @@ Future loginAPI({
password: password, password: password,
instituteCode: instituteCode, instituteCode: instituteCode,
)); ));
if (res != null) { if (res != null) {
if (res.containsKey("error")) { if (res.containsKey("error")) {
if (res["error"] == "invalid_grant") { if (res["error"] == "invalid_grant") {
@ -148,6 +153,7 @@ Future loginAPI({
name: student.name, name: student.name,
student: student, student: student,
role: JwtUtils.getRoleFromJWT(res["access_token"])!, role: JwtUtils.getRoleFromJWT(res["access_token"])!,
refreshToken: '',
); );
if (onLogin != null) onLogin(user); if (onLogin != null) onLogin(user);
@ -157,7 +163,8 @@ Future loginAPI({
.store .store
.storeUser(user); .storeUser(user);
Provider.of<UserProvider>(context, listen: false).addUser(user); Provider.of<UserProvider>(context, listen: false).addUser(user);
Provider.of<UserProvider>(context, listen: false).setUser(user.id); Provider.of<UserProvider>(context, listen: false)
.setUser(user.id);
// Get user data // Get user data
try { try {
@ -167,7 +174,8 @@ Future loginAPI({
.fetch(week: Week.current()), .fetch(week: Week.current()),
Provider.of<ExamProvider>(context, listen: false).fetch(), Provider.of<ExamProvider>(context, listen: false).fetch(),
Provider.of<HomeworkProvider>(context, listen: false).fetch(), Provider.of<HomeworkProvider>(context, listen: false).fetch(),
Provider.of<MessageProvider>(context, listen: false).fetchAll(), Provider.of<MessageProvider>(context, listen: false)
.fetchAll(),
Provider.of<MessageProvider>(context, listen: false) Provider.of<MessageProvider>(context, listen: false)
.fetchAllRecipients(), .fetchAllRecipients(),
Provider.of<NoteProvider>(context, listen: false).fetch(), Provider.of<NoteProvider>(context, listen: false).fetch(),
@ -195,3 +203,112 @@ Future loginAPI({
return LoginState.failed; 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<KretaClient>(context, listen: false).userAgent =
Provider.of<SettingsProvider>(context, listen: false).config.userAgent;
Map<String, String> 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<KretaClient>(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<KretaClient>(context, listen: false).accessToken =
res["access_token"];
Provider.of<KretaClient>(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<KretaClient>(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<DatabaseProvider>(context, listen: false)
.store
.storeUser(user);
Provider.of<UserProvider>(context, listen: false).addUser(user);
Provider.of<UserProvider>(context, listen: false).setUser(user.id);
// Get user data
try {
await Future.wait([
Provider.of<GradeProvider>(context, listen: false).fetch(),
Provider.of<TimetableProvider>(context, listen: false)
.fetch(week: Week.current()),
Provider.of<ExamProvider>(context, listen: false).fetch(),
Provider.of<HomeworkProvider>(context, listen: false).fetch(),
Provider.of<MessageProvider>(context, listen: false).fetchAll(),
Provider.of<MessageProvider>(context, listen: false)
.fetchAllRecipients(),
Provider.of<NoteProvider>(context, listen: false).fetch(),
Provider.of<EventProvider>(context, listen: false).fetch(),
Provider.of<AbsenceProvider>(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;
}

View File

@ -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/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_route.dart' as mobile;
import 'package:refilc_mobile_ui/screens/login/login_screen.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' import 'package:refilc_mobile_ui/screens/navigation/navigation_screen.dart'
as mobile; as mobile;
import 'package:refilc_mobile_ui/screens/settings/settings_route.dart' import 'package:refilc_mobile_ui/screens/settings/settings_route.dart'
@ -80,7 +81,8 @@ class App extends StatelessWidget {
CorePalette? corePalette; CorePalette? corePalette;
final status = StatusProvider(); 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 = final timetable =
TimetableProvider(user: user, database: database, kreta: kreta); TimetableProvider(user: user, database: database, kreta: kreta);
final premium = PlusProvider(settings: settings); final premium = PlusProvider(settings: settings);

View File

@ -66,6 +66,7 @@ const usersDB = DatabaseStruct("users", {
"institute_code": String, "student": String, "role": int, "institute_code": String, "student": String, "role": int,
"nickname": String, "picture": String, // premium only (it's now plus btw) "nickname": String, "picture": String, // premium only (it's now plus btw)
"grade_streak": int, "grade_streak": int,
"refresh_token": String,
}); });
const userDataDB = DatabaseStruct("user_data", { const userDataDB = DatabaseStruct("user_data", {
"id": String, "grades": String, "timetable": String, "exams": String, "id": String, "grades": String, "timetable": String, "exams": String,
@ -138,7 +139,8 @@ Future<Database> initDB(DatabaseProvider database) async {
"role": 0, "role": 0,
"nickname": "", "nickname": "",
"picture": "", "picture": "",
"grade_streak": 0 "grade_streak": 0,
"refresh_token": "",
}, },
); );
await migrateDB(db, struct: userDataDB, defaultValues: { await migrateDB(db, struct: userDataDB, defaultValues: {

View File

@ -71,9 +71,11 @@ class NotificationsHelper {
// Refresh kreta login for current user // Refresh kreta login for current user
final status = StatusProvider(); final status = StatusProvider();
KretaClient kretaClientForUser = KretaClient( KretaClient kretaClientForUser = KretaClient(
user: userProviderForUser, user: userProviderForUser,
settings: settingsProvider, settings: settingsProvider,
status: status); database: database,
status: status,
);
await kretaClientForUser.refreshLogin(); await kretaClientForUser.refreshLogin();
// Process notifications for current user // 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 nem lesz tőle használhatatlan az app, de kikommenteltem, mert még a végén kima bántani fog
Future<void> liveNotification(UserProvider currentuserProvider, KretaClient currentKretaClient) async { Future<void> liveNotification(UserProvider currentuserProvider, KretaClient currentKretaClient) async {

View File

@ -17,6 +17,8 @@ class User {
String nickname; String nickname;
String picture; String picture;
int gradeStreak; int gradeStreak;
// new login method
String refreshToken;
String get displayName => nickname != '' ? nickname : name; String get displayName => nickname != '' ? nickname : name;
bool get hasStreak => gradeStreak > 0; bool get hasStreak => gradeStreak > 0;
@ -32,6 +34,7 @@ class User {
this.nickname = "", this.nickname = "",
this.picture = "", this.picture = "",
this.gradeStreak = 0, this.gradeStreak = 0,
required this.refreshToken,
}) { }) {
if (id != null) { if (id != null) {
this.id = id; this.id = id;
@ -61,6 +64,7 @@ class User {
nickname: map["nickname"] ?? "", nickname: map["nickname"] ?? "",
picture: map["picture"] ?? "", picture: map["picture"] ?? "",
gradeStreak: map["grade_streak"] ?? 0, gradeStreak: map["grade_streak"] ?? 0,
refreshToken: map["refresh_token"] ?? "",
); );
} }
@ -75,6 +79,8 @@ class User {
"role": role.index, "role": role.index,
"nickname": nickname, "nickname": nickname,
"picture": picture, "picture": picture,
"grade_streak": gradeStreak,
"refresh_token": refreshToken,
}; };
} }

View File

@ -39,4 +39,16 @@ class JwtUtils {
} }
return null; 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"];
}
} }

View File

@ -3,7 +3,7 @@ description: "Egy nem hivatalos e-KRÉTA kliens, diákoktól diákoknak."
homepage: https://refilc.hu homepage: https://refilc.hu
publish_to: "none" publish_to: "none"
version: 5.0.3+269 version: 5.0.4+272
environment: environment:
sdk: ">=3.3.2 <=3.4.3" sdk: ">=3.3.2 <=3.4.3"

View File

@ -5,7 +5,7 @@ class KretaAPI {
static const login = BaseKreta.kretaIdp + KretaApiEndpoints.token; static const login = BaseKreta.kretaIdp + KretaApiEndpoints.token;
static const logout = BaseKreta.kretaIdp + KretaApiEndpoints.revoke; static const logout = BaseKreta.kretaIdp + KretaApiEndpoints.revoke;
static const nonce = BaseKreta.kretaIdp + KretaApiEndpoints.nonce; static const nonce = BaseKreta.kretaIdp + KretaApiEndpoints.nonce;
static const clientId = "kreta-ellenorzo-mobile-android"; static const clientId = "kreta-ellenorzo-student-mobile-ios";
// ELLENORZO API // ELLENORZO API
static String notes(String iss) => static String notes(String iss) =>

View File

@ -1,15 +1,16 @@
// ignore_for_file: avoid_print // ignore_for_file: avoid_print, use_build_context_synchronously
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:refilc/api/login.dart'; // import 'package:refilc/api/login.dart';
import 'package:refilc/api/nonce.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/user_provider.dart';
import 'package:refilc/api/providers/status_provider.dart'; import 'package:refilc/api/providers/status_provider.dart';
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc/models/user.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:refilc_kreta_api/client/api.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/io_client.dart' as http; import 'package:http/io_client.dart' as http;
@ -24,6 +25,7 @@ class KretaClient {
late final SettingsProvider _settings; late final SettingsProvider _settings;
late final UserProvider _user; late final UserProvider _user;
late final DatabaseProvider _database;
late final StatusProvider _status; late final StatusProvider _status;
bool _loginRefreshing = false; bool _loginRefreshing = false;
@ -32,9 +34,11 @@ class KretaClient {
this.accessToken, this.accessToken,
required SettingsProvider settings, required SettingsProvider settings,
required UserProvider user, required UserProvider user,
required DatabaseProvider database,
required StatusProvider status, required StatusProvider status,
}) : _settings = settings, }) : _settings = settings,
_user = user, _user = user,
_database = database,
_status = status, _status = status,
userAgent = settings.config.userAgent { userAgent = settings.config.userAgent {
var ioclient = HttpClient(); var ioclient = HttpClient();
@ -212,7 +216,7 @@ class KretaClient {
res = await request.send(); res = await request.send();
if (res.statusCode == 401) { if (res.statusCode == 401) {
headerMap.remove("authorization"); headerMap.remove("authorization");
await refreshLogin(); await refreshLogin();
} else { } else {
break; break;
@ -232,65 +236,74 @@ class KretaClient {
} }
} }
Future<void> refreshLogin() async { Future<String?> refreshLogin() async {
if (_loginRefreshing) return; if (_loginRefreshing) return null;
_loginRefreshing = true; _loginRefreshing = true;
User? loginUser = _user.user; User? loginUser = _user.user;
if (loginUser == null) return; if (loginUser == null) return null;
Map<String, String> headers = { Map<String, String> 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) { if (_settings.presentationMode) {
print("DEBUG: refreshLogin: ${loginUser.id}"); print("DEBUG: refreshLogin: ${loginUser.id}");
} else { } else {
print("DEBUG: refreshLogin: ${loginUser.id} ${loginUser.name}"); print("DEBUG: refreshLogin: ${loginUser.id} ${loginUser.name}");
} }
Map? loginRes = await postAPI( refreshToken ??= loginUser.refreshToken;
KretaAPI.login,
headers: headers,
body: User.loginBody(
username: loginUser.username,
password: loginUser.password,
instituteCode: loginUser.instituteCode,
),
);
if (loginRes != null) { print("REFRESH TOKEN BELOW");
if (loginRes.containsKey("access_token")) { print(refreshToken);
accessToken = loginRes["access_token"];
}
if (loginRes.containsKey("refresh_token")) {
refreshToken = loginRes["refresh_token"];
}
// Update role
loginUser.role =
JwtUtils.getRoleFromJWT(accessToken ?? "") ?? Role.student;
}
if (refreshToken != null) { if (refreshToken != null) {
Map? refreshRes = await postAPI(KretaAPI.login, print("REFRESHING LOGIN");
Map? res = await postAPI(KretaAPI.login,
headers: headers, headers: headers,
body: User.refreshBody( body: User.refreshBody(
refreshToken: refreshToken!, refreshToken: loginUser.refreshToken,
instituteCode: loginUser.instituteCode)); instituteCode: loginUser.instituteCode,
if (refreshRes != null) { ));
if (refreshRes.containsKey("id_token")) { print("REFRESH RESPONSE BELOW");
idToken = refreshRes["id_token"]; 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<void> logout() async { Future<void> logout() async {

View File

@ -1,12 +1,13 @@
import 'package:flutter/foundation.dart'; // ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart'; 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'; import 'package:webview_flutter/webview_flutter.dart';
class KretenLoginScreen extends StatefulWidget { 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 @override
State<KretenLoginScreen> createState() => _KretenLoginScreenState(); State<KretenLoginScreen> createState() => _KretenLoginScreenState();
@ -29,112 +30,29 @@ class _KretenLoginScreenState extends State<KretenLoginScreen> {
currentUrl = url; currentUrl = url;
}); });
// final String instituteCode = widget.selectedSchool;
if (!url.startsWith(
'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) {
return;
}
List<String> requiredThings = url List<String> requiredThings = url
.replaceAll( .replaceAll(
'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=', 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=',
'') '')
.replaceAll( .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(':'); .split(':');
String code = requiredThings[0]; String code = requiredThings[0];
// String sessionState = requiredThings[1]; // String sessionState = requiredThings[1];
debugPrint('url: $url'); widget.onLogin(code);
// Future.delayed(const Duration(milliseconds: 500), () {
// actual login (token grant) logic // Navigator.of(context).pop();
Map<String, String> headers = { // });
"content-type": "application/x-www-form-urlencoded", // Navigator.of(context).pop();
"accept": "*/*",
"user-agent":
"eKretaStudent/264745 CFNetwork/1494.0.7 Darwin/23.4.0",
"code_verifier": "THDUSddKOOndwCkqBtVHvRjh2LK0V2kMyLP2QirqVWQ",
};
Map? res = await Provider.of<KretaClient>(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<KretaClient>(context, listen: false).accessToken =
// res["access_token"];
// Map? studentJson =
// await Provider.of<KretaClient>(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<DatabaseProvider>(context, listen: false)
// .store
// .storeUser(user);
// Provider.of<UserProvider>(context, listen: false)
// .addUser(user);
// Provider.of<UserProvider>(context, listen: false)
// .setUser(user.id);
// // Get user data
// try {
// await Future.wait([
// Provider.of<GradeProvider>(context, listen: false)
// .fetch(),
// Provider.of<TimetableProvider>(context, listen: false)
// .fetch(week: Week.current()),
// Provider.of<ExamProvider>(context, listen: false).fetch(),
// Provider.of<HomeworkProvider>(context, listen: false)
// .fetch(),
// Provider.of<MessageProvider>(context, listen: false)
// .fetchAll(),
// Provider.of<MessageProvider>(context, listen: false)
// .fetchAllRecipients(),
// Provider.of<NoteProvider>(context, listen: false).fetch(),
// Provider.of<EventProvider>(context, listen: false)
// .fetch(),
// Provider.of<AbsenceProvider>(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;
// }
// }
// }
}
}, },
onProgress: (progress) { onProgress: (progress) {
setState(() { setState(() {
@ -149,7 +67,7 @@ class _KretenLoginScreenState extends State<KretenLoginScreen> {
)) ))
..loadRequest( ..loadRequest(
Uri.parse( 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}
); );
} }

View File

@ -5,13 +5,13 @@ import 'package:refilc/api/login.dart';
import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_mobile_ui/common/custom_snack_bar.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/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/login/school_input/school_input.dart';
import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart'; import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'login_screen.i18n.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 { class LoginScreen extends StatefulWidget {
const LoginScreen({super.key, this.back = false}); const LoginScreen({super.key, this.back = false});
@ -27,7 +27,8 @@ class LoginScreenState extends State<LoginScreen> {
final passwordController = TextEditingController(); final passwordController = TextEditingController();
final schoolController = SchoolInputController(); final schoolController = SchoolInputController();
final _scrollController = ScrollController(); final _scrollController = ScrollController();
// new controllers
final codeController = TextEditingController();
LoginState _loginState = LoginState.normal; LoginState _loginState = LoginState.normal;
bool showBack = false; bool showBack = false;
@ -75,223 +76,230 @@ class LoginScreenState extends State<LoginScreen> {
@override @override
Widget build(BuildContext context) { 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( return Scaffold(
body: Container( body: Container(
decoration: BoxDecoration(color: AppColors.of(context).loginBackground), decoration: const BoxDecoration(color: Color(0xFFDAE4F7)),
child: SingleChildScrollView( child: SingleChildScrollView(
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
controller: _scrollController, controller: _scrollController,
child: Container( child: Container(
decoration: decoration: const BoxDecoration(color: Color(0xFFDAE4F7)),
BoxDecoration(color: AppColors.of(context).loginBackground),
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height, height: MediaQuery.of(context).size.height,
child: SafeArea( child: SafeArea(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [ 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 // app icon
Padding( Padding(
padding: EdgeInsets.zero, padding: const EdgeInsets.only(left: 24, top: 20),
child: Image.asset( child: Row(
'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,
children: [ children: [
// username Image.asset(
Padding( 'assets/icons/ic_rounded.png',
padding: const EdgeInsets.only(bottom: 6.0), width: 30.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( const SizedBox(width: 8),
padding: const EdgeInsets.only(bottom: 12.0), const Text(
child: LoginInput( 'reFilc',
style: LoginInputStyle.username, style: TextStyle(
controller: usernameController, color: Color(0xFF050B15),
), fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Montserrat'),
), ),
Material(
// password type: MaterialType.transparency,
Padding( child: showBack
padding: const EdgeInsets.only(bottom: 6.0), ? BackButton(color: AppColors.of(context).text)
child: Row( : const SizedBox(height: 48.0),
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( Stack(
style: LoginInputStyle.password, alignment: Alignment.bottomCenter,
controller: passwordController, children: [
), Column(
), //login buttons and ui starts here
mainAxisAlignment: MainAxisAlignment.end,
// school crossAxisAlignment: CrossAxisAlignment.end,
Padding( children: [
padding: const EdgeInsets.only(bottom: 6.0), const SizedBox(height: 21),
child: Text( CarouselSlider(
"school".i18n, options: CarouselOptions(
maxLines: 1, height: MediaQuery.of(context).size.height,
style: TextStyle( viewportFraction: 1,
color: AppColors.of(context).loginPrimary, autoPlay: true,
fontWeight: FontWeight.w500, autoPlayInterval: const Duration(seconds: 6),
fontSize: 12.0, pauseAutoPlayOnTouch: true),
), items: [1, 2, 3, 4].map((i) {
), return Builder(
), builder: (BuildContext context) {
SchoolInput( return Column(
scroll: _scrollController, crossAxisAlignment:
controller: schoolController, 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,
// login button decoration: const BoxDecoration(
Padding( gradient: LinearGradient(
padding: const EdgeInsets.only( colors: [Color(0x00DAE4F7), Color(0xFFDAE4F7)],
top: 35.0, stops: [0, 0.12],
left: 22.0, begin: Alignment.topCenter,
right: 22.0, end: Alignment.bottomCenter,
), ),
child: Visibility( ),
visible: _loginState != LoginState.inProgress, child: Padding(
replacement: const Padding( padding: EdgeInsets.only(
padding: EdgeInsets.symmetric(vertical: 6.0), top: 50,
child: CircularProgressIndicator( bottom: MediaQuery.of(context).viewInsets.bottom),
valueColor: child: Column(
AlwaysStoppedAnimation<Color>(Colors.white), 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 || if (_loginState == LoginState.missingFields ||
_loginState == LoginState.invalidGrant || _loginState == LoginState.invalidGrant ||
_loginState == LoginState.failed) _loginState == LoginState.failed)
@ -312,8 +320,6 @@ class LoginScreenState extends State<LoginScreen> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
const SizedBox(height: 22.0),
// privacy policy // privacy policy
GestureDetector( GestureDetector(
onTap: () => PrivacyView.show(context), onTap: () => PrivacyView.show(context),
@ -326,10 +332,6 @@ class LoginScreenState extends State<LoginScreen> {
), ),
), ),
), ),
const Spacer(
flex: 1,
),
], ],
), ),
), ),
@ -339,24 +341,71 @@ class LoginScreenState extends State<LoginScreen> {
); );
} }
void _loginAPI({required BuildContext context}) { // void _loginAPI({required BuildContext context}) {
String username = usernameController.text; // String username = usernameController.text;
String password = passwordController.text; // String password = passwordController.text;
tempUsername = username; // tempUsername = username;
if (username == "" || // if (username == "" ||
password == "" || // password == "" ||
schoolController.selectedSchool == null) { // schoolController.selectedSchool == null) {
return setState(() => _loginState = LoginState.missingFields); // 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 // ignore: no_leading_underscores_for_local_identifiers
void _callAPI() { void _callAPI() {
loginAPI( newLoginAPI(
username: username, code: code,
password: password,
instituteCode: schoolController.selectedSchool!.instituteCode,
context: context, context: context,
onLogin: (user) { onLogin: (user) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(

View File

@ -33,6 +33,7 @@ extension Localization on String {
"welcome_title_4": "Take as many notes as you want.", "welcome_title_4": "Take as many notes as you want.",
"welcome_text_4": "welcome_text_4":
"You can also organise your notes by lesson in the built-in notebook, so you can find everything in one app.", "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": { "hu_hu": {
"username": "Felhasználónév", "username": "Felhasználónév",
@ -64,6 +65,7 @@ extension Localization on String {
"welcome_title_4": "Füzetelj annyit, amennyit csak szeretnél.", "welcome_title_4": "Füzetelj annyit, amennyit csak szeretnél.",
"welcome_text_4": "welcome_text_4":
"A beépített jegyzetfüzetbe órák szerint is rendezheted a jegyzeteidet, így mindent megtalálsz egy appban.", "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": { "de_de": {
"username": "Benutzername", "username": "Benutzername",
@ -95,6 +97,7 @@ extension Localization on String {
"welcome_title_4": "Take as many notes as you want.", "welcome_title_4": "Take as many notes as you want.",
"welcome_text_4": "welcome_text_4":
"You can also organise your notes by lesson in the built-in notebook, so you can find everything in one app.", "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",
}, },
}; };

View File

@ -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<LoginScreen> {
// 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();
// }
// }

View File

@ -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<LoginScreen> {
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<Color>(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();
}
}

View File

@ -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/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/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/system_chrome.dart';
import 'package:refilc_mobile_ui/common/widgets/update/updates_view.dart'; import 'package:refilc_mobile_ui/common/widgets/update/updates_view.dart';
import 'package:refilc_mobile_ui/screens/news/news_screen.dart'; import 'package:refilc_mobile_ui/screens/news/news_screen.dart';
@ -105,9 +106,11 @@ class SettingsScreenState extends State<SettingsScreen>
Provider.of<NoteProvider>(context, listen: false).restore(), Provider.of<NoteProvider>(context, listen: false).restore(),
Provider.of<EventProvider>(context, listen: false).restore(), Provider.of<EventProvider>(context, listen: false).restore(),
Provider.of<AbsenceProvider>(context, listen: false).restore(), Provider.of<AbsenceProvider>(context, listen: false).restore(),
Provider.of<KretaClient>(context, listen: false).refreshLogin(),
]); ]);
Future<String?> refresh() =>
Provider.of<KretaClient>(context, listen: false).refreshLogin();
void buildAccountTiles() { void buildAccountTiles() {
accountTiles = []; accountTiles = [];
user.getUsers().forEach((account) { user.getUsers().forEach((account) {
@ -143,8 +146,58 @@ class SettingsScreenState extends State<SettingsScreen>
//? ColorUtils.stringToColor(account.name) //? ColorUtils.stringToColor(account.name)
//: Theme.of(context).colorScheme.secondary, //: Theme.of(context).colorScheme.secondary,
), ),
onTap: () { onTap: () async {
user.setUser(account.id); 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<DatabaseProvider>(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)); restore().then((_) => user.setUser(account.id));
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -749,6 +802,72 @@ class SettingsScreenState extends State<SettingsScreen>
// plus subscribe inline // plus subscribe inline
const PlusSettingsInline(), 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 // settings submenus
const SizedBox( const SizedBox(
height: 16.0, height: 16.0,
@ -839,6 +958,18 @@ class SettingsScreenState extends State<SettingsScreen>
), ),
], ],
), ),
// 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<SettingsScreen>
// // icon gallery (debug mode) // // icon gallery (debug mode)
if (kDebugMode) if (kDebugMode)
Padding( Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.only(
vertical: 12.0, horizontal: 24.0), bottom: 16.0, left: 24.0, right: 24.0),
child: Panel( child: Panel(
title: const Text("Debug"), title: const Text("Debug"),
child: Column( child: Column(
@ -870,6 +1001,43 @@ class SettingsScreenState extends State<SettingsScreen>
), ),
), ),
// 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 // // extra settings
// Padding( // Padding(
// padding: // padding:
@ -902,19 +1070,6 @@ class SettingsScreenState extends State<SettingsScreen>
title: Text("about".i18n), title: Text("about".i18n),
cardPadding: const EdgeInsets.all(4.0), cardPadding: const EdgeInsets.all(4.0),
children: [ 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( PanelButton(
leading: Icon( leading: Icon(
FeatherIcons.lock, FeatherIcons.lock,
@ -927,7 +1082,7 @@ class SettingsScreenState extends State<SettingsScreen>
// mode: LaunchMode.inAppWebView), // mode: LaunchMode.inAppWebView),
onPressed: () => _openPrivacy(context), onPressed: () => _openPrivacy(context),
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(4.0), top: Radius.circular(12.0),
bottom: Radius.circular(4.0), bottom: Radius.circular(4.0),
), ),
), ),

View File

@ -129,6 +129,8 @@ extension SettingsLocalization on String {
"grade_exporting": "Grade Exporting", "grade_exporting": "Grade Exporting",
"custom": "Custom", "custom": "Custom",
"feedback": "Feedback", "feedback": "Feedback",
"other": "Other",
"stickermap": "Sticker Map",
}, },
"hu_hu": { "hu_hu": {
"heads_up": "Figyelem!", "heads_up": "Figyelem!",
@ -256,6 +258,8 @@ extension SettingsLocalization on String {
"grade_exporting": "Jegy exportálás", "grade_exporting": "Jegy exportálás",
"custom": "Egyedi", "custom": "Egyedi",
"feedback": "Visszajelzés", "feedback": "Visszajelzés",
"other": "Egyéb",
"stickermap": "Matrica térkép",
}, },
"de_de": { "de_de": {
"heads_up": "Achtung!", "heads_up": "Achtung!",
@ -383,6 +387,8 @@ extension SettingsLocalization on String {
"grade_exporting": "Noten exportieren", "grade_exporting": "Noten exportieren",
"custom": "Benutzerdefiniert", "custom": "Benutzerdefiniert",
"feedback": "Feedback", "feedback": "Feedback",
"other": "Sonstiges",
"stickermap": "Sticker Map",
}, },
}; };

View File

@ -7,6 +7,8 @@ extension SettingsLocalization on String {
"general": "General", "general": "General",
"personalization": "Personalization", "personalization": "Personalization",
"extras": "Extras", "extras": "Extras",
"other": "Other",
"stickermap": "reFilc Stickermap",
"surprise_grades": "Surprise Grades", "surprise_grades": "Surprise Grades",
"cancel": "Cancel", "cancel": "Cancel",
"done": "Done", "done": "Done",
@ -33,6 +35,8 @@ extension SettingsLocalization on String {
"general": "Általános", "general": "Általános",
"personalization": "Személyre szabás", "personalization": "Személyre szabás",
"extras": "Extrák", "extras": "Extrák",
"other": "Egyéb",
"stickermap": "reFilc Matricatérkép",
"surprise_grades": "Meglepetés jegyek", "surprise_grades": "Meglepetés jegyek",
"cancel": "Mégse", "cancel": "Mégse",
"done": "Kész", "done": "Kész",
@ -59,6 +63,8 @@ extension SettingsLocalization on String {
"general": "Allgemeine", "general": "Allgemeine",
"personalization": "Personalisierung", "personalization": "Personalisierung",
"extras": "Extras", "extras": "Extras",
"other": "Andere",
"stickermap": "reFilc Aufkleberkarte",
"surprise_grades": "Überraschende Noten", "surprise_grades": "Überraschende Noten",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"done": "Bereit", "done": "Bereit",

@ -1 +1 @@
Subproject commit 3207cdcf6d7e6cd0d2f17851b3597597c14ea4ff Subproject commit 26cd3fc163d72ddb849edfeb7fdb7b64c7df44bc