Compare commits

..

73 Commits
5.0.3 ... 5.0.6

Author SHA1 Message Date
Márton Kiss
a74b2cd3d7 Merge pull request #137 from refilc/dev
dev to master
2024-11-13 21:08:13 +01:00
Kima
414755c777 okay that's it bye 2024-11-13 20:51:24 +01:00
Kima
3708b917c4 finished qr scanner (test) 2024-11-13 20:25:13 +01:00
Kima
986b13de68 changed version number 2024-11-13 19:12:09 +01:00
Kima
d391448870 remove prints 2024-11-13 19:11:08 +01:00
Kima
38d9b5f3b2 fixed login finally 2024-11-13 19:10:48 +01:00
Kima
2dafe5ed02 updated packages, did things and maybe finally fixed login issue 2024-11-12 23:27:14 +01:00
Kima
939761695f working error handling for theme sharing 2024-10-10 20:48:21 +02:00
Kima
f1ba5230fc added theme share error handling for ratelimit response 2024-10-10 18:11:41 +02:00
Kima
a50f449f7c added extra fields in news objects 2024-10-07 22:33:52 +02:00
Kima
fe3ed31830 added new analytics option to db 2024-10-06 23:56:56 +02:00
Kima
0ec33f8631 changed subscription document acceptance 2024-10-06 23:53:24 +02:00
Kima
6634010b97 made re-activation easier 2024-10-02 21:17:03 +02:00
Kima
816ddf58a2 changed how analytics work 2024-10-02 21:06:01 +02:00
Kima
d7741ca1c4 tried testing sync bug and fixed ads even more 2024-09-28 17:33:57 +02:00
Kima
a2cbe5d90b changed version number 2024-09-28 17:04:28 +02:00
Kima
7919d0e284 added grade delay to details and other small shit 2024-09-27 23:07:18 +02:00
Kima
92fe3b7dcd fixed yellow lines at profile image grade streak indicator 2024-09-27 22:02:31 +02:00
Kima
63fd37c31f the ads got more acceptable 2024-09-27 21:37:06 +02:00
Kima
aa10f0672e show ads only in even hours 2024-09-27 21:30:32 +02:00
Kima
a3694b59ec doing something with ads 2024-09-27 21:28:07 +02:00
Kima
9ecee0bb01 hide "ads" if user has plus 2024-09-27 20:51:01 +02:00
zypherift
51297ddc09 Merge branch 'dev' of github.com:refilc/naplo into dev 2024-08-22 22:46:13 +02:00
zypherift
4d64705e59 fix for padding 2024-08-22 22:46:11 +02:00
Márton Kiss
d642f19834 Merge pull request #130 from refilc/dev
dev to master
2024-08-22 22:08:37 +02:00
Kima
51b25395c1 version change 2024-08-22 22:08:11 +02:00
Kima
4474562538 login fix 2024-08-22 22:04:05 +02:00
zypherift
d93dce7857 maybe fix 2024-08-22 21:57:42 +02:00
zypherift
d6fe2812c7 fine tune anim speed 2024-08-22 21:27:22 +02:00
zypherift
ed0f69d155 add animation to fade in (pain) 2024-08-22 21:23:20 +02:00
zypherift
c4a17633f8 fix that, and add new progress indicator 2024-08-22 20:58:12 +02:00
zypherift
d426d4866a fix this 2024-08-22 20:57:56 +02:00
zypherift
89adf5a26f change trans from E-kreta to e-KRETA 2024-08-22 20:47:07 +02:00
zypherift
117ee63b18 Merge branch 'dev' of github.com:refilc/naplo into dev 2024-08-22 20:45:16 +02:00
zypherift
d27b5f8a51 re-revert new login 2024-08-22 20:45:14 +02:00
Kima
9671c250b9 Revert "change kretenlogin to widget"
This reverts commit 2d5c270641.
2024-08-21 23:33:15 +02:00
zypherift
2d5c270641 change kretenlogin to widget 2024-08-21 23:22:27 +02:00
zypherift
eda093a9b5 change trans 2024-08-21 21:54:49 +02:00
Kima
544e9c214a some modifications in login refresh 2024-08-18 13:27:52 +02:00
Kima
5c3dbcbd52 fixed privacy button and back button on new login screen 2024-08-18 13:00:40 +02:00
Kima
cd5f86db00 Merge branch 'dev' of github.com:refilc/naplo into dev 2024-08-17 23:34:43 +02:00
Kima
9adfe636d6 maybe finally fixed ios login problem (i hope so) 2024-08-17 23:34:39 +02:00
zypherift
210e8ce0d4 change gradient start 2024-08-16 18:14:14 +02:00
zypherift
b1a7deca4a change height again, and make text proportional 2024-08-16 17:49:44 +02:00
zypherift
fa96770c9c fix padding :3 2024-08-16 17:46:14 +02:00
zypherift
5be67693c2 recolor navbar, and change padding on login btn part 2024-08-16 17:38:26 +02:00
zypherift
9ee5e8a35e image again 2024-08-16 16:58:53 +02:00
zypherift
3372c1ffde Merge branch 'dev' of github.com:refilc/naplo into dev 2024-08-16 16:42:13 +02:00
zypherift
94989687fa add higher kualiti images 2024-08-16 16:40:03 +02:00
Kima
52b9b4f5db maybe fixed ios login 2024-08-16 14:40:48 +02:00
Kima
75a2fa3726 changed location of privacy button 2024-08-16 11:48:41 +02:00
Márton Kiss
2c1bde9398 Merge pull request #128 from refilc/dev
dev to master
2024-08-16 01:06:26 +02:00
Kima
fd9794f3bf changed build number 2024-08-16 00:49:00 +02:00
Kima
a673d3f1b3 finally the new login is completely working with refresh token as well 2024-08-16 00:25:46 +02:00
Kima
f2c8e869b5 moved news 2024-08-14 23:42:26 +02:00
Kima
b4f2d38e99 changed launch mode to in-app 2024-08-14 23:28:57 +02:00
Kima
d842b2d588 sticker map shit 2024-08-14 23:27:30 +02:00
zypherift
f4fd9a3c2f forgot this icon 2024-08-14 23:20:25 +02:00
zypherift
a215bd7313 create trans(lation), and finish url opening 2024-08-14 23:17:30 +02:00
zypherift
3211279a53 added trans, changed icon 2024-08-14 23:05:22 +02:00
zypherift
2d4e281682 rename classes, and create new file for other 2024-08-14 23:01:15 +02:00
zypherift
148f945f3b replace images with new ones 2024-08-14 21:11:12 +02:00
Kima
aaa783ac45 updated build number 2024-08-14 13:22:51 +02:00
Kima
d70f92d4e5 changed login button 2024-08-14 12:32:32 +02:00
Kima
0f08400d63 shit 2024-08-14 11:32:38 +02:00
Kima
140a8f8e78 revert shit in rfplus 2024-08-14 11:30:59 +02:00
zypherift
17c9d1c447 what 2024-08-14 02:54:03 +02:00
zypherift
d8e23d8fa9 asd 2024-08-14 02:44:29 +02:00
zypherift
c4491eb0c6 merged new login screen with web login 2024-08-14 02:44:05 +02:00
zypherift
4945468e02 yes 2024-08-14 02:09:14 +02:00
Kima
9845ae9539 changed version number 2024-08-14 00:55:24 +02:00
Kima
3a2fc67cb2 new login system complete, that's it for today :3 2024-08-14 00:53:12 +02:00
Kima
148a43663c made kreten web login work hah 2024-08-13 01:08:05 +02:00
44 changed files with 2058 additions and 1173 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

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: 696 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 KiB

After

Width:  |  Height:  |  Size: 616 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 KiB

After

Width:  |  Height:  |  Size: 1009 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 549 KiB

View File

@@ -18,11 +18,11 @@ import 'package:connectivity_plus/connectivity_plus.dart';
class FilcAPI { class FilcAPI {
// API base // API base
static const baseUrl = "https://api.refilc.hu"; static const baseUrl = "https://api.refilcapp.hu";
// Public API // Public API
static const schoolList = "$baseUrl/v3/public/school-list"; static const schoolList = "$baseUrl/v3/public/school-list";
static const news = "$baseUrl/v3/public/news"; static const news = "$baseUrl/v4/public/news";
static const supporters = "$baseUrl/v3/public/supporters"; static const supporters = "$baseUrl/v3/public/supporters";
// Private API // Private API
@@ -51,7 +51,7 @@ class FilcAPI {
static const gradeColorsByID = "$gradeColorsGet/"; static const gradeColorsByID = "$gradeColorsGet/";
// Payment API // Payment API
static const payment = "$baseUrl/v3/payment"; static const payment = "$baseUrl/v4/payment";
static const stripeSheet = "$payment/stripe-sheet"; static const stripeSheet = "$payment/stripe-sheet";
static Future<bool> checkConnectivity() async => static Future<bool> checkConnectivity() async =>
@@ -93,10 +93,14 @@ class FilcAPI {
"x-filc-id": settings.xFilcId, "x-filc-id": settings.xFilcId,
"user-agent": userAgent, "user-agent": userAgent,
// platform things // platform things
"rf-platform": Platform.operatingSystem, "rf-platform":
"rf-platform-version": Platform.operatingSystemVersion, settings.analyticsEnabled ? Platform.operatingSystem : "unknown",
"rf-app-version": "rf-platform-version": settings.analyticsEnabled
const String.fromEnvironment("APPVER", defaultValue: "?"), ? Platform.operatingSystemVersion
: "unknown",
"rf-app-version": settings.analyticsEnabled
? const String.fromEnvironment("APPVER", defaultValue: "?")
: "unknown",
"rf-uinid": settings.xFilcId, "rf-uinid": settings.xFilcId,
}; };
@@ -231,7 +235,7 @@ class FilcAPI {
} }
// sharing // sharing
static Future<void> addSharedTheme(SharedTheme theme) async { static Future<int> addSharedTheme(SharedTheme theme) async {
try { try {
theme.json.remove('json'); theme.json.remove('json');
theme.json['is_public'] = theme.isPublic.toString(); theme.json['is_public'] = theme.isPublic.toString();
@@ -263,13 +267,19 @@ class FilcAPI {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}, headers: {'Content-Type': 'application/x-www-form-urlencoded'},
); );
if (res.statusCode != 201) { // if (res.statusCode != 201) {
throw "HTTP ${res.statusCode}: ${res.body}"; // throw "HTTP ${res.statusCode}: ${res.body}";
// }
if (res.statusCode == 201) {
log('Shared theme successfully with ID: ${theme.id}');
} }
log('Shared theme successfully with ID: ${theme.id}'); return res.statusCode;
} on Exception catch (error, stacktrace) { } on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.addSharedTheme: $error $stacktrace"); log("ERROR: FilcAPI.addSharedTheme: $error $stacktrace");
return 696;
} }
} }
@@ -303,8 +313,7 @@ class FilcAPI {
return null; return null;
} }
static Future<void> addSharedGradeColors( static Future<int> addSharedGradeColors(SharedGradeColors gradeColors) async {
SharedGradeColors gradeColors) async {
try { try {
gradeColors.json.remove('json'); gradeColors.json.remove('json');
gradeColors.json['is_public'] = gradeColors.isPublic.toString(); gradeColors.json['is_public'] = gradeColors.isPublic.toString();
@@ -320,13 +329,19 @@ class FilcAPI {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}, headers: {'Content-Type': 'application/x-www-form-urlencoded'},
); );
if (res.statusCode != 201) { // if (res.statusCode != 201) {
throw "HTTP ${res.statusCode}: ${res.body}"; // throw "HTTP ${res.statusCode}: ${res.body}";
// }
if (res.statusCode == 201) {
log('Shared grade colors successfully with ID: ${gradeColors.id}');
} }
log('Shared grade colors successfully with ID: ${gradeColors.id}'); return res.statusCode;
} on Exception catch (error, stacktrace) { } on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.addSharedGradeColors: $error $stacktrace"); log("ERROR: FilcAPI.addSharedGradeColors: $error $stacktrace");
return 696;
} }
} }

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';
@@ -64,8 +65,12 @@ Future loginAPI({
parents: ['Teszt András', 'Teszt Linda'], parents: ['Teszt András', 'Teszt Linda'],
json: {"a": "b"}, json: {"a": "b"},
address: '1117 Budapest, Gábor Dénes utca 4.', address: '1117 Budapest, Gábor Dénes utca 4.',
gradeDelay: 0,
), ),
role: Role.parent, role: Role.parent,
accessToken: '',
accessTokenExpire: DateTime.now(),
refreshToken: '',
); );
if (onLogin != null) onLogin(user); if (onLogin != null) onLogin(user);
@@ -107,7 +112,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 +134,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 +156,134 @@ Future loginAPI({
name: student.name, name: student.name,
student: student, student: student,
role: JwtUtils.getRoleFromJWT(res["access_token"])!, role: JwtUtils.getRoleFromJWT(res["access_token"])!,
accessToken: res["access_token"],
accessTokenExpire: DateTime.now(),
refreshToken: '',
);
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;
}
}
}
}
break;
}
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);
// const splitSize = 1000;
// RegExp exp = RegExp(r"\w{" "$splitSize" "}");
// // String str = "0102031522";
// Iterable<Match> matches = exp.allMatches(res.toString());
// var list = matches.map((m) => m.group(0));
// list.forEach((e) {
// print(e);
// });
}
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,
accessToken: res["access_token"],
accessTokenExpire:
DateTime.now().add(Duration(seconds: (res["expires_in"] - 30))),
refreshToken: res["refresh_token"],
); );
if (onLogin != null) onLogin(user); if (onLogin != null) onLogin(user);
@@ -190,8 +326,6 @@ Future loginAPI({
} }
} }
} }
break;
}
return LoginState.failed; return LoginState.failed;
} }

View File

@@ -2,6 +2,7 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/status_provider.dart'; import 'package:refilc/api/providers/status_provider.dart';
import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/api/providers/user_provider.dart';
@@ -40,6 +41,12 @@ Future<void> syncAll(BuildContext context) {
StatusProvider statusProvider = StatusProvider statusProvider =
Provider.of<StatusProvider>(context, listen: false); Provider.of<StatusProvider>(context, listen: false);
// check if access token isn't expired
// if (user.user?.accessToken == null) {
// lock = false;
// return Future.value();
// }
List<Future<void>> tasks = []; List<Future<void>> tasks = [];
int taski = 0; int taski = 0;
@@ -50,6 +57,28 @@ Future<void> syncAll(BuildContext context) {
} }
tasks = [ tasks = [
// refresh login
syncStatus(() async {
// print(user.user?.accessTokenExpire);
// print('${user.user?.accessToken ?? "no token"} - ACCESS TOKEN');
if (user.user == null) return;
if (user.user!.accessTokenExpire.isBefore(DateTime.now())) {
String authRes = await Provider.of<KretaClient>(context, listen: false)
.refreshLogin() ??
'';
if (authRes != 'success') {
if (kDebugMode) print('ERROR: failed to refresh login');
lock = false;
return Future.value();
} else {
if (kDebugMode) print('INFO: access token refreshed');
}
} else {
if (kDebugMode) print('INFO: access token is not expired');
}
}()),
syncStatus(Provider.of<GradeProvider>(context, listen: false).fetch()), syncStatus(Provider.of<GradeProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<TimetableProvider>(context, listen: false) syncStatus(Provider.of<TimetableProvider>(context, listen: false)
.fetch(week: Week.current())), .fetch(week: Week.current())),
@@ -71,6 +100,8 @@ Future<void> syncAll(BuildContext context) {
if (studentJson == null) return; if (studentJson == null) return;
Student student = Student.fromJson(studentJson); Student student = Student.fromJson(studentJson);
// print(studentJson);
user.user?.name = student.name; user.user?.name = student.name;
// Store user // Store user
@@ -89,8 +120,6 @@ Future<void> syncAll(BuildContext context) {
return false; return false;
} }
return Future.wait(tasks).then((value) { return Future.wait(tasks).then((value) {
// Unlock // Unlock
lock = false; lock = false;

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

@@ -27,7 +27,8 @@ const settingsDB = DatabaseStruct("settings", {
"notifications_absences": int, "notifications_absences": int,
"notifications_messages": int, "notifications_messages": int,
"notifications_lessons": int, // notifications "notifications_lessons": int, // notifications
"x_filc_id": String, "graph_class_avg": int, "presentation_mode": int, "x_filc_id": String, "graph_class_avg": int,
"analytics_enabled": int, "presentation_mode": int,
"bell_delay": int, "bell_delay_enabled": int, "bell_delay": int, "bell_delay_enabled": int,
"grade_opening_fun": int, "icon_pack": String, "premium_scopes": String, "grade_opening_fun": int, "icon_pack": String, "premium_scopes": String,
"premium_token": String, "premium_login": String, "premium_token": String, "premium_login": String,
@@ -66,6 +67,8 @@ 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,
"access_token": String, "access_token_expire": String,
"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 +141,10 @@ Future<Database> initDB(DatabaseProvider database) async {
"role": 0, "role": 0,
"nickname": "", "nickname": "",
"picture": "", "picture": "",
"grade_streak": 0 "grade_streak": 0,
"access_token": "",
"access_token_expire": "",
"refresh_token": "",
}, },
); );
await migrateDB(db, struct: userDataDB, defaultValues: { await migrateDB(db, struct: userDataDB, defaultValues: {

View File

@@ -73,7 +73,9 @@ class NotificationsHelper {
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

@@ -7,6 +7,8 @@ class News {
String platform; String platform;
bool emergency; bool emergency;
DateTime expireDate; DateTime expireDate;
List<String>? appVersions;
String? specificAppId;
Map? json; Map? json;
News({ News({
@@ -18,6 +20,8 @@ class News {
required this.platform, required this.platform,
required this.emergency, required this.emergency,
required this.expireDate, required this.expireDate,
this.appVersions,
this.specificAppId,
this.json, this.json,
}); });
@@ -31,6 +35,10 @@ class News {
platform: json["platform"] ?? "", platform: json["platform"] ?? "",
emergency: json["emergency"] ?? false, emergency: json["emergency"] ?? false,
expireDate: DateTime.parse(json["expire_date"] ?? ''), expireDate: DateTime.parse(json["expire_date"] ?? ''),
appVersions: json["app_versions"] != null
? List<String>.from(json["app_versions"])
: null,
specificAppId: json["specific_app_id"],
json: json, json: json,
); );
} }

View File

@@ -60,6 +60,7 @@ class SettingsProvider extends ChangeNotifier {
UpdateChannel _updateChannel; UpdateChannel _updateChannel;
Config _config; Config _config;
String _xFilcId; String _xFilcId;
bool _analyticsEnabled;
bool _graphClassAvg; bool _graphClassAvg;
bool _goodStudent; bool _goodStudent;
bool _presentationMode; bool _presentationMode;
@@ -137,6 +138,7 @@ class SettingsProvider extends ChangeNotifier {
required UpdateChannel updateChannel, required UpdateChannel updateChannel,
required Config config, required Config config,
required String xFilcId, required String xFilcId,
required bool analyticsEnabled,
required bool graphClassAvg, required bool graphClassAvg,
required bool goodStudent, required bool goodStudent,
required bool presentationMode, required bool presentationMode,
@@ -208,6 +210,7 @@ class SettingsProvider extends ChangeNotifier {
_updateChannel = updateChannel, _updateChannel = updateChannel,
_config = config, _config = config,
_xFilcId = xFilcId, _xFilcId = xFilcId,
_analyticsEnabled = analyticsEnabled,
_graphClassAvg = graphClassAvg, _graphClassAvg = graphClassAvg,
_goodStudent = goodStudent, _goodStudent = goodStudent,
_presentationMode = presentationMode, _presentationMode = presentationMode,
@@ -297,6 +300,7 @@ class SettingsProvider extends ChangeNotifier {
updateChannel: UpdateChannel.values[map["update_channel"]], updateChannel: UpdateChannel.values[map["update_channel"]],
config: Config.fromJson(configMap ?? {}), config: Config.fromJson(configMap ?? {}),
xFilcId: map["x_filc_id"], xFilcId: map["x_filc_id"],
analyticsEnabled: map["analytics_enabled"] == 1,
graphClassAvg: map["graph_class_avg"] == 1, graphClassAvg: map["graph_class_avg"] == 1,
goodStudent: false, goodStudent: false,
presentationMode: map["presentation_mode"] == 1, presentationMode: map["presentation_mode"] == 1,
@@ -377,6 +381,7 @@ class SettingsProvider extends ChangeNotifier {
"notification_poll_interval": _notificationPollInterval, "notification_poll_interval": _notificationPollInterval,
"config": jsonEncode(config.json), "config": jsonEncode(config.json),
"x_filc_id": _xFilcId, "x_filc_id": _xFilcId,
"analytics_enabled": _analyticsEnabled ? 1 : 0,
"graph_class_avg": _graphClassAvg ? 1 : 0, "graph_class_avg": _graphClassAvg ? 1 : 0,
"presentation_mode": _presentationMode ? 1 : 0, "presentation_mode": _presentationMode ? 1 : 0,
"bell_delay_enabled": _bellDelayEnabled ? 1 : 0, "bell_delay_enabled": _bellDelayEnabled ? 1 : 0,
@@ -458,6 +463,7 @@ class SettingsProvider extends ChangeNotifier {
updateChannel: UpdateChannel.stable, updateChannel: UpdateChannel.stable,
config: Config.fromJson({}), config: Config.fromJson({}),
xFilcId: const Uuid().v4(), xFilcId: const Uuid().v4(),
analyticsEnabled: true,
graphClassAvg: false, graphClassAvg: false,
goodStudent: false, goodStudent: false,
presentationMode: false, presentationMode: false,
@@ -532,6 +538,7 @@ class SettingsProvider extends ChangeNotifier {
UpdateChannel get updateChannel => _updateChannel; UpdateChannel get updateChannel => _updateChannel;
Config get config => _config; Config get config => _config;
String get xFilcId => _xFilcId; String get xFilcId => _xFilcId;
bool get analyticsEnabled => _analyticsEnabled;
bool get graphClassAvg => _graphClassAvg; bool get graphClassAvg => _graphClassAvg;
bool get goodStudent => _goodStudent; bool get goodStudent => _goodStudent;
bool get presentationMode => _presentationMode; bool get presentationMode => _presentationMode;
@@ -604,6 +611,7 @@ class SettingsProvider extends ChangeNotifier {
UpdateChannel? updateChannel, UpdateChannel? updateChannel,
Config? config, Config? config,
String? xFilcId, String? xFilcId,
bool? analyticsEnabled,
bool? graphClassAvg, bool? graphClassAvg,
bool? goodStudent, bool? goodStudent,
bool? presentationMode, bool? presentationMode,
@@ -708,6 +716,9 @@ class SettingsProvider extends ChangeNotifier {
} }
if (config != null && config != _config) _config = config; if (config != null && config != _config) _config = config;
if (xFilcId != null && xFilcId != _xFilcId) _xFilcId = xFilcId; if (xFilcId != null && xFilcId != _xFilcId) _xFilcId = xFilcId;
if (analyticsEnabled != null && analyticsEnabled != _analyticsEnabled) {
_analyticsEnabled = analyticsEnabled;
}
if (graphClassAvg != null && graphClassAvg != _graphClassAvg) { if (graphClassAvg != null && graphClassAvg != _graphClassAvg) {
_graphClassAvg = graphClassAvg; _graphClassAvg = graphClassAvg;
} }

View File

@@ -17,6 +17,10 @@ class User {
String nickname; String nickname;
String picture; String picture;
int gradeStreak; int gradeStreak;
// new login method
String accessToken;
DateTime accessTokenExpire;
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 +36,9 @@ class User {
this.nickname = "", this.nickname = "",
this.picture = "", this.picture = "",
this.gradeStreak = 0, this.gradeStreak = 0,
required this.accessToken,
required this.accessTokenExpire,
required this.refreshToken,
}) { }) {
if (id != null) { if (id != null) {
this.id = id; this.id = id;
@@ -56,11 +63,16 @@ class User {
birth: DateTime.now(), birth: DateTime.now(),
yearId: '1', yearId: '1',
parents: [], parents: [],
gradeDelay: 0,
), ),
role: Role.values[map["role"] ?? 0], role: Role.values[map["role"] ?? 0],
nickname: map["nickname"] ?? "", nickname: map["nickname"] ?? "",
picture: map["picture"] ?? "", picture: map["picture"] ?? "",
gradeStreak: map["grade_streak"] ?? 0, gradeStreak: map["grade_streak"] ?? 0,
accessToken: map["access_token"] ?? "",
accessTokenExpire: DateTime.parse(
map["access_token_expire"] ?? DateTime.now().toIso8601String()),
refreshToken: map["refresh_token"] ?? "",
); );
} }
@@ -75,6 +87,10 @@ class User {
"role": role.index, "role": role.index,
"nickname": nickname, "nickname": nickname,
"picture": picture, "picture": picture,
"grade_streak": gradeStreak,
"access_token": accessToken,
"access_token_expire": accessTokenExpire.toIso8601String(),
"refresh_token": refreshToken,
}; };
} }

View File

@@ -178,7 +178,8 @@ Future<List<DateWidget>> getFilterWidgets(FilterType activeData,
// Ads // Ads
case FilterType.ads: case FilterType.ads:
if (adProvider.available) { if (adProvider.available) {
items = ad_filter.getWidgets(adProvider.ads); items = ad_filter.getWidgets(
adProvider.ads, context);
} }
break; break;
} }

View File

@@ -1,14 +1,48 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:refilc/models/ad.dart'; import 'package:refilc/models/ad.dart';
import 'package:refilc/ui/date_widget.dart'; import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_mobile_ui/common/widgets/ad/ad_tile.dart';
import 'package:refilc_mobile_ui/common/widgets/ad/ad_viewable.dart' as mobile; import 'package:refilc_mobile_ui/common/widgets/ad/ad_viewable.dart' as mobile;
import 'package:refilc_mobile_ui/plus/plus_screen.dart';
import 'package:refilc_plus/providers/plus_provider.dart';
import 'package:uuid/uuid.dart';
List<DateWidget> getWidgets(List<Ad> providerAds) { List<DateWidget> getWidgets(List<Ad> providerAds, BuildContext context) {
List<DateWidget> items = []; List<DateWidget> items = [];
bool hasPlus = Provider.of<PlusProvider>(context).hasPremium;
DateWidget plusWidget = DateWidget(
key: const Uuid().v4(),
date: DateTime.now(),
widget: AdTile(
Ad(
title: 'reFilc+',
description:
'Fizess elő reFilc+-ra, rejtsd el a hirdetéseket és támogasd az app működését!',
author: '',
logoUrl: Uri.parse('https://refilc.hu/image/brand/logo.png'),
overridePremium: false,
date: DateTime(2007, 6, 29, 9, 41),
expireDate: DateTime.now().add(const Duration(days: 11)),
launchUrl: Uri.parse('https://refilc.hu/plus'),
),
onTap: () => Navigator.of(context, rootNavigator: true)
.push(MaterialPageRoute(builder: (context) {
return const PlusScreen();
})),
padding: const EdgeInsets.symmetric(horizontal: 5.0),
showExternalIcon: false,
),
);
if (providerAds.isNotEmpty) { if (providerAds.isNotEmpty) {
for (var ad in providerAds) { for (var ad in providerAds) {
if (ad.date.isBefore(DateTime.now()) && if (ad.date.isBefore(DateTime.now()) &&
ad.expireDate.isAfter(DateTime.now())) { ad.expireDate.isAfter(DateTime.now()) &&
DateTime.now().hour.isOdd) {
if (!hasPlus || ad.overridePremium) {
providerAds.sort((a, b) => -a.date.compareTo(b.date)); providerAds.sort((a, b) => -a.date.compareTo(b.date));
items.add(DateWidget( items.add(DateWidget(
@@ -17,6 +51,19 @@ List<DateWidget> getWidgets(List<Ad> providerAds) {
widget: mobile.AdViewable(ad), widget: mobile.AdViewable(ad),
)); ));
} }
} else {
if (DateTime.now().weekday == DateTime.saturday &&
items.isEmpty &&
!hasPlus) {
items.add(plusWidget);
}
}
}
} else {
if (DateTime.now().weekday == DateTime.saturday &&
items.isEmpty &&
!hasPlus) {
items.add(plusWidget);
} }
} }

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.6+276
environment: environment:
sdk: ">=3.3.2 <=3.4.3" sdk: ">=3.3.2 <=3.4.3"
@@ -39,7 +39,7 @@ dependencies:
# ref: master # ref: master
path_provider: ^2.0.2 path_provider: ^2.0.2
permission_handler: ^11.0.1 permission_handler: ^11.0.1
share_plus: ^9.0.0 share_plus: ^10.0.3
connectivity_plus: ^6.0.3 connectivity_plus: ^6.0.3
flutter_displaymode: ^0.6.0 flutter_displaymode: ^0.6.0
quick_actions: ^1.0.1 quick_actions: ^1.0.1

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,17 +25,20 @@ 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;
KretaClient({ 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();
@@ -63,10 +67,14 @@ class KretaClient {
headerMap = {}; headerMap = {};
} }
if (accessToken == null || accessToken == '') {
accessToken = _user.user?.accessToken;
}
try { try {
http.Response? res; http.Response? res;
for (int i = 0; i < 3; i++) { for (int i = 0; i < 2; i++) {
if (autoHeader) { if (autoHeader) {
if (!headerMap.containsKey("authorization") && accessToken != null) { if (!headerMap.containsKey("authorization") && accessToken != null) {
headerMap["authorization"] = "Bearer $accessToken"; headerMap["authorization"] = "Bearer $accessToken";
@@ -81,13 +89,15 @@ class KretaClient {
if (res.statusCode == 401) { if (res.statusCode == 401) {
headerMap.remove("authorization"); headerMap.remove("authorization");
await refreshLogin(); print("DEBUG: 401 error, refreshing login");
print("DEBUG: 401 error, URL: $url");
// await refreshLogin();
} else { } else {
break; break;
} }
// Wait before retrying // Wait before retrying
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 1500));
} }
if (res == null) throw "Login error"; if (res == null) throw "Login error";
@@ -125,10 +135,14 @@ class KretaClient {
headerMap = {}; headerMap = {};
} }
if (accessToken == null || accessToken == '') {
accessToken = _user.user?.accessToken;
}
try { try {
http.Response? res; http.Response? res;
for (int i = 0; i < 3; i++) { for (int i = 0; i < 2; i++) {
if (autoHeader) { if (autoHeader) {
if (!headerMap.containsKey("authorization") && accessToken != null) { if (!headerMap.containsKey("authorization") && accessToken != null) {
headerMap["authorization"] = "Bearer $accessToken"; headerMap["authorization"] = "Bearer $accessToken";
@@ -146,11 +160,14 @@ class KretaClient {
res = await client.post(Uri.parse(url), headers: headerMap, body: body); res = await client.post(Uri.parse(url), headers: headerMap, body: body);
if (res.statusCode == 401) { if (res.statusCode == 401) {
await refreshLogin(); // await refreshLogin();
headerMap.remove("authorization"); headerMap.remove("authorization");
} else { } else {
break; break;
} }
// Wait before retrying
await Future.delayed(const Duration(milliseconds: 1500));
} }
if (res == null) throw "Login error"; if (res == null) throw "Login error";
@@ -183,6 +200,10 @@ class KretaClient {
headerMap = {}; headerMap = {};
} }
if (accessToken == null || accessToken == '') {
accessToken = _user.user?.accessToken;
}
try { try {
http.StreamedResponse? res; http.StreamedResponse? res;
@@ -213,7 +234,7 @@ class KretaClient {
if (res.statusCode == 401) { if (res.statusCode == 401) {
headerMap.remove("authorization"); headerMap.remove("authorization");
await refreshLogin(); // await refreshLogin();
} else { } else {
break; break;
} }
@@ -232,65 +253,86 @@ 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);
print("invalid refresh token (invalid_grant)");
// return error
return "refresh_token_expired";
} }
} }
_loginRefreshing = false; if (res.containsKey("access_token")) {
accessToken = res["access_token"];
loginUser.accessToken = res["access_token"];
loginUser.accessTokenExpire =
DateTime.now().add(Duration(seconds: (res["expires_in"] - 30)));
_database.store.storeUser(loginUser);
_user.refresh();
}
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;
print('successful refresh');
return 'success';
} else {
// _loginRefreshing = false;
return null;
}
} else {
// _loginRefreshing = false;
return null;
}
// return null;
} }
Future<void> logout() async { Future<void> logout() async {

View File

@@ -11,6 +11,8 @@ class Student {
String? address; String? address;
String? groupId; String? groupId;
List<String> parents; List<String> parents;
int gradeDelay;
String? bankAccount;
// List<String> parentsPhone; // List<String> parentsPhone;
String? className; String? className;
@@ -22,6 +24,8 @@ class Student {
required this.yearId, required this.yearId,
this.address, this.address,
required this.parents, required this.parents,
required this.gradeDelay,
this.bankAccount,
// required this.parentsPhone, // required this.parentsPhone,
this.json, this.json,
}); });
@@ -57,6 +61,10 @@ class Student {
: null : null
: null, : null,
parents: parents, parents: parents,
gradeDelay: json["Intezmeny"]["TestreszabasBeallitasok"]
["ErtekelesekMegjelenitesenekKesleltetesenekMerteke"] ??
0,
bankAccount: json["Bankszamla"]["BankszamlaSzam"],
json: json, json: json,
); );
} }

View File

@@ -19,7 +19,7 @@ class ShareProvider extends ChangeNotifier {
// } // }
// themes // themes
Future<SharedTheme> shareCurrentTheme( Future<(SharedTheme?, int)> shareCurrentTheme(
BuildContext context, { BuildContext context, {
bool isPublic = false, bool isPublic = false,
bool shareNick = true, bool shareNick = true,
@@ -56,9 +56,13 @@ class ShareProvider extends ChangeNotifier {
}; };
SharedTheme theme = SharedTheme.fromJson(themeJson, gradeColors); SharedTheme theme = SharedTheme.fromJson(themeJson, gradeColors);
FilcAPI.addSharedTheme(theme); int shareResult = await FilcAPI.addSharedTheme(theme);
return theme; if (shareResult == 201) {
return (theme, 201);
} else {
return (null, shareResult);
}
} }
Future<SharedTheme?> getThemeById(BuildContext context, Future<SharedTheme?> getThemeById(BuildContext context,
@@ -142,7 +146,7 @@ class ShareProvider extends ChangeNotifier {
} }
// grade colors // grade colors
Future<SharedGradeColors> shareCurrentGradeColors( Future<(SharedGradeColors?, int)> shareCurrentGradeColors(
BuildContext context, { BuildContext context, {
bool isPublic = false, bool isPublic = false,
bool shareNick = true, bool shareNick = true,
@@ -162,9 +166,13 @@ class ShareProvider extends ChangeNotifier {
}; };
SharedGradeColors gradeColors = SharedGradeColors.fromJson(gradeColorsJson); SharedGradeColors gradeColors = SharedGradeColors.fromJson(gradeColorsJson);
FilcAPI.addSharedGradeColors(gradeColors); int shareResult = await FilcAPI.addSharedGradeColors(gradeColors);
return gradeColors; if (shareResult == 201) {
return (gradeColors, 201);
} else {
return (null, shareResult);
}
} }
Future<SharedGradeColors?> getGradeColorsById(BuildContext context, Future<SharedGradeColors?> getGradeColorsById(BuildContext context,

View File

@@ -267,10 +267,14 @@ class _ProfileImageState extends State<ProfileImage> {
offset: Offset(-widget.radius / 4, -widget.radius / 4), offset: Offset(-widget.radius / 4, -widget.radius / 4),
child: Container( child: Container(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: Text( child: Image.asset(
'🔥', 'assets/images/apple_fire_emoji.png',
style: TextStyle(fontSize: widget.radius * 0.8), width: widget.radius,
), )
// Text(
// '🔥',
// style: TextStyle(fontSize: widget.radius * 0.8),
// ),
), ),
), ),
), ),

View File

@@ -5,11 +5,13 @@ import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
class AdTile extends StatelessWidget { class AdTile extends StatelessWidget {
const AdTile(this.ad, {super.key, this.onTap, this.padding}); const AdTile(this.ad,
{super.key, this.onTap, this.padding, this.showExternalIcon = true});
final Ad ad; final Ad ad;
final Function()? onTap; final Function()? onTap;
final EdgeInsetsGeometry? padding; final EdgeInsetsGeometry? padding;
final bool showExternalIcon;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -28,6 +30,7 @@ class AdTile extends StatelessWidget {
Text( Text(
ad.description, ad.description,
style: TextStyle( style: TextStyle(
fontSize: 14.5,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.of(context).text.withOpacity(0.7), color: AppColors.of(context).text.withOpacity(0.7),
), ),
@@ -38,6 +41,8 @@ class AdTile extends StatelessWidget {
? ClipRRect( ? ClipRRect(
borderRadius: BorderRadius.circular(50.0), borderRadius: BorderRadius.circular(50.0),
child: Image.network( child: Image.network(
width: 42.0,
height: 42.0,
ad.logoUrl.toString(), ad.logoUrl.toString(),
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
ad.logoUrl = null; ad.logoUrl = null;
@@ -46,7 +51,12 @@ class AdTile extends StatelessWidget {
), ),
) )
: null, : null,
trailing: const Icon(FeatherIcons.externalLink), trailing: showExternalIcon
? const Icon(
FeatherIcons.externalLink,
size: 20.0,
)
: null,
), ),
); );
} }

View File

@@ -12,6 +12,7 @@ class AdViewable extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AdTile( return AdTile(
padding: const EdgeInsets.symmetric(horizontal: 5.0),
ad, ad,
onTap: () => launchUrl( onTap: () => launchUrl(
ad.launchUrl, ad.launchUrl,

View File

@@ -51,7 +51,7 @@ class ActiveSponsorCard extends StatelessWidget {
return const SizedBox(); return const SizedBox();
} }
Color? glow = Colors.white; //TODO: only temp fix kima (idk what but die) Color? glow = Colors.white;
switch (level) { switch (level) {
case PremiumFeatureLevel.cap: case PremiumFeatureLevel.cap:

View File

@@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc_mobile_ui/common/action_button.dart';
import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/providers/plus_provider.dart';
import 'package:refilc_plus/ui/mobile/plus/activation_view/activation_view.dart'; import 'package:refilc_plus/ui/mobile/plus/activation_view/activation_view.dart';
import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart'; import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.dart';
class PlusPlanCard extends StatelessWidget { class PlusPlanCard extends StatelessWidget {
const PlusPlanCard({ const PlusPlanCard({
@@ -38,33 +40,39 @@ class PlusPlanCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
if (!docsAccepted) { // if (!docsAccepted) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar( // ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text( // content: Text(
"El kell fogadnod az ÁSZF-et és az Adatkezelési Tájékoztatót!", // "El kell fogadnod az ÁSZF-et és az Adatkezelési Tájékoztatót!",
style: // style:
TextStyle(color: Colors.black, fontWeight: FontWeight.bold), // TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
), // ),
backgroundColor: Colors.white, // backgroundColor: Colors.white,
)); // ));
return; // return;
} // }
if (Provider.of<SettingsProvider>(context, listen: false).xFilcId == if (Provider.of<SettingsProvider>(context, listen: false).xFilcId ==
"none") { "none") {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar( Provider.of<SettingsProvider>(context, listen: false)
content: Text( .update(xFilcId: const Uuid().v4(), store: true);
"Be kell kapcsolnod a Névtelen Analitikát a beállítások főoldalán, mielőtt reFilc+ előfizetést vásárolnál!",
style:
TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
),
backgroundColor: Colors.white,
));
return;
} }
// if (Provider.of<SettingsProvider>(context, listen: false).xFilcId ==
// "none") {
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
// content: Text(
// "Be kell kapcsolnod a Névtelen Analitikát a beállítások főoldalán, mielőtt reFilc+ előfizetést vásárolnál!",
// style:
// TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
// ),
// backgroundColor: Colors.white,
// ));
// return;
// }
if (Provider.of<PlusProvider>(context, listen: false).hasPremium) { if (Provider.of<PlusProvider>(context, listen: false).hasPremium) {
if (!active) { if (!active) {
launchUrl( launchUrl(
@@ -77,10 +85,30 @@ class PlusPlanCard extends StatelessWidget {
return; return;
} }
Navigator.of(context).push(MaterialPageRoute(builder: (context) { showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
title: Text('docs'.i18n),
content: Text('docs_acceptance'.i18n),
actions: [
ActionButton(
label: "next".i18n,
onTap: () {
// pop dialog
Navigator.of(context).pop();
// start payment process
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return PremiumActivationView(product: id); return PremiumActivationView(product: id);
})); }));
}, },
),
],
),
);
},
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: gradient, gradient: gradient,

View File

@@ -1,5 +1,6 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'package:flutter/services.dart';
import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart'; import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart';
import 'package:refilc_mobile_ui/plus/components/plan_card.dart'; import 'package:refilc_mobile_ui/plus/components/plan_card.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
@@ -335,6 +336,46 @@ class PlusScreenState extends State<PlusScreen> {
contentPadding: contentPadding:
const EdgeInsets.only(left: 15.0, right: 10.0), const EdgeInsets.only(left: 15.0, right: 10.0),
onTap: () async { onTap: () async {
// try clipboard re-activation
final data = await Clipboard.getData("text/plain");
if (data != null &&
data.text != null &&
data.text != "") {
// activate using clipboard data
final result = await context
.read<PlusProvider>()
.auth
.finishAuth(data.text!);
if (!result && mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
"Sikertelen aktiválás. Kérlek próbáld újra később!",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold),
),
backgroundColor: Colors.red,
));
} else {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
"Sikeres aktiválás!",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold),
),
backgroundColor: Colors.green,
));
Future.delayed(const Duration(seconds: 2),
() => Navigator.of(context).pop());
}
}
// try re-activation using refresh
final result = await context final result = await context
.read<PlusProvider>() .read<PlusProvider>()
.auth .auth
@@ -379,41 +420,41 @@ class PlusScreenState extends State<PlusScreen> {
), ),
), ),
// aszf warning // aszf warning
const SizedBox( // const SizedBox(
height: 18.0, // height: 18.0,
), // ),
Container( // Container(
decoration: BoxDecoration( // decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0), // borderRadius: BorderRadius.circular(16.0),
border: Border.all( // border: Border.all(
color: Colors.black.withOpacity(0.2), // color: Colors.black.withOpacity(0.2),
), // ),
), // ),
child: CheckboxListTile( // child: CheckboxListTile(
side: // side:
const BorderSide(color: Colors.black, width: 2.0), // const BorderSide(color: Colors.black, width: 2.0),
contentPadding: // contentPadding:
const EdgeInsets.only(left: 15.0, right: 10.0), // const EdgeInsets.only(left: 15.0, right: 10.0),
value: docsAccepted, // value: docsAccepted,
onChanged: (value) { // onChanged: (value) {
setState(() { // setState(() {
docsAccepted = !docsAccepted; // docsAccepted = !docsAccepted;
}); // });
}, // },
// title: Text( // // title: Text(
// 'show_lifetime'.i18n, // // 'show_lifetime'.i18n,
// style: const TextStyle( // // style: const TextStyle(
// color: Colors.black, // // color: Colors.black,
// fontWeight: FontWeight.w500, // // fontWeight: FontWeight.w500,
// // ),
// // ),
// subtitle: const Text(
// 'Elfogadod a reFilc előfizetésekkel kapcsolatos Általános Szerződési Feltételeit (elérhető az alábbi link-en: filc.one/pay-terms), valamint Adatkezelési Tájékoztatónkat (elérhető az alábbi link-en: filc.one/pay-privacy)?',
// textAlign: TextAlign.start,
// style: TextStyle(color: Colors.black),
// ),
// ), // ),
// ), // ),
subtitle: const Text(
'Elfogadod a reFilc előfizetésekkel kapcsolatos Általános Szerződési Feltételeit (elérhető az alábbi link-en: filc.one/pay-terms), valamint Adatkezelési Tájékoztatónkat (elérhető az alábbi link-en: filc.one/pay-privacy)?',
textAlign: TextAlign.start,
style: TextStyle(color: Colors.black),
),
),
),
// CheckboxListTile(value: false, onChanged: onChanged) // CheckboxListTile(value: false, onChanged: onChanged)
// Padding( // Padding(
// padding: const EdgeInsets.symmetric(horizontal: 12.0), // padding: const EdgeInsets.symmetric(horizontal: 12.0),

View File

@@ -47,13 +47,19 @@ extension SettingsLocalization on String {
"rfp_16": "Private leaks and informations about upcoming features", "rfp_16": "Private leaks and informations about upcoming features",
"rfp_17": "Grade exporting", "rfp_17": "Grade exporting",
"rfp_18": "Viewing exported grades", "rfp_18": "Viewing exported grades",
// docs popup
"docs": "Documents",
"docs_acceptance":
"By pressing the \"Next\" button, you accept reFilc's Terms and Conditions for subscriptions (available at the following link: filc.one/pay-terms) and our Privacy Policy (available at the following link: filc.one/pay-privacy).",
"next": "Next",
// other // other
"and": " and ", "and": " and ",
"every": "Every ", "every": "Every ",
"benefit": " benefit", "benefit": " benefit",
"show_lifetime": "Show Lifetime Plans", "show_lifetime": "Show Lifetime Plans",
"more_soon": "More coming soon...", "more_soon": "More coming soon...",
"faq_dc": "To redeem your benefits, contact us on Discord in DMs!", "faq_dc":
"To redeem your Discord-related benefits, contact us on Discord in DMs!",
"reactivate": "Reactivate Existing Subscription", "reactivate": "Reactivate Existing Subscription",
}, },
"hu_hu": { "hu_hu": {
@@ -100,6 +106,11 @@ extension SettingsLocalization on String {
"rfp_16": "Privát betekintések és információk közelgő újításokról", "rfp_16": "Privát betekintések és információk közelgő újításokról",
"rfp_17": "Jegy exportálás", "rfp_17": "Jegy exportálás",
"rfp_18": "Exportált jegyek megtekintése", "rfp_18": "Exportált jegyek megtekintése",
// docs popup
"docs": "Dokumentumok",
"docs_acceptance":
"A \"Tovább\" gombra kattintva elfogadod a reFilc előfizetésekkel kapcsolatos Általános Szerződési Feltételeit (elérhető az alábbi link-en: filc.one/pay-terms), valamint Adatkezelési Tájékoztatónkat (elérhető az alábbi link-en: filc.one/pay-privacy).",
"next": "Tovább",
// other // other
"and": " és ", "and": " és ",
"every": "Minden ", "every": "Minden ",
@@ -107,7 +118,7 @@ extension SettingsLocalization on String {
"show_lifetime": "Örökre szóló csomagok", "show_lifetime": "Örökre szóló csomagok",
"more_soon": "Hamarosan mégtöbb finomság...", "more_soon": "Hamarosan mégtöbb finomság...",
"faq_dc": "faq_dc":
"Az előnyök beváltásához írj nekünk Discord-on privát üzenetet!", "A Discord-al kapcsolatos előnyök beváltásához írj nekünk Discord-on privát üzenetet!",
"reactivate": "Meglévő előfizetés újraaktiválása", "reactivate": "Meglévő előfizetés újraaktiválása",
}, },
"de_de": { "de_de": {
@@ -156,6 +167,11 @@ extension SettingsLocalization on String {
"rfp_16": "Private Leaks und Informationen über kommende Funktionen", "rfp_16": "Private Leaks und Informationen über kommende Funktionen",
"rfp_17": "Notenexport", "rfp_17": "Notenexport",
"rfp_18": "Anzeigen exportierter Noten", "rfp_18": "Anzeigen exportierter Noten",
// docs popup
"docs": "Dokumente",
"docs_acceptance":
"Durch Drücken der Schaltfläche \"Weiter\" akzeptieren Sie die Allgemeinen Geschäftsbedingungen von reFilc für Abonnements (verfügbar unter folgendem Link: filc.one/pay-terms) und unsere Datenschutzrichtlinie (verfügbar unter folgendem Link: filc.one/pay-privacy).",
"next": "Weiter",
// other // other
"and": " und ", "and": " und ",
"every": "Jeder ", "every": "Jeder ",

View File

@@ -1,140 +1,103 @@
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 KretenLoginWidget extends StatefulWidget {
const KretenLoginScreen({super.key}); const KretenLoginWidget({super.key, required this.onLogin});
// final String selectedSchool;
final void Function(String code) onLogin;
@override @override
State<KretenLoginScreen> createState() => _KretenLoginScreenState(); State<KretenLoginWidget> createState() => _KretenLoginWidgetState();
} }
class _KretenLoginScreenState extends State<KretenLoginScreen> { class _KretenLoginWidgetState extends State<KretenLoginWidget>
with TickerProviderStateMixin {
late final WebViewController controller; late final WebViewController controller;
late AnimationController _animationController;
var loadingPercentage = 0; var loadingPercentage = 0;
var currentUrl = ''; var currentUrl = '';
bool _hasFadedIn = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_animationController = AnimationController(
vsync: this, // Use the TickerProviderStateMixin
duration: const Duration(milliseconds: 350),
);
controller = WebViewController() controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted) ..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(NavigationDelegate( ..setNavigationDelegate(NavigationDelegate(
onPageStarted: (url) async { onNavigationRequest: (n) async {
if (n.url.startsWith('https://mobil.e-kreta.hu')) {
setState(() { setState(() {
loadingPercentage = 0; loadingPercentage = 0;
currentUrl = url; currentUrl = n.url;
}); });
List<String> requiredThings = url // final String instituteCode = widget.selectedSchool;
// if (!n.url.startsWith(
// 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) {
// return;
// }
List<String> requiredThings = n.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), () {
// Navigator.of(context).pop();
// });
// Navigator.of(context).pop();
// actual login (token grant) logic return NavigationDecision.prevent;
Map<String, String> headers = { } else {
"content-type": "application/x-www-form-urlencoded", return NavigationDecision.navigate;
"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);
} }
},
onPageStarted: (url) async {
// setState(() {
// loadingPercentage = 0;
// currentUrl = url;
// });
// if (res.containsKey("error")) { // // final String instituteCode = widget.selectedSchool;
// if (res["error"] == "invalid_grant") { // if (!url.startsWith(
// print("ERROR: invalid_grant"); // 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) {
// return; // 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); // List<String> requiredThings = url
// .replaceAll(
// 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=',
// '')
// .replaceAll(
// '&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(':');
// // Store User in the database // String code = requiredThings[0];
// await Provider.of<DatabaseProvider>(context, listen: false) // // String sessionState = requiredThings[1];
// .store
// .storeUser(user);
// Provider.of<UserProvider>(context, listen: false)
// .addUser(user);
// Provider.of<UserProvider>(context, listen: false)
// .setUser(user.id);
// // Get user data // widget.onLogin(code);
// try { // // Future.delayed(const Duration(milliseconds: 500), () {
// await Future.wait([ // // Navigator.of(context).pop();
// Provider.of<GradeProvider>(context, listen: false) // // });
// .fetch(), // // Navigator.of(context).pop();
// 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 +112,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}
); );
} }
@@ -160,24 +123,57 @@ class _KretenLoginScreenState extends State<KretenLoginScreen> {
// Nonce nonce = getNonce(nonceStr, ); // Nonce nonce = getNonce(nonceStr, );
// } // }
@override
void dispose() {
// Step 3: Dispose of the animation controller
_animationController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( // Trigger the fade-in animation only once when loading reaches 100%
appBar: AppBar( if (loadingPercentage == 100 && !_hasFadedIn) {
leading: const BackButton(), _animationController.forward(); // Play the animation
title: const Text('e-KRÉTA Bejelentkezés'), _hasFadedIn =
), true; // Set the flag to true, so the animation is not replayed
body: Stack( }
return Stack(
children: [ children: [
WebViewWidget( // Webview that will be displayed only when the loading is 100%
if (loadingPercentage == 100)
FadeTransition(
opacity: Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeIn,
),
),
child: WebViewWidget(
controller: controller, controller: controller,
), ),
),
// Show the CircularProgressIndicator while loading is not 100%
if (loadingPercentage < 100) if (loadingPercentage < 100)
LinearProgressIndicator( Center(
value: loadingPercentage / 100.0, child: TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: loadingPercentage / 100.0),
duration: const Duration(milliseconds: 300),
builder: (context, double value, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
value: value, // Smoothly animates the progress
), ),
], ],
);
},
), ),
),
],
); );
} }
} }

View File

@@ -1,17 +1,18 @@
// import 'dart:async'; // import 'dart:async';
import 'package:refilc/api/client.dart'; import 'package:refilc/api/client.dart';
import 'dart:io' show Platform;
import 'package:refilc/api/login.dart'; 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,6 +28,7 @@ class LoginScreenState extends State<LoginScreen> {
final passwordController = TextEditingController(); final passwordController = TextEditingController();
final schoolController = SchoolInputController(); final schoolController = SchoolInputController();
final _scrollController = ScrollController(); final _scrollController = ScrollController();
final codeController = TextEditingController();
LoginState _loginState = LoginState.normal; LoginState _loginState = LoginState.normal;
bool showBack = false; bool showBack = false;
@@ -73,225 +75,292 @@ class LoginScreenState extends State<LoginScreen> {
}); });
} }
double paddingTop = 0;
@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);
if (Platform.isIOS) {
paddingTop = 0;
} else if (Platform.isAndroid) {
paddingTop = 20;
}
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: EdgeInsets.only(left: 24, top: paddingTop),
child: Image.asset( child: Row(
children: [
Image.asset(
'assets/icons/ic_rounded.png', 'assets/icons/ic_rounded.png',
width: 50.0, width: 30.0,
), ),
), const SizedBox(width: 8),
const Text(
// texts
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10.0,
vertical: 12.0,
),
child: Text(
'reFilc', 'reFilc',
style: TextStyle( style: TextStyle(
color: AppColors.of(context).loginPrimary, color: Color(0xFF050B15),
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, 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: [
// 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, fontWeight: FontWeight.bold,
fontSize: 20.0, fontFamily: 'Montserrat'),
),
Material(
type: MaterialType.transparency,
child: showBack
? BackButton(color: AppColors.of(context).text)
: const SizedBox(height: 48.0),
),
],
)), )),
onPressed: () => _loginAPI(context: context), 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: 280,
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,
isScrollControlled:
true, // This ensures the modal accommodates input fields properly
builder: (BuildContext context) {
return Container(
height: MediaQuery.of(context)
.size
.height *
0.9 +
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
.symmetric(
vertical: 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,
),
),
Expanded(
child: Padding(
padding:
const EdgeInsets.only(
right: 14,
left: 14,
bottom: 24),
child: ClipRRect(
borderRadius:
BorderRadius
.circular(16),
child: Container(
decoration:
BoxDecoration(
borderRadius:
BorderRadius
.circular(
16),
),
child:
KretenLoginWidget(
onLogin:
(String code) {
codeController
.text = code;
Navigator.of(
context)
.pop();
},
),
),
),
),
)
],
),
);
},
).then((value) {
// After closing the modal bottom sheet, check if the code is set
if (codeController.text.isNotEmpty) {
// Call your API after retrieving the code
_NewLoginAPI(context: context);
}
});
},
child: Text(
"login_w_kreta_acc".i18n,
style: const TextStyle(
fontFamily: 'Montserrat',
fontSize: 16,
fontWeight: FontWeight.w700),
)),
),
),
const SizedBox(height: 19),
// privacy policy
GestureDetector(
onTap: () => PrivacyView.show(context),
child: Text(
'privacy'.i18n,
style: TextStyle(
color: AppColors.of(context).loginSecondary,
fontWeight: FontWeight.w500,
fontSize: 14.0,
),
),
),
],
),
),
),
],
), ),
// error messages
if (_loginState == LoginState.missingFields || if (_loginState == LoginState.missingFields ||
_loginState == LoginState.invalidGrant || _loginState == LoginState.invalidGrant ||
_loginState == LoginState.failed) _loginState == LoginState.failed)
@@ -312,8 +381,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 +393,6 @@ class LoginScreenState extends State<LoginScreen> {
), ),
), ),
), ),
const Spacer(
flex: 1,
),
], ],
), ),
), ),
@@ -339,24 +402,65 @@ 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;
// // }
// }),
// );
// }
// 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 your e-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": "Bejelentkezés e-KRÉTA fió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 anmelden",
}, },
}; };

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

@@ -56,6 +56,16 @@ class AccountView extends StatelessWidget {
Detail( Detail(
title: "parents".plural(user.student.parents.length), title: "parents".plural(user.student.parents.length),
description: user.student.parents.join(", ")), description: user.student.parents.join(", ")),
if (user.student.gradeDelay > 0)
Detail(
title: "grade_delay".i18n,
description: "hrs".i18n.fill([user.student.gradeDelay]),
),
// if ((user.student.bankAccount ?? "").isNotEmpty)
// Detail(
// title: "bank_account".i18n,
// description: (user.student.bankAccount ?? "not_provided".i18n),
// ),
const SizedBox( const SizedBox(
height: 10.0, height: 10.0,
), ),

View File

@@ -10,6 +10,8 @@ extension Localization on String {
"address": "Home address", "address": "Home address",
"parents": "Parent(s)", "parents": "Parent(s)",
"parents_phone": "Parents' phone number: ", "parents_phone": "Parents' phone number: ",
"grade_delay": "Grade visibility delay",
"hrs": "%s hour(s)",
}, },
"hu_hu": { "hu_hu": {
"birthdate": "Születési dátum", "birthdate": "Születési dátum",
@@ -17,6 +19,8 @@ extension Localization on String {
"class": "Osztály", "class": "Osztály",
"address": "Lakcím", "address": "Lakcím",
"parents": "Szülő(k)", "parents": "Szülő(k)",
"grade_delay": "Jegy megjelenítési késleltetés",
"hrs": "%s óra",
}, },
"de_de": { "de_de": {
"birthdate": "Geburtsdatum", "birthdate": "Geburtsdatum",
@@ -24,6 +28,8 @@ extension Localization on String {
"class": "Klasse", "class": "Klasse",
"address": "Wohnanschrift", "address": "Wohnanschrift",
"parents": "Elter(n)", "parents": "Elter(n)",
"grade_delay": "Notenverzögerung",
"hrs": "%s Stunde(n)",
}, },
}; };

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';
@@ -41,6 +42,7 @@ import 'package:refilc_mobile_ui/screens/settings/accounts/account_view.dart';
import 'package:refilc_mobile_ui/screens/settings/notifications_screen.dart'; import 'package:refilc_mobile_ui/screens/settings/notifications_screen.dart';
import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart'; import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart'; import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
import 'package:refilc_mobile_ui/screens/settings/submenu/code_scanner.dart';
import 'package:refilc_mobile_ui/screens/settings/submenu/extras_screen.dart'; import 'package:refilc_mobile_ui/screens/settings/submenu/extras_screen.dart';
import 'package:refilc_mobile_ui/screens/settings/submenu/personalize_screen.dart'; import 'package:refilc_mobile_ui/screens/settings/submenu/personalize_screen.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@@ -105,9 +107,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 +147,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();
}, },
@@ -729,10 +783,14 @@ class SettingsScreenState extends State<SettingsScreen>
color: AppColors.of(context).text.withOpacity(0.75), color: AppColors.of(context).text.withOpacity(0.75),
), ),
), ),
leading: const Text( leading: Image.asset(
"🔥", 'assets/images/apple_fire_emoji.png',
style: TextStyle(fontSize: 22.0), width: 24.0,
), ),
// leading: const Text(
// "🔥",
// style: TextStyle(fontSize: 22.0),
// ),
trailing: Text( trailing: Text(
"${user.gradeStreak}", "${user.gradeStreak}",
style: TextStyle( style: TextStyle(
@@ -749,6 +807,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 +963,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 +982,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 +1006,60 @@ class SettingsScreenState extends State<SettingsScreen>
), ),
), ),
// other secion
SplittedPanel(
title: Text("other".i18n),
cardPadding: const EdgeInsets.all(4.0),
children: [
PanelButton(
leading: Icon(
Icons.qr_code,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
title: Text("qr_scanner".i18n),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const CodeScannerScreen(),
),
),
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(4.0),
),
),
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(4.0),
bottom: Radius.circular(12.0),
),
),
],
),
// // extra settings // // extra settings
// Padding( // Padding(
// padding: // padding:
@@ -902,19 +1092,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 +1104,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),
), ),
), ),
@@ -1018,7 +1195,7 @@ class SettingsScreenState extends State<SettingsScreen>
secondary: Icon( secondary: Icon(
FeatherIcons.barChart2, FeatherIcons.barChart2,
size: 22.0, size: 22.0,
color: settings.xFilcId != "none" color: settings.analyticsEnabled
? AppColors.of(context).text.withOpacity(0.95) ? AppColors.of(context).text.withOpacity(0.95)
: AppColors.of(context).text.withOpacity(.25), : AppColors.of(context).text.withOpacity(.25),
), ),
@@ -1028,28 +1205,29 @@ class SettingsScreenState extends State<SettingsScreen>
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 16.0, fontSize: 16.0,
color: AppColors.of(context).text.withOpacity( color: AppColors.of(context).text.withOpacity(
settings.xFilcId != "none" ? 1.0 : .5), settings.analyticsEnabled ? 1.0 : .5),
), ),
), ),
subtitle: Text( subtitle: Text(
"Anonymous Usage Analytics".i18n, "Anonymous Usage Analytics".i18n,
style: TextStyle( style: TextStyle(
color: AppColors.of(context).text.withOpacity( color: AppColors.of(context)
settings.xFilcId != "none" ? .5 : .2), .text
.withOpacity(settings.analyticsEnabled ? .5 : .2),
), ),
), ),
onChanged: (v) { onChanged: (v) {
String newId; // String newId;
if (v == false) { // if (v == false) {
newId = "none"; // newId = "none";
} else if (settings.xFilcId == "none") { // } else if (settings.xFilcId == "none") {
newId = SettingsProvider.defaultSettings().xFilcId; // newId = SettingsProvider.defaultSettings().xFilcId;
} else { // } else {
newId = settings.xFilcId; // newId = settings.xFilcId;
} // }
settings.update(xFilcId: newId); settings.update(analyticsEnabled: v);
}, },
value: settings.xFilcId != "none", value: settings.analyticsEnabled,
activeColor: Theme.of(context).colorScheme.secondary, activeColor: Theme.of(context).colorScheme.secondary,
), ),
), ),

View File

@@ -129,6 +129,13 @@ 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",
"qr_scanner": "QR Scanner",
"camera_perm_error":
"Camera permission is required to scan QR codes.",
"invalid_qr_code": "Invalid QR code!",
"success": "Success!",
}, },
"hu_hu": { "hu_hu": {
"heads_up": "Figyelem!", "heads_up": "Figyelem!",
@@ -256,6 +263,13 @@ 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",
"qr_scanner": "QR Kódolvasó",
"camera_perm_error":
"A kamera engedély szükséges a QR kódok beolvasásához.",
"invalid_qr_code": "Érvénytelen QR kód!",
"success": "Siker!",
}, },
"de_de": { "de_de": {
"heads_up": "Achtung!", "heads_up": "Achtung!",
@@ -383,6 +397,13 @@ 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",
"qr_scanner": "QR-Scanner",
"camera_perm_error":
"Kameraberechtigung ist erforderlich, um QR-Codes zu scannen.",
"invalid_qr_code": "Ungültiger QR-Code!",
"success": "Erfolg!",
}, },
}; };

View File

@@ -0,0 +1,172 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_mobile_ui/common/custom_snack_bar.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
import 'package:url_launcher/url_launcher.dart';
class CodeScannerScreen extends StatefulWidget {
const CodeScannerScreen({super.key});
@override
State<StatefulWidget> createState() => _CodeScannerScreenState();
}
class _CodeScannerScreenState extends State<CodeScannerScreen> {
Barcode? result;
QRViewController? controller;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
@override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
controller!.pauseCamera();
}
controller!.resumeCamera();
}
// @override
// void initState() {
// super.initState();
// controller!.resumeCamera();
// }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
title: Text('qr_scanner'.i18n),
leading: const BackButton(),
actions: [
IconButton(
icon: FutureBuilder(
future: controller?.getFlashStatus(),
builder: (context, snapshot) {
return Icon(
snapshot.data == true
? FeatherIcons.zapOff
: FeatherIcons.zap,
);
},
),
onPressed: () async {
await controller?.toggleFlash();
setState(() {});
},
),
],
),
body: _buildQrView(context),
);
}
Widget _buildQrView(BuildContext context) {
var scanArea = (MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400)
? 150.0
: 280.0;
return QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Theme.of(context).primaryColor,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea,
),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
);
}
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
});
controller.scannedDataStream.listen((scanData) {
// controller.pauseCamera();
if (result?.code == scanData.code) return;
setState(() {
result = scanData;
});
if (scanData.code != null) {
if (scanData.code!.startsWith('qw://')) {
// String data = scanData.code!.replaceFirst('qw://', '');
// check the qr id from api
// TODO: this qr shit
} else if (scanData.code!.startsWith('https://') ||
scanData.code!.startsWith('http://')) {
Uri uri =
Uri.parse(scanData.code!.replaceFirst('http://', 'https://'));
// print(uri);
if (uri.host.contains('refilc.hu') ||
uri.host.contains('refilcapp.hu') ||
uri.host.contains('filc.one')) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("success".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: const Color(0xFF00A900),
context: context,
));
// launch refilc url
Future.delayed(const Duration(seconds: 1), () {
Navigator.of(context).pop();
launchUrl(uri, mode: LaunchMode.inAppBrowserView);
});
} else {
// show invalid code error
// Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("invalid_qr_code".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
controller.resumeCamera();
}
} else {
// show invalid code error
// Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("invalid_qr_code".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
controller.resumeCamera();
}
}
});
}
void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
if (!p) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("camera_perm_error".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
}
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
}

View File

@@ -4,9 +4,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
// import 'package:refilc/models/settings.dart'; // import 'package:refilc/models/settings.dart';
import 'package:refilc/models/shared_theme.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_kreta_api/providers/share_provider.dart'; import 'package:refilc_kreta_api/providers/share_provider.dart';
import 'package:refilc_mobile_ui/common/action_button.dart'; import 'package:refilc_mobile_ui/common/action_button.dart';
import 'package:refilc_mobile_ui/common/custom_snack_bar.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:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'submenu_screen.i18n.dart'; import 'submenu_screen.i18n.dart';
@@ -131,15 +132,59 @@ class ShareThemeDialogState extends State<ShareThemeDialog> {
), ),
onPressed: () async { onPressed: () async {
// share the fucking theme // share the fucking theme
SharedGradeColors gradeColors = var (gradeColors, gradeColorsStatus) =
await shareProvider.shareCurrentGradeColors(context); await shareProvider.shareCurrentGradeColors(context);
SharedTheme theme = await shareProvider.shareCurrentTheme(
if (gradeColorsStatus == 429) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("theme_share_ratelimit".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
return;
} else if (gradeColorsStatus != 201) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("theme_share_failed".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
return;
}
var (theme, themeStatus) = await shareProvider.shareCurrentTheme(
context, context,
gradeColors: gradeColors, gradeColors: gradeColors!,
isPublic: isPublic, isPublic: isPublic,
displayName: _title.text, displayName: _title.text,
); );
if (themeStatus == 429) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("theme_share_ratelimit".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
return;
} else if (themeStatus != 201) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("theme_share_failed".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
return;
}
// print(theme);
// print(themeStatus);
// save theme id in settings // save theme id in settings
// Provider.of<SettingsProvider>(context, listen: false) // Provider.of<SettingsProvider>(context, listen: false)
// .update(currentThemeId: theme.id); // .update(currentThemeId: theme.id);
@@ -149,7 +194,7 @@ class ShareThemeDialogState extends State<ShareThemeDialog> {
// show the share popup // show the share popup
Share.share( Share.share(
theme.id, theme!.id,
subject: 'share_subj_theme'.i18n, subject: 'share_subj_theme'.i18n,
); );
}, },

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",
@@ -28,11 +30,15 @@ extension SettingsLocalization on String {
"share_disclaimer": "share_disclaimer":
"By sharing the theme, you agree that the nickname you set and all settings of the theme will be shared publicly.", "By sharing the theme, you agree that the nickname you set and all settings of the theme will be shared publicly.",
"understand": "I understand", "understand": "I understand",
"theme_share_failed": "An error occurred while sharing the theme.",
"theme_share_ratelimit": "You can only share 1 theme per minute.",
}, },
"hu_hu": { "hu_hu": {
"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",
@@ -54,11 +60,15 @@ extension SettingsLocalization on String {
"share_disclaimer": "share_disclaimer":
"A téma megosztásával elfogadod, hogy az általad beállított becenév és a téma minden beállítása nyilvánosan megosztásra kerüljön.", "A téma megosztásával elfogadod, hogy az általad beállított becenév és a téma minden beállítása nyilvánosan megosztásra kerüljön.",
"understand": "Értem", "understand": "Értem",
"theme_share_failed": "Hiba történt a téma megosztása közben.",
"theme_share_ratelimit": "Csak 1 témát oszthatsz meg percenként.",
}, },
"de_de": { "de_de": {
"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",
@@ -80,6 +90,9 @@ extension SettingsLocalization on String {
"share_disclaimer": "share_disclaimer":
"Durch das Teilen des Themes erklären Sie sich damit einverstanden, dass der von Ihnen festgelegte Spitzname und alle Einstellungen des Themes öffentlich geteilt werden.", "Durch das Teilen des Themes erklären Sie sich damit einverstanden, dass der von Ihnen festgelegte Spitzname und alle Einstellungen des Themes öffentlich geteilt werden.",
"understand": "Ich verstehe", "understand": "Ich verstehe",
"theme_share_failed":
"Beim Teilen des Themas ist ein Fehler aufgetreten.",
"theme_share_ratelimit": "Sie können nur 1 Thema pro Minute teilen.",
}, },
}; };

View File

@@ -58,7 +58,7 @@ dependencies:
auto_size_text: ^3.0.0 auto_size_text: ^3.0.0
connectivity_plus: ^6.0.3 connectivity_plus: ^6.0.3
collection: ^1.18.0 collection: ^1.18.0
share_plus: ^9.0.0 share_plus: ^10.0.3
image_picker: ^1.0.7 image_picker: ^1.0.7
path_provider: ^2.1.2 path_provider: ^2.1.2
image_crop: image_crop:
@@ -77,6 +77,7 @@ dependencies:
webview_flutter: ^4.8.0 webview_flutter: ^4.8.0
file_picker: ^8.0.5 file_picker: ^8.0.5
shake_flutter: ^17.0.0 shake_flutter: ^17.0.0
qr_code_scanner_plus: ^2.0.6
dev_dependencies: dev_dependencies:
flutter_lints: ^4.0.0 flutter_lints: ^4.0.0