diff --git a/firka/lib/helpers/active_account_helper.dart b/firka/lib/helpers/active_account_helper.dart index d073ee5..4b5b0a1 100644 --- a/firka/lib/helpers/active_account_helper.dart +++ b/firka/lib/helpers/active_account_helper.dart @@ -8,8 +8,7 @@ int resolveActiveAccountIndex(dynamic settings) { if (accountIndex is int && accountIndex >= 0) { return accountIndex; } - } catch (_) { - } + } catch (_) {} return 0; } diff --git a/firka/lib/helpers/api/client/kreta_client.dart b/firka/lib/helpers/api/client/kreta_client.dart index 7966542..a5e6ee5 100644 --- a/firka/lib/helpers/api/client/kreta_client.dart +++ b/firka/lib/helpers/api/client/kreta_client.dart @@ -42,12 +42,7 @@ class ApiResponse { String? err; bool cached; - ApiResponse( - this.response, - this.statusCode, - this.err, - this.cached, - ); + ApiResponse(this.response, this.statusCode, this.err, this.cached); @override String toString() { @@ -85,7 +80,8 @@ class KretaClient { KretaClient(this.model, this.isar); Future _refreshModelWithCrossDeviceLease( - TokenModel sourceToken) async { + TokenModel sourceToken, + ) async { final studentIdNorm = sourceToken.studentIdNorm; String? leaseOperationId; @@ -111,9 +107,7 @@ class KretaClient { final extended = await extendToken(sourceToken); return TokenModel.fromResp(extended); } finally { - if (Platform.isIOS && - studentIdNorm != null && - leaseOperationId != null) { + if (Platform.isIOS && studentIdNorm != null && leaseOperationId != null) { await WatchSyncHelper.releaseIPhoneRefreshLease( studentIdNorm: studentIdNorm, operationId: leaseOperationId, @@ -133,7 +127,8 @@ class KretaClient { final watchInstalled = await WatchSyncHelper.isWatchAppInstalled(); if (!watchInstalled) { debugPrint( - '[KretaClient] Skipping Apple token sync because no paired Watch app is installed'); + '[KretaClient] Skipping Apple token sync because no paired Watch app is installed', + ); return; } @@ -186,7 +181,8 @@ class KretaClient { if (localExpiry != null && localExpiry.isAfter(now.add(const Duration(seconds: 60)))) { logger.info( - "[Recovery] Existing token is still valid, skipping recovery steps"); + "[Recovery] Existing token is still valid, skipping recovery steps", + ); clearReauthFlag(); return true; } @@ -209,8 +205,9 @@ class KretaClient { } if (!Platform.isIOS || !initDone) { - logger - .warning("[Recovery] Not iOS or not initialized, cannot try iCloud"); + logger.warning( + "[Recovery] Not iOS or not initialized, cannot try iCloud", + ); return false; } @@ -227,12 +224,14 @@ class KretaClient { break; } logger.info( - "[Recovery] Waiting ${delay}s before attempt ${attempt + 1}..."); + "[Recovery] Waiting ${delay}s before attempt ${attempt + 1}...", + ); await Future.delayed(Duration(seconds: delay)); } logger.info( - "[Recovery] iCloud attempt ${attempt + 1}/${retryDelays.length}..."); + "[Recovery] iCloud attempt ${attempt + 1}/${retryDelays.length}...", + ); final recovered = await WatchSyncHelper.checkAndRecoverFromiCloud( isar: isar, @@ -244,20 +243,24 @@ class KretaClient { if (recovered) { iCloudHasToken = true; await _reloadActiveTokenModel( - preferredStudentIdNorm: model.studentIdNorm); + preferredStudentIdNorm: model.studentIdNorm, + ); final recoveredExpiry = model.expiryDate; if (recoveredExpiry != null && - recoveredExpiry - .isAfter(timeNow().add(const Duration(seconds: 60)))) { + recoveredExpiry.isAfter( + timeNow().add(const Duration(seconds: 60)), + )) { logger.info( - "[Recovery] Step 2 SUCCESS on attempt ${attempt + 1}: usable iCloud token applied without immediate refresh"); + "[Recovery] Step 2 SUCCESS on attempt ${attempt + 1}: usable iCloud token applied without immediate refresh", + ); clearReauthFlag(); return true; } logger.info( - "[Recovery] Found iCloud token close to expiry, trying refresh..."); + "[Recovery] Found iCloud token close to expiry, trying refresh...", + ); try { var tokenModel = await _refreshModelWithCrossDeviceLease(model); @@ -272,12 +275,14 @@ class KretaClient { return true; } catch (e) { logger.warning( - "[Recovery] iCloud token refresh failed on attempt ${attempt + 1}: $e"); + "[Recovery] iCloud token refresh failed on attempt ${attempt + 1}: $e", + ); iCloudHasToken = true; } } else { logger.info( - "[Recovery] No fresh token in iCloud on attempt ${attempt + 1}"); + "[Recovery] No fresh token in iCloud on attempt ${attempt + 1}", + ); if (attempt == 0) { iCloudHasToken = false; } @@ -295,7 +300,8 @@ class KretaClient { if (model.expiryDate == null || model.expiryDate!.isBefore(fiveMinutesFromNow)) { logger.info( - "[Proactive] Token expired or expiring soon, starting recovery..."); + "[Proactive] Token expired or expiring soon, starting recovery...", + ); final recovered = await recoverToken(); if (recovered) { @@ -315,7 +321,8 @@ class KretaClient { } logger.fine( - "[Proactive] Token still valid until ${model.expiryDate}, no refresh needed"); + "[Proactive] Token still valid until ${model.expiryDate}, no refresh needed", + ); return true; } @@ -326,7 +333,8 @@ class KretaClient { while (_tokenMutex) { if (DateTime.now().difference(startTime) > maxWaitTime) { logger.warning( - "[Mutex] Timeout waiting for token mutex, forcing release"); + "[Mutex] Timeout waiting for token mutex, forcing release", + ); _tokenMutex = false; break; } @@ -347,7 +355,8 @@ class KretaClient { if (now.millisecondsSinceEpoch >= model.expiryDate!.millisecondsSinceEpoch) { logger.info( - "Token expired at ${model.expiryDate}, starting recovery for user: ${model.studentId}"); + "Token expired at ${model.expiryDate}, starting recovery for user: ${model.studentId}", + ); final recovered = await recoverToken(); if (!recovered) { @@ -364,15 +373,21 @@ class KretaClient { "accept": "*/*", "user-agent": Constants.userAgent, "authorization": "Bearer $localToken", - "apiKey": "21ff6c25-d1da-4a68-a811-c881a6057463" + "apiKey": "21ff6c25-d1da-4a68-a811-c881a6057463", }; - return await dio.get(url, - options: Options(method: method, headers: headers), data: data); + return await dio.get( + url, + options: Options(method: method, headers: headers), + data: data, + ); } - Future<(dynamic, int)> _authJson(String method, String url, - [Object? data]) async { + Future<(dynamic, int)> _authJson( + String method, + String url, [ + Object? data, + ]) async { Response resp; try { @@ -388,13 +403,17 @@ class KretaClient { (responseData is List && responseData.isEmpty) || (responseData is Map && responseData.isEmpty)) { logger.warning( - "API returned ${resp.statusCode} with empty data for: $url - possible stale session"); + "API returned ${resp.statusCode} with empty data for: $url - possible stale session", + ); } } } catch (ex) { if (ex is Error) { logger.shout( - "Request to url: $url failed", ex.toString(), ex.stackTrace); + "Request to url: $url failed", + ex.toString(), + ex.stackTrace, + ); } else { logger.shout("Request to url: $url failed", ex.toString()); } @@ -406,7 +425,11 @@ class KretaClient { } Future<(dynamic, int, Object?, bool)> _cachingGet( - CacheId id, String url, bool forceCache, int counter) async { + CacheId id, + String url, + bool forceCache, + int counter, + ) async { // it would be *ideal* to use xor and left shift here, however // binary operations seem to round the number down to // 32 bits for some reason??? @@ -418,7 +441,8 @@ class KretaClient { try { if (forceCache && cache != null) { logger.finest( - "_cachingGet(forceCache: $forceCache}): decoding cached response for: $url"); + "_cachingGet(forceCache: $forceCache}): decoding cached response for: $url", + ); return (jsonDecode(cache.cacheData!), 200, null, true); } @@ -428,14 +452,18 @@ class KretaClient { if (statusCode >= 400) { if (cache != null) { logger.finest( - "_cachingGet(forceCache: $forceCache}): decoding uncached response for: $url"); + "_cachingGet(forceCache: $forceCache}): decoding uncached response for: $url", + ); return (jsonDecode(cache.cacheData!), statusCode, null, true); } } } catch (ex) { if (ex is Error) { logger.finest( - "Request failed for $url", ex.toString(), ex.stackTrace); + "Request failed for $url", + ex.toString(), + ex.stackTrace, + ); } else { logger.finest("Request failed for $url", ex.toString()); } @@ -494,8 +522,12 @@ class KretaClient { } else if (studentCache != null) { return studentCache!; } - var (resp, status, ex, cached) = await _cachingGet(CacheId.getStudent, - KretaEndpoints.getStudentUrl(model.iss!), forceCache, 0); + var (resp, status, ex, cached) = await _cachingGet( + CacheId.getStudent, + KretaEndpoints.getStudentUrl(model.iss!), + forceCache, + 0, + ); Student? student; String? err; @@ -516,15 +548,20 @@ class KretaClient { ApiResponse>? classGroupCache; - Future>> getClassGroups( - {bool forceCache = true}) async { + Future>> getClassGroups({ + bool forceCache = true, + }) async { if (!forceCache) { classGroupCache = null; } else { if (classGroupCache != null) return classGroupCache!; } - var (resp, status, ex, cached) = await _cachingGet(CacheId.getClassGroup, - KretaEndpoints.getClassGroups(model.iss!), forceCache, 0); + var (resp, status, ex, cached) = await _cachingGet( + CacheId.getClassGroup, + KretaEndpoints.getClassGroups(model.iss!), + forceCache, + 0, + ); final classGroups = List.empty(growable: true); String? err; @@ -548,15 +585,20 @@ class KretaClient { ApiResponse>? noticeBoardCache; - Future>> getNoticeBoard( - {bool forceCache = true}) async { + Future>> getNoticeBoard({ + bool forceCache = true, + }) async { if (!forceCache) { noticeBoardCache = null; } else if (noticeBoardCache != null) { return noticeBoardCache!; } - var (resp, status, ex, cached) = await _cachingGet(CacheId.getNoticeBoard, - KretaEndpoints.getNoticeBoard(model.iss!), forceCache, 0); + var (resp, status, ex, cached) = await _cachingGet( + CacheId.getNoticeBoard, + KretaEndpoints.getNoticeBoard(model.iss!), + forceCache, + 0, + ); var items = List.empty(growable: true); String? err; @@ -580,11 +622,16 @@ class KretaClient { ApiResponse>? infoBoardCache; - Future>> getInfoBoard( - {bool forceCache = true}) async { + Future>> getInfoBoard({ + bool forceCache = true, + }) async { if (forceCache && infoBoardCache != null) return infoBoardCache!; - var (resp, status, ex, cached) = await _cachingGet(CacheId.getInfoBoard, - KretaEndpoints.getInfoBoard(model.iss!), forceCache, 0); + var (resp, status, ex, cached) = await _cachingGet( + CacheId.getInfoBoard, + KretaEndpoints.getInfoBoard(model.iss!), + forceCache, + 0, + ); var items = List.empty(growable: true); String? err; @@ -615,7 +662,11 @@ class KretaClient { return gradeCache!; } var (resp, status, ex, cached) = await _cachingGet( - CacheId.getGrades, KretaEndpoints.getGrades(model.iss!), forceCache, 0); + CacheId.getGrades, + KretaEndpoints.getGrades(model.iss!), + forceCache, + 0, + ); var items = List.empty(growable: true); String? err; @@ -642,14 +693,19 @@ class KretaClient { ApiResponse>? subjectAverageCache; Future>> getSubjectAverage( - ClassGroup classGroup, - {bool forceCache = true}) async { + ClassGroup classGroup, { + bool forceCache = true, + }) async { String? err; if (classGroup.studyTask == null) { err = "classGroup.studyTask is null"; logger.warning(err); return ApiResponse( - List.empty(growable: true), 0, err, false); + List.empty(growable: true), + 0, + err, + false, + ); } if (!forceCache) { subjectAverageCache = null; @@ -657,8 +713,12 @@ class KretaClient { return subjectAverageCache!; } var studyTaskUid = classGroup.studyTask!.uid.toString().split(",").first; - var (resp, status, ex, cached) = await _cachingGet(CacheId.getSubjectAvg, - KretaEndpoints.getSubjectAvg(model.iss!, studyTaskUid), forceCache, 0); + var (resp, status, ex, cached) = await _cachingGet( + CacheId.getSubjectAvg, + KretaEndpoints.getSubjectAvg(model.iss!, studyTaskUid), + forceCache, + 0, + ); var items = List.empty(growable: true); try { @@ -679,14 +739,15 @@ class KretaClient { } Future<(List, int, Object?, bool)> - _timedCachingGet( - IsarCollection cacheModel, - String endpoint, - DateTime from, - DateTime? to, - bool forceCache, - int counter, - Future Function(dynamic, int) storeCache) async { + _timedCachingGet( + IsarCollection cacheModel, + String endpoint, + DateTime from, + DateTime? to, + bool forceCache, + int counter, + Future Function(dynamic, int) storeCache, + ) async { var cacheKey = genCacheKey(from, model.studentIdNorm!); var cache = await cacheModel.get(cacheKey); var formatter = DateFormat('yyyy-MM-dd'); @@ -712,14 +773,16 @@ class KretaClient { try { if (toStr == null) { (resp, statusCode) = await _authJson( - "GET", - "$endpoint?" - "datumTol=$fromStr"); + "GET", + "$endpoint?" + "datumTol=$fromStr", + ); } else { (resp, statusCode) = await _authJson( - "GET", - "$endpoint?" - "datumTol=$fromStr&datumIg=$toStr"); + "GET", + "$endpoint?" + "datumTol=$fromStr&datumIg=$toStr", + ); } if (statusCode >= 400) { @@ -739,16 +802,25 @@ class KretaClient { } await Future.delayed( - Duration(milliseconds: backoffMin + (counter * backoffStep))); + Duration(milliseconds: backoffMin + (counter * backoffStep)), + ); - return _timedCachingGet(cacheModel, endpoint, from, to, forceCache, - counter + 1, storeCache); + return _timedCachingGet( + cacheModel, + endpoint, + from, + to, + forceCache, + counter + 1, + storeCache, + ); } } catch (ex) { if (_isTokenExpired(ex)) { await _setReauthFlag(); logger.warning( - "Token expired in timed request, setting needsReauth flag"); + "Token expired in timed request, setting needsReauth flag", + ); } if (cache != null) { @@ -779,27 +851,36 @@ class KretaClient { /// Expects from and to to be 7 days apart Future>> _getTimeTable( - DateTime from, DateTime to, bool forceCache) async { - var (resp, status, ex, cached) = - await _timedCachingGet( - isar.timetableCacheModels, - KretaEndpoints.getTimeTable(model.iss!), - from, - to, - forceCache, - 0, (dynamic resp, int cacheKey) async { - TimetableCacheModel cache = TimetableCacheModel(); - var rawClasses = List.empty(growable: true); + DateTime from, + DateTime to, + bool forceCache, + ) async { + var ( + resp, + status, + ex, + cached, + ) = await _timedCachingGet( + isar.timetableCacheModels, + KretaEndpoints.getTimeTable(model.iss!), + from, + to, + forceCache, + 0, + (dynamic resp, int cacheKey) async { + TimetableCacheModel cache = TimetableCacheModel(); + var rawClasses = List.empty(growable: true); - for (var obj in resp) { - rawClasses.add(jsonEncode(obj)); - } + for (var obj in resp) { + rawClasses.add(jsonEncode(obj)); + } - cache.cacheKey = cacheKey; - cache.values = rawClasses; + cache.cacheKey = cacheKey; + cache.values = rawClasses; - await isar.timetableCacheModels.put(cache as dynamic); - }); + await isar.timetableCacheModels.put(cache as dynamic); + }, + ); var items = List.empty(growable: true); String? err; @@ -819,16 +900,18 @@ class KretaClient { return ApiResponse(items, status, err, cached); } - Future>> getHomework( - {bool forceCache = true}) async { + Future>> getHomework({ + bool forceCache = true, + }) async { final now = timeNow().subtract(Duration(days: 365)); var formatter = DateFormat('yyyy-MM-dd'); var start = formatter.format(now); var (resp, status, ex, cached) = await _cachingGet( - CacheId.getHomework, - "${KretaEndpoints.getHomework(model.iss!)}?datumTol=$start", - forceCache, - 0); + CacheId.getHomework, + "${KretaEndpoints.getHomework(model.iss!)}?datumTol=$start", + forceCache, + 0, + ); var items = List.empty(growable: true); String? err; @@ -851,15 +934,20 @@ class KretaClient { } /// Automatically aligns requests to start at Monday and end at Sunday - Future>> getTimeTable(DateTime from, DateTime to, - {bool forceCache = true}) async { + Future>> getTimeTable( + DateTime from, + DateTime to, { + bool forceCache = true, + }) async { var lessons = List.empty(growable: true); String? err; bool cached = true; - for (var i = from.millisecondsSinceEpoch; - i < to.millisecondsSinceEpoch; - i += 604800000) { + for ( + var i = from.millisecondsSinceEpoch; + i < to.millisecondsSinceEpoch; + i += 604800000 + ) { var from = DateTime.fromMillisecondsSinceEpoch(i); var start = from.subtract(Duration(days: from.weekday - 1)); var end = start.add(Duration(days: 6)); @@ -881,14 +969,16 @@ class KretaClient { lessons.sort((a, b) => a.start.compareTo(b.start)); lessons = lessons .where( - (lesson) => lesson.start.isAfter(from) && lesson.end.isBefore(to)) + (lesson) => lesson.start.isAfter(from) && lesson.end.isBefore(to), + ) .toList(); return ApiResponse(lessons, 200, err, cached); } - Future>> getLessons( - {bool forceCache = true}) async { + Future>> getLessons({ + bool forceCache = true, + }) async { var (resp, status, ex, cached) = await _cachingGet( CacheId.getLessons, KretaEndpoints.getLessons(model.iss!), @@ -925,7 +1015,11 @@ class KretaClient { Future>> getTests({bool forceCache = true}) async { var (resp, status, ex, cached) = await _cachingGet( - CacheId.getTests, KretaEndpoints.getTests(model.iss!), forceCache, 0); + CacheId.getTests, + KretaEndpoints.getTests(model.iss!), + forceCache, + 0, + ); var items = List.empty(growable: true); String? err; @@ -949,15 +1043,20 @@ class KretaClient { ApiResponse>? omissionsCache; - Future>> getOmissions( - {bool forceCache = true}) async { + Future>> getOmissions({ + bool forceCache = true, + }) async { if (!forceCache) { omissionsCache = null; } else { if (omissionsCache != null) return omissionsCache!; } - var (resp, status, ex, cached) = await _cachingGet(CacheId.getOmissions, - KretaEndpoints.getOmissions(model.iss!), forceCache, 0); + var (resp, status, ex, cached) = await _cachingGet( + CacheId.getOmissions, + KretaEndpoints.getOmissions(model.iss!), + forceCache, + 0, + ); var items = List.empty(growable: true); String? err; diff --git a/firka/lib/helpers/api/client/kreta_stream.dart b/firka/lib/helpers/api/client/kreta_stream.dart index 8f871b5..b0ba063 100644 --- a/firka/lib/helpers/api/client/kreta_stream.dart +++ b/firka/lib/helpers/api/client/kreta_stream.dart @@ -21,8 +21,9 @@ bool getTestsStreamFL = false; bool getOmissionsStreamFL = false; extension KretaStream on KretaClient { - Stream> getStudentStream( - {bool cacheOnly = true}) async* { + Stream> getStudentStream({ + bool cacheOnly = true, + }) async* { while (getStudentFL) { await Future.delayed(Duration(milliseconds: 10)); } @@ -35,8 +36,9 @@ extension KretaStream on KretaClient { getStudentFL = false; } - Stream>> getClassGroupsStream( - {bool cacheOnly = true}) async* { + Stream>> getClassGroupsStream({ + bool cacheOnly = true, + }) async* { while (getClassGroupsFL) { await Future.delayed(Duration(milliseconds: 10)); } @@ -49,8 +51,9 @@ extension KretaStream on KretaClient { getClassGroupsFL = false; } - Stream>> getNoticeBoardStream( - {bool cacheOnly = true}) async* { + Stream>> getNoticeBoardStream({ + bool cacheOnly = true, + }) async* { while (getNoticeBoardStreamFL) { await Future.delayed(Duration(milliseconds: 10)); } @@ -63,8 +66,9 @@ extension KretaStream on KretaClient { getNoticeBoardStreamFL = false; } - Stream>> getInfoBoardStream( - {bool cacheOnly = true}) async* { + Stream>> getInfoBoardStream({ + bool cacheOnly = true, + }) async* { while (getInfoBoardStreamFL) { await Future.delayed(Duration(milliseconds: 10)); } @@ -77,8 +81,9 @@ extension KretaStream on KretaClient { getInfoBoardStreamFL = false; } - Stream>> getGradesStream( - {bool cacheOnly = true}) async* { + Stream>> getGradesStream({ + bool cacheOnly = true, + }) async* { while (getGradesStreamFL) { await Future.delayed(Duration(milliseconds: 10)); } @@ -92,8 +97,9 @@ extension KretaStream on KretaClient { } Stream>> getSubjectAverageStream( - ClassGroup classGroup, - {bool cacheOnly = true}) async* { + ClassGroup classGroup, { + bool cacheOnly = true, + }) async* { while (getSubjectAverageStreamFL) { await Future.delayed(Duration(milliseconds: 10)); } @@ -106,8 +112,9 @@ extension KretaStream on KretaClient { getSubjectAverageStreamFL = false; } - Stream>> getHomeworkStream( - {bool cacheOnly = true}) async* { + Stream>> getHomeworkStream({ + bool cacheOnly = true, + }) async* { while (getHomeworkStreamFL) { await Future.delayed(Duration(milliseconds: 10)); } @@ -121,8 +128,10 @@ extension KretaStream on KretaClient { } Stream>> getTimeTableStream( - DateTime from, DateTime to, - {bool cacheOnly = true}) async* { + DateTime from, + DateTime to, { + bool cacheOnly = true, + }) async* { while (getTimeTableStreamFL) { await Future.delayed(Duration(milliseconds: 10)); } @@ -135,8 +144,9 @@ extension KretaStream on KretaClient { getTimeTableStreamFL = false; } - Stream>> getTestsStream( - {bool cacheOnly = true}) async* { + Stream>> getTestsStream({ + bool cacheOnly = true, + }) async* { while (getTestsStreamFL) { await Future.delayed(Duration(milliseconds: 10)); } @@ -149,8 +159,9 @@ extension KretaStream on KretaClient { getTestsStreamFL = false; } - Stream>> getOmissionsStream( - {bool cacheOnly = true}) async* { + Stream>> getOmissionsStream({ + bool cacheOnly = true, + }) async* { while (getOmissionsStreamFL) { await Future.delayed(Duration(milliseconds: 10)); } diff --git a/firka/lib/helpers/api/client/live_activity_backend_client.dart b/firka/lib/helpers/api/client/live_activity_backend_client.dart index 69d1453..4541894 100644 --- a/firka/lib/helpers/api/client/live_activity_backend_client.dart +++ b/firka/lib/helpers/api/client/live_activity_backend_client.dart @@ -55,7 +55,8 @@ class LiveActivityBackendClient { 'roomName': lesson.roomName, 'isSubstitution': lesson.substituteTeacher != null, 'substituteTeacher': lesson.substituteTeacher, - 'isCancelled': lesson.state.name?.toLowerCase().contains('elmarad') ?? false, + 'isCancelled': + lesson.state.name?.toLowerCase().contains('elmarad') ?? false, 'lastModified': validLastModified.toIso8601String(), }; }).toList(); @@ -86,7 +87,9 @@ class LiveActivityBackendClient { requestData['liveActivityEnabled'] = liveActivityEnabled; } - _logger.info('Registering device with backend. Sending ${lessonsData.length} lessons.'); + _logger.info( + 'Registering device with backend. Sending ${lessonsData.length} lessons.', + ); if (_logger.isLoggable(Level.FINE)) { for (var lesson in lessonsData) { _logger.fine(' Lesson data: $lesson'); @@ -99,7 +102,9 @@ class LiveActivityBackendClient { ); if (response.statusCode == 200 || response.statusCode == 201) { - _logger.info('Device registered successfully with ${timetable.length} lessons'); + _logger.info( + 'Device registered successfully with ${timetable.length} lessons', + ); return true; } @@ -136,12 +141,15 @@ class LiveActivityBackendClient { 'roomName': lesson.roomName, 'isSubstitution': lesson.substituteTeacher != null, 'substituteTeacher': lesson.substituteTeacher, - 'isCancelled': lesson.state.name?.toLowerCase().contains('elmarad') ?? false, + 'isCancelled': + lesson.state.name?.toLowerCase().contains('elmarad') ?? false, 'lastModified': validLastModified.toIso8601String(), }; }).toList(); - _logger.info('Updating timetable with backend. Sending ${lessonsData.length} lessons.'); + _logger.info( + 'Updating timetable with backend. Sending ${lessonsData.length} lessons.', + ); if (_logger.isLoggable(Level.FINE)) { for (var lesson in lessonsData) { _logger.fine(' Lesson data: $lesson'); @@ -177,15 +185,11 @@ class LiveActivityBackendClient { } /// Unregister device (called when user logs out) - Future unregisterDevice({ - required String deviceToken, - }) async { + Future unregisterDevice({required String deviceToken}) async { try { final response = await _dio.delete( '/live-activity/unregister', - data: { - 'deviceToken': deviceToken, - }, + data: {'deviceToken': deviceToken}, ); if (response.statusCode == 200) { @@ -228,15 +232,11 @@ class LiveActivityBackendClient { } /// Get current timetable from backend - Future?> getTimetable({ - required String deviceToken, - }) async { + Future?> getTimetable({required String deviceToken}) async { try { final response = await _dio.get( '/live-activity/timetable', - queryParameters: { - 'deviceToken': deviceToken, - }, + queryParameters: {'deviceToken': deviceToken}, ); if (response.statusCode == 200 && response.data is Map) { @@ -261,10 +261,7 @@ class LiveActivityBackendClient { try { final response = await _dio.post( '/live-activity/push-token', - data: { - 'deviceToken': deviceToken, - 'pushToken': pushToken, - }, + data: {'deviceToken': deviceToken, 'pushToken': pushToken}, ); if (response.statusCode == 200) { @@ -288,10 +285,7 @@ class LiveActivityBackendClient { try { final response = await _dio.post( '/live-activity/apns-token', - data: { - 'deviceToken': deviceToken, - 'apnsPushToken': apnsPushToken, - }, + data: {'deviceToken': deviceToken, 'apnsPushToken': apnsPushToken}, ); if (response.statusCode == 200) { @@ -299,7 +293,9 @@ class LiveActivityBackendClient { return true; } - _logger.warning('Failed to update APNs push token: ${response.statusCode}'); + _logger.warning( + 'Failed to update APNs push token: ${response.statusCode}', + ); return false; } catch (e) { _logger.severe('Error updating APNs push token: $e'); @@ -308,15 +304,11 @@ class LiveActivityBackendClient { } /// Send a test notification (for debugging) - Future sendTestNotification({ - required String deviceToken, - }) async { + Future sendTestNotification({required String deviceToken}) async { try { final response = await _dio.post( '/live-activity/test-notification', - data: { - 'deviceToken': deviceToken, - }, + data: {'deviceToken': deviceToken}, ); if (response.statusCode == 200) { @@ -324,7 +316,9 @@ class LiveActivityBackendClient { return true; } - _logger.warning('Failed to send test notification: ${response.statusCode}'); + _logger.warning( + 'Failed to send test notification: ${response.statusCode}', + ); return false; } catch (e) { _logger.severe('Error sending test notification: $e'); @@ -340,10 +334,7 @@ class LiveActivityBackendClient { try { final response = await _dio.put( '/live-activity/language', - data: { - 'deviceToken': deviceToken, - 'language': language, - }, + data: {'deviceToken': deviceToken, 'language': language}, ); if (response.statusCode == 200) { @@ -367,10 +358,7 @@ class LiveActivityBackendClient { try { final response = await _dio.put( '/live-activity/bell-delay', - data: { - 'deviceToken': deviceToken, - 'bellDelay': bellDelay, - }, + data: {'deviceToken': deviceToken, 'bellDelay': bellDelay}, ); if (response.statusCode == 200) { @@ -397,17 +385,25 @@ class LiveActivityBackendClient { '/live-activity/morning-notification', data: { 'deviceToken': deviceToken, - if (morningNotificationTime != null) 'morningNotificationTime': morningNotificationTime, - if (morningNotificationEnabled != null) 'morningNotificationEnabled': morningNotificationEnabled, + ...?(morningNotificationTime != null + ? {'morningNotificationTime': morningNotificationTime} + : null), + ...?(morningNotificationEnabled != null + ? {'morningNotificationEnabled': morningNotificationEnabled} + : null), }, ); if (response.statusCode == 200) { - _logger.info('Morning notification settings updated successfully: enabled=$morningNotificationEnabled, time=$morningNotificationTime'); + _logger.info( + 'Morning notification settings updated successfully: enabled=$morningNotificationEnabled, time=$morningNotificationTime', + ); return true; } - _logger.warning('Failed to update morning notification settings: ${response.statusCode}'); + _logger.warning( + 'Failed to update morning notification settings: ${response.statusCode}', + ); return false; } catch (e) { _logger.severe('Error updating morning notification settings: $e'); @@ -430,7 +426,9 @@ class LiveActivityBackendClient { ); if (response.statusCode == 200) { - _logger.info('Live Activity ${liveActivityEnabled ? "enabled" : "disabled"} successfully'); + _logger.info( + 'Live Activity ${liveActivityEnabled ? "enabled" : "disabled"} successfully', + ); return true; } @@ -442,4 +440,3 @@ class LiveActivityBackendClient { } } } - diff --git a/firka/lib/helpers/api/consts.dart b/firka/lib/helpers/api/consts.dart index 1ad2617..36c272d 100644 --- a/firka/lib/helpers/api/consts.dart +++ b/firka/lib/helpers/api/consts.dart @@ -117,7 +117,7 @@ class KretaEndpoints { static String getTests(String iss) => "${kreta(iss)}/ellenorzo/v3/sajat/BejelentettSzamonkeresek"; - + static String getLessons(String iss) => "${kreta(iss)}/dktapi/intezmenyek/munkaterek/tanulok"; - } +} diff --git a/firka/lib/helpers/api/model/all_lessons.dart b/firka/lib/helpers/api/model/all_lessons.dart index f1c0804..c09b44d 100644 --- a/firka/lib/helpers/api/model/all_lessons.dart +++ b/firka/lib/helpers/api/model/all_lessons.dart @@ -62,67 +62,66 @@ class AllLessons { }); factory AllLessons.fromJson(Map json) => AllLessons( - schoolId: json['intezmenyId']?.toString() ?? '', - yearId: json['tanevId']?.toString() ?? '', - classId: json['osztalyId'], - className: json['osztalyNev']?.toString(), - classWorkspace: json['osztalyMunkaTer'] == true, - groupId: json['csoportId'], - groupName: json['csoportNev']?.toString(), - groupWorkspace: json['csoportMunkaTer'] == true, - groupWorkspaceName: json['osztalyCsoportNev']?.toString() ?? '', - subjectId: json['tantargyId'], - subjectName: json['tantargyNev']?.toString() ?? '', - teacherId: json['alkalmazottId'], - teacherGuid: json['alkalmazottGuid']?.toString() ?? '', - teacherName: json['alkalmazottNev']?.toString() ?? '', - teacherAnnoId: json['alkalmazottUzenoFalId'], - annoId: json['uzenoFalId'], - languageId: json['nyelvId']?.toString(), - subjectCategoryId: json['tantargyKategoriaId'], - subjectCategoryName: json['tantargyKategoriaNev']?.toString() ?? '', - typeId: json['tipusId'], - typeName: json['tipusNev']?.toString() ?? '', - gradeTypeId: json['evfolyamTipusId'], - gradeTypeName: json['evfolyamTipusNev']?.toString() ?? '', - taskPlaceId: json['feladatEllatasiHelyId'], - taskPlaceName: json['feladatEllatasiHelyNev']?.toString() ?? '', - teacherAvatarTypeId: json['alkalmazottAvatarTipusId'], - teacherAvatarTypePath: - json['alkalmazottAvatarEleres']?.toString() ?? '', - taskGroupId: json['oraiFeladatGroupId'], - ); + schoolId: json['intezmenyId']?.toString() ?? '', + yearId: json['tanevId']?.toString() ?? '', + classId: json['osztalyId'], + className: json['osztalyNev']?.toString(), + classWorkspace: json['osztalyMunkaTer'] == true, + groupId: json['csoportId'], + groupName: json['csoportNev']?.toString(), + groupWorkspace: json['csoportMunkaTer'] == true, + groupWorkspaceName: json['osztalyCsoportNev']?.toString() ?? '', + subjectId: json['tantargyId'], + subjectName: json['tantargyNev']?.toString() ?? '', + teacherId: json['alkalmazottId'], + teacherGuid: json['alkalmazottGuid']?.toString() ?? '', + teacherName: json['alkalmazottNev']?.toString() ?? '', + teacherAnnoId: json['alkalmazottUzenoFalId'], + annoId: json['uzenoFalId'], + languageId: json['nyelvId']?.toString(), + subjectCategoryId: json['tantargyKategoriaId'], + subjectCategoryName: json['tantargyKategoriaNev']?.toString() ?? '', + typeId: json['tipusId'], + typeName: json['tipusNev']?.toString() ?? '', + gradeTypeId: json['evfolyamTipusId'], + gradeTypeName: json['evfolyamTipusNev']?.toString() ?? '', + taskPlaceId: json['feladatEllatasiHelyId'], + taskPlaceName: json['feladatEllatasiHelyNev']?.toString() ?? '', + teacherAvatarTypeId: json['alkalmazottAvatarTipusId'], + teacherAvatarTypePath: json['alkalmazottAvatarEleres']?.toString() ?? '', + taskGroupId: json['oraiFeladatGroupId'], + ); Map toJson() => { - 'intezmenyId': schoolId, - 'tanevId': yearId, - 'osztalyId': classId, - 'osztalyNev': className, - 'osztalyMunkaTer': classWorkspace, - 'csoportId': groupId, - 'csoportNev': groupName, - 'csoportMunkaTer': groupWorkspace, - 'osztalyCsoportNev': groupWorkspaceName, - 'tantargyId': subjectId, - 'tantargyNev': subjectName, - 'alkalmazottId': teacherId, - 'alkalmazottGuid': teacherGuid, - 'alkalmazottNev': teacherName, - 'alkalmazottUzenoFalId': teacherAnnoId, - 'uzenoFalId': annoId, - 'nyelvId': languageId, - 'tantargyKategoriaId': subjectCategoryId, - 'tantargyKategoriaNev': subjectCategoryName, - 'tipusId': typeId, - 'tipusNev': typeName, - 'evfolyamTipusId': gradeTypeId, - 'evfolyamTipusNev': gradeTypeName, - 'feladatEllatasiHelyId': taskPlaceId, - 'feladatEllatasiHelyNev': taskPlaceName, - 'alkalmazottAvatarTipusId': teacherAvatarTypeId, - 'alkalmazottAvatarEleres': teacherAvatarTypePath, - 'oraiFeladatGroupId': taskGroupId, - }; + 'intezmenyId': schoolId, + 'tanevId': yearId, + 'osztalyId': classId, + 'osztalyNev': className, + 'osztalyMunkaTer': classWorkspace, + 'csoportId': groupId, + 'csoportNev': groupName, + 'csoportMunkaTer': groupWorkspace, + 'osztalyCsoportNev': groupWorkspaceName, + 'tantargyId': subjectId, + 'tantargyNev': subjectName, + 'alkalmazottId': teacherId, + 'alkalmazottGuid': teacherGuid, + 'alkalmazottNev': teacherName, + 'alkalmazottUzenoFalId': teacherAnnoId, + 'uzenoFalId': annoId, + 'nyelvId': languageId, + 'tantargyKategoriaId': subjectCategoryId, + 'tantargyKategoriaNev': subjectCategoryName, + 'tipusId': typeId, + 'tipusNev': typeName, + 'evfolyamTipusId': gradeTypeId, + 'evfolyamTipusNev': gradeTypeName, + 'feladatEllatasiHelyId': taskPlaceId, + 'feladatEllatasiHelyNev': taskPlaceName, + 'alkalmazottAvatarTipusId': teacherAvatarTypeId, + 'alkalmazottAvatarEleres': teacherAvatarTypePath, + 'oraiFeladatGroupId': taskGroupId, + }; } List lessonsFromJson(String str) => diff --git a/firka/lib/helpers/api/model/class_group.dart b/firka/lib/helpers/api/model/class_group.dart index d4fe2b7..44e1d4d 100644 --- a/firka/lib/helpers/api/model/class_group.dart +++ b/firka/lib/helpers/api/model/class_group.dart @@ -11,34 +11,36 @@ class ClassGroup { final bool isActive; final String type; - ClassGroup( - {required this.uid, - required this.name, - required this.headTeacher, - required this.substituteHeadTeacher, - required this.studyGroup, - required this.studyGroupSortIndex, - required this.studyTask, - required this.isActive, - required this.type}); + ClassGroup({ + required this.uid, + required this.name, + required this.headTeacher, + required this.substituteHeadTeacher, + required this.studyGroup, + required this.studyGroupSortIndex, + required this.studyTask, + required this.isActive, + required this.type, + }); factory ClassGroup.fromJson(Map json) { return ClassGroup( - uid: json['Uid'], - name: json['Nev'], - headTeacher: json['OsztalyFonok'] != null - ? UidObj.fromJson(json['OsztalyFonok']) - : null, - substituteHeadTeacher: json['OsztalyFonokHelyettes'] != null - ? UidObj.fromJson(json['OsztalyFonokHelyettes']) - : null, - studyGroup: NameUidDesc.fromJson(json['OktatasNevelesiKategoria']), - studyGroupSortIndex: json['OktatasNevelesiKategoriaSortIndex'], - studyTask: json['OktatasNevelesiFeladat'] != null - ? NameUidDesc.fromJson(json['OktatasNevelesiFeladat']) - : null, - isActive: json['IsAktiv'], - type: json['Tipus']); + uid: json['Uid'], + name: json['Nev'], + headTeacher: json['OsztalyFonok'] != null + ? UidObj.fromJson(json['OsztalyFonok']) + : null, + substituteHeadTeacher: json['OsztalyFonokHelyettes'] != null + ? UidObj.fromJson(json['OsztalyFonokHelyettes']) + : null, + studyGroup: NameUidDesc.fromJson(json['OktatasNevelesiKategoria']), + studyGroupSortIndex: json['OktatasNevelesiKategoriaSortIndex'], + studyTask: json['OktatasNevelesiFeladat'] != null + ? NameUidDesc.fromJson(json['OktatasNevelesiFeladat']) + : null, + isActive: json['IsAktiv'], + type: json['Tipus'], + ); } @override diff --git a/firka/lib/helpers/api/model/generic.dart b/firka/lib/helpers/api/model/generic.dart index e668fbc..cb2d276 100644 --- a/firka/lib/helpers/api/model/generic.dart +++ b/firka/lib/helpers/api/model/generic.dart @@ -3,12 +3,18 @@ class NameUidDesc { final String? name; final String? description; - NameUidDesc( - {required this.uid, required this.name, required this.description}); + NameUidDesc({ + required this.uid, + required this.name, + required this.description, + }); factory NameUidDesc.fromJson(Map json) { return NameUidDesc( - uid: json['Uid'], name: json['Nev'], description: json['Leiras']); + uid: json['Uid'], + name: json['Nev'], + description: json['Leiras'], + ); } Map toJson() { @@ -29,23 +35,14 @@ class NameUid { final String uid; final String name; - NameUid({ - required this.uid, - required this.name, - }); + NameUid({required this.uid, required this.name}); factory NameUid.fromJson(Map json) { - return NameUid( - uid: json['Uid'], - name: json['Nev'], - ); + return NameUid(uid: json['Uid'], name: json['Nev']); } Map toJson() { - return { - 'Uid': uid, - 'Nev': name, - }; + return {'Uid': uid, 'Nev': name}; } } @@ -55,9 +52,7 @@ class UidObj { UidObj({required this.uid}); factory UidObj.fromJson(Map json) { - return UidObj( - uid: json['Uid'], - ); + return UidObj(uid: json['Uid']); } @override diff --git a/firka/lib/helpers/api/model/grade.dart b/firka/lib/helpers/api/model/grade.dart index 1d20ff2..c2c145b 100644 --- a/firka/lib/helpers/api/model/grade.dart +++ b/firka/lib/helpers/api/model/grade.dart @@ -20,24 +20,25 @@ class Grade { final UidObj? classGroup; final int sortIndex; - Grade( - {required this.uid, - required this.recordDate, - required this.creationDate, - this.ackDate, - required this.subject, - this.topic, - required this.type, - this.mode, - required this.valueType, - required this.teacher, - this.kind, - this.numericValue, - required this.strValue, - this.weightPercentage, - this.shortStrValue, - this.classGroup, - required this.sortIndex}); + Grade({ + required this.uid, + required this.recordDate, + required this.creationDate, + this.ackDate, + required this.subject, + this.topic, + required this.type, + this.mode, + required this.valueType, + required this.teacher, + this.kind, + this.numericValue, + required this.strValue, + this.weightPercentage, + this.shortStrValue, + this.classGroup, + required this.sortIndex, + }); factory Grade.fromJson(Map json) { return Grade( diff --git a/firka/lib/helpers/api/model/guardian.dart b/firka/lib/helpers/api/model/guardian.dart index 4e6e516..123bf4f 100644 --- a/firka/lib/helpers/api/model/guardian.dart +++ b/firka/lib/helpers/api/model/guardian.dart @@ -5,20 +5,22 @@ class Guardian { final String? phoneNumber; final String uid; - Guardian( - {required this.email, - required this.isLegalRepresentative, - required this.name, - required this.phoneNumber, - required this.uid}); + Guardian({ + required this.email, + required this.isLegalRepresentative, + required this.name, + required this.phoneNumber, + required this.uid, + }); factory Guardian.fromJson(Map json) { return Guardian( - email: json['EmailCim'], - isLegalRepresentative: json['IsTorvenyesKepviselo'], - name: json['Nev'], - phoneNumber: json['Telefonszam'], - uid: json['Uid']); + email: json['EmailCim'], + isLegalRepresentative: json['IsTorvenyesKepviselo'], + name: json['Nev'], + phoneNumber: json['Telefonszam'], + uid: json['Uid'], + ); } @override diff --git a/firka/lib/helpers/api/model/homework.dart b/firka/lib/helpers/api/model/homework.dart index 8618e26..f665778 100644 --- a/firka/lib/helpers/api/model/homework.dart +++ b/firka/lib/helpers/api/model/homework.dart @@ -17,36 +17,38 @@ class Homework { final UidObj classGroup; final bool canAttach; - Homework( - {required this.uid, - required this.subject, - required this.subjectName, - required this.teacherName, - required this.description, - required this.startDate, - required this.dueDate, - required this.creationDate, - required this.isCreatedByTeacher, - required this.isDone, - required this.canBeSubmitted, - required this.classGroup, - required this.canAttach}); + Homework({ + required this.uid, + required this.subject, + required this.subjectName, + required this.teacherName, + required this.description, + required this.startDate, + required this.dueDate, + required this.creationDate, + required this.isCreatedByTeacher, + required this.isDone, + required this.canBeSubmitted, + required this.classGroup, + required this.canAttach, + }); factory Homework.fromJson(Map json) { return Homework( - uid: json["Uid"], - subject: Subject.fromJson(json["Tantargy"]), - subjectName: json["TantargyNeve"], - teacherName: json["RogzitoTanarNeve"], - description: json["Szoveg"], - startDate: DateTime.parse(json["FeladasDatuma"]).toLocal(), - dueDate: DateTime.parse(json["HataridoDatuma"]).toLocal(), - creationDate: DateTime.parse(json["RogzitesIdopontja"]).toLocal(), - isCreatedByTeacher: json["IsTanarRogzitette"], - isDone: json["IsMegoldva"], - canBeSubmitted: json["IsBeadhato"], - classGroup: UidObj.fromJson(json["OsztalyCsoport"]), - canAttach: json["IsCsatolasEngedelyezes"]); + uid: json["Uid"], + subject: Subject.fromJson(json["Tantargy"]), + subjectName: json["TantargyNeve"], + teacherName: json["RogzitoTanarNeve"], + description: json["Szoveg"], + startDate: DateTime.parse(json["FeladasDatuma"]).toLocal(), + dueDate: DateTime.parse(json["HataridoDatuma"]).toLocal(), + creationDate: DateTime.parse(json["RogzitesIdopontja"]).toLocal(), + isCreatedByTeacher: json["IsTanarRogzitette"], + isDone: json["IsMegoldva"], + canBeSubmitted: json["IsBeadhato"], + classGroup: UidObj.fromJson(json["OsztalyCsoport"]), + canAttach: json["IsCsatolasEngedelyezes"], + ); } @override diff --git a/firka/lib/helpers/api/model/institution.dart b/firka/lib/helpers/api/model/institution.dart index 45ea845..5424854 100644 --- a/firka/lib/helpers/api/model/institution.dart +++ b/firka/lib/helpers/api/model/institution.dart @@ -4,11 +4,12 @@ class Institution { final List systemModuleList; final String uid; - Institution( - {required this.customizationSettings, - required this.shortName, - required this.systemModuleList, - required this.uid}); + Institution({ + required this.customizationSettings, + required this.shortName, + required this.systemModuleList, + required this.uid, + }); factory Institution.fromJson(Map json) { var systemModuleList = List.empty(growable: true); @@ -18,8 +19,9 @@ class Institution { } return Institution( - customizationSettings: - CustomizationSettings.fromJson(json['TestreszabasBeallitasok']), + customizationSettings: CustomizationSettings.fromJson( + json['TestreszabasBeallitasok'], + ), shortName: json['RovidNev'], systemModuleList: systemModuleList, uid: json['Uid'], @@ -33,19 +35,21 @@ class CustomizationSettings { final bool isLessonsThemeVisible; final String nextServerDeployAsString; - CustomizationSettings( - {required this.delayForNotifications, - required this.isClassAverageVisible, - required this.isLessonsThemeVisible, - required this.nextServerDeployAsString}); + CustomizationSettings({ + required this.delayForNotifications, + required this.isClassAverageVisible, + required this.isLessonsThemeVisible, + required this.nextServerDeployAsString, + }); factory CustomizationSettings.fromJson(Map json) { return CustomizationSettings( - delayForNotifications: - json['ErtekelesekMegjelenitesenekKesleltetesenekMerteke'], - isClassAverageVisible: json['IsOsztalyAtlagMegjeleniteseEllenorzoben'], - isLessonsThemeVisible: json['IsTanorakTemajaMegtekinthetoEllenorzoben'], - nextServerDeployAsString: json['KovetkezoTelepitesDatuma']); + delayForNotifications: + json['ErtekelesekMegjelenitesenekKesleltetesenekMerteke'], + isClassAverageVisible: json['IsOsztalyAtlagMegjeleniteseEllenorzoben'], + isLessonsThemeVisible: json['IsTanorakTemajaMegtekinthetoEllenorzoben'], + nextServerDeployAsString: json['KovetkezoTelepitesDatuma'], + ); } @override @@ -68,7 +72,10 @@ class SystemModule { factory SystemModule.fromJson(Map json) { return SystemModule( - isActive: json['IsAktiv'], type: json['Tipus'], url: json['Url']); + isActive: json['IsAktiv'], + type: json['Tipus'], + url: json['Url'], + ); } @override diff --git a/firka/lib/helpers/api/model/notice_board.dart b/firka/lib/helpers/api/model/notice_board.dart index a39e0e1..908ebfa 100644 --- a/firka/lib/helpers/api/model/notice_board.dart +++ b/firka/lib/helpers/api/model/notice_board.dart @@ -9,24 +9,26 @@ class NoticeBoardItem { final String contentHTML; final String contentText; - NoticeBoardItem( - {required this.uid, - required this.author, - required this.validFrom, - required this.validTo, - required this.title, - required this.contentHTML, - required this.contentText}); + NoticeBoardItem({ + required this.uid, + required this.author, + required this.validFrom, + required this.validTo, + required this.title, + required this.contentHTML, + required this.contentText, + }); factory NoticeBoardItem.fromJson(Map json) { return NoticeBoardItem( - uid: json['Uid'], - author: json['RogzitoNeve'], - validFrom: DateTime.parse(json['ErvenyessegKezdete']), - validTo: DateTime.parse(json['ErvenyessegVege']), - title: json['Cim'], - contentHTML: json['Tartalom'], - contentText: json['TartalomText']); + uid: json['Uid'], + author: json['RogzitoNeve'], + validFrom: DateTime.parse(json['ErvenyessegKezdete']), + validTo: DateTime.parse(json['ErvenyessegVege']), + title: json['Cim'], + contentHTML: json['Tartalom'], + contentText: json['TartalomText'], + ); } @override @@ -53,26 +55,28 @@ class InfoBoardItem { final String contentText; final NameUidDesc type; - InfoBoardItem( - {required this.uid, - required this.title, - required this.date, - required this.author, - required this.createdAt, - required this.contentHTML, - required this.contentText, - required this.type}); + InfoBoardItem({ + required this.uid, + required this.title, + required this.date, + required this.author, + required this.createdAt, + required this.contentHTML, + required this.contentText, + required this.type, + }); factory InfoBoardItem.fromJson(Map json) { return InfoBoardItem( - uid: json['Uid'], - title: json['Cim'], - date: DateTime.parse(json['Datum']), - author: json['KeszitoTanarNeve'], - createdAt: DateTime.parse(json['KeszitesDatuma']), - contentText: json['Tartalom'], - contentHTML: json['TartalomFormazott'], - type: NameUidDesc.fromJson(json['Tipus'])); + uid: json['Uid'], + title: json['Cim'], + date: DateTime.parse(json['Datum']), + author: json['KeszitoTanarNeve'], + createdAt: DateTime.parse(json['KeszitesDatuma']), + contentText: json['Tartalom'], + contentHTML: json['TartalomFormazott'], + type: NameUidDesc.fromJson(json['Tipus']), + ); } @override diff --git a/firka/lib/helpers/api/model/omission.dart b/firka/lib/helpers/api/model/omission.dart index 6db31fa..0c44015 100644 --- a/firka/lib/helpers/api/model/omission.dart +++ b/firka/lib/helpers/api/model/omission.dart @@ -75,11 +75,7 @@ class Class { final DateTime end; final int classNo; - Class({ - required this.start, - required this.end, - required this.classNo, - }); + Class({required this.start, required this.end, required this.classNo}); factory Class.fromJson(Map json) { return Class( diff --git a/firka/lib/helpers/api/model/student.dart b/firka/lib/helpers/api/model/student.dart index 438370d..f2c6742 100644 --- a/firka/lib/helpers/api/model/student.dart +++ b/firka/lib/helpers/api/model/student.dart @@ -25,22 +25,23 @@ class Student { final Institution institution; - Student( - {required this.addressDataList, - required this.bankAccount, - // required this.yearOfBirth, - // required this.monthOfBirth, - // required this.dayOfBirth, - required this.birthdate, - required this.emailAddress, - required this.name, - required this.phoneNumber, - required this.schoolYearUID, - required this.uid, - required this.guardianList, - required this.instituteCode, - required this.instituteName, - required this.institution}); + Student({ + required this.addressDataList, + required this.bankAccount, + // required this.yearOfBirth, + // required this.monthOfBirth, + // required this.dayOfBirth, + required this.birthdate, + required this.emailAddress, + required this.name, + required this.phoneNumber, + required this.schoolYearUID, + required this.uid, + required this.guardianList, + required this.instituteCode, + required this.instituteName, + required this.institution, + }); factory Student.fromJson(Map json) { var guardianList = List.empty(growable: true); @@ -50,19 +51,21 @@ class Student { } return Student( - addressDataList: listToTyped(json['Cimek']), - bankAccount: BankAccount.fromJson(json['Bankszamla']), - birthdate: DateFormat('yyyy-M-d').parse( - "${json['SzuletesiEv']}-${json['SzuletesiHonap']}-${json['SzuletesiNap']}"), - emailAddress: json['EmailCim'], - name: json['Nev'], - phoneNumber: json['Telefonszam'], - schoolYearUID: json['TanevUid'], - uid: json['Uid'], - guardianList: guardianList, - instituteCode: json['IntezmenyAzonosito'], - instituteName: json['IntezmenyNev'], - institution: Institution.fromJson(json['Intezmeny'])); + addressDataList: listToTyped(json['Cimek']), + bankAccount: BankAccount.fromJson(json['Bankszamla']), + birthdate: DateFormat('yyyy-M-d').parse( + "${json['SzuletesiEv']}-${json['SzuletesiHonap']}-${json['SzuletesiNap']}", + ), + emailAddress: json['EmailCim'], + name: json['Nev'], + phoneNumber: json['Telefonszam'], + schoolYearUID: json['TanevUid'], + uid: json['Uid'], + guardianList: guardianList, + instituteCode: json['IntezmenyAzonosito'], + instituteName: json['IntezmenyNev'], + institution: Institution.fromJson(json['Intezmeny']), + ); } @override @@ -89,18 +92,20 @@ class BankAccount { final String? ownerName; final int? ownerType; - BankAccount( - {required this.accountNumber, - required this.isReadOnly, - required this.ownerName, - required this.ownerType}); + BankAccount({ + required this.accountNumber, + required this.isReadOnly, + required this.ownerName, + required this.ownerType, + }); factory BankAccount.fromJson(Map json) { return BankAccount( - accountNumber: json['BankszamlaSzam'], - isReadOnly: json['IsReadOnly'], - ownerName: json['BankszamlaTulajdonosNeve'], - ownerType: json['BankszamlaTulajdonosTipusId']); + accountNumber: json['BankszamlaSzam'], + isReadOnly: json['IsReadOnly'], + ownerName: json['BankszamlaTulajdonosNeve'], + ownerType: json['BankszamlaTulajdonosTipusId'], + ); } @override diff --git a/firka/lib/helpers/api/model/timetable.dart b/firka/lib/helpers/api/model/timetable.dart index 264d0af..6827c11 100644 --- a/firka/lib/helpers/api/model/timetable.dart +++ b/firka/lib/helpers/api/model/timetable.dart @@ -83,8 +83,9 @@ class Lesson { ? NameUid.fromJson(json['OsztalyCsoport']) : null, teacher: json['TanarNeve'], - subject: - json['Tantargy'] != null ? Subject.fromJson(json['Tantargy']) : null, + subject: json['Tantargy'] != null + ? Subject.fromJson(json['Tantargy']) + : null, theme: json['Tema'], roomName: json['TeremNeve'], type: NameUidDesc.fromJson(json['Tipus']), @@ -105,8 +106,8 @@ class Lesson { digitalPlatformType: json['DigitalisPlatformTipus'], digitalSupportDeviceTypeList: json['DigitalisTamogatoEszkozTipusList'] != null - ? List.from(json['DigitalisTamogatoEszkozTipusList']) - : List.empty(), + ? List.from(json['DigitalisTamogatoEszkozTipusList']) + : List.empty(), createdAt: DateTime.parse(json['Letrehozas']).toLocal(), lastModifiedAt: DateTime.parse(json['UtolsoModositas']).toLocal(), ); diff --git a/firka/lib/helpers/api/resp/token_grant.dart b/firka/lib/helpers/api/resp/token_grant.dart index 42b3f64..1bedf05 100644 --- a/firka/lib/helpers/api/resp/token_grant.dart +++ b/firka/lib/helpers/api/resp/token_grant.dart @@ -6,22 +6,24 @@ class TokenGrantResponse { final String refreshToken; final String scope; - TokenGrantResponse( - {required this.idToken, - required this.accessToken, - required this.expiresIn, - required this.tokenType, - required this.refreshToken, - required this.scope}); + TokenGrantResponse({ + required this.idToken, + required this.accessToken, + required this.expiresIn, + required this.tokenType, + required this.refreshToken, + required this.scope, + }); factory TokenGrantResponse.fromJson(Map json) { return TokenGrantResponse( - idToken: json['id_token'], - accessToken: json['access_token'], - expiresIn: json['expires_in'], - tokenType: json['token_type'], - refreshToken: json['refresh_token'], - scope: json['scope']); + idToken: json['id_token'], + accessToken: json['access_token'], + expiresIn: json['expires_in'], + tokenType: json['token_type'], + refreshToken: json['refresh_token'], + scope: json['scope'], + ); } @override diff --git a/firka/lib/helpers/api/token_grant.dart b/firka/lib/helpers/api/token_grant.dart index dae89af..9746e94 100644 --- a/firka/lib/helpers/api/token_grant.dart +++ b/firka/lib/helpers/api/token_grant.dart @@ -23,8 +23,11 @@ Future getAccessToken(String code) async { }; try { - final response = await dio.post(KretaEndpoints.tokenGrantUrl, - options: Options(headers: headers), data: formData); + final response = await dio.post( + KretaEndpoints.tokenGrantUrl, + options: Options(headers: headers), + data: formData, + ); switch (response.statusCode) { case 200: @@ -33,7 +36,8 @@ Future getAccessToken(String code) async { throw Exception("Invalid grant"); default: throw Exception( - "Failed to get access token, response code: ${response.statusCode}"); + "Failed to get access token, response code: ${response.statusCode}", + ); } } catch (e) { rethrow; @@ -44,7 +48,8 @@ const _tokenRefreshRetryDelays = [1000, 3000, 5000]; Future extendToken(TokenModel model) async { logger.info( - "Extending token for user: ${model.studentId}, institute: ${model.iss}"); + "Extending token for user: ${model.studentId}, institute: ${model.iss}", + ); final headers = { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", @@ -66,30 +71,38 @@ Future extendToken(TokenModel model) async { if (attempt > 0) { final delay = _tokenRefreshRetryDelays[attempt - 1]; logger.info( - "Token refresh attempt ${attempt + 1}, waiting ${delay}ms..."); + "Token refresh attempt ${attempt + 1}, waiting ${delay}ms...", + ); await Future.delayed(Duration(milliseconds: delay)); } - final response = await dio.post(KretaEndpoints.tokenGrantUrl, - options: Options(headers: headers), data: formData); + final response = await dio.post( + KretaEndpoints.tokenGrantUrl, + options: Options(headers: headers), + data: formData, + ); switch (response.statusCode) { case 200: - logger - .info("Token extended successfully for user: ${model.studentId}"); + logger.info( + "Token extended successfully for user: ${model.studentId}", + ); return TokenGrantResponse.fromJson(response.data); case 400: case 401: logger.warning( - "Token refresh failed (${response.statusCode}) - refresh token invalid for user: ${model.studentId}"); + "Token refresh failed (${response.statusCode}) - refresh token invalid for user: ${model.studentId}", + ); throw response.statusCode == 400 ? TokenExpiredException() : InvalidGrantException(); default: logger.warning( - "Token refresh failed (${response.statusCode}) for user: ${model.studentId}, attempt ${attempt + 1}"); + "Token refresh failed (${response.statusCode}) for user: ${model.studentId}, attempt ${attempt + 1}", + ); lastError = Exception( - "Failed to get access token, response code: ${response.statusCode}"); + "Failed to get access token, response code: ${response.statusCode}", + ); // Continue to retry for network errors continue; } @@ -99,7 +112,8 @@ Future extendToken(TokenModel model) async { rethrow; } on DioException catch (e) { logger.warning( - "Token refresh network error for user: ${model.studentId}, attempt ${attempt + 1}: $e"); + "Token refresh network error for user: ${model.studentId}, attempt ${attempt + 1}: $e", + ); lastError = e; continue; } catch (e) { @@ -109,7 +123,8 @@ Future extendToken(TokenModel model) async { } } - logger - .severe("All token refresh attempts failed for user: ${model.studentId}"); + logger.severe( + "All token refresh attempts failed for user: ${model.studentId}", + ); throw lastError ?? Exception("Token refresh failed after all retries"); } diff --git a/firka/lib/helpers/average_helper.dart b/firka/lib/helpers/average_helper.dart index ccabc82..928d2dc 100644 --- a/firka/lib/helpers/average_helper.dart +++ b/firka/lib/helpers/average_helper.dart @@ -20,4 +20,4 @@ double calculateAverage(List sortedGrades) { final avg = weightedSum / totalWeight; return double.parse(avg.toStringAsFixed(2)); -} \ No newline at end of file +} diff --git a/firka/lib/helpers/db/ios_widget_helper.dart b/firka/lib/helpers/db/ios_widget_helper.dart index 378a562..1a55944 100644 --- a/firka/lib/helpers/db/ios_widget_helper.dart +++ b/firka/lib/helpers/db/ios_widget_helper.dart @@ -14,7 +14,9 @@ class IOSWidgetHelper { if (!Platform.isIOS) return null; try { - final result = await _channel.invokeMethod('getAppGroupDirectory'); + final result = await _channel.invokeMethod( + 'getAppGroupDirectory', + ); if (result != null) { return Directory(result); } @@ -39,8 +41,12 @@ class IOSWidgetHelper { if (!Platform.isIOS) return; debugPrint('[IOSWidget] Starting updateWidgetData...'); - debugPrint('[IOSWidget] todayLessons: ${todayLessons.length}, tomorrowLessons: ${tomorrowLessons.length}'); - debugPrint('[IOSWidget] grades: ${grades.length}, subjectAverages: ${subjectAverages.length}'); + debugPrint( + '[IOSWidget] todayLessons: ${todayLessons.length}, tomorrowLessons: ${tomorrowLessons.length}', + ); + debugPrint( + '[IOSWidget] grades: ${grades.length}, subjectAverages: ${subjectAverages.length}', + ); final dir = await _getAppGroupDirectory(); if (dir == null) { @@ -56,23 +62,31 @@ class IOSWidgetHelper { 'timetable': { 'today': todayLessons.map((l) => _lessonToJson(l)).toList(), 'tomorrow': tomorrowLessons.map((l) => _lessonToJson(l)).toList(), - 'nextSchoolDay': nextSchoolDayLessons.map((l) => _lessonToJson(l)).toList(), + 'nextSchoolDay': nextSchoolDayLessons + .map((l) => _lessonToJson(l)) + .toList(), 'nextSchoolDayDate': nextSchoolDayDate?.toIso8601String(), - 'currentBreak': currentBreak != null ? { - 'name': currentBreak.name, - 'nameKey': currentBreak.nameKey, - 'endDate': currentBreak.endDate.toIso8601String(), - } : null, + 'currentBreak': currentBreak != null + ? { + 'name': currentBreak.name, + 'nameKey': currentBreak.nameKey, + 'endDate': currentBreak.endDate.toIso8601String(), + } + : null, }, 'grades': grades.take(20).map((g) => _gradeToJson(g)).toList(), 'averages': { 'overall': overallAverage, - 'subjects': subjectAverages.entries.map((e) => { - 'uid': e.key, - 'name': _getSubjectNameFromGrades(e.key, grades), - 'average': e.value, - 'gradeCount': _getGradeCount(e.key, grades), - }).toList(), + 'subjects': subjectAverages.entries + .map( + (e) => { + 'uid': e.key, + 'name': _getSubjectNameFromGrades(e.key, grades), + 'average': e.value, + 'gradeCount': _getGradeCount(e.key, grades), + }, + ) + .toList(), }, }; @@ -118,26 +132,29 @@ class IOSWidgetHelper { 'name': lesson.name, 'lessonNumber': lesson.lessonNumber, 'teacher': lesson.teacher, - 'subject': subject != null ? { - 'uid': subject.uid, - 'name': subject.name, - 'category': subject.category != null ? { - 'uid': subject.category!.uid, - 'name': subject.category!.name, - 'description': subject.category!.description, - } : null, - 'sortIndex': subject.sortIndex, - 'teacherName': subject.teacherName, - } : { - 'uid': '', - 'name': lesson.name, - 'category': null, - 'sortIndex': 0, - 'teacherName': null, - }, + 'subject': subject != null + ? { + 'uid': subject.uid, + 'name': subject.name, + 'category': { + 'uid': subject.category.uid, + 'name': subject.category.name, + 'description': subject.category.description, + }, + 'sortIndex': subject.sortIndex, + 'teacherName': subject.teacherName, + } + : { + 'uid': '', + 'name': lesson.name, + 'category': null, + 'sortIndex': 0, + 'teacherName': null, + }, 'theme': lesson.theme, 'roomName': lesson.roomName, - 'isCancelled': lesson.state.name?.toLowerCase().contains('elmarad') ?? false, + 'isCancelled': + lesson.state.name?.toLowerCase().contains('elmarad') ?? false, 'isSubstitution': lesson.substituteTeacher != null, }; } @@ -149,11 +166,11 @@ class IOSWidgetHelper { 'subject': { 'uid': grade.subject.uid, 'name': grade.subject.name, - 'category': grade.subject.category != null ? { - 'uid': grade.subject.category!.uid, - 'name': grade.subject.category!.name, - 'description': grade.subject.category!.description, - } : null, + 'category': { + 'uid': grade.subject.category.uid, + 'name': grade.subject.category.name, + 'description': grade.subject.category.description, + }, 'sortIndex': grade.subject.sortIndex, // Use the grade's teacher field, not subject.teacherName (which is usually null for grades) 'teacherName': grade.teacher, diff --git a/firka/lib/helpers/db/models/generic_cache_model.dart b/firka/lib/helpers/db/models/generic_cache_model.dart index 777ec08..4a9ecfe 100644 --- a/firka/lib/helpers/db/models/generic_cache_model.dart +++ b/firka/lib/helpers/db/models/generic_cache_model.dart @@ -12,7 +12,7 @@ enum CacheId { getClassGroup, getSubjectAvg, getLessons, - getHomework + getHomework, } @collection diff --git a/firka/lib/helpers/db/models/homework_cache_model.dart b/firka/lib/helpers/db/models/homework_cache_model.dart index 1090ad7..bc6aa0f 100644 --- a/firka/lib/helpers/db/models/homework_cache_model.dart +++ b/firka/lib/helpers/db/models/homework_cache_model.dart @@ -28,7 +28,7 @@ Future resetOldHomeworkCache(Isar isar) async { }); } -@collection +@collection class HomeworkDoneModel { Id? id; @@ -36,13 +36,15 @@ class HomeworkDoneModel { late DateTime doneAt; HomeworkDoneModel(); - } + Future markAsDone(Isar isar, String homeWorkUid) async { await isar.writeTxn(() async { - await isar.homeworkDoneModels.put(HomeworkDoneModel() - ..homeworkId = homeWorkUid - ..doneAt = DateTime.now()); + await isar.homeworkDoneModels.put( + HomeworkDoneModel() + ..homeworkId = homeWorkUid + ..doneAt = DateTime.now(), + ); }); } @@ -63,4 +65,4 @@ Future isHomeworkDone(Isar isar, String homeWorkUid) async { .homeworkIdEqualTo(homeWorkUid) .findFirst(); return existing != null; -} \ No newline at end of file +} diff --git a/firka/lib/helpers/db/models/token_model.dart b/firka/lib/helpers/db/models/token_model.dart index c0e9d68..763bd30 100644 --- a/firka/lib/helpers/db/models/token_model.dart +++ b/firka/lib/helpers/db/models/token_model.dart @@ -64,7 +64,8 @@ class TokenModel { // you would expect all usernames to be numeric // and for them be the student's student id, but NO final hash = sha256.convert(utf8.encode(username)); - final value = ((hash.bytes[0] << 24) | + final value = + ((hash.bytes[0] << 24) | (hash.bytes[1] << 16) | (hash.bytes[2] << 8) | (hash.bytes[3])) >>> diff --git a/firka/lib/helpers/db/widget.dart b/firka/lib/helpers/db/widget.dart index d1507e4..017896d 100644 --- a/firka/lib/helpers/db/widget.dart +++ b/firka/lib/helpers/db/widget.dart @@ -57,7 +57,9 @@ class WidgetCacheHelper { } static Future updateWidgetCache( - FirkaStyle style, KretaClient client) async { + FirkaStyle style, + KretaClient client, + ) async { final dataDir = await getApplicationDocumentsDirectory(); final now = timeNow(); @@ -69,9 +71,12 @@ class WidgetCacheHelper { final widgetFile = File(p.join(dataDir.path, "widget_state.json")); if (lessons.response != null) { - debugPrint('Android widget cache: ${lessons.response!.length} lessons (cached: ${lessons.cached})'); + debugPrint( + 'Android widget cache: ${lessons.response!.length} lessons (cached: ${lessons.cached})', + ); widgetFile.writeAsString( - jsonEncode(WidgetCacheHelper.toJson(style, lessons.response!))); + jsonEncode(WidgetCacheHelper.toJson(style, lessons.response!)), + ); } else { debugPrint('Android widget cache: No lessons to cache'); } @@ -105,13 +110,17 @@ class WidgetCacheHelper { /// Comprehensive iOS widget refresh that collects all necessary data /// Call this on: app open, user switch, data refresh - static Future refreshIOSWidgets(KretaClient client, SettingsStore settings) async { + static Future refreshIOSWidgets( + KretaClient client, + SettingsStore settings, + ) async { if (!Platform.isIOS) return; try { - final langIndex = (settings.group("settings").subGroup("application")["language"] - as SettingsItemsRadio) - .activeIndex; + final langIndex = + (settings.group("settings").subGroup("application")["language"] + as SettingsItemsRadio) + .activeIndex; String locale; switch (langIndex) { case 1: @@ -127,9 +136,10 @@ class WidgetCacheHelper { locale = 'hu'; } - final themeIndex = (settings.group("settings").subGroup("customization")["theme"] - as SettingsItemsRadio) - .activeIndex; + final themeIndex = + (settings.group("settings").subGroup("customization")["theme"] + as SettingsItemsRadio) + .activeIndex; String theme; switch (themeIndex) { case 1: @@ -160,7 +170,9 @@ class WidgetCacheHelper { final todayLessons = todayResponse.response ?? []; final tomorrowLessons = tomorrowResponse.response ?? []; - debugPrint('iOS widget refresh: ${todayLessons.length} today lessons, ${tomorrowLessons.length} tomorrow lessons'); + debugPrint( + 'iOS widget refresh: ${todayLessons.length} today lessons, ${tomorrowLessons.length} tomorrow lessons', + ); List nextSchoolDayLessons = []; DateTime? nextSchoolDayDate; @@ -176,7 +188,9 @@ class WidgetCacheHelper { if (dayLessons.isNotEmpty) { nextSchoolDayLessons = dayLessons; nextSchoolDayDate = dayMidnight; - debugPrint('iOS widget: Next school day found ${i} days ahead with ${dayLessons.length} lessons'); + debugPrint( + 'iOS widget: Next school day found $i days ahead with ${dayLessons.length} lessons', + ); break; } } @@ -185,7 +199,9 @@ class WidgetCacheHelper { final gradesResponse = await client.getGrades(forceCache: false); final grades = gradesResponse.response ?? []; - debugPrint('iOS widget refresh: ${grades.length} grades fetched (cached: ${gradesResponse.cached})'); + debugPrint( + 'iOS widget refresh: ${grades.length} grades fetched (cached: ${gradesResponse.cached})', + ); final Map subjectAverages = {}; final Set subjectUids = {}; @@ -198,7 +214,9 @@ class WidgetCacheHelper { int validSubjectCount = 0; for (var uid in subjectUids) { - final subjectGrades = grades.where((g) => g.subject.uid == uid).toList(); + final subjectGrades = grades + .where((g) => g.subject.uid == uid) + .toList(); final avg = _calculateWeightedAverage(subjectGrades); if (!avg.isNaN && avg > 0) { subjectAverages[uid] = avg; diff --git a/firka/lib/helpers/extensions.dart b/firka/lib/helpers/extensions.dart index d83b793..a96bc6e 100644 --- a/firka/lib/helpers/extensions.dart +++ b/firka/lib/helpers/extensions.dart @@ -12,29 +12,43 @@ extension TimetableExtension on Iterable { if (lesson.lessonNumber == null) continue; if (lessons.firstWhereOrNull( - (lesson2) => lesson.lessonNumber == lesson2.lessonNumber) == + (lesson2) => lesson.lessonNumber == lesson2.lessonNumber, + ) == null) { final ref = reference.start; - final newStart = DateTime(ref.year, ref.month, ref.day, - lesson.start.hour, lesson.start.minute, lesson.start.second); - final newEnd = DateTime(ref.year, ref.month, ref.day, lesson.end.hour, - lesson.end.minute, lesson.end.second); + final newStart = DateTime( + ref.year, + ref.month, + ref.day, + lesson.start.hour, + lesson.start.minute, + lesson.start.second, + ); + final newEnd = DateTime( + ref.year, + ref.month, + ref.day, + lesson.end.hour, + lesson.end.minute, + lesson.end.second, + ); final lessonCopy = Lesson( - uid: lesson.uid, - date: lesson.date, - start: newStart, - end: newEnd, - name: lesson.name, - type: lesson.type, - state: lesson.state, - canStudentEditHomework: lesson.canStudentEditHomework, - isHomeworkComplete: lesson.isHomeworkComplete, - attachments: lesson.attachments, - lessonNumber: lesson.lessonNumber, - isDigitalLesson: lesson.isDigitalLesson, - digitalSupportDeviceTypeList: lesson.digitalSupportDeviceTypeList, - createdAt: lesson.createdAt, - lastModifiedAt: lesson.lastModifiedAt); + uid: lesson.uid, + date: lesson.date, + start: newStart, + end: newEnd, + name: lesson.name, + type: lesson.type, + state: lesson.state, + canStudentEditHomework: lesson.canStudentEditHomework, + isHomeworkComplete: lesson.isHomeworkComplete, + attachments: lesson.attachments, + lessonNumber: lesson.lessonNumber, + isDigitalLesson: lesson.isDigitalLesson, + digitalSupportDeviceTypeList: lesson.digitalSupportDeviceTypeList, + createdAt: lesson.createdAt, + lastModifiedAt: lesson.lastModifiedAt, + ); lessons.add(lessonCopy); } } @@ -85,7 +99,7 @@ enum FormatMode { yyyymmddwedd, yyyymmmm, yyyymmdd, - yyyymmddhhmmss + yyyymmddhhmmss, } enum Cycle { morning, day, afternoon, night } @@ -105,7 +119,10 @@ extension DateExtension on DateTime { switch (mode) { case FormatMode.grades: if (isBefore(yesterdayLim)) { - final month = DateFormat('MMMM', l10n.localeName).format(this).firstUpper(); + final month = DateFormat( + 'MMMM', + l10n.localeName, + ).format(this).firstUpper(); final day = DateFormat('d', l10n.localeName).format(this); return "$month $day"; } @@ -125,17 +142,23 @@ extension DateExtension on DateTime { case FormatMode.hmm: return DateFormat('H:mm', l10n.localeName).format(this); case FormatMode.welcome: - final dayName = DateFormat('EEEE', l10n.localeName).format(this).firstUpper(); - final monthAbbr = DateFormat('MMM', l10n.localeName).format(this).firstUpper(); + final dayName = DateFormat( + 'EEEE', + l10n.localeName, + ).format(this).firstUpper(); + final monthAbbr = DateFormat( + 'MMM', + l10n.localeName, + ).format(this).firstUpper(); final day = DateFormat('d', l10n.localeName).format(this); return "$dayName, $monthAbbr $day"; case FormatMode.d: return DateFormat('d', l10n.localeName).format(this); case FormatMode.da: - return DateFormat('EEEE', l10n.localeName) - .format(this) - .substring(0, 2) - .firstUpper(); + return DateFormat( + 'EEEE', + l10n.localeName, + ).format(this).substring(0, 2).firstUpper(); case FormatMode.dd: return DateFormat('dd', l10n.localeName).format(this); case FormatMode.yyyymmddwedd: @@ -167,12 +190,15 @@ extension DateExtension on DateTime { } DateTime getMidnight() { - return subtract(Duration( + return subtract( + Duration( hours: hour, minutes: minute, seconds: second, milliseconds: millisecond, - microseconds: microsecond)); + microseconds: microsecond, + ), + ); } Cycle getDayCycle() { @@ -199,22 +225,28 @@ extension DateGrouper on Iterable { Map> newList = {}; var today = timeNow(); - today = today.subtract(Duration( + today = today.subtract( + Duration( hours: today.hour, minutes: today.minute, seconds: today.second, - milliseconds: today.millisecond)); + milliseconds: today.millisecond, + ), + ); var tomorrow = today.add(Duration(days: 1)); var yesterday = today.subtract(Duration(days: 1)); for (var elem in this) { var date = getDate(elem); - var day = date.subtract(Duration( + var day = date.subtract( + Duration( hours: date.hour, minutes: date.minute, seconds: date.second, - milliseconds: date.millisecond)); + milliseconds: date.millisecond, + ), + ); if (date.isAfter(tomorrow.add(Duration(days: 1)))) { if (newList[day] == null) { @@ -260,17 +292,20 @@ extension LessonExtension on List { Lesson? getCurrentLesson(DateTime now) { return firstWhereOrNull( - (lesson) => now.isAfter(lesson.start) && now.isBefore(lesson.end)); + (lesson) => now.isAfter(lesson.start) && now.isBefore(lesson.end), + ); } Lesson? getPrevLesson(DateTime now) { return firstWhereOrNull( - (lesson) => lesson.end.isBefore(now.add(Duration(milliseconds: 1)))); + (lesson) => lesson.end.isBefore(now.add(Duration(milliseconds: 1))), + ); } Lesson? getNextLesson(DateTime now) { return firstWhereOrNull( - (lesson) => lesson.start.isAfter(now.add(Duration(milliseconds: 1)))); + (lesson) => lesson.start.isAfter(now.add(Duration(milliseconds: 1))), + ); } } diff --git a/firka/lib/helpers/firka_bundle.dart b/firka/lib/helpers/firka_bundle.dart index 60dc48f..fd5a74b 100644 --- a/firka/lib/helpers/firka_bundle.dart +++ b/firka/lib/helpers/firka_bundle.dart @@ -34,8 +34,9 @@ class FirkaBundle extends CachingAssetBundle { @override Future load(String key) async { if (!_compressedBundle) { - logger - .finest("Loading asset from root bundle: assets/flutter_assets/$key"); + logger.finest( + "Loading asset from root bundle: assets/flutter_assets/$key", + ); return rootBundle.load(key); } else { index ??= await loadIndex(); @@ -43,7 +44,8 @@ class FirkaBundle extends CachingAssetBundle { final gzip = GZipCodec(); logger.finest( - "Loading asset from firka bundle: assets/flutter_assets/$key"); + "Loading asset from firka bundle: assets/flutter_assets/$key", + ); switch (index!["assets/flutter_assets/$key"]!) { case "b": // brotli return decode(brotli, await rootBundle.load(key)); diff --git a/firka/lib/helpers/icon_helper.dart b/firka/lib/helpers/icon_helper.dart index 6221f33..34d5343 100644 --- a/firka/lib/helpers/icon_helper.dart +++ b/firka/lib/helpers/icon_helper.dart @@ -34,7 +34,7 @@ enum ClassIcon { linux, database, applications, - project + project, } Map _descriptors = { @@ -49,8 +49,9 @@ Map _descriptors = { ClassIcon.pe: RegExp(r'^tes(i|tneveles)|sport|edzeselmelet'), ClassIcon.chemistry: RegExp(r'kemia'), ClassIcon.biology: RegExp(r'biologia'), - ClassIcon.env: - RegExp(r'kornyezet|termeszet ?(tudomany|ismeret)|hon( es nep)?ismeret'), + ClassIcon.env: RegExp( + r'kornyezet|termeszet ?(tudomany|ismeret)|hon( es nep)?ismeret', + ), ClassIcon.religion: RegExp(r'(hit|erkolcs)tan|vallas|etika|bibliaismeret'), ClassIcon.economics: RegExp(r'penzugy|gazdasag'), ClassIcon.it: RegExp(r'informatika|szoftver|iroda|digitalis'), @@ -66,12 +67,13 @@ Map _descriptors = { ClassIcon.ofo: RegExp(r'osztaly(fonoki|kozosseg)|kozossegi|neveles'), ClassIcon.diligence: RegExp(r'szorgalom'), ClassIcon.attitude: RegExp(r'magatartas'), - ClassIcon.language: - RegExp(r'angol|nemet|francia|olasz|orosz|spanyol|latin|kinai|nyelv'), + ClassIcon.language: RegExp( + r'angol|nemet|francia|olasz|orosz|spanyol|latin|kinai|nyelv', + ), ClassIcon.linux: RegExp(r'linux'), ClassIcon.database: RegExp(r'adatbazis.*'), ClassIcon.applications: RegExp(r'asztali alkalmazasok'), - ClassIcon.project: RegExp(r'projekt') + ClassIcon.project: RegExp(r'projekt'), }; Map _iconMap = { @@ -117,17 +119,19 @@ ClassIcon? getIconType(String uid, String className, String category) { if (icon == null) { for (var desc in _descriptors.entries) { - if (desc.value.hasMatch(className - .replaceAll("ö", "o") - .replaceAll("ü", "u") - .replaceAll("ó", "o") - .replaceAll("ő", "o") - .replaceAll("ú", "u") - .replaceAll("é", "e") - .replaceAll("á", "a") - .replaceAll("ű", "u") - .replaceAll("í", "i") - .toLowerCase())) { + if (desc.value.hasMatch( + className + .replaceAll("ö", "o") + .replaceAll("ü", "u") + .replaceAll("ó", "o") + .replaceAll("ő", "o") + .replaceAll("ú", "u") + .replaceAll("é", "e") + .replaceAll("á", "a") + .replaceAll("ű", "u") + .replaceAll("í", "i") + .toLowerCase(), + )) { icon = desc.key; break; diff --git a/firka/lib/helpers/image_preloader.dart b/firka/lib/helpers/image_preloader.dart index 69d77b4..6b8b154 100644 --- a/firka/lib/helpers/image_preloader.dart +++ b/firka/lib/helpers/image_preloader.dart @@ -10,7 +10,9 @@ class ImagePreloader { static final Map> _loadingFutures = {}; static Future preloadAssetImage( - AssetBundle bundle, String assetPath) async { + AssetBundle bundle, + String assetPath, + ) async { if (_cache.containsKey(assetPath)) { return _cache[assetPath]!; } @@ -32,9 +34,12 @@ class ImagePreloader { } static Future> preloadMultipleAssets( - AssetBundle bundle, List assetPaths) async { - final futures = - assetPaths.map((path) => preloadAssetImage(bundle, path)).toList(); + AssetBundle bundle, + List assetPaths, + ) async { + final futures = assetPaths + .map((path) => preloadAssetImage(bundle, path)) + .toList(); return await Future.wait(futures); } @@ -91,7 +96,9 @@ class ImagePreloader { } static Future _loadAssetImage( - AssetBundle bundle, String assetPath) async { + AssetBundle bundle, + String assetPath, + ) async { logger.finest("Caching: $assetPath"); final ByteData data = await bundle.load(assetPath); final Uint8List bytes = data.buffer.asUint8List(); @@ -119,7 +126,9 @@ class PreloadedImageProvider extends ImageProvider { @override ImageStreamCompleter loadImage( - PreloadedImageProvider key, ImageDecoderCallback decode) { + PreloadedImageProvider key, + ImageDecoderCallback decode, + ) { return OneFrameImageStreamCompleter(_loadAsync(key)); } @@ -133,8 +142,10 @@ class PreloadedImageProvider extends ImageProvider { } try { - final image = - await ImagePreloader.preloadAssetImage(assetBundle, key.assetPath); + final image = await ImagePreloader.preloadAssetImage( + assetBundle, + key.assetPath, + ); return ImageInfo(image: image.clone()); } catch (e) { final ByteData data = await assetBundle.load(key.assetPath); diff --git a/firka/lib/helpers/live_activity_manager.dart b/firka/lib/helpers/live_activity_manager.dart index e43a6a5..03aa6d9 100644 --- a/firka/lib/helpers/live_activity_manager.dart +++ b/firka/lib/helpers/live_activity_manager.dart @@ -5,7 +5,9 @@ import 'package:firka/helpers/api/model/timetable.dart'; import 'package:logging/logging.dart'; class LiveActivityManager { - static const MethodChannel _channel = MethodChannel('firka.app/live_activity'); + static const MethodChannel _channel = MethodChannel( + 'firka.app/live_activity', + ); static final Logger _logger = Logger('LiveActivityManager'); static String? _activityId; @@ -31,7 +33,9 @@ class LiveActivityManager { if (activeActivities.isNotEmpty) { _activityId = activeActivities.first; _isActivityActive = true; - _logger.info('Synced activity state: Found existing activity $_activityId'); + _logger.info( + 'Synced activity state: Found existing activity $_activityId', + ); } else { _activityId = null; _isActivityActive = false; @@ -48,7 +52,9 @@ class LiveActivityManager { final args = call.arguments as Map; final activityId = args['activityId'] as String; final pushToken = args['pushToken'] as String; - _logger.info('Received LiveActivity push token: ${pushToken.substring(0, 10)}...'); + _logger.info( + 'Received LiveActivity push token: ${pushToken.substring(0, 10)}...', + ); _onPushTokenReceived?.call(activityId, pushToken); break; default: @@ -56,7 +62,9 @@ class LiveActivityManager { } } - static void setOnPushTokenReceived(Function(String activityId, String pushToken) callback) { + static void setOnPushTokenReceived( + Function(String activityId, String pushToken) callback, + ) { _onPushTokenReceived = callback; } @@ -92,7 +100,9 @@ class LiveActivityManager { try { await _syncActivityState(); if (_isActivityActive) { - _logger.info('Activity already exists, ending it to create new one with fresh token'); + _logger.info( + 'Activity already exists, ending it to create new one with fresh token', + ); await endAllActivities(); await Future.delayed(const Duration(milliseconds: 500)); } @@ -217,18 +227,23 @@ class LiveActivityManager { final payload = { 'isBreak': isBeforeSchool ? false : isBreak, - 'lessonName': isBeforeSchool ? currentLesson.name : (isBreak ? 'Szünet' : currentLesson.name), + 'lessonName': isBeforeSchool + ? currentLesson.name + : (isBreak ? 'Szünet' : currentLesson.name), 'lessonTheme': (isBeforeSchool || isBreak) ? null : currentLesson.theme, 'roomName': (isBeforeSchool || isBreak) ? null : currentLesson.roomName, 'teacherName': (isBeforeSchool || isBreak) ? null : currentLesson.teacher, 'startTime': startTimeForActivity.toUtc().toIso8601String(), 'endTime': endTimeForActivity.toUtc().toIso8601String(), - 'lessonNumber': (isBeforeSchool || isBreak) ? null : currentLesson.lessonNumber, + 'lessonNumber': (isBeforeSchool || isBreak) + ? null + : currentLesson.lessonNumber, 'nextLessonName': isBeforeSchool ? null : nextLesson?.name, 'nextRoomName': isBeforeSchool ? null : nextLesson?.roomName, 'nextStartTime': nextStartTimeForActivity?.toUtc().toIso8601String(), 'isSubstitution': currentLesson.substituteTeacher != null, - 'isCancelled': currentLesson.state.name?.toLowerCase().contains('elmarad') ?? false, + 'isCancelled': + currentLesson.state.name?.toLowerCase().contains('elmarad') ?? false, 'substituteTeacher': currentLesson.substituteTeacher, 'currentTime': now.toUtc().toIso8601String(), 'mode': mode, diff --git a/firka/lib/helpers/live_activity_service.dart b/firka/lib/helpers/live_activity_service.dart index 94952bf..1587b15 100644 --- a/firka/lib/helpers/live_activity_service.dart +++ b/firka/lib/helpers/live_activity_service.dart @@ -21,7 +21,8 @@ import '../main.dart'; /// Handles timetable synchronization, device token management, and activity updates class LiveActivityService { static final Logger _logger = Logger('LiveActivityService'); - static final LiveActivityBackendClient _backendClient = LiveActivityBackendClient(); + static final LiveActivityBackendClient _backendClient = + LiveActivityBackendClient(); static const String _deviceTokenKey = 'live_activity_device_token'; static const String _lastTimetableUpdateKey = 'live_activity_last_update'; @@ -41,7 +42,9 @@ class LiveActivityService { static bool? _pendingMorningNotificationEnabled; static double? _lastSentMorningNotificationTime; static bool? _lastSentMorningNotificationEnabled; - static const Duration _morningNotificationDebounceInterval = Duration(seconds: 3); + static const Duration _morningNotificationDebounceInterval = Duration( + seconds: 3, + ); static bool _tokenExpired = false; @@ -82,7 +85,10 @@ class LiveActivityService { } /// Set user-specific Live Activity enabled state to SharedPreferences - static Future _setUserLiveActivityEnabled(bool value, {KretaClient? client}) async { + static Future _setUserLiveActivityEnabled( + bool value, { + KretaClient? client, + }) async { final studentId = _getCurrentStudentId(client: client); if (studentId == null) return; @@ -103,7 +109,10 @@ class LiveActivityService { } /// Set user-specific privacy declined state to SharedPreferences - static Future _setUserPrivacyEverDeclined(bool value, {KretaClient? client}) async { + static Future _setUserPrivacyEverDeclined( + bool value, { + KretaClient? client, + }) async { final studentId = _getCurrentStudentId(client: client); if (studentId == null) return; @@ -114,7 +123,9 @@ class LiveActivityService { } /// Get user-specific morning notification enabled state from SharedPreferences - static Future _getUserMorningNotificationEnabled({KretaClient? client}) async { + static Future _getUserMorningNotificationEnabled({ + KretaClient? client, + }) async { final studentId = _getCurrentStudentId(client: client); if (studentId == null) return true; @@ -124,18 +135,25 @@ class LiveActivityService { } /// Set user-specific morning notification enabled state to SharedPreferences - static Future _setUserMorningNotificationEnabled(bool value, {KretaClient? client}) async { + static Future _setUserMorningNotificationEnabled( + bool value, { + KretaClient? client, + }) async { final studentId = _getCurrentStudentId(client: client); if (studentId == null) return; final prefs = await SharedPreferences.getInstance(); final key = 'morning_notification_enabled_$studentId'; await prefs.setBool(key, value); - _logger.info('Saved morning notification enabled=$value for user $studentId'); + _logger.info( + 'Saved morning notification enabled=$value for user $studentId', + ); } /// Get user-specific morning notification time from SharedPreferences - static Future _getUserMorningNotificationTime({KretaClient? client}) async { + static Future _getUserMorningNotificationTime({ + KretaClient? client, + }) async { final studentId = _getCurrentStudentId(client: client); if (studentId == null) return 120; @@ -145,7 +163,10 @@ class LiveActivityService { } /// Set user-specific morning notification time to SharedPreferences - static Future _setUserMorningNotificationTime(int value, {KretaClient? client}) async { + static Future _setUserMorningNotificationTime( + int value, { + KretaClient? client, + }) async { final studentId = _getCurrentStudentId(client: client); if (studentId == null) return; @@ -166,7 +187,10 @@ class LiveActivityService { } /// Set user-specific bell delay to SharedPreferences - static Future _setUserBellDelay(double value, {KretaClient? client}) async { + static Future _setUserBellDelay( + double value, { + KretaClient? client, + }) async { final studentId = _getCurrentStudentId(client: client); if (studentId == null) return; @@ -178,7 +202,9 @@ class LiveActivityService { /// Sync global settings with current user's settings /// This ensures the Settings UI shows the correct state for the current user - static Future syncGlobalSettingWithCurrentUser({KretaClient? client}) async { + static Future syncGlobalSettingWithCurrentUser({ + KretaClient? client, + }) async { if (!Platform.isIOS) return; try { @@ -188,56 +214,80 @@ class LiveActivityService { return; } - final userLiveActivityEnabled = await _getUserLiveActivityEnabled(client: client); - final globalLiveActivitySetting = initData.settings - .group("settings") - .subGroup("notifications")["live_activity_enabled"] as SettingsBoolean; + final userLiveActivityEnabled = await _getUserLiveActivityEnabled( + client: client, + ); + final globalLiveActivitySetting = + initData.settings + .group("settings") + .subGroup("notifications")["live_activity_enabled"] + as SettingsBoolean; if (globalLiveActivitySetting.value != userLiveActivityEnabled) { globalLiveActivitySetting.value = userLiveActivityEnabled; await initData.isar.writeTxn(() async { await globalLiveActivitySetting.save(initData.isar.appSettingsModels); }); - _logger.info('Global LiveActivity setting synced: $userLiveActivityEnabled for user $studentId'); + _logger.info( + 'Global LiveActivity setting synced: $userLiveActivityEnabled for user $studentId', + ); } - final userMorningEnabled = await _getUserMorningNotificationEnabled(client: client); - final globalMorningEnabledSetting = initData.settings - .group("settings") - .subGroup("notifications")["morning_notification_enabled"] as SettingsBoolean; + final userMorningEnabled = await _getUserMorningNotificationEnabled( + client: client, + ); + final globalMorningEnabledSetting = + initData.settings + .group("settings") + .subGroup("notifications")["morning_notification_enabled"] + as SettingsBoolean; if (globalMorningEnabledSetting.value != userMorningEnabled) { globalMorningEnabledSetting.value = userMorningEnabled; await initData.isar.writeTxn(() async { - await globalMorningEnabledSetting.save(initData.isar.appSettingsModels); + await globalMorningEnabledSetting.save( + initData.isar.appSettingsModels, + ); }); - _logger.info('Global morning notification enabled synced: $userMorningEnabled for user $studentId'); + _logger.info( + 'Global morning notification enabled synced: $userMorningEnabled for user $studentId', + ); } - final userMorningTime = await _getUserMorningNotificationTime(client: client); - final globalMorningTimeSetting = initData.settings - .group("settings") - .subGroup("notifications")["morning_notification_time"] as SettingsDouble; + final userMorningTime = await _getUserMorningNotificationTime( + client: client, + ); + final globalMorningTimeSetting = + initData.settings + .group("settings") + .subGroup("notifications")["morning_notification_time"] + as SettingsDouble; if (globalMorningTimeSetting.value.toInt() != userMorningTime) { globalMorningTimeSetting.value = userMorningTime.toDouble(); await initData.isar.writeTxn(() async { await globalMorningTimeSetting.save(initData.isar.appSettingsModels); }); - _logger.info('Global morning notification time synced: $userMorningTime for user $studentId'); + _logger.info( + 'Global morning notification time synced: $userMorningTime for user $studentId', + ); } final userBellDelay = await _getUserBellDelay(client: client); - final globalBellDelaySetting = initData.settings - .group("settings") - .subGroup("application")["bell_delay"] as SettingsDouble; + final globalBellDelaySetting = + initData.settings + .group("settings") + .subGroup("application")["bell_delay"] + as SettingsDouble; if (globalBellDelaySetting.value != userBellDelay) { globalBellDelaySetting.value = userBellDelay; await initData.isar.writeTxn(() async { await globalBellDelaySetting.save(initData.isar.appSettingsModels); }); - _logger.info('Global bell delay synced: $userBellDelay for user $studentId'); + _logger.info( + 'Global bell delay synced: $userBellDelay for user $studentId', + ); } globalUpdate.update(); @@ -253,15 +303,21 @@ class LiveActivityService { return 'hu'; } - final languageSetting = initData.settings.group("settings") - .subGroup("application")["language"] as SettingsItemsRadio?; + final languageSetting = + initData.settings + .group("settings") + .subGroup("application")["language"] + as SettingsItemsRadio?; if (languageSetting == null) return 'hu'; switch (languageSetting.activeIndex) { - case 1: return 'hu'; - case 2: return 'en'; - case 3: return 'de'; + case 1: + return 'hu'; + case 2: + return 'en'; + case 3: + return 'de'; default: // auto final systemLang = Platform.localeName.split('_').first; if (['hu', 'en', 'de'].contains(systemLang)) { @@ -323,7 +379,8 @@ class LiveActivityService { LiveActivityManager.setOnPushTokenReceived(_onPushTokenReceived); - final deviceToken = await LiveActivityManager.registerForPushNotifications(); + final deviceToken = + await LiveActivityManager.registerForPushNotifications(); if (deviceToken != null) { _cachedDeviceToken = deviceToken; @@ -381,7 +438,9 @@ class LiveActivityService { /// This is called by iOS BGTaskScheduler when the app is in background static Future _performBackgroundFetch() async { if (!Platform.isIOS || !_isInitialized || !initDone) { - _logger.warning('Background fetch skipped: not initialized or initDone=false'); + _logger.warning( + 'Background fetch skipped: not initialized or initDone=false', + ); return false; } @@ -404,28 +463,46 @@ class LiveActivityService { List allLessons = []; try { - _logger.info('Background fetch: attempting to fetch fresh data from KRÉTA API'); - final timetableResponse = await client.getTimeTable(startOfWeek, endOfWeek, forceCache: false); + _logger.info( + 'Background fetch: attempting to fetch fresh data from KRÉTA API', + ); + final timetableResponse = await client.getTimeTable( + startOfWeek, + endOfWeek, + forceCache: false, + ); if (timetableResponse.response != null) { allLessons = List.from(timetableResponse.response!); - _logger.info('Background fetch: successfully fetched ${allLessons.length} lessons from KRÉTA API'); + _logger.info( + 'Background fetch: successfully fetched ${allLessons.length} lessons from KRÉTA API', + ); } else { throw Exception('KRÉTA API returned null response'); } } catch (e) { - _logger.warning('Background fetch: KRÉTA API failed ($e), falling back to cache'); + _logger.warning( + 'Background fetch: KRÉTA API failed ($e), falling back to cache', + ); try { - final cachedResponse = await client.getTimeTable(startOfWeek, endOfWeek, forceCache: true); + final cachedResponse = await client.getTimeTable( + startOfWeek, + endOfWeek, + forceCache: true, + ); if (cachedResponse.response != null) { allLessons = List.from(cachedResponse.response!); - _logger.info('Background fetch: successfully loaded ${allLessons.length} lessons from cache'); + _logger.info( + 'Background fetch: successfully loaded ${allLessons.length} lessons from cache', + ); } else { _logger.severe('Background fetch: both API and cache failed'); return false; } } catch (cacheError) { - _logger.severe('Background fetch: cache fallback also failed: $cacheError'); + _logger.severe( + 'Background fetch: cache fallback also failed: $cacheError', + ); return false; } } @@ -442,18 +519,25 @@ class LiveActivityService { for (int dayOffset = 1; dayOffset <= 5; dayOffset++) { final candidateDay = endOfWeek.add(Duration(days: dayOffset)); - if (candidateDay.weekday == DateTime.saturday || candidateDay.weekday == DateTime.sunday) { + if (candidateDay.weekday == DateTime.saturday || + candidateDay.weekday == DateTime.sunday) { continue; } try { final candidateDayEnd = candidateDay.add(const Duration(days: 1)); - final response = await client.getTimeTable(candidateDay, candidateDayEnd, forceCache: false); + final response = await client.getTimeTable( + candidateDay, + candidateDayEnd, + forceCache: false, + ); if (response.response != null && response.response!.isNotEmpty) { final schoolLessons = response.response!.where((lesson) { final uid = lesson.uid.toLowerCase(); - return uid.contains('orarendiora') || uid.contains('tanitasiora') || uid.contains('uresora'); + return uid.contains('orarendiora') || + uid.contains('tanitasiora') || + uid.contains('uresora'); }).toList(); if (schoolLessons.isNotEmpty) { @@ -477,28 +561,41 @@ class LiveActivityService { isHomeworkComplete: firstLesson.isHomeworkComplete, attachments: firstLesson.attachments, isDigitalLesson: firstLesson.isDigitalLesson, - digitalSupportDeviceTypeList: firstLesson.digitalSupportDeviceTypeList, + digitalSupportDeviceTypeList: + firstLesson.digitalSupportDeviceTypeList, createdAt: firstLesson.createdAt, lastModifiedAt: firstLesson.lastModifiedAt, ); allLessons.add(markedLesson); - const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; + const dayNames = [ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + ]; final dayName = dayNames[candidateDay.weekday - 1]; - _logger.info('Background fetch: added first lesson from next week $dayName (${firstLesson.name}) marked for notification scheduling only'); + _logger.info( + 'Background fetch: added first lesson from next week $dayName (${firstLesson.name}) marked for notification scheduling only', + ); foundFirstSchoolDay = true; break; } } } catch (e) { - _logger.warning('Background fetch: could not fetch lessons for day offset $dayOffset: $e'); + _logger.warning( + 'Background fetch: could not fetch lessons for day offset $dayOffset: $e', + ); } } if (!foundFirstSchoolDay) { - _logger.info('Background fetch: no school lessons found in next week for push notification scheduling'); + _logger.info( + 'Background fetch: no school lessons found in next week for push notification scheduling', + ); } if (allLessons.isEmpty) { @@ -513,7 +610,9 @@ class LiveActivityService { } final userBellDelay = await _getUserBellDelay(); - _logger.info('Background fetch: sending ${allLessons.length} lessons to backend'); + _logger.info( + 'Background fetch: sending ${allLessons.length} lessons to backend', + ); final success = await _backendClient.updateTimetable( deviceToken: deviceToken, timetable: allLessons, @@ -522,11 +621,15 @@ class LiveActivityService { if (success) { await _saveLastUpdate(); - _logger.info('Background fetch: successfully sent timetable to backend'); + _logger.info( + 'Background fetch: successfully sent timetable to backend', + ); _logger.info('Background fetch: keeping periodic scheduling active'); return true; } else { - _logger.warning('Background fetch: failed to send timetable to backend'); + _logger.warning( + 'Background fetch: failed to send timetable to backend', + ); return false; } } catch (e, stackTrace) { @@ -536,7 +639,10 @@ class LiveActivityService { } /// Check if LiveActivity is enabled in settings - static Future isEnabled([SettingsStore? settingsStore, KretaClient? client]) async { + static Future isEnabled([ + SettingsStore? settingsStore, + KretaClient? client, + ]) async { try { return await _getUserLiveActivityEnabled(client: client); } catch (e) { @@ -547,7 +653,11 @@ class LiveActivityService { /// Handle LiveActivity enabled state change /// Called from settings toggle callback - static Future handleEnabledChange(bool enabled, {bool isManual = false, KretaClient? client}) async { + static Future handleEnabledChange( + bool enabled, { + bool isManual = false, + KretaClient? client, + }) async { if (!Platform.isIOS) return; try { @@ -559,7 +669,8 @@ class LiveActivityService { } if (!enabled) { - final deviceToken = _cachedDeviceToken ?? await LiveActivityManager.getDeviceToken(); + final deviceToken = + _cachedDeviceToken ?? await LiveActivityManager.getDeviceToken(); if (deviceToken != null) { _logger.info('Notifying backend that Live Activity is disabled'); await _backendClient.toggleLiveActivity( @@ -580,7 +691,9 @@ class LiveActivityService { await _setUserLiveActivityEnabled(false, client: effectiveClient); await syncGlobalSettingWithCurrentUser(client: effectiveClient); - _logger.info('LiveActivity disabled and user data cleared (device remains in backend for push notifications)'); + _logger.info( + 'LiveActivity disabled and user data cleared (device remains in backend for push notifications)', + ); } else { _logger.info('Showing privacy consent screen (manual: $isManual)'); final bool? accepted = await _showPrivacyConsentScreen(); @@ -591,7 +704,8 @@ class LiveActivityService { await _setUserLiveActivityEnabled(true, client: effectiveClient); await syncGlobalSettingWithCurrentUser(client: effectiveClient); - final deviceToken = _cachedDeviceToken ?? await LiveActivityManager.getDeviceToken(); + final deviceToken = + _cachedDeviceToken ?? await LiveActivityManager.getDeviceToken(); if (deviceToken != null) { _logger.info('Notifying backend that Live Activity is enabled'); @@ -607,7 +721,8 @@ class LiveActivityService { settings: initData.settings, preferredStudentIdNorm: effectiveClient.model.studentIdNorm, ); - final studentName = studentResp.response?.name ?? activeToken?.studentId ?? "Student"; + final studentName = + studentResp.response?.name ?? activeToken?.studentId ?? "Student"; await onUserLogin( client: effectiveClient, @@ -643,14 +758,26 @@ class LiveActivityService { await syncGlobalSettingWithCurrentUser(client: effectiveClient); - final enabled = await _getUserLiveActivityEnabled(client: effectiveClient); - final everDeclined = await _getUserPrivacyEverDeclined(client: effectiveClient); + final enabled = await _getUserLiveActivityEnabled( + client: effectiveClient, + ); + final everDeclined = await _getUserPrivacyEverDeclined( + client: effectiveClient, + ); if (!enabled && !everDeclined) { - _logger.info('First use or new user - showing privacy consent automatically'); - await handleEnabledChange(true, isManual: false, client: effectiveClient); + _logger.info( + 'First use or new user - showing privacy consent automatically', + ); + await handleEnabledChange( + true, + isManual: false, + client: effectiveClient, + ); } else { - _logger.info('User already has LiveActivity setting: enabled=$enabled, declined=$everDeclined'); + _logger.info( + 'User already has LiveActivity setting: enabled=$enabled, declined=$everDeclined', + ); } } catch (e) { _logger.warning('Error checking if consent screen needed: $e'); @@ -668,9 +795,7 @@ class LiveActivityService { final bool? result = await Navigator.push( context, MaterialPageRoute( - builder: (context) => LiveActivityConsentScreen( - data: initData, - ), + builder: (context) => LiveActivityConsentScreen(data: initData), ), ); @@ -687,18 +812,21 @@ class LiveActivityService { await LiveActivityManager.endAllActivities(); _stopTimetableMonitoring(); - } catch (e) { _logger.severe('Error handling token expiration for LiveActivity: $e'); } } /// Handle LiveActivity push token received from Swift side - static Future _onPushTokenReceived(String activityId, String pushToken) async { + static Future _onPushTokenReceived( + String activityId, + String pushToken, + ) async { _logger.info('LiveActivity push token received, updating backend...'); try { - final deviceToken = _cachedDeviceToken ?? await LiveActivityManager.getDeviceToken(); + final deviceToken = + _cachedDeviceToken ?? await LiveActivityManager.getDeviceToken(); if (deviceToken == null) { _logger.warning('No device token available to update push token'); return; @@ -729,16 +857,23 @@ class LiveActivityService { _logger.info('onUserLogin: Function called for $studentName'); if (!Platform.isIOS || !_isInitialized) { - _logger.warning('onUserLogin: Returning early - Platform.isIOS=${Platform.isIOS}, _isInitialized=$_isInitialized'); + _logger.warning( + 'onUserLogin: Returning early - Platform.isIOS=${Platform.isIOS}, _isInitialized=$_isInitialized', + ); return; } final liveActivityEnabled = await isEnabled(settingsStore, client); - final morningNotificationEnabled = _getCurrentMorningNotificationEnabled() ?? false; - _logger.info('onUserLogin: liveActivityEnabled=$liveActivityEnabled, morningNotificationEnabled=$morningNotificationEnabled'); + final morningNotificationEnabled = + _getCurrentMorningNotificationEnabled() ?? false; + _logger.info( + 'onUserLogin: liveActivityEnabled=$liveActivityEnabled, morningNotificationEnabled=$morningNotificationEnabled', + ); if (!liveActivityEnabled && !morningNotificationEnabled) { - _logger.warning('onUserLogin: Both Live Activity and Morning Notifications are disabled, returning early'); + _logger.warning( + 'onUserLogin: Both Live Activity and Morning Notifications are disabled, returning early', + ); return; } @@ -752,28 +887,46 @@ class LiveActivityService { List allLessons = []; try { - _logger.info('onUserLogin: Attempting to fetch fresh timetable from KRÉTA API'); - final timetableResponse = await client.getTimeTable(startOfWeek, endOfWeek, forceCache: false); + _logger.info( + 'onUserLogin: Attempting to fetch fresh timetable from KRÉTA API', + ); + final timetableResponse = await client.getTimeTable( + startOfWeek, + endOfWeek, + forceCache: false, + ); if (timetableResponse.response != null) { allLessons = List.from(timetableResponse.response!); - _logger.info('onUserLogin: Successfully fetched ${allLessons.length} lessons from KRÉTA API'); + _logger.info( + 'onUserLogin: Successfully fetched ${allLessons.length} lessons from KRÉTA API', + ); } else { throw Exception('KRÉTA API returned null response'); } } catch (e) { - _logger.warning('onUserLogin: KRÉTA API failed ($e), falling back to cache'); + _logger.warning( + 'onUserLogin: KRÉTA API failed ($e), falling back to cache', + ); try { - final cachedResponse = await client.getTimeTable(startOfWeek, endOfWeek, forceCache: true); + final cachedResponse = await client.getTimeTable( + startOfWeek, + endOfWeek, + forceCache: true, + ); if (cachedResponse.response != null) { allLessons = List.from(cachedResponse.response!); - _logger.info('onUserLogin: Successfully loaded ${allLessons.length} lessons from cache'); + _logger.info( + 'onUserLogin: Successfully loaded ${allLessons.length} lessons from cache', + ); } else { _logger.severe('onUserLogin: Both API and cache failed'); return; } } catch (cacheError) { - _logger.severe('onUserLogin: Cache fallback also failed: $cacheError'); + _logger.severe( + 'onUserLogin: Cache fallback also failed: $cacheError', + ); return; } } @@ -786,19 +939,25 @@ class LiveActivityService { final nextWeekStart = endOfWeek.add(const Duration(days: 1)); final nextWeekEnd = nextWeekStart.add(const Duration(days: 6)); - _logger.info('[GlobalSearch] onUserLogin: Checking next week (${nextWeekStart.toString().split(' ')[0]} - ${nextWeekEnd.toString().split(' ')[0]}) for events'); + _logger.info( + '[GlobalSearch] onUserLogin: Checking next week (${nextWeekStart.toString().split(' ')[0]} - ${nextWeekEnd.toString().split(' ')[0]}) for events', + ); List nextWeekBreakEvents = []; try { - final nextWeekResponse = await client.getTimeTable(nextWeekStart, nextWeekEnd, forceCache: false); + final nextWeekResponse = await client.getTimeTable( + nextWeekStart, + nextWeekEnd, + forceCache: false, + ); if (nextWeekResponse.response != null) { nextWeekBreakEvents = nextWeekResponse.response!.where((lesson) { final uid = lesson.uid.toLowerCase(); final name = lesson.name.toLowerCase(); return uid.contains('tanevrendjeesemeny') && - !name.contains('tanítási nap') && - (name.contains('szünet') || + !name.contains('tanítási nap') && + (name.contains('szünet') || name.contains('pihenőnap') || name.contains('munkaszüneti') || name.contains('ünnepnap') || @@ -807,18 +966,24 @@ class LiveActivityService { }).toList(); if (nextWeekBreakEvents.isNotEmpty) { - _logger.info('[GlobalSearch] onUserLogin: Found ${nextWeekBreakEvents.length} break event(s) in next week - triggering global searcher'); + _logger.info( + '[GlobalSearch] onUserLogin: Found ${nextWeekBreakEvents.length} break event(s) in next week - triggering global searcher', + ); } } } catch (e) { - _logger.warning('[GlobalSearch] onUserLogin: Could not fetch next week: $e'); + _logger.warning( + '[GlobalSearch] onUserLogin: Could not fetch next week: $e', + ); } if (nextWeekBreakEvents.isNotEmpty) { nextWeekBreakEvents.sort((a, b) => a.start.compareTo(b.start)); final firstBreakEvent = nextWeekBreakEvents.first; - _logger.info('[GlobalSearch] onUserLogin: Triggering global break searcher from ${firstBreakEvent.date.split('T')[0]}'); + _logger.info( + '[GlobalSearch] onUserLogin: Triggering global break searcher from ${firstBreakEvent.date.split('T')[0]}', + ); final searchResult = await _globalBreakSearcher( client: client, @@ -827,16 +992,21 @@ class LiveActivityService { if (searchResult.tokenExpired) { _tokenExpired = true; - _logger.warning('[GlobalSearch] onUserLogin: Token expired during global search'); + _logger.warning( + '[GlobalSearch] onUserLogin: Token expired during global search', + ); } allLessons.addAll(searchResult.allLessons); - _logger.info('[GlobalSearch] onUserLogin: Global searcher returned ${searchResult.allLessons.length} lessons'); + _logger.info( + '[GlobalSearch] onUserLogin: Global searcher returned ${searchResult.allLessons.length} lessons', + ); if (searchResult.firstSchoolDayAfterBreak != null) { final notificationLesson = Lesson( - uid: '${searchResult.firstSchoolDayAfterBreak!.uid}__FOR_NOTIFICATION_ONLY', + uid: + '${searchResult.firstSchoolDayAfterBreak!.uid}__FOR_NOTIFICATION_ONLY', date: searchResult.firstSchoolDayAfterBreak!.date, start: searchResult.firstSchoolDayAfterBreak!.start, end: searchResult.firstSchoolDayAfterBreak!.end, @@ -845,23 +1015,34 @@ class LiveActivityService { teacher: searchResult.firstSchoolDayAfterBreak!.teacher, theme: searchResult.firstSchoolDayAfterBreak!.theme, roomName: searchResult.firstSchoolDayAfterBreak!.roomName, - substituteTeacher: searchResult.firstSchoolDayAfterBreak!.substituteTeacher, + substituteTeacher: + searchResult.firstSchoolDayAfterBreak!.substituteTeacher, type: searchResult.firstSchoolDayAfterBreak!.type, state: searchResult.firstSchoolDayAfterBreak!.state, - canStudentEditHomework: searchResult.firstSchoolDayAfterBreak!.canStudentEditHomework, - isHomeworkComplete: searchResult.firstSchoolDayAfterBreak!.isHomeworkComplete, + canStudentEditHomework: + searchResult.firstSchoolDayAfterBreak!.canStudentEditHomework, + isHomeworkComplete: + searchResult.firstSchoolDayAfterBreak!.isHomeworkComplete, attachments: searchResult.firstSchoolDayAfterBreak!.attachments, - isDigitalLesson: searchResult.firstSchoolDayAfterBreak!.isDigitalLesson, - digitalSupportDeviceTypeList: searchResult.firstSchoolDayAfterBreak!.digitalSupportDeviceTypeList, + isDigitalLesson: + searchResult.firstSchoolDayAfterBreak!.isDigitalLesson, + digitalSupportDeviceTypeList: searchResult + .firstSchoolDayAfterBreak! + .digitalSupportDeviceTypeList, createdAt: searchResult.firstSchoolDayAfterBreak!.createdAt, - lastModifiedAt: searchResult.firstSchoolDayAfterBreak!.lastModifiedAt, + lastModifiedAt: + searchResult.firstSchoolDayAfterBreak!.lastModifiedAt, ); allLessons.add(notificationLesson); - _logger.info('[GlobalSearch] onUserLogin: Added first school day after break for push notification: ${notificationLesson.date.split('T')[0]}'); + _logger.info( + '[GlobalSearch] onUserLogin: Added first school day after break for push notification: ${notificationLesson.date.split('T')[0]}', + ); } } else { - _logger.info('[GlobalSearch] onUserLogin: No break events in next week, searching for first school day...'); + _logger.info( + '[GlobalSearch] onUserLogin: No break events in next week, searching for first school day...', + ); bool foundFirstSchoolDay = false; for (int dayOffset = 1; dayOffset <= 14; dayOffset++) { @@ -869,12 +1050,18 @@ class LiveActivityService { try { final candidateDayEnd = candidateDay.add(const Duration(days: 1)); - final response = await client.getTimeTable(candidateDay, candidateDayEnd, forceCache: false); + final response = await client.getTimeTable( + candidateDay, + candidateDayEnd, + forceCache: false, + ); if (response.response != null && response.response!.isNotEmpty) { final schoolLessons = response.response!.where((lesson) { final uid = lesson.uid.toLowerCase(); - return uid.contains('orarendiora') || uid.contains('tanitasiora') || uid.contains('uresora'); + return uid.contains('orarendiora') || + uid.contains('tanitasiora') || + uid.contains('uresora'); }).toList(); if (schoolLessons.isNotEmpty) { @@ -898,25 +1085,32 @@ class LiveActivityService { isHomeworkComplete: firstLesson.isHomeworkComplete, attachments: firstLesson.attachments, isDigitalLesson: firstLesson.isDigitalLesson, - digitalSupportDeviceTypeList: firstLesson.digitalSupportDeviceTypeList, + digitalSupportDeviceTypeList: + firstLesson.digitalSupportDeviceTypeList, createdAt: firstLesson.createdAt, lastModifiedAt: firstLesson.lastModifiedAt, ); allLessons.add(markedLesson); - _logger.info('[GlobalSearch] onUserLogin: Found first school day for push notification: ${candidateDay.toString().split(' ')[0]}'); + _logger.info( + '[GlobalSearch] onUserLogin: Found first school day for push notification: ${candidateDay.toString().split(' ')[0]}', + ); foundFirstSchoolDay = true; break; } } } catch (e) { - _logger.warning('[GlobalSearch] onUserLogin: Could not fetch day offset $dayOffset: $e'); + _logger.warning( + '[GlobalSearch] onUserLogin: Could not fetch day offset $dayOffset: $e', + ); } } if (!foundFirstSchoolDay) { - _logger.info('[GlobalSearch] onUserLogin: No school lessons found in next 14 days for push notification scheduling'); + _logger.info( + '[GlobalSearch] onUserLogin: No school lessons found in next 14 days for push notification scheduling', + ); } } @@ -928,8 +1122,10 @@ class LiveActivityService { String? currentLanguage = _getCurrentLanguageCode(); - final userMorningNotificationTime = await _getUserMorningNotificationTime(); - final userMorningNotificationEnabled = await _getUserMorningNotificationEnabled(); + final userMorningNotificationTime = + await _getUserMorningNotificationTime(); + final userMorningNotificationEnabled = + await _getUserMorningNotificationEnabled(); final userLiveActivityEnabled = await _getUserLiveActivityEnabled(); final userBellDelay = await _getUserBellDelay(); @@ -987,7 +1183,9 @@ class LiveActivityService { final activeActivities = await LiveActivityManager.getActiveActivities(); if (activeActivities.isNotEmpty) { - _logger.info('Ending existing activity to refresh push token (8-hour expiration)'); + _logger.info( + 'Ending existing activity to refresh push token (8-hour expiration)', + ); await LiveActivityManager.endAllActivities(); await Future.delayed(const Duration(milliseconds: 500)); } @@ -996,7 +1194,10 @@ class LiveActivityService { final todayStart = DateTime(now.year, now.month, now.day); final startOfWeek = todayStart.subtract(Duration(days: now.weekday - 1)); final endOfWeek = startOfWeek.add(const Duration(days: 6)); - final timetableResponse = await client.getTimeTable(startOfWeek, endOfWeek); + final timetableResponse = await client.getTimeTable( + startOfWeek, + endOfWeek, + ); final allLessons = timetableResponse.response ?? []; await _startPlaceholderActivity(allLessons, studentName); @@ -1004,13 +1205,12 @@ class LiveActivityService { _logger.info('New activity created with fresh push token'); await checkAndUpdateTimetable( - client: client, - studentName: studentName, - settingsStore: settingsStore + client: client, + studentName: studentName, + settingsStore: settingsStore, ); await scheduleBackgroundFetch(); - } catch (e) { _logger.severe('Error handling onAppOpened for LiveActivity: $e'); } @@ -1029,8 +1229,11 @@ class LiveActivityService { _logger.info('onUserLogout: Ending all activities'); await LiveActivityManager.endAllActivities(); - final deviceToken = _cachedDeviceToken ?? await LiveActivityManager.getDeviceToken(); - _logger.info('onUserLogout: Device token = ${deviceToken?.substring(0, 10)}...'); + final deviceToken = + _cachedDeviceToken ?? await LiveActivityManager.getDeviceToken(); + _logger.info( + 'onUserLogout: Device token = ${deviceToken?.substring(0, 10)}...', + ); if (deviceToken != null) { _logger.info('onUserLogout: Unregistering device from backend'); @@ -1059,10 +1262,13 @@ class LiveActivityService { if (!Platform.isIOS || !_isInitialized) return; final liveActivityEnabled = await isEnabled(settingsStore, client); - final morningNotificationEnabled = _getCurrentMorningNotificationEnabled() ?? false; + final morningNotificationEnabled = + _getCurrentMorningNotificationEnabled() ?? false; if (!liveActivityEnabled && !morningNotificationEnabled) { - _logger.info('Both Live Activity and Morning Notifications are disabled, skipping timetable fetch'); + _logger.info( + 'Both Live Activity and Morning Notifications are disabled, skipping timetable fetch', + ); return; } @@ -1075,7 +1281,11 @@ class LiveActivityService { List allLessons = []; try { - final timetableResponse = await client.getTimeTable(startOfWeek, endOfWeek, forceCache: false); + final timetableResponse = await client.getTimeTable( + startOfWeek, + endOfWeek, + forceCache: false, + ); if (timetableResponse.response != null) { allLessons = List.from(timetableResponse.response!); @@ -1083,17 +1293,27 @@ class LiveActivityService { throw Exception('KRÉTA API returned null response'); } } catch (e) { - _logger.warning('checkAndUpdateTimetable: KRÉTA API failed ($e), falling back to cache'); + _logger.warning( + 'checkAndUpdateTimetable: KRÉTA API failed ($e), falling back to cache', + ); try { - final cachedResponse = await client.getTimeTable(startOfWeek, endOfWeek, forceCache: true); + final cachedResponse = await client.getTimeTable( + startOfWeek, + endOfWeek, + forceCache: true, + ); if (cachedResponse.response != null) { allLessons = List.from(cachedResponse.response!); } else { - _logger.severe('checkAndUpdateTimetable: Both API and cache failed'); + _logger.severe( + 'checkAndUpdateTimetable: Both API and cache failed', + ); return; } } catch (cacheError) { - _logger.severe('checkAndUpdateTimetable: Cache fallback also failed: $cacheError'); + _logger.severe( + 'checkAndUpdateTimetable: Cache fallback also failed: $cacheError', + ); return; } } @@ -1101,19 +1321,25 @@ class LiveActivityService { final nextWeekStart = endOfWeek.add(const Duration(days: 1)); final nextWeekEnd = nextWeekStart.add(const Duration(days: 6)); - _logger.info('[GlobalSearch] Checking next week (${nextWeekStart.toString().split(' ')[0]} - ${nextWeekEnd.toString().split(' ')[0]}) for events'); + _logger.info( + '[GlobalSearch] Checking next week (${nextWeekStart.toString().split(' ')[0]} - ${nextWeekEnd.toString().split(' ')[0]}) for events', + ); List nextWeekBreakEvents = []; try { - final nextWeekResponse = await client.getTimeTable(nextWeekStart, nextWeekEnd, forceCache: false); + final nextWeekResponse = await client.getTimeTable( + nextWeekStart, + nextWeekEnd, + forceCache: false, + ); if (nextWeekResponse.response != null) { nextWeekBreakEvents = nextWeekResponse.response!.where((lesson) { final uid = lesson.uid.toLowerCase(); final name = lesson.name.toLowerCase(); return uid.contains('tanevrendjeesemeny') && - !name.contains('tanítási nap') && - (name.contains('szünet') || + !name.contains('tanítási nap') && + (name.contains('szünet') || name.contains('pihenőnap') || name.contains('munkaszüneti') || name.contains('ünnepnap') || @@ -1122,7 +1348,9 @@ class LiveActivityService { }).toList(); if (nextWeekBreakEvents.isNotEmpty) { - _logger.info('[GlobalSearch] Found ${nextWeekBreakEvents.length} break event(s) in next week - triggering global searcher'); + _logger.info( + '[GlobalSearch] Found ${nextWeekBreakEvents.length} break event(s) in next week - triggering global searcher', + ); } } } catch (e) { @@ -1133,7 +1361,9 @@ class LiveActivityService { nextWeekBreakEvents.sort((a, b) => a.start.compareTo(b.start)); final firstBreakEvent = nextWeekBreakEvents.first; - _logger.info('[GlobalSearch] Triggering global break searcher from ${firstBreakEvent.date.split('T')[0]}'); + _logger.info( + '[GlobalSearch] Triggering global break searcher from ${firstBreakEvent.date.split('T')[0]}', + ); final searchResult = await _globalBreakSearcher( client: client, @@ -1147,11 +1377,14 @@ class LiveActivityService { allLessons.addAll(searchResult.allLessons); - _logger.info('[GlobalSearch] Global searcher returned ${searchResult.allLessons.length} lessons'); + _logger.info( + '[GlobalSearch] Global searcher returned ${searchResult.allLessons.length} lessons', + ); if (searchResult.firstSchoolDayAfterBreak != null) { final notificationLesson = Lesson( - uid: '${searchResult.firstSchoolDayAfterBreak!.uid}__FOR_NOTIFICATION_ONLY', + uid: + '${searchResult.firstSchoolDayAfterBreak!.uid}__FOR_NOTIFICATION_ONLY', date: searchResult.firstSchoolDayAfterBreak!.date, start: searchResult.firstSchoolDayAfterBreak!.start, end: searchResult.firstSchoolDayAfterBreak!.end, @@ -1160,23 +1393,34 @@ class LiveActivityService { teacher: searchResult.firstSchoolDayAfterBreak!.teacher, theme: searchResult.firstSchoolDayAfterBreak!.theme, roomName: searchResult.firstSchoolDayAfterBreak!.roomName, - substituteTeacher: searchResult.firstSchoolDayAfterBreak!.substituteTeacher, + substituteTeacher: + searchResult.firstSchoolDayAfterBreak!.substituteTeacher, type: searchResult.firstSchoolDayAfterBreak!.type, state: searchResult.firstSchoolDayAfterBreak!.state, - canStudentEditHomework: searchResult.firstSchoolDayAfterBreak!.canStudentEditHomework, - isHomeworkComplete: searchResult.firstSchoolDayAfterBreak!.isHomeworkComplete, + canStudentEditHomework: + searchResult.firstSchoolDayAfterBreak!.canStudentEditHomework, + isHomeworkComplete: + searchResult.firstSchoolDayAfterBreak!.isHomeworkComplete, attachments: searchResult.firstSchoolDayAfterBreak!.attachments, - isDigitalLesson: searchResult.firstSchoolDayAfterBreak!.isDigitalLesson, - digitalSupportDeviceTypeList: searchResult.firstSchoolDayAfterBreak!.digitalSupportDeviceTypeList, + isDigitalLesson: + searchResult.firstSchoolDayAfterBreak!.isDigitalLesson, + digitalSupportDeviceTypeList: searchResult + .firstSchoolDayAfterBreak! + .digitalSupportDeviceTypeList, createdAt: searchResult.firstSchoolDayAfterBreak!.createdAt, - lastModifiedAt: searchResult.firstSchoolDayAfterBreak!.lastModifiedAt, + lastModifiedAt: + searchResult.firstSchoolDayAfterBreak!.lastModifiedAt, ); allLessons.add(notificationLesson); - _logger.info('[GlobalSearch] Added first school day after break for push notification: ${notificationLesson.date.split('T')[0]}'); + _logger.info( + '[GlobalSearch] Added first school day after break for push notification: ${notificationLesson.date.split('T')[0]}', + ); } } else { - _logger.info('[GlobalSearch] No break events in next week, searching for first school day...'); + _logger.info( + '[GlobalSearch] No break events in next week, searching for first school day...', + ); bool foundFirstSchoolDay = false; for (int dayOffset = 1; dayOffset <= 14; dayOffset++) { @@ -1184,12 +1428,18 @@ class LiveActivityService { try { final candidateDayEnd = candidateDay.add(const Duration(days: 1)); - final response = await client.getTimeTable(candidateDay, candidateDayEnd, forceCache: false); + final response = await client.getTimeTable( + candidateDay, + candidateDayEnd, + forceCache: false, + ); if (response.response != null && response.response!.isNotEmpty) { final schoolLessons = response.response!.where((lesson) { final uid = lesson.uid.toLowerCase(); - return uid.contains('orarendiora') || uid.contains('tanitasiora') || uid.contains('uresora'); + return uid.contains('orarendiora') || + uid.contains('tanitasiora') || + uid.contains('uresora'); }).toList(); if (schoolLessons.isNotEmpty) { @@ -1213,25 +1463,32 @@ class LiveActivityService { isHomeworkComplete: firstLesson.isHomeworkComplete, attachments: firstLesson.attachments, isDigitalLesson: firstLesson.isDigitalLesson, - digitalSupportDeviceTypeList: firstLesson.digitalSupportDeviceTypeList, + digitalSupportDeviceTypeList: + firstLesson.digitalSupportDeviceTypeList, createdAt: firstLesson.createdAt, lastModifiedAt: firstLesson.lastModifiedAt, ); allLessons.add(markedLesson); - _logger.info('[GlobalSearch] Found first school day for push notification: ${candidateDay.toString().split(' ')[0]}'); + _logger.info( + '[GlobalSearch] Found first school day for push notification: ${candidateDay.toString().split(' ')[0]}', + ); foundFirstSchoolDay = true; break; } } } catch (e) { - _logger.warning('[GlobalSearch] Could not fetch day offset $dayOffset: $e'); + _logger.warning( + '[GlobalSearch] Could not fetch day offset $dayOffset: $e', + ); } } if (!foundFirstSchoolDay) { - _logger.info('[GlobalSearch] No school lessons found in next 14 days for push notification scheduling'); + _logger.info( + '[GlobalSearch] No school lessons found in next 14 days for push notification scheduling', + ); } } @@ -1256,7 +1513,9 @@ class LiveActivityService { if (shouldUpdate) { if (forceUpdate) { - _logger.info('Forcing timetable update (notification settings changed)...'); + _logger.info( + 'Forcing timetable update (notification settings changed)...', + ); } else { _logger.info('Timetable changes detected, sending to backend...'); } @@ -1270,7 +1529,9 @@ class LiveActivityService { if (success) { await _saveLastUpdate(); - _logger.info('Timetable sent to backend successfully. Backend will update LiveActivity.'); + _logger.info( + 'Timetable sent to backend successfully. Backend will update LiveActivity.', + ); } } } catch (e) { @@ -1295,16 +1556,13 @@ class LiveActivityService { // Pediódikus frissítés (minden 30 percben) // Ez azért kell, hogy a KRETA változásokat észleljük, // és összevessük az adatbázisban tárolt adatokkal. - _updateTimer = Timer.periodic( - const Duration(minutes: 30), - (timer) async { - await checkAndUpdateTimetable( - client: client, - studentName: studentName, - settingsStore: settingsStore, - ); - }, - ); + _updateTimer = Timer.periodic(const Duration(minutes: 30), (timer) async { + await checkAndUpdateTimetable( + client: client, + studentName: studentName, + settingsStore: settingsStore, + ); + }); _logger.info('Timetable monitoring started'); } @@ -1321,23 +1579,27 @@ class LiveActivityService { required KretaClient client, required String studentName, }) async { - await checkAndUpdateTimetable( - client: client, - studentName: studentName, - ); + await checkAndUpdateTimetable(client: client, studentName: studentName); } /// Starts a minimal placeholder activity shell - backend will update with real data - static Future _startPlaceholderActivity(List allLessons, String studentName) async { + static Future _startPlaceholderActivity( + List allLessons, + String studentName, + ) async { // Always end existing activities to ensure fresh token (8-hour expiration) final activeActivities = await LiveActivityManager.getActiveActivities(); if (activeActivities.isNotEmpty) { - _logger.info('_startPlaceholderActivity: Ending existing activities before creating new one'); + _logger.info( + '_startPlaceholderActivity: Ending existing activities before creating new one', + ); await LiveActivityManager.endAllActivities(); await Future.delayed(const Duration(milliseconds: 500)); } - _logger.info('_startPlaceholderActivity: Creating minimal loading shell, backend will update.'); + _logger.info( + '_startPlaceholderActivity: Creating minimal loading shell, backend will update.', + ); final now = DateTime.now(); @@ -1361,8 +1623,16 @@ class LiveActivityService { lastModifiedAt: now, ); } else { - final emptyType = NameUidDesc(uid: 'placeholder', name: 'Placeholder', description: null); - final emptyState = NameUidDesc(uid: 'active', name: 'Active', description: null); + final emptyType = NameUidDesc( + uid: 'placeholder', + name: 'Placeholder', + description: null, + ); + final emptyState = NameUidDesc( + uid: 'active', + name: 'Active', + description: null, + ); placeholderLesson = Lesson( uid: 'loading-placeholder', @@ -1390,7 +1660,9 @@ class LiveActivityService { mode: 'loading', ); - _logger.info('_startPlaceholderActivity: Placeholder created, waiting for backend update.'); + _logger.info( + '_startPlaceholderActivity: Placeholder created, waiting for backend update.', + ); } static Future _saveDeviceToken(String token) async { @@ -1439,7 +1711,9 @@ class LiveActivityService { } /// Try to get cached token or wait a short period until iOS provides it - static Future _getOrWaitDeviceToken({Duration timeout = const Duration(seconds: 5)}) async { + static Future _getOrWaitDeviceToken({ + Duration timeout = const Duration(seconds: 5), + }) async { if (_cachedDeviceToken != null) { return _cachedDeviceToken; } @@ -1498,7 +1772,9 @@ class LiveActivityService { static void onBellDelayChanged(double newValue) { if (!Platform.isIOS || !_isInitialized) return; - _logger.info('BellDelay changed to $newValue minutes, scheduling debounced update'); + _logger.info( + 'BellDelay changed to $newValue minutes, scheduling debounced update', + ); _setUserBellDelay(newValue); @@ -1518,7 +1794,9 @@ class LiveActivityService { final bellDelayToSend = _pendingBellDelay!; if (_lastSentBellDelay == bellDelayToSend) { - _logger.info('BellDelay $bellDelayToSend already sent to backend, skipping'); + _logger.info( + 'BellDelay $bellDelayToSend already sent to backend, skipping', + ); _pendingBellDelay = null; return; } @@ -1530,7 +1808,9 @@ class LiveActivityService { return; } - _logger.info('Sending bellDelay update to backend: $bellDelayToSend minutes'); + _logger.info( + 'Sending bellDelay update to backend: $bellDelayToSend minutes', + ); final success = await _backendClient.updateBellDelay( deviceToken: deviceToken, @@ -1542,7 +1822,9 @@ class LiveActivityService { _logger.info('BellDelay updated successfully in backend'); if (_pendingBellDelay != bellDelayToSend) { - _logger.info('BellDelay changed to $_pendingBellDelay during update, scheduling another update'); + _logger.info( + 'BellDelay changed to $_pendingBellDelay during update, scheduling another update', + ); _bellDelayDebounceTimer?.cancel(); _bellDelayDebounceTimer = Timer(_bellDelayDebounceInterval, () async { await _sendBellDelayUpdate(); @@ -1564,7 +1846,9 @@ class LiveActivityService { static void onMorningNotificationEnabledChanged(bool newValue) { if (!Platform.isIOS || !_isInitialized) return; - _logger.info('Morning notification enabled changed to $newValue, scheduling debounced update'); + _logger.info( + 'Morning notification enabled changed to $newValue, scheduling debounced update', + ); _setUserMorningNotificationEnabled(newValue); @@ -1572,9 +1856,12 @@ class LiveActivityService { _pendingMorningNotificationEnabled = newValue; - _morningNotificationDebounceTimer = Timer(_morningNotificationDebounceInterval, () async { - await _sendMorningNotificationUpdate(); - }); + _morningNotificationDebounceTimer = Timer( + _morningNotificationDebounceInterval, + () async { + await _sendMorningNotificationUpdate(); + }, + ); } /// Handle morning notification time change with debounce @@ -1583,7 +1870,9 @@ class LiveActivityService { static void onMorningNotificationTimeChanged(double newValue) { if (!Platform.isIOS || !_isInitialized) return; - _logger.info('Morning notification time changed to $newValue minutes, scheduling debounced update'); + _logger.info( + 'Morning notification time changed to $newValue minutes, scheduling debounced update', + ); _setUserMorningNotificationTime(newValue.toInt()); @@ -1591,19 +1880,27 @@ class LiveActivityService { _pendingMorningNotificationTime = newValue; - _morningNotificationDebounceTimer = Timer(_morningNotificationDebounceInterval, () async { - await _sendMorningNotificationUpdate(); - }); + _morningNotificationDebounceTimer = Timer( + _morningNotificationDebounceInterval, + () async { + await _sendMorningNotificationUpdate(); + }, + ); } /// Internal function to send morning notification settings update to backend static Future _sendMorningNotificationUpdate() async { - final enabledToSend = _pendingMorningNotificationEnabled ?? _getCurrentMorningNotificationEnabled(); - final timeToSend = _pendingMorningNotificationTime ?? _getCurrentMorningNotificationTime(); + final enabledToSend = + _pendingMorningNotificationEnabled ?? + _getCurrentMorningNotificationEnabled(); + final timeToSend = + _pendingMorningNotificationTime ?? _getCurrentMorningNotificationTime(); if (_lastSentMorningNotificationEnabled == enabledToSend && _lastSentMorningNotificationTime == timeToSend) { - _logger.info('Morning notification settings already sent to backend, skipping'); + _logger.info( + 'Morning notification settings already sent to backend, skipping', + ); _pendingMorningNotificationEnabled = null; _pendingMorningNotificationTime = null; return; @@ -1612,11 +1909,15 @@ class LiveActivityService { try { final deviceToken = await _getOrWaitDeviceToken(); if (deviceToken == null) { - _logger.warning('No device token available to update morning notification settings'); + _logger.warning( + 'No device token available to update morning notification settings', + ); return; } - _logger.info('Sending morning notification settings update to backend: enabled=$enabledToSend, time=$timeToSend minutes'); + _logger.info( + 'Sending morning notification settings update to backend: enabled=$enabledToSend, time=$timeToSend minutes', + ); final success = await _backendClient.updateMorningNotificationSettings( deviceToken: deviceToken, @@ -1630,16 +1931,23 @@ class LiveActivityService { _lastSentMorningNotificationEnabled = enabledToSend; _lastSentMorningNotificationTime = timeToSend; - _logger.info('Morning notification settings updated successfully in backend'); + _logger.info( + 'Morning notification settings updated successfully in backend', + ); if (wasDisabled && isNowEnabled) { - _logger.info('Morning notifications re-enabled, fetching timetable to recreate notifications'); + _logger.info( + 'Morning notifications re-enabled, fetching timetable to recreate notifications', + ); try { final client = initData.client; final settingsStore = initData.settings; final studentResp = await client.getStudent(); - final studentName = studentResp.response?.name ?? client.model.studentId ?? 'Student'; + final studentName = + studentResp.response?.name ?? + client.model.studentId ?? + 'Student'; await checkAndUpdateTimetable( client: client, @@ -1647,28 +1955,43 @@ class LiveActivityService { settingsStore: settingsStore, forceUpdate: true, ); - _logger.info('Timetable fetch completed after re-enabling notifications'); + _logger.info( + 'Timetable fetch completed after re-enabling notifications', + ); } catch (e) { - _logger.severe('Error fetching timetable after re-enabling notifications: $e'); + _logger.severe( + 'Error fetching timetable after re-enabling notifications: $e', + ); } } - final currentEnabled = _pendingMorningNotificationEnabled ?? _getCurrentMorningNotificationEnabled(); - final currentTime = _pendingMorningNotificationTime ?? _getCurrentMorningNotificationTime(); + final currentEnabled = + _pendingMorningNotificationEnabled ?? + _getCurrentMorningNotificationEnabled(); + final currentTime = + _pendingMorningNotificationTime ?? + _getCurrentMorningNotificationTime(); if (_lastSentMorningNotificationEnabled != currentEnabled || _lastSentMorningNotificationTime != currentTime) { - _logger.info('Morning notification settings changed during update, scheduling another update'); + _logger.info( + 'Morning notification settings changed during update, scheduling another update', + ); _morningNotificationDebounceTimer?.cancel(); - _morningNotificationDebounceTimer = Timer(_morningNotificationDebounceInterval, () async { - await _sendMorningNotificationUpdate(); - }); + _morningNotificationDebounceTimer = Timer( + _morningNotificationDebounceInterval, + () async { + await _sendMorningNotificationUpdate(); + }, + ); } else { _pendingMorningNotificationEnabled = null; _pendingMorningNotificationTime = null; } } else { - _logger.warning('Failed to update morning notification settings in backend'); + _logger.warning( + 'Failed to update morning notification settings in backend', + ); } } catch (e) { _logger.severe('Error updating morning notification settings: $e'); @@ -1681,8 +2004,11 @@ class LiveActivityService { if (!initDone) { return null; } - final setting = initData.settings.group("settings") - .subGroup("notifications")["morning_notification_enabled"] as SettingsBoolean?; + final setting = + initData.settings + .group("settings") + .subGroup("notifications")["morning_notification_enabled"] + as SettingsBoolean?; return setting?.value; } catch (e) { _logger.warning('Error getting current morning notification enabled: $e'); @@ -1696,8 +2022,11 @@ class LiveActivityService { if (!initDone) { return null; } - final setting = initData.settings.group("settings") - .subGroup("notifications")["morning_notification_time"] as SettingsDouble?; + final setting = + initData.settings + .group("settings") + .subGroup("notifications")["morning_notification_time"] + as SettingsDouble?; return setting?.value; } catch (e) { _logger.warning('Error getting current morning notification time: $e'); @@ -1719,16 +2048,24 @@ class LiveActivityService { int weeksSearched = 0; const maxWeeks = 26; - _logger.info('[GlobalBreakSearcher] Starting search from ${currentSearchDate.toString().split(' ')[0]}'); + _logger.info( + '[GlobalBreakSearcher] Starting search from ${currentSearchDate.toString().split(' ')[0]}', + ); while (weeksSearched < maxWeeks) { final weekStart = currentSearchDate; final weekEnd = weekStart.add(const Duration(days: 6)); - _logger.info('[GlobalBreakSearcher] Fetching week ${weeksSearched + 1}: ${weekStart.toString().split(' ')[0]} - ${weekEnd.toString().split(' ')[0]}'); + _logger.info( + '[GlobalBreakSearcher] Fetching week ${weeksSearched + 1}: ${weekStart.toString().split(' ')[0]} - ${weekEnd.toString().split(' ')[0]}', + ); try { - final response = await client.getTimeTable(weekStart, weekEnd, forceCache: false); + final response = await client.getTimeTable( + weekStart, + weekEnd, + forceCache: false, + ); if (response.response != null && response.response!.isNotEmpty) { final weekLessons = response.response!; @@ -1737,8 +2074,8 @@ class LiveActivityService { final uid = lesson.uid.toLowerCase(); final name = lesson.name.toLowerCase(); return uid.contains('tanevrendjeesemeny') && - !name.contains('tanítási nap') && - (name.contains('pihenőnap') || + !name.contains('tanítási nap') && + (name.contains('pihenőnap') || name.contains('munkaszüneti') || name.contains('ünnepnap') || name.contains('tanítás nélküli') || @@ -1747,7 +2084,9 @@ class LiveActivityService { final schoolLessons = weekLessons.where((lesson) { final uid = lesson.uid.toLowerCase(); - return uid.contains('orarendiora') || uid.contains('tanitasiora') || uid.contains('uresora'); + return uid.contains('orarendiora') || + uid.contains('tanitasiora') || + uid.contains('uresora'); }).toList(); allLessons.addAll(weekLessons); @@ -1755,42 +2094,62 @@ class LiveActivityService { if (breakEvents.isNotEmpty) { breakEvents.sort((a, b) => a.start.compareTo(b.start)); lastBreakDay = breakEvents.last; - _logger.info('[GlobalBreakSearcher] Found ${breakEvents.length} break event(s) in week ${weeksSearched + 1}, last: ${lastBreakDay.name} on ${lastBreakDay.date.split('T')[0]}'); + _logger.info( + '[GlobalBreakSearcher] Found ${breakEvents.length} break event(s) in week ${weeksSearched + 1}, last: ${lastBreakDay.name} on ${lastBreakDay.date.split('T')[0]}', + ); } else if (schoolLessons.isNotEmpty) { schoolLessons.sort((a, b) => a.start.compareTo(b.start)); firstSchoolDayAfterBreak = schoolLessons.first; - _logger.info('[GlobalBreakSearcher] Found first school day after break: ${firstSchoolDayAfterBreak.name} on ${firstSchoolDayAfterBreak.date.split('T')[0]}'); + _logger.info( + '[GlobalBreakSearcher] Found first school day after break: ${firstSchoolDayAfterBreak.name} on ${firstSchoolDayAfterBreak.date.split('T')[0]}', + ); break; } else { - _logger.info('[GlobalBreakSearcher] Week ${weeksSearched + 1} is empty, continuing search...'); + _logger.info( + '[GlobalBreakSearcher] Week ${weeksSearched + 1} is empty, continuing search...', + ); } } else { - _logger.info('[GlobalBreakSearcher] Week ${weeksSearched + 1} returned no data, continuing search...'); + _logger.info( + '[GlobalBreakSearcher] Week ${weeksSearched + 1} returned no data, continuing search...', + ); } } catch (e) { - final isTokenError = e.toString().contains('TokenExpiredException') || - e.toString().contains('InvalidGrantException'); + final isTokenError = + e.toString().contains('TokenExpiredException') || + e.toString().contains('InvalidGrantException'); if (isTokenError) { tokenExpired = true; - _logger.warning('[GlobalBreakSearcher] Token expired during week ${weeksSearched + 1}, falling back to cache'); + _logger.warning( + '[GlobalBreakSearcher] Token expired during week ${weeksSearched + 1}, falling back to cache', + ); } else { - _logger.warning('[GlobalBreakSearcher] Error fetching week ${weeksSearched + 1}: $e'); + _logger.warning( + '[GlobalBreakSearcher] Error fetching week ${weeksSearched + 1}: $e', + ); } try { - final cachedResponse = await client.getTimeTable(weekStart, weekEnd, forceCache: true); + final cachedResponse = await client.getTimeTable( + weekStart, + weekEnd, + forceCache: true, + ); - if (cachedResponse.response != null && cachedResponse.response!.isNotEmpty) { + if (cachedResponse.response != null && + cachedResponse.response!.isNotEmpty) { final weekLessons = cachedResponse.response!; - _logger.info('[GlobalBreakSearcher] Loaded ${weekLessons.length} lessons from cache for week ${weeksSearched + 1}'); + _logger.info( + '[GlobalBreakSearcher] Loaded ${weekLessons.length} lessons from cache for week ${weeksSearched + 1}', + ); final breakEvents = weekLessons.where((lesson) { final uid = lesson.uid.toLowerCase(); final name = lesson.name.toLowerCase(); return uid.contains('tanevrendjeesemeny') && - !name.contains('tanítási nap') && - (name.contains('pihenőnap') || + !name.contains('tanítási nap') && + (name.contains('pihenőnap') || name.contains('munkaszüneti') || name.contains('ünnepnap') || name.contains('tanítás nélküli') || @@ -1799,7 +2158,9 @@ class LiveActivityService { final schoolLessons = weekLessons.where((lesson) { final uid = lesson.uid.toLowerCase(); - return uid.contains('orarendiora') || uid.contains('tanitasiora') || uid.contains('uresora'); + return uid.contains('orarendiora') || + uid.contains('tanitasiora') || + uid.contains('uresora'); }).toList(); allLessons.addAll(weekLessons); @@ -1807,18 +2168,26 @@ class LiveActivityService { if (breakEvents.isNotEmpty) { breakEvents.sort((a, b) => a.start.compareTo(b.start)); lastBreakDay = breakEvents.last; - _logger.info('[GlobalBreakSearcher] Found ${breakEvents.length} break event(s) in cached week ${weeksSearched + 1}, last: ${lastBreakDay.name} on ${lastBreakDay.date.split('T')[0]}'); + _logger.info( + '[GlobalBreakSearcher] Found ${breakEvents.length} break event(s) in cached week ${weeksSearched + 1}, last: ${lastBreakDay.name} on ${lastBreakDay.date.split('T')[0]}', + ); } else if (schoolLessons.isNotEmpty) { schoolLessons.sort((a, b) => a.start.compareTo(b.start)); firstSchoolDayAfterBreak = schoolLessons.first; - _logger.info('[GlobalBreakSearcher] Found first school day after break in cache: ${firstSchoolDayAfterBreak.name} on ${firstSchoolDayAfterBreak.date.split('T')[0]}'); + _logger.info( + '[GlobalBreakSearcher] Found first school day after break in cache: ${firstSchoolDayAfterBreak.name} on ${firstSchoolDayAfterBreak.date.split('T')[0]}', + ); break; } } else { - _logger.info('[GlobalBreakSearcher] No cache available for week ${weeksSearched + 1}'); + _logger.info( + '[GlobalBreakSearcher] No cache available for week ${weeksSearched + 1}', + ); } } catch (cacheError) { - _logger.warning('[GlobalBreakSearcher] Cache fallback also failed for week ${weeksSearched + 1}: $cacheError'); + _logger.warning( + '[GlobalBreakSearcher] Cache fallback also failed for week ${weeksSearched + 1}: $cacheError', + ); } } @@ -1831,10 +2200,14 @@ class LiveActivityService { } if (weeksSearched >= maxWeeks) { - _logger.warning('[GlobalBreakSearcher] Reached maximum search limit ($maxWeeks weeks)'); + _logger.warning( + '[GlobalBreakSearcher] Reached maximum search limit ($maxWeeks weeks)', + ); } - _logger.info('[GlobalBreakSearcher] Search completed: ${allLessons.length} lessons found, last break: ${lastBreakDay?.date.split('T')[0] ?? 'none'}, first school day: ${firstSchoolDayAfterBreak?.date.split('T')[0] ?? 'none'}, tokenExpired: $tokenExpired'); + _logger.info( + '[GlobalBreakSearcher] Search completed: ${allLessons.length} lessons found, last break: ${lastBreakDay?.date.split('T')[0] ?? 'none'}, first school day: ${firstSchoolDayAfterBreak?.date.split('T')[0] ?? 'none'}, tokenExpired: $tokenExpired', + ); return _GlobalSearchResult( allLessons: allLessons, diff --git a/firka/lib/helpers/profile_picture.dart b/firka/lib/helpers/profile_picture.dart index a42ab14..5ee4b04 100644 --- a/firka/lib/helpers/profile_picture.dart +++ b/firka/lib/helpers/profile_picture.dart @@ -7,7 +7,9 @@ import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; Future pickProfilePicture( - AppInitialization data, ImagePicker picker) async { + AppInitialization data, + ImagePicker picker, +) async { var imageFile = await picker.pickImage(source: ImageSource.gallery); if (imageFile == null) return; diff --git a/firka/lib/helpers/settings.dart b/firka/lib/helpers/settings.dart index ff284f0..629e421 100644 --- a/firka/lib/helpers/settings.dart +++ b/firka/lib/helpers/settings.dart @@ -89,403 +89,496 @@ class SettingsStore { SettingsStore(AppLocalizations l10n) { items["settings"] = SettingsGroup( - 0, - LinkedHashMap.of({ - "back": SettingsBackHeader(0, l10n.s_a, always), - "settings_header": SettingsHeader(0, l10n.s_settings, always), - "settings_padding": SettingsPadding(0, 20, always), - "application": SettingsSubGroup( + 0, + LinkedHashMap.of({ + "back": SettingsBackHeader(0, l10n.s_a, always), + "settings_header": SettingsHeader(0, l10n.s_settings, always), + "settings_padding": SettingsPadding(0, 20, always), + "application": SettingsSubGroup( + 0, + FirkaIconType.majesticons, + Majesticon.settingsCogSolid, + l10n.s_a, + LinkedHashMap.of({ + "back": SettingsBackHeader(0, l10n.s_settings, always), + "settings_header": SettingsHeader(0, l10n.s_ag, always), + "settings_padding": SettingsPadding(0, 23, always), + "bell_delay": SettingsDouble( + bellRing, + FirkaIconType.majesticons, + Majesticon.bellSolid, + l10n.s_ag_bell_delay, + 0, + 0, + 120, + 0, + always, + ), + "rounding": SettingsSubGroup( 0, FirkaIconType.majesticons, - Majesticon.settingsCogSolid, - l10n.s_a, + Majesticon.ruler2Solid, + l10n.s_ag_rounding, LinkedHashMap.of({ - "back": SettingsBackHeader(0, l10n.s_settings, always), - "settings_header": SettingsHeader(0, l10n.s_ag, always), - "settings_padding": SettingsPadding(0, 23, always), - "bell_delay": SettingsDouble( - bellRing, - FirkaIconType.majesticons, - Majesticon.bellSolid, - l10n.s_ag_bell_delay, - 0, - 0, - 120, - 0, - always), - "rounding": SettingsSubGroup( - 0, - FirkaIconType.majesticons, - Majesticon.ruler2Solid, - l10n.s_ag_rounding, - LinkedHashMap.of({ - "back": SettingsBackHeader(0, l10n.s_ag_rounding, always), - "1": SettingsDouble(rounding1, null, null, l10n.s_ag_r1, - 0.1, 0.5, 0.99, 2, always), - "2": SettingsDouble(rounding2, null, null, l10n.s_ag_r2, - 0.1, 0.5, 0.99, 2, always), - "3": SettingsDouble(rounding3, null, null, l10n.s_ag_r3, - 0.1, 0.5, 0.99, 2, always), - "4": SettingsDouble(rounding4, null, null, l10n.s_ag_r4, - 0.1, 0.5, 0.99, 2, always), - }), - always, - null), - "class_avg_on_graph": SettingsBoolean(classAvgOnGraph, null, - null, l10n.s_ag_class_avg_on_graph, true, never), - "navbar": SettingsSubGroup( - 0, - null, // TODO: icon - null, - l10n.s_ag_navbar, - LinkedHashMap.of({}), - never, - null), - "left_handed_mode": SettingsBoolean(leftHandedMode, null, null, - l10n.s_ag_left_handed_mode, false, never), - "live_activity_privacy_ever_declined": SettingsBoolean( - liveActivityPrivacyEverDeclined, - null, - null, - "Privacy Ever Declined", - false, - never), - "language_header": - SettingsHeaderSmall(0, l10n.s_ag_language_header, always), - "language": SettingsItemsRadio( - language, - null, - null, - [ - l10n.s_ag_language_auto, - l10n.s_ag_language_hu, - l10n.s_ag_language_en, - l10n.s_ag_language_de - ], - 0, - always, () async { - await initLang(initData); - initData.settings = SettingsStore(initData.l10n); - await initData.settings.load(initData.isar.appSettingsModels); + "back": SettingsBackHeader(0, l10n.s_ag_rounding, always), + "1": SettingsDouble( + rounding1, + null, + null, + l10n.s_ag_r1, + 0.1, + 0.5, + 0.99, + 2, + always, + ), + "2": SettingsDouble( + rounding2, + null, + null, + l10n.s_ag_r2, + 0.1, + 0.5, + 0.99, + 2, + always, + ), + "3": SettingsDouble( + rounding3, + null, + null, + l10n.s_ag_r3, + 0.1, + 0.5, + 0.99, + 2, + always, + ), + "4": SettingsDouble( + rounding4, + null, + null, + l10n.s_ag_r4, + 0.1, + 0.5, + 0.99, + 2, + always, + ), + }), + always, + null, + ), + "class_avg_on_graph": SettingsBoolean( + classAvgOnGraph, + null, + null, + l10n.s_ag_class_avg_on_graph, + true, + never, + ), + "navbar": SettingsSubGroup( + 0, + null, // TODO: icon + null, + l10n.s_ag_navbar, + LinkedHashMap.of({}), + never, + null, + ), + "left_handed_mode": SettingsBoolean( + leftHandedMode, + null, + null, + l10n.s_ag_left_handed_mode, + false, + never, + ), + "live_activity_privacy_ever_declined": SettingsBoolean( + liveActivityPrivacyEverDeclined, + null, + null, + "Privacy Ever Declined", + false, + never, + ), + "language_header": SettingsHeaderSmall( + 0, + l10n.s_ag_language_header, + always, + ), + "language": SettingsItemsRadio( + language, + null, + null, + [ + l10n.s_ag_language_auto, + l10n.s_ag_language_hu, + l10n.s_ag_language_en, + l10n.s_ag_language_de, + ], + 0, + always, + () async { + await initLang(initData); + initData.settings = SettingsStore(initData.l10n); + await initData.settings.load(initData.isar.appSettingsModels); - globalUpdate.update(); - runApp(InitializationScreen()); - }) - }), - always, - null), - "customization": SettingsSubGroup( + globalUpdate.update(); + runApp(InitializationScreen()); + }, + ), + }), + always, + null, + ), + "customization": SettingsSubGroup( + 0, + FirkaIconType.majesticons, + Majesticon.flower2Solid, + l10n.s_c, + LinkedHashMap.of({ + "back": SettingsBackHeader(0, l10n.s_settings, always), + "settings_header": SettingsHeader(0, l10n.s_customization, always), + "icon_header": SettingsHeaderSmall(0, l10n.s_c_icon_header, always), + "icon_header_preview_padding": SettingsPadding(0, 16, always), + "icon_preview": SettingsAppIconPreview(0, always), + "icon_picker": SettingsSubGroup( 0, - FirkaIconType.majesticons, - Majesticon.flower2Solid, - l10n.s_c, + null, + null, + l10n.s_c_replace_icon, LinkedHashMap.of({ - "back": SettingsBackHeader(0, l10n.s_settings, always), - "settings_header": - SettingsHeader(0, l10n.s_customization, always), - "icon_header": - SettingsHeaderSmall(0, l10n.s_c_icon_header, always), - "icon_header_preview_padding": SettingsPadding(0, 16, always), + "icon_header": SettingsHeader(0, l10n.s_ci_icon_header, always), + "warning_header": SettingsHeader( + 0, + l10n.s_ci_warning_header, + isDebug, + ), + "icon_subtitle": SettingsSubtitle( + 0, + l10n.s_ci_icon_subtitle, + always, + ), + "settings_padding": SettingsPadding(0, 24, always), "icon_preview": SettingsAppIconPreview(0, always), - "icon_picker": SettingsSubGroup( - 0, - null, - null, - l10n.s_c_replace_icon, - LinkedHashMap.of({ - "icon_header": - SettingsHeader(0, l10n.s_ci_icon_header, always), - "warning_header": - SettingsHeader(0, l10n.s_ci_warning_header, isDebug), - "icon_subtitle": - SettingsSubtitle(0, l10n.s_ci_icon_subtitle, always), - "settings_padding": SettingsPadding(0, 24, always), - "icon_preview": SettingsAppIconPreview(0, always), - "settings_padding2": SettingsPadding(0, 24, always), - "child_protection": SettingsBoolean( - childProtection, - FirkaIconType.majesticons, - Majesticon.shieldSolid, - l10n.s_ci_child_protection, - true, - never), - "icon_picker": SettingsAppIconPicker( - 0, - "original", - { - l10n.s_ci_icon_g1: ["original", "proto", "pride"], - l10n.s_ci_icon_g2: [ - "pixel", - "galaxy", - "cactus", - ], - l10n.s_ci_icon_g3: [ - "old", - "refilc", - "filc", - "szivacs" - ], - l10n.s_ci_icon_g4: [ - "modern", - "cc", - "paper", - "filco", - "o1g", - "pear", - "half_firka_2", - "nuke", - "refulc" - ], - l10n.s_ci_icon_g5: [ - "kreta", - "cc", - "repont", - "void_icon", - "pixelized", - "mkkp", - "fidesz", - "tisza" - ], - l10n.s_ci_icon_g6: ["xmas1", "xmas2", "xmas3"], - l10n.s_ci_icon_g7: [ - "lgbtq", - "lgbtqp", - "trans", - "enby", - "ace", - "gay", - "lesb", - "bi" - ], - l10n.s_ci_icon_g8: [ - "lgbtq_f", - "lgbtqp_f", - "trans_f", - "enby_f", - "ace_f", - "gay_f", - "lesb_f", - "bi_f" - ] - }, - always), - }), - isAndroid, - null), - "icon_theme_padding": SettingsPadding(0, 16, always), - "theme_header": - SettingsHeaderSmall(0, l10n.s_c_theme_header, always), - "theme": SettingsItemsRadio( - themeBrightness, - null, - null, - [ - l10n.s_c_theme_auto, - l10n.s_c_theme_light, - l10n.s_c_theme_dark - ], - 0, - always, () async { - initTheme(initData); - globalUpdate.update(); - }) + "settings_padding2": SettingsPadding(0, 24, always), + "child_protection": SettingsBoolean( + childProtection, + FirkaIconType.majesticons, + Majesticon.shieldSolid, + l10n.s_ci_child_protection, + true, + never, + ), + "icon_picker": SettingsAppIconPicker(0, "original", { + l10n.s_ci_icon_g1: ["original", "proto", "pride"], + l10n.s_ci_icon_g2: ["pixel", "galaxy", "cactus"], + l10n.s_ci_icon_g3: ["old", "refilc", "filc", "szivacs"], + l10n.s_ci_icon_g4: [ + "modern", + "cc", + "paper", + "filco", + "o1g", + "pear", + "half_firka_2", + "nuke", + "refulc", + ], + l10n.s_ci_icon_g5: [ + "kreta", + "cc", + "repont", + "void_icon", + "pixelized", + "mkkp", + "fidesz", + "tisza", + ], + l10n.s_ci_icon_g6: ["xmas1", "xmas2", "xmas3"], + l10n.s_ci_icon_g7: [ + "lgbtq", + "lgbtqp", + "trans", + "enby", + "ace", + "gay", + "lesb", + "bi", + ], + l10n.s_ci_icon_g8: [ + "lgbtq_f", + "lgbtqp_f", + "trans_f", + "enby_f", + "ace_f", + "gay_f", + "lesb_f", + "bi_f", + ], + }, always), }), + isAndroid, + null, + ), + "icon_theme_padding": SettingsPadding(0, 16, always), + "theme_header": SettingsHeaderSmall( + 0, + l10n.s_c_theme_header, always, - null), - "notifications": SettingsSubGroup( + ), + "theme": SettingsItemsRadio( + themeBrightness, + null, + null, + [l10n.s_c_theme_auto, l10n.s_c_theme_light, l10n.s_c_theme_dark], + 0, + always, + () async { + initTheme(initData); + globalUpdate.update(); + }, + ), + }), + always, + null, + ), + "notifications": SettingsSubGroup( + 0, + FirkaIconType.majesticons, + Majesticon.bellSolid, + l10n.s_n, + LinkedHashMap.of({ + "back": SettingsBackHeader(0, l10n.s_settings, always), + "settings_header": SettingsHeader(0, l10n.s_n, always), + "settings_padding": SettingsPadding(0, 23, always), + "morning_notification_enabled": SettingsBoolean( + morningNotificationEnabled, + FirkaIconType.majesticons, + Majesticon.bellSolid, + l10n.s_n_morning, + true, + always, + () async { + final setting = + initData.settings + .group("settings") + .subGroup( + "notifications", + )["morning_notification_enabled"] + as SettingsBoolean; + + LiveActivityService.onMorningNotificationEnabledChanged( + setting.value, + ); + }, + ), + "morning_notification_time": SettingsDouble( + morningNotificationTime, + FirkaIconType.majesticons, + Majesticon.clockSolid, + l10n.s_n_morning_time, + 30, // minValue + 120, // defaultValue + 240, // maxValue + 0, // precision (0 = whole numbers) + isMorningNotificationEnabled, + step: 15, + ), // 15 minute steps + "live_activity_enabled": SettingsBoolean( + liveActivityEnabled, + FirkaIconType.majesticons, + Majesticon.clockSolid, + l10n.s_n_live_activity, + false, + always, + () async { + final globalSetting = + initData.settings + .group("settings") + .subGroup("notifications")["live_activity_enabled"] + as SettingsBoolean; + + final enabled = globalSetting.value; + + await LiveActivityService.handleEnabledChange( + enabled, + isManual: true, + ); + + await LiveActivityService.syncGlobalSettingWithCurrentUser(); + }, + ), + "test_notification": SettingsButton( 0, FirkaIconType.majesticons, Majesticon.bellSolid, - l10n.s_n, - LinkedHashMap.of({ - "back": SettingsBackHeader(0, l10n.s_settings, always), - "settings_header": SettingsHeader(0, l10n.s_n, always), - "settings_padding": SettingsPadding(0, 23, always), - "morning_notification_enabled": SettingsBoolean( - morningNotificationEnabled, - FirkaIconType.majesticons, - Majesticon.bellSolid, - l10n.s_n_morning, - true, - always, - () async { - final setting = initData.settings - .group("settings") - .subGroup("notifications")["morning_notification_enabled"] as SettingsBoolean; + l10n.s_n_test, + isDebugIOS, + () async { + await LiveActivityService.sendTestNotification(); + }, + ), + }), + isIOS, + null, + ), + "extras": SettingsSubGroup( + 0, + FirkaIconType.majesticons, + Majesticon.lightningBoltSolid, + "Extrák", + LinkedHashMap.of({ + "back": SettingsBackHeader(0, l10n.s_settings, always), + }), + never, + null, + ), + "settings_other_padding": SettingsPadding(0, 20, never), + "settings_other_header": SettingsHeaderSmall(0, "Egyéb", never), - LiveActivityService.onMorningNotificationEnabledChanged(setting.value); - }), - "morning_notification_time": SettingsDouble( - morningNotificationTime, - FirkaIconType.majesticons, - Majesticon.clockSolid, - l10n.s_n_morning_time, - 30, // minValue - 120, // defaultValue - 240, // maxValue - 0, // precision (0 = whole numbers) - isMorningNotificationEnabled, - step: 15), // 15 minute steps - "live_activity_enabled": SettingsBoolean( - liveActivityEnabled, - FirkaIconType.majesticons, - Majesticon.clockSolid, - l10n.s_n_live_activity, - false, - always, - () async { - final globalSetting = initData.settings - .group("settings") - .subGroup("notifications")["live_activity_enabled"] as SettingsBoolean; - - final enabled = globalSetting.value; - - await LiveActivityService.handleEnabledChange(enabled, isManual: true); - - await LiveActivityService.syncGlobalSettingWithCurrentUser(); - }), - "test_notification": SettingsButton( - 0, - FirkaIconType.majesticons, - Majesticon.bellSolid, - l10n.s_n_test, - isDebugIOS, - () async { - await LiveActivityService.sendTestNotification(); - }), - }), - isIOS, - null), - "extras": SettingsSubGroup( - 0, - FirkaIconType.majesticons, - Majesticon.lightningBoltSolid, - "Extrák", - LinkedHashMap.of({ - "back": SettingsBackHeader(0, l10n.s_settings, always), - }), - never, - null), - "settings_other_padding": SettingsPadding(0, 20, never), - "settings_other_header": SettingsHeaderSmall(0, "Egyéb", never), - - "developer": SettingsSubGroup( - 0, + "developer": SettingsSubGroup( + 0, + FirkaIconType.majesticonsLocal, + "wrenchSolid", + 'Developer', + LinkedHashMap.of({ + "back": SettingsBackHeader(0, l10n.s_settings, always), + "stats_for_nerds": SettingsBoolean( + statsForNerds, FirkaIconType.majesticonsLocal, "wrenchSolid", - 'Developer', - LinkedHashMap.of({ - "back": SettingsBackHeader(0, l10n.s_settings, always), - "stats_for_nerds": SettingsBoolean( - statsForNerds, - FirkaIconType.majesticonsLocal, - "wrenchSolid", - l10n.s_stats_for_nerds, - false, - always), - "logs": SettingsLogs(0, always), - }), - isDeveloper, - null), + l10n.s_stats_for_nerds, + false, + always, + ), + "logs": SettingsLogs(0, always), + }), + isDeveloper, + null, + ), - // misc - "beta_warning": SettingsBoolean( - betaWarning, null, null, "Beta warning", false, never), - "timetable_toast": SettingsSubGroup( + // misc + "beta_warning": SettingsBoolean( + betaWarning, + null, + null, + "Beta warning", + false, + never, + ), + "timetable_toast": SettingsSubGroup( + 0, + null, + null, + l10n.tt_settings_toast, + LinkedHashMap.of({ + "header": SettingsMediumHeader(0, l10n.tt_settings_toast, always), + "padding": SettingsPadding(0, 16, always), + "lesson_no": SettingsBoolean( + ttToastLessonNo, + FirkaIconType.majesticons, + Majesticon.clockSolid, + l10n.tt_settings_toast_lesson_nos, + true, + always, + ), + "tests_and_homework": SettingsBoolean( + ttToastTestsAndHw, + FirkaIconType.majesticons, + Majesticon.editPen4Solid, + l10n.tt_settings_toast_lesson_tests, + true, + always, + ), + "substitution": SettingsBoolean( + ttToastSubstitution, + FirkaIconType.majesticons, + Majesticon.usersSolid, + l10n.tt_settings_toast_lesson_substitution, + true, + always, + ), + "breaks": SettingsBoolean( + ttToastBreaks, + FirkaIconType.majesticons, + Majesticon.viewRowsLine, + l10n.tt_settings_toast_lesson_breaks, + true, + always, + ), + "ab_timetable": SettingsBoolean( + ttToastABTimetable, + FirkaIconType.majesticons, + Majesticon.calendarSolid, + l10n.tt_settings_toast_lesson_ab_timetable, + true, + always, + ), + }), + never, + null, + ), + "settings_about_padding": SettingsPadding(0, 20, always), + "settings_about_header": SettingsHeaderSmall(0, "Névjegy", always), + "discord_button": SettingsSubGroup( + 0, + FirkaIconType.majesticons, + Majesticon.chatSolid, + "Discord", + LinkedHashMap.of({}), + always, + "discord", + ), + "privacy_policy_button": SettingsSubGroup( + 0, + FirkaIconType.majesticons, + Majesticon.lockSolid, + l10n.privacyLabel, + LinkedHashMap.of({}), + always, + "privacy", + ), + "license_page": SettingsSubGroup( + 0, + FirkaIconType.majesticons, + Majesticon.awardSolid, + l10n.licensesLabel, + LinkedHashMap.of({ + "back": SettingsBackHeader(0, l10n.s_settings, always), + "header": SettingsMediumHeader(0, l10n.licensesLabel, always), + "licenses_header": SettingsHeaderSmall( 0, - null, - null, - l10n.tt_settings_toast, - LinkedHashMap.of({ - "header": - SettingsMediumHeader(0, l10n.tt_settings_toast, always), - "padding": SettingsPadding(0, 16, always), - "lesson_no": SettingsBoolean( - ttToastLessonNo, - FirkaIconType.majesticons, - Majesticon.clockSolid, - l10n.tt_settings_toast_lesson_nos, - true, - always), - "tests_and_homework": SettingsBoolean( - ttToastTestsAndHw, - FirkaIconType.majesticons, - Majesticon.editPen4Solid, - l10n.tt_settings_toast_lesson_tests, - true, - always), - "substitution": SettingsBoolean( - ttToastSubstitution, - FirkaIconType.majesticons, - Majesticon.usersSolid, - l10n.tt_settings_toast_lesson_substitution, - true, - always), - "breaks": SettingsBoolean( - ttToastBreaks, - FirkaIconType.majesticons, - Majesticon.viewRowsLine, - l10n.tt_settings_toast_lesson_breaks, - true, - always), - "ab_timetable": SettingsBoolean( - ttToastABTimetable, - FirkaIconType.majesticons, - Majesticon.calendarSolid, - l10n.tt_settings_toast_lesson_ab_timetable, - true, - always), - }), - never, - null), - "settings_about_padding": SettingsPadding(0, 20, always), - "settings_about_header": SettingsHeaderSmall(0, "Névjegy", always), - "discord_button": SettingsSubGroup( - 0, - FirkaIconType.majesticons, - Majesticon.chatSolid, - "Discord", - LinkedHashMap.of({}), - always, - "discord" - ), - "privacy_policy_button": SettingsSubGroup( - 0, - FirkaIconType.majesticons, - Majesticon.lockSolid, - l10n.privacyLabel, - LinkedHashMap.of({}), - always, - "privacy" - ), - "license_page": SettingsSubGroup( - 0, - FirkaIconType.majesticons, - Majesticon.awardSolid, - l10n.licensesLabel, - LinkedHashMap.of({ - "back": SettingsBackHeader(0, l10n.s_settings, always), - "header": SettingsMediumHeader(0, l10n.licensesLabel, always), - "licenses_header": - SettingsHeaderSmall(0, l10n.licenseDescription, always), - "show_license": ShowLicensePage(), - }), - always, - null - ), - "developer_enabled": SettingsBoolean( - developerOptsEnabled, null, null, "Developer", false, never), - }), - always); + l10n.licenseDescription, + always, + ), + "show_license": ShowLicensePage(), + }), + always, + null, + ), + "developer_enabled": SettingsBoolean( + developerOptsEnabled, + null, + null, + "Developer", + false, + never, + ), + }), + always, + ); items["profile_settings"] = SettingsGroup( - 0, - LinkedHashMap.of({ - "back": SettingsBackHeader(0, l10n.s_your_account, always), - "e_kreta_accounts": SettingsHeaderSmall(0, l10n.s_acc_kreta, always), - "e_padding": SettingsPadding(0, 8, always), - "e_kreta_account_picker": SettingsKretenAccountPicker(0, always), - }), - never); + 0, + LinkedHashMap.of({ + "back": SettingsBackHeader(0, l10n.s_your_account, always), + "e_kreta_accounts": SettingsHeaderSmall(0, l10n.s_acc_kreta, always), + "e_padding": SettingsPadding(0, 8, always), + "e_kreta_account_picker": SettingsKretenAccountPicker(0, always), + }), + never, + ); appIcons = { "ace": l10n.ic_ace, "ace_f": l10n.ic_ace_f, @@ -531,20 +624,26 @@ class SettingsStore { "void_icon": l10n.ic_void_icon, "xmas1": l10n.ic_xmas1, "xmas2": l10n.ic_xmas2, - "xmas3": l10n.ic_xmas3 + "xmas3": l10n.ic_xmas3, }; if (Platform.isIOS) { - final bellDelaySetting = group("settings") - .subGroup("application")["bell_delay"] as SettingsDouble; + final bellDelaySetting = + group("settings").subGroup("application")["bell_delay"] + as SettingsDouble; bellDelaySetting.postUpdate = () async { LiveActivityService.onBellDelayChanged(bellDelaySetting.value); }; - final morningNotificationTimeSetting = group("settings") - .subGroup("notifications")["morning_notification_time"] as SettingsDouble; + final morningNotificationTimeSetting = + group( + "settings", + ).subGroup("notifications")["morning_notification_time"] + as SettingsDouble; morningNotificationTimeSetting.postUpdate = () async { - LiveActivityService.onMorningNotificationTimeChanged(morningNotificationTimeSetting.value); + LiveActivityService.onMorningNotificationTimeChanged( + morningNotificationTimeSetting.value, + ); }; } } @@ -671,8 +770,15 @@ class SettingsSubGroup implements SettingsItem { LinkedHashMap children; String? redirectTo; - SettingsSubGroup(this.key, this.iconType, this.iconData, this.title, - this.children, this.visibilityProvider, this.redirectTo); + SettingsSubGroup( + this.key, + this.iconType, + this.iconData, + this.title, + this.children, + this.visibilityProvider, + this.redirectTo, + ); @override Future load(IsarCollection model) async { @@ -814,7 +920,7 @@ class ShowLicensePage implements SettingsItem { Future Function() postUpdate = () async {}; ShowLicensePage(); - + @override Future load(IsarCollection model) async {} @@ -945,7 +1051,11 @@ class SettingsAppIconPicker implements SettingsItem { Map> iconGroups; SettingsAppIconPicker( - this.key, this.defaultValue, this.iconGroups, this.visibilityProvider); + this.key, + this.defaultValue, + this.iconGroups, + this.visibilityProvider, + ); @override Future load(IsarCollection model) async { @@ -984,8 +1094,15 @@ class SettingsBoolean implements SettingsItem { bool value = false; bool defaultValue; - SettingsBoolean(this.key, this.iconType, this.iconData, this.title, - this.defaultValue, this.visibilityProvider, [Future Function()? postUpdateFn]) { + SettingsBoolean( + this.key, + this.iconType, + this.iconData, + this.title, + this.defaultValue, + this.visibilityProvider, [ + Future Function()? postUpdateFn, + ]) { if (postUpdateFn != null) { postUpdate = postUpdateFn; } @@ -1028,8 +1145,15 @@ class SettingsItemsRadio implements SettingsItem { int activeIndex = 0; int defaultIndex; - SettingsItemsRadio(this.key, this.iconType, this.iconData, this.values, - this.defaultIndex, this.visibilityProvider, this.postUpdate); + SettingsItemsRadio( + this.key, + this.iconType, + this.iconData, + this.values, + this.defaultIndex, + this.visibilityProvider, + this.postUpdate, + ); @override Future load(IsarCollection model) async { @@ -1073,16 +1197,17 @@ class SettingsDouble implements SettingsItem { double? step; SettingsDouble( - this.key, - this.iconType, - this.iconData, - this.title, - this.minValue, - this.defaultValue, - this.maxValue, - this.precision, - this.visibilityProvider, - {this.step}); + this.key, + this.iconType, + this.iconData, + this.title, + this.minValue, + this.defaultValue, + this.maxValue, + this.precision, + this.visibilityProvider, { + this.step, + }); double toRoundedDouble() { return double.parse(toRoundedString()); @@ -1092,8 +1217,8 @@ class SettingsDouble implements SettingsItem { return precision == 0 ? value.toString().split(".")[0] : value.toStringAsPrecision(precision) == "0.0" - ? "0" - : value.toStringAsPrecision(precision); + ? "0" + : value.toStringAsPrecision(precision); } @override @@ -1133,8 +1258,14 @@ class SettingsString implements SettingsItem { String value = ""; String defaultValue; - SettingsString(this.key, this.iconType, this.iconData, this.title, - this.defaultValue, this.visibilityProvider); + SettingsString( + this.key, + this.iconType, + this.iconData, + this.title, + this.defaultValue, + this.visibilityProvider, + ); @override Future load(IsarCollection model) async { @@ -1172,8 +1303,14 @@ class SettingsButton implements SettingsItem { String title; Future Function() onTap; - SettingsButton(this.key, this.iconType, this.iconData, this.title, - this.visibilityProvider, this.onTap); + SettingsButton( + this.key, + this.iconType, + this.iconData, + this.title, + this.visibilityProvider, + this.onTap, + ); @override Future load(IsarCollection model) async {} diff --git a/firka/lib/helpers/swear_generator.dart b/firka/lib/helpers/swear_generator.dart index 701dcbe..9cf2c76 100644 --- a/firka/lib/helpers/swear_generator.dart +++ b/firka/lib/helpers/swear_generator.dart @@ -11,7 +11,7 @@ Future loadDirtyWords() async { for (final node in document.findAllElements('Word')) { final type = node.getAttribute('type'); - final text = node.text.trim(); + final text = node.innerText.trim(); if (type == 'f') { _nouns.add(text); @@ -21,8 +21,11 @@ Future loadDirtyWords() async { } } -String generateSwearSentence( - {int words = 3, bool capitalize = true, bool exclamation = true}) { +String generateSwearSentence({ + int words = 3, + bool capitalize = true, + bool exclamation = true, +}) { if (words < 1) { throw ArgumentError('Words must be at least 1'); } diff --git a/firka/lib/helpers/ui/common_bottom_sheets.dart b/firka/lib/helpers/ui/common_bottom_sheets.dart index c4b3985..ae61266 100644 --- a/firka/lib/helpers/ui/common_bottom_sheets.dart +++ b/firka/lib/helpers/ui/common_bottom_sheets.dart @@ -25,25 +25,24 @@ import 'grade.dart'; import '../../helpers/api/model/test.dart'; Future showLessonBottomSheet( - BuildContext context, - AppInitialization data, - Lesson lesson, - int? lessonNo, - Color accent, - Color secondary, - Color bgColor, - Test? test, - - ) async { + BuildContext context, + AppInitialization data, + Lesson lesson, + int? lessonNo, + Color accent, + Color secondary, + Color bgColor, + Test? test, +) async { final statsForNerdsEnabled = data.settings .group("settings") .subGroup("developer") .boolean("stats_for_nerds"); final showTests = data.settings - .group("settings") - .subGroup("timetable_toast") - .boolean("tests_and_homework"); + .group("settings") + .subGroup("timetable_toast") + .boolean("tests_and_homework"); showModalBottomSheet( context: context, elevation: 100, @@ -61,11 +60,12 @@ Future showLessonBottomSheet( "${data.l10n.stats_date}: ${lesson.start.isAfter(y2k) ? lesson.start.format(data.l10n, FormatMode.yyyymmddhhmmss) : "N/A"}\n" "${data.l10n.stats_created_at}: ${lesson.createdAt.isAfter(y2k) ? lesson.createdAt.format(data.l10n, FormatMode.yyyymmddhhmmss) : "N/A"}\n" "${data.l10n.stats_last_mod}: ${lesson.lastModifiedAt.isAfter(y2k) ? lesson.lastModifiedAt.format(data.l10n, FormatMode.yyyymmddhhmmss) : "N/A"}"; - statsForNerds = Text(stats, - style: - appStyle.fonts.B_16R.apply(color: appStyle.colors.textPrimary)); + statsForNerds = Text( + stats, + style: appStyle.fonts.B_16R.apply(color: appStyle.colors.textPrimary), + ); } - + return Stack( children: [ Positioned.fill( @@ -104,7 +104,9 @@ Future showLessonBottomSheet( ), Text( lessonNo.toString(), - style: appStyle.fonts.B_12R.apply(color: secondary), + style: appStyle.fonts.B_12R.apply( + color: secondary, + ), textAlign: TextAlign.center, ), ], @@ -116,7 +118,8 @@ Future showLessonBottomSheet( shadowColor: Colors.transparent, color: bgColor, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16)), + borderRadius: BorderRadius.circular(16), + ), child: Padding( padding: EdgeInsetsGeometry.all(4), child: ClassIconWidget( @@ -138,63 +141,75 @@ Future showLessonBottomSheet( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row(children: [ - Text( - "${lesson.name} ${statsForNerdsEnabled ? "(${lesson.classGroup?.name ?? ''})" : ""}", - style: appStyle.fonts.H_18px - .apply(color: appStyle.colors.textPrimary), - ), - Card( - shadowColor: Colors.transparent, - color: appStyle.colors.a15p, - child: Padding( - padding: EdgeInsets.all(5), - child: Text(lesson.roomName ?? 'N/A', - style: appStyle.fonts.B_12R.apply( - color: appStyle.colors.secondary)), + Row( + children: [ + Text( + "${lesson.name} ${statsForNerdsEnabled ? "(${lesson.classGroup?.name ?? ''})" : ""}", + style: appStyle.fonts.H_18px.apply( + color: appStyle.colors.textPrimary, + ), ), - ), - ]), + Card( + shadowColor: Colors.transparent, + color: appStyle.colors.a15p, + child: Padding( + padding: EdgeInsets.all(5), + child: Text( + lesson.roomName ?? 'N/A', + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.secondary, + ), + ), + ), + ), + ], + ), Text( lesson.teacher ?? 'N/A', - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), ), Text( '${lesson.start.format(data.l10n, FormatMode.hmm)} - ${lesson.end.format(data.l10n, FormatMode.hmm)}', - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), ), ], ), ), SizedBox(height: 8), - FirkaCard(left: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - data.l10n.lesson_subject, - style: appStyle.fonts.H_14px - .apply(color: appStyle.colors.textPrimary), - ), - SizedBox(height: 4), + FirkaCard( + left: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + data.l10n.lesson_subject, + style: appStyle.fonts.H_14px.apply( + color: appStyle.colors.textPrimary, + ), + ), + SizedBox(height: 4), SizedBox( width: MediaQuery.of(context).size.width * 0.7, child: Text( lesson.theme ?? 'N/A', - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), maxLines: 3, overflow: TextOverflow.ellipsis, ), ), - SizedBox(height: 4), - statsForNerds - ], - ) - ]), - if (test != null && showTests) + SizedBox(height: 4), + statsForNerds, + ], + ), + ], + ), + if (test != null && showTests) FirkaCard( left: [ Container( @@ -217,26 +232,32 @@ Future showLessonBottomSheet( alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ + children: [ SizedBox( - width: MediaQuery.of(context).size.width * 0.6, + width: + MediaQuery.of(context).size.width * 0.6, child: Text( test.theme, - style: appStyle.fonts.B_16SB.apply(color: appStyle.colors.textPrimary), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ) - ), - SizedBox(height: 4), - SizedBox( - width: MediaQuery.of(context).size.width * 0.6, - child: Text( - test.method.description ?? 'N/A', - style: appStyle.fonts.B_16R.apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), maxLines: 2, overflow: TextOverflow.ellipsis, ), - ) + ), + SizedBox(height: 4), + SizedBox( + width: + MediaQuery.of(context).size.width * 0.6, + child: Text( + test.method.description ?? 'N/A', + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), ], ), ), @@ -255,7 +276,8 @@ Future showLessonBottomSheet( ), ), ), - ]), + ], + ), SizedBox(height: 8), SizedBox( width: MediaQuery.of(context).size.width / 1.1, @@ -265,15 +287,20 @@ Future showLessonBottomSheet( center: [ Text( data.l10n.view_subject_btn, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), - ) + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), ], color: appStyle.colors.buttonSecondaryFill, ), onTap: () { Navigator.pop(context); - pageNavNotifier.value = PageNavData(HomePage.grades, lesson.subject!.uid, lesson.subject!.name); + pageNavNotifier.value = PageNavData( + HomePage.grades, + lesson.subject!.uid, + lesson.subject!.name, + ); }, ), ), @@ -289,19 +316,24 @@ Future showLessonBottomSheet( } Future showTestBottomSheet( - BuildContext context, - AppInitialization data, - Lesson lesson, - int? lessonNo, - Color accent, - Color secondary, - Color bgColor, - Test? test, - - ) async { + BuildContext context, + AppInitialization data, + Lesson lesson, + int? lessonNo, + Color accent, + Color secondary, + Color bgColor, + Test? test, +) async { final date = lesson.start; - final formattedDate = DateFormat('MMMM d, EEEE', data.l10n.localeName).format(date); - final formattedTime = DateFormat('MMMM d, HH:mm', data.l10n.localeName).format(date); + final formattedDate = DateFormat( + 'MMMM d, EEEE', + data.l10n.localeName, + ).format(date); + final formattedTime = DateFormat( + 'MMMM d, HH:mm', + data.l10n.localeName, + ).format(date); final statsForNerdsEnabled = data.settings .group("settings") @@ -309,9 +341,9 @@ Future showTestBottomSheet( .boolean("stats_for_nerds"); final showTests = data.settings - .group("settings") - .subGroup("timetable_toast") - .boolean("tests_and_homework"); + .group("settings") + .subGroup("timetable_toast") + .boolean("tests_and_homework"); showModalBottomSheet( context: context, elevation: 100, @@ -328,11 +360,12 @@ Future showTestBottomSheet( "${data.l10n.stats_date}: ${lesson.start.isAfter(y2k) ? lesson.start.format(data.l10n, FormatMode.yyyymmddhhmmss) : "N/A"}\n" "${data.l10n.stats_created_at}: ${lesson.createdAt.isAfter(y2k) ? lesson.createdAt.format(data.l10n, FormatMode.yyyymmddhhmmss) : "N/A"}\n" "${data.l10n.stats_last_mod}: ${lesson.lastModifiedAt.isAfter(y2k) ? lesson.lastModifiedAt.format(data.l10n, FormatMode.yyyymmddhhmmss) : "N/A"}"; - statsForNerds = Text(stats, - style: - appStyle.fonts.B_16R.apply(color: appStyle.colors.textPrimary)); + statsForNerds = Text( + stats, + style: appStyle.fonts.B_16R.apply(color: appStyle.colors.textPrimary), + ); } - + return Stack( children: [ Positioned.fill( @@ -376,47 +409,54 @@ Future showTestBottomSheet( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row(children: [ - Expanded( - child: Text( - "${test?.theme ?? 'N/A'} ${statsForNerdsEnabled ? "(${lesson.classGroup?.name ?? ''})" : ""}", - style: appStyle.fonts.H_18px - .apply(color: appStyle.colors.textPrimary), - maxLines: 4, - overflow: TextOverflow.ellipsis, + Row( + children: [ + Expanded( + child: Text( + "${test?.theme ?? 'N/A'} ${statsForNerdsEnabled ? "(${lesson.classGroup?.name ?? ''})" : ""}", + style: appStyle.fonts.H_18px.apply( + color: appStyle.colors.textPrimary, + ), + maxLines: 4, + overflow: TextOverflow.ellipsis, + ), ), - ), - ]), + ], + ), Text( test?.method.description ?? 'N/A', - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), ), ], ), ), SizedBox(height: 8), - FirkaCard(left: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 4), + FirkaCard( + left: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 4), SizedBox( width: MediaQuery.of(context).size.width * 0.7, child: Text( "${data.l10n.data}: $formattedDate", - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), maxLines: 3, overflow: TextOverflow.ellipsis, ), ), - SizedBox(height: 4), - statsForNerds - ], - ) - ]), - if (test != null && showTests) + SizedBox(height: 4), + statsForNerds, + ], + ), + ], + ), + if (test != null && showTests) FirkaCard( left: [ Row( @@ -434,10 +474,13 @@ Future showTestBottomSheet( ), Padding( padding: EdgeInsets.only(left: 8, top: 4), - child: Text(lessonNo.toString(), - style: appStyle.fonts.B_12R - .apply(color: secondary)), - ) + child: Text( + lessonNo.toString(), + style: appStyle.fonts.B_12R.apply( + color: secondary, + ), + ), + ), ], ), ), @@ -447,7 +490,8 @@ Future showTestBottomSheet( shadowColor: Colors.transparent, color: bgColor, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16)), + borderRadius: BorderRadius.circular(16), + ), child: Padding( padding: EdgeInsetsGeometry.all(4), child: ClassIconWidget( @@ -469,26 +513,31 @@ Future showTestBottomSheet( alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ + children: [ SizedBox( - width: MediaQuery.of(context).size.width * 0.3, + width: + MediaQuery.of(context).size.width * 0.3, child: Text( lesson.name, - style: appStyle.fonts.B_16SB.apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), maxLines: 2, overflow: TextOverflow.ellipsis, - ) + ), ), ], ), ), - ], - right: [ + ], + right: [ Text( - formattedTime, - style: appStyle.fonts.B_14R.apply(color: appStyle.colors.textSecondary), - ) - ], + formattedTime, + style: appStyle.fonts.B_14R.apply( + color: appStyle.colors.textSecondary, + ), + ), + ], ), SizedBox(height: 8), SizedBox( @@ -499,14 +548,24 @@ Future showTestBottomSheet( center: [ Text( data.l10n.view_lesson_btn, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), - ) + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), ], color: appStyle.colors.buttonSecondaryFill, ), onTap: () { - showLessonBottomSheet(context, data, lesson, lessonNo, accent, secondary, bgColor, test); + showLessonBottomSheet( + context, + data, + lesson, + lessonNo, + accent, + secondary, + bgColor, + test, + ); }, ), ), @@ -522,7 +581,10 @@ Future showTestBottomSheet( } Future showGradeBottomSheet( - BuildContext context, AppInitialization data, Grade grade) async { + BuildContext context, + AppInitialization data, + Grade grade, +) async { showModalBottomSheet( context: context, elevation: 100, @@ -535,7 +597,10 @@ Future showGradeBottomSheet( ), builder: (BuildContext context) { final gradeCreationDate = grade.creationDate; - final formattedDate = DateFormat('yyyy. MMMM d., EEEE', data.l10n.localeName).format(gradeCreationDate); + final formattedDate = DateFormat( + 'yyyy. MMMM d., EEEE', + data.l10n.localeName, + ).format(gradeCreationDate); return Stack( children: [ @@ -559,18 +624,19 @@ Future showGradeBottomSheet( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Row( - children: [GradeWidget(grade)], - ), + Row(children: [GradeWidget(grade)]), SizedBox(height: 4), Padding( padding: const EdgeInsets.only(left: 6), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(grade.topic ?? grade.type.description!, - style: appStyle.fonts.H_18px - .apply(color: appStyle.colors.textPrimary)), + Text( + grade.topic ?? grade.type.description!, + style: appStyle.fonts.H_18px.apply( + color: appStyle.colors.textPrimary, + ), + ), grade.mode?.description != null ? SizedBox( width: @@ -578,61 +644,73 @@ Future showGradeBottomSheet( child: Text( grade.mode!.description!, style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textSecondary), + color: appStyle.colors.textSecondary, + ), ), ) : SizedBox(), - SizedBox( - height: 20, - ), + SizedBox(height: 20), LessonWidget( data, [], - [], + [], null, Lesson( - uid: "-2", - date: "", - start: grade.creationDate, - end: grade.creationDate, - name: grade.subject.name, - type: NameUidDesc( - uid: "", name: "", description: ""), - state: NameUidDesc( - uid: "", name: "", description: ""), - canStudentEditHomework: false, - isHomeworkComplete: false, - attachments: [], - isDigitalLesson: false, - digitalSupportDeviceTypeList: [], - createdAt: timeNow(), - subject: grade.subject, - lastModifiedAt: timeNow()), + uid: "-2", + date: "", + start: grade.creationDate, + end: grade.creationDate, + name: grade.subject.name, + type: NameUidDesc( + uid: "", + name: "", + description: "", + ), + state: NameUidDesc( + uid: "", + name: "", + description: "", + ), + canStudentEditHomework: false, + isHomeworkComplete: false, + attachments: [], + isDigitalLesson: false, + digitalSupportDeviceTypeList: [], + createdAt: timeNow(), + subject: grade.subject, + lastModifiedAt: timeNow(), + ), null, null, placeholderMode: true, ), - FirkaCard(left: [ - Column( + FirkaCard( + left: [ + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${data.l10n.tt_added}$formattedDate", style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textPrimary), + color: appStyle.colors.textPrimary, + ), ), Text( "${data.l10n.grade_teacherName}${grade.teacher}", style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textPrimary), + color: appStyle.colors.textPrimary, + ), ), Text( "${data.l10n.grade_strValue}${grade.strValue}", style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textPrimary), - ) - ]) - ]), + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + ], + ), SizedBox(height: 8), SizedBox( width: MediaQuery.of(context).size.width / 1.1, @@ -642,15 +720,20 @@ Future showGradeBottomSheet( center: [ Text( data.l10n.view_subject_btn, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), - ) + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), ], color: appStyle.colors.buttonSecondaryFill, ), onTap: () { Navigator.pop(context); - pageNavNotifier.value = PageNavData(HomePage.grades, grade.subject.uid, grade.subject.name); + pageNavNotifier.value = PageNavData( + HomePage.grades, + grade.subject.uid, + grade.subject.name, + ); }, ), ), @@ -669,7 +752,10 @@ Future showGradeBottomSheet( } Future showHomeworkBottomSheet( - BuildContext context, AppInitialization data, Homework homework) async { + BuildContext context, + AppInitialization data, + Homework homework, +) async { showModalBottomSheet( context: context, elevation: 100, @@ -681,8 +767,10 @@ Future showHomeworkBottomSheet( minHeight: MediaQuery.of(context).size.height * 0.34, ), builder: (BuildContext context) { - - final formattedDate = DateFormat('yyyy. MMMM d.', data.l10n.localeName).format(homework.dueDate); + final formattedDate = DateFormat( + 'yyyy. MMMM d.', + data.l10n.localeName, + ).format(homework.dueDate); return Stack( children: [ @@ -711,17 +799,21 @@ Future showHomeworkBottomSheet( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row(children: [ - Text( - data.l10n.homework, - style: appStyle.fonts.H_18px - .apply(color: appStyle.colors.textPrimary), - ), - ]), + Row( + children: [ + Text( + data.l10n.homework, + style: appStyle.fonts.H_18px.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), Text( formattedDate, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), ), ], ), @@ -730,26 +822,25 @@ Future showHomeworkBottomSheet( LessonWidget( data, [], - [], + [], null, Lesson( - uid: "-1", - date: "", - start: homework.startDate, - end: homework.dueDate , - name: homework.subjectName, - type: NameUidDesc( - uid: "", name: "", description: ""), - state: NameUidDesc( - uid: "", name: "", description: ""), - canStudentEditHomework: false, - isHomeworkComplete: false, - attachments: [], - isDigitalLesson: false, - digitalSupportDeviceTypeList: [], - createdAt: timeNow(), - subject: homework.subject, - lastModifiedAt: timeNow()), + uid: "-1", + date: "", + start: homework.startDate, + end: homework.dueDate, + name: homework.subjectName, + type: NameUidDesc(uid: "", name: "", description: ""), + state: NameUidDesc(uid: "", name: "", description: ""), + canStudentEditHomework: false, + isHomeworkComplete: false, + attachments: [], + isDigitalLesson: false, + digitalSupportDeviceTypeList: [], + createdAt: timeNow(), + subject: homework.subject, + lastModifiedAt: timeNow(), + ), null, null, placeholderMode: true, @@ -760,11 +851,16 @@ Future showHomeworkBottomSheet( shadow: true, child: Card( color: appStyle.colors.card, - shadowColor: isLightMode.value ? null : Colors.transparent, + shadowColor: isLightMode.value + ? null + : Colors.transparent, child: Align( alignment: Alignment.centerLeft, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0), + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + vertical: 20.0, + ), child: Html( data: homework.description, style: { @@ -778,11 +874,11 @@ Future showHomeworkBottomSheet( textAlign: TextAlign.start, ), }, - ), + ), ), ), ), - ) + ), ), FutureBuilder( future: isHomeworkDone(data.isar, homework.uid), @@ -805,8 +901,9 @@ Future showHomeworkBottomSheet( Text( data.l10n.mark_as_done, style: appStyle.fonts.B_16SB.apply( - color: appStyle.colors.textSecondary), - ) + color: appStyle.colors.textSecondary, + ), + ), ], color: appStyle.colors.accent, ), @@ -826,8 +923,9 @@ Future showHomeworkBottomSheet( Text( data.l10n.mark_as_not_done, style: appStyle.fonts.B_16SB.apply( - color: appStyle.colors.textSecondary), - ) + color: appStyle.colors.textSecondary, + ), + ), ], color: appStyle.colors.accent, ), @@ -849,15 +947,20 @@ Future showHomeworkBottomSheet( center: [ Text( data.l10n.view_subject_btn, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), - ) + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), ], color: appStyle.colors.buttonSecondaryFill, ), onTap: () { Navigator.pop(context); - pageNavNotifier.value = PageNavData(HomePage.grades, homework.subject.uid, homework.subjectName); + pageNavNotifier.value = PageNavData( + HomePage.grades, + homework.subject.uid, + homework.subjectName, + ); }, ), ), @@ -873,7 +976,10 @@ Future showHomeworkBottomSheet( } Future showSubjectBottomSheetSettings( - BuildContext context, AppInitialization data, Subject subject) async { + BuildContext context, + AppInitialization data, + Subject subject, +) async { showModalBottomSheet( context: context, elevation: 100, @@ -882,8 +988,6 @@ Future showSubjectBottomSheetSettings( backgroundColor: Colors.transparent, barrierColor: appStyle.colors.a15p, builder: (BuildContext context) { - - return Stack( children: [ Align( @@ -904,13 +1008,16 @@ Future showSubjectBottomSheetSettings( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row(children: [ - Text( - subject.name, - style: appStyle.fonts.H_18px - .apply(color: appStyle.colors.textPrimary), - ), - ]), + Row( + children: [ + Text( + subject.name, + style: appStyle.fonts.H_18px.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), ], ), ), @@ -924,19 +1031,23 @@ Future showSubjectBottomSheetSettings( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - Row(children: [ - FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.editPen4Solid, - size: 24.0, - color: appStyle.colors.accent, - ), - SizedBox(width: 8), - Text( - "Tantárgy átnevezése", - style: appStyle.fonts.B_16R.apply(color: appStyle.colors.textPrimary), - ), - ]) + Row( + children: [ + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.editPen4Solid, + size: 24.0, + color: appStyle.colors.accent, + ), + SizedBox(width: 8), + Text( + "Tantárgy átnevezése", + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), ], ), ), @@ -946,7 +1057,7 @@ Future showSubjectBottomSheetSettings( logger.finest("Tantárgy átnevezése"); // Navigator.pop(context); }, - ) + ), ], ), ), diff --git a/firka/lib/helpers/ui/firka_button.dart b/firka/lib/helpers/ui/firka_button.dart index 1c271f3..6a0c07c 100644 --- a/firka/lib/helpers/ui/firka_button.dart +++ b/firka/lib/helpers/ui/firka_button.dart @@ -6,12 +6,13 @@ class FirkaButton extends StatelessWidget { final TextStyle fontStyle; final Icon? icon; - const FirkaButton( - {required this.text, - required this.bgColor, - required this.fontStyle, - this.icon, - super.key}); + const FirkaButton({ + required this.text, + required this.bgColor, + required this.fontStyle, + this.icon, + super.key, + }); @override Widget build(BuildContext context) { @@ -21,24 +22,25 @@ class FirkaButton extends StatelessWidget { borderRadius: BorderRadius.all(Radius.circular(16)), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: Colors.black.withValues(alpha: 0.1), blurRadius: 4, offset: Offset(0, 2), ), ], ), child: Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - child: icon != null - ? Row( - mainAxisSize: MainAxisSize.min, - children: [ - icon!, - SizedBox(width: 8), - Text(text, style: fontStyle), - ], - ) - : Text(text, style: fontStyle)), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + child: icon != null + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + icon!, + SizedBox(width: 8), + Text(text, style: fontStyle), + ], + ) + : Text(text, style: fontStyle), + ), ); } } diff --git a/firka/lib/helpers/ui/firka_card.dart b/firka/lib/helpers/ui/firka_card.dart index 1f11865..76b1c57 100644 --- a/firka/lib/helpers/ui/firka_card.dart +++ b/firka/lib/helpers/ui/firka_card.dart @@ -16,16 +16,17 @@ class FirkaCard extends StatelessWidget { final Attach? attached; final Color? color; - const FirkaCard( - {required this.left, - this.shadow = true, - this.center, - this.right, - this.extra, - this.attached, - this.color, - this.height, - super.key}); + const FirkaCard({ + required this.left, + this.shadow = true, + this.center, + this.right, + this.extra, + this.attached, + this.color, + this.height, + super.key, + }); @override Widget build(BuildContext context) { @@ -40,81 +41,95 @@ class FirkaCard extends StatelessWidget { width: MediaQuery.of(context).size.width, height: height, child: FirkaShadow( - shadow: shadow, - child: Card( - color: color ?? appStyle.colors.card, - shadowColor: - isLightMode.value && shadow ? null : Colors.transparent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(attached == Attach.top - ? attachedRounding - : defaultRounding), - topRight: Radius.circular(attached == Attach.top - ? attachedRounding - : defaultRounding), - bottomLeft: Radius.circular(attached == Attach.bottom - ? attachedRounding - : defaultRounding), - bottomRight: Radius.circular(attached == Attach.bottom - ? attachedRounding - : defaultRounding)), - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row(children: left), - Row(children: center ?? []), - Row(children: right), - ], - ), - extra ?? SizedBox(), - ], + shadow: shadow, + child: Card( + color: color ?? appStyle.colors.card, + shadowColor: isLightMode.value && shadow + ? null + : Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular( + attached == Attach.top ? attachedRounding : defaultRounding, + ), + topRight: Radius.circular( + attached == Attach.top ? attachedRounding : defaultRounding, + ), + bottomLeft: Radius.circular( + attached == Attach.bottom + ? attachedRounding + : defaultRounding, + ), + bottomRight: Radius.circular( + attached == Attach.bottom + ? attachedRounding + : defaultRounding, ), ), - )), + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row(children: left), + Row(children: center ?? []), + Row(children: right), + ], + ), + extra ?? SizedBox(), + ], + ), + ), + ), + ), ); } else { return SizedBox( width: MediaQuery.of(context).size.width, height: height, child: FirkaShadow( - shadow: shadow, - child: Card( - color: color ?? appStyle.colors.card, - shadowColor: - isLightMode.value && shadow ? null : Colors.transparent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(attached == Attach.top - ? attachedRounding - : defaultRounding), - topRight: Radius.circular(attached == Attach.top - ? attachedRounding - : defaultRounding), - bottomLeft: Radius.circular(attached == Attach.bottom - ? attachedRounding - : defaultRounding), - bottomRight: Radius.circular(attached == Attach.bottom - ? attachedRounding - : defaultRounding)), - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row(children: left), - Row(children: center ?? []), - Row(children: right), - ], + shadow: shadow, + child: Card( + color: color ?? appStyle.colors.card, + shadowColor: isLightMode.value && shadow + ? null + : Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular( + attached == Attach.top ? attachedRounding : defaultRounding, + ), + topRight: Radius.circular( + attached == Attach.top ? attachedRounding : defaultRounding, + ), + bottomLeft: Radius.circular( + attached == Attach.bottom + ? attachedRounding + : defaultRounding, + ), + bottomRight: Radius.circular( + attached == Attach.bottom + ? attachedRounding + : defaultRounding, ), ), - )), + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row(children: left), + Row(children: center ?? []), + Row(children: right), + ], + ), + ), + ), + ), ); } } diff --git a/firka/lib/helpers/ui/firka_shadow.dart b/firka/lib/helpers/ui/firka_shadow.dart index f4cf2ad..eb8a309 100644 --- a/firka/lib/helpers/ui/firka_shadow.dart +++ b/firka/lib/helpers/ui/firka_shadow.dart @@ -14,16 +14,18 @@ class FirkaShadow extends StatelessWidget { final borderRadius = BorderRadius.circular(8.0); final shadowBox = BoxDecoration( - color: Colors.transparent, - shape: BoxShape.rectangle, - boxShadow: [ - BoxShadow( - color: appStyle.colors.shadowColor, - spreadRadius: -4, - blurRadius: 0, - offset: Offset(0, 2)) - ], - borderRadius: BorderRadius.all(Radius.circular(16))); + color: Colors.transparent, + shape: BoxShape.rectangle, + boxShadow: [ + BoxShadow( + color: appStyle.colors.shadowColor, + spreadRadius: -4, + blurRadius: 0, + offset: Offset(0, 2), + ), + ], + borderRadius: BorderRadius.all(Radius.circular(16)), + ); if (!shadow) { return ClipRRect(borderRadius: borderRadius, child: child); diff --git a/firka/lib/helpers/ui/grade.dart b/firka/lib/helpers/ui/grade.dart index de53d06..49fb46c 100644 --- a/firka/lib/helpers/ui/grade.dart +++ b/firka/lib/helpers/ui/grade.dart @@ -18,8 +18,9 @@ class GradeWidget extends StatelessWidget { if (grade.valueType.name == "Szazalekos") { gradeStr = grade.strValue.replaceAll("%", ""); if (grade.numericValue != null) { - gradeColor = - getGradeColor(percentageToGrade(grade.numericValue!).toDouble()); + gradeColor = getGradeColor( + percentageToGrade(grade.numericValue!).toDouble(), + ); } if (grade.numericValue != null && grade.numericValue == 100) { @@ -29,10 +30,14 @@ class GradeWidget extends StatelessWidget { color: gradeColor.withAlpha(38), child: Padding( padding: EdgeInsets.only(left: 8, right: 8), - child: Row(children: [ - Text("100", // TODO: Make this curved - style: appStyle.fonts.P_14.copyWith(color: gradeColor)) - ]), + child: Row( + children: [ + Text( + "100", // TODO: Make this curved + style: appStyle.fonts.P_14.copyWith(color: gradeColor), + ), + ], + ), ), ); } else { @@ -42,11 +47,18 @@ class GradeWidget extends StatelessWidget { color: gradeColor.withAlpha(38), child: Padding( padding: EdgeInsets.only(left: 8, right: 8), - child: Row(children: [ - Text(gradeStr, - style: appStyle.fonts.P_14.copyWith(color: gradeColor)), - Text("%", style: appStyle.fonts.P_12.copyWith(color: gradeColor)) - ]), + child: Row( + children: [ + Text( + gradeStr, + style: appStyle.fonts.P_14.copyWith(color: gradeColor), + ), + Text( + "%", + style: appStyle.fonts.P_12.copyWith(color: gradeColor), + ), + ], + ), ), ); } @@ -54,26 +66,36 @@ class GradeWidget extends StatelessWidget { if (grade.numericValue != null) { gradeColor = getGradeColor(grade.numericValue!.toDouble()); } - if (gradeStr == "0"){ + if (gradeStr == "0") { return Card( shadowColor: Colors.transparent, color: gradeColor.withAlpha(38), child: Padding( - padding: EdgeInsets.only(left: 8, right: 8, top:2, bottom:2), - child: Text(grade.strValue, - style: appStyle.fonts.H_H1 - .copyWith(fontSize: 16, color: gradeColor))), + padding: EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2), + child: Text( + grade.strValue, + style: appStyle.fonts.H_H1.copyWith( + fontSize: 16, + color: gradeColor, + ), + ), + ), ); - }else { + } else { return Card( shape: CircleBorder(eccentricity: eccentricity), shadowColor: Colors.transparent, color: gradeColor.withAlpha(38), child: Padding( - padding: EdgeInsets.only(left: 8, right: 8), - child: Text(gradeStr, - style: appStyle.fonts.H_H1 - .copyWith(fontSize: 24, color: gradeColor))), + padding: EdgeInsets.only(left: 8, right: 8), + child: Text( + gradeStr, + style: appStyle.fonts.H_H1.copyWith( + fontSize: 24, + color: gradeColor, + ), + ), + ), ); } } diff --git a/firka/lib/helpers/watch_sync_helper.dart b/firka/lib/helpers/watch_sync_helper.dart index ccccc83..5294ef6 100644 --- a/firka/lib/helpers/watch_sync_helper.dart +++ b/firka/lib/helpers/watch_sync_helper.dart @@ -36,11 +36,15 @@ class WatchSyncHelper { try { return await _watchChannel .invokeMethod(method, arguments) - .timeout(timeout, onTimeout: () { - debugPrint( - '[WatchSync] Timeout calling $method after ${timeout.inSeconds}s'); - return null; - }); + .timeout( + timeout, + onTimeout: () { + debugPrint( + '[WatchSync] Timeout calling $method after ${timeout.inSeconds}s', + ); + return null; + }, + ); } catch (e) { debugPrint('[WatchSync] Error calling $method: $e'); return null; @@ -96,8 +100,9 @@ class WatchSyncHelper { if (parts.length < 2) return null; try { - final payloadJson = - utf8.decode(base64Url.decode(base64Url.normalize(parts[1]))); + final payloadJson = utf8.decode( + base64Url.decode(base64Url.normalize(parts[1])), + ); final payload = jsonDecode(payloadJson); if (payload is! Map) return null; final iatSeconds = _asInt(payload['iat']); @@ -142,7 +147,8 @@ class WatchSyncHelper { return true; } - final incomingVersion = incomingTokenVersion ?? + final incomingVersion = + incomingTokenVersion ?? _extractTokenVersionFromIdToken(incomingIdToken); final currentVersion = _resolveTokenVersionForModel(currentToken); final currentUpdatedAtMs = _resolveUpdatedAtForModel(currentToken); @@ -233,11 +239,13 @@ class WatchSyncHelper { _watchChannel.setMethodCallHandler(_handleMethodCall); debugPrint('[WatchSync] Handler initialized'); - unawaited(_invokeMethodWithTimeout( - 'watchSyncReady', - null, - const Duration(seconds: 2), - )); + unawaited( + _invokeMethodWithTimeout( + 'watchSyncReady', + null, + const Duration(seconds: 2), + ), + ); } static Future isWatchAppInstalled({bool forceRefresh = false}) async { @@ -262,11 +270,14 @@ class WatchSyncHelper { return _watchAppInstalledCache; } - static Future isWatchReachable({bool forceRefreshInstall = false}) async { + static Future isWatchReachable({ + bool forceRefreshInstall = false, + }) async { if (!Platform.isIOS) return false; - final watchInstalled = - await isWatchAppInstalled(forceRefresh: forceRefreshInstall); + final watchInstalled = await isWatchAppInstalled( + forceRefresh: forceRefreshInstall, + ); if (!watchInstalled) return false; final result = await _invokeMethodWithTimeout( @@ -309,16 +320,13 @@ class WatchSyncHelper { if (!watchInstalled) return true; final timeout = maxWait + const Duration(seconds: 5); - final result = await _invokeMethodWithTimeout( - 'waitForPeerRefreshLease', - { - 'owner': _leaseOwnerIPhone, - 'studentIdNorm': studentIdNorm, - 'maxWaitMs': maxWait.inMilliseconds, - 'pollIntervalMs': _leasePollInterval.inMilliseconds, - }, - timeout, - ); + final result = + await _invokeMethodWithTimeout('waitForPeerRefreshLease', { + 'owner': _leaseOwnerIPhone, + 'studentIdNorm': studentIdNorm, + 'maxWaitMs': maxWait.inMilliseconds, + 'pollIntervalMs': _leasePollInterval.inMilliseconds, + }, timeout); if (result is! Map) { debugPrint('[WatchSync] Lease wait returned invalid response: $result'); @@ -330,7 +338,8 @@ class WatchSyncHelper { final waitedMs = result['waitedMs']; final leaseChanged = result['leaseChanged'] == true; debugPrint( - '[WatchSync] Lease wait status=$status ready=$ready waitedMs=$waitedMs leaseChanged=$leaseChanged'); + '[WatchSync] Lease wait status=$status ready=$ready waitedMs=$waitedMs leaseChanged=$leaseChanged', + ); return ready; } @@ -342,18 +351,17 @@ class WatchSyncHelper { final watchInstalled = await isWatchAppInstalled(); if (!watchInstalled) return null; - final result = await _invokeMethodWithTimeout( - 'acquireRefreshLease', - { - 'owner': _leaseOwnerIPhone, - 'studentIdNorm': studentIdNorm, - 'ttlMs': ttl.inMilliseconds, - }, - const Duration(seconds: 5), - ); + final result = + await _invokeMethodWithTimeout('acquireRefreshLease', { + 'owner': _leaseOwnerIPhone, + 'studentIdNorm': studentIdNorm, + 'ttlMs': ttl.inMilliseconds, + }, const Duration(seconds: 5)); if (result is! Map) { - debugPrint('[WatchSync] Lease acquire returned invalid response: $result'); + debugPrint( + '[WatchSync] Lease acquire returned invalid response: $result', + ); return null; } if (result['skipped'] == true) { @@ -372,28 +380,20 @@ class WatchSyncHelper { required String operationId, }) async { if (!Platform.isIOS) return; - await _invokeMethodWithTimeout( - 'releaseRefreshLease', - { - 'owner': _leaseOwnerIPhone, - 'studentIdNorm': studentIdNorm, - 'operationId': operationId, - }, - const Duration(seconds: 5), - ); + await _invokeMethodWithTimeout('releaseRefreshLease', { + 'owner': _leaseOwnerIPhone, + 'studentIdNorm': studentIdNorm, + 'operationId': operationId, + }, const Duration(seconds: 5)); } static Future clearRefreshLeaseForAccount(int studentIdNorm) async { if (!Platform.isIOS) return; final watchInstalled = await isWatchAppInstalled(); if (!watchInstalled) return; - await _invokeMethodWithTimeout( - 'clearRefreshLeaseForAccount', - { - 'studentIdNorm': studentIdNorm, - }, - const Duration(seconds: 5), - ); + await _invokeMethodWithTimeout('clearRefreshLeaseForAccount', { + 'studentIdNorm': studentIdNorm, + }, const Duration(seconds: 5)); } static Future clearAllRefreshLeases() async { @@ -430,7 +430,8 @@ class WatchSyncHelper { } debugPrint( - '[WatchSync] Fresh iOS install detected, clearing iCloud and local auth state'); + '[WatchSync] Fresh iOS install detected, clearing iCloud and local auth state', + ); await clearICloudToken(notifyWatch: true); await clearAllRefreshLeases(); @@ -463,7 +464,8 @@ class WatchSyncHelper { return await _processTokenFromWatch(call.arguments); case 'onTokenRecoveredFromiCloud': debugPrint( - '[WatchSync] Token recovered from iCloud notification received'); + '[WatchSync] Token recovered from iCloud notification received', + ); await _handleTokenRecoveredFromiCloud(); return null; default: @@ -476,7 +478,8 @@ class WatchSyncHelper { static Future _handleTokenRecoveredFromiCloud() async { if (!initDone) { debugPrint( - '[WatchSync] Cannot handle iCloud recovery: app not initialized'); + '[WatchSync] Cannot handle iCloud recovery: app not initialized', + ); return; } @@ -489,7 +492,8 @@ class WatchSyncHelper { if (recovered) { debugPrint( - '[WatchSync] Token recovered from iCloud, reauth flag cleared'); + '[WatchSync] Token recovered from iCloud, reauth flag cleared', + ); } else { final token = pickActiveToken( tokens: initData.tokens, @@ -499,7 +503,8 @@ class WatchSyncHelper { if (expiryDate != null && expiryDate.isAfter(DateTime.now())) { KretaClient.clearReauthFlag(); debugPrint( - '[WatchSync] Cleared reauth flag after iCloud notification (token is valid)'); + '[WatchSync] Cleared reauth flag after iCloud notification (token is valid)', + ); } } } catch (e) { @@ -536,7 +541,8 @@ class WatchSyncHelper { if (!_isAccessTokenUsable(token.expiryDate, skew: const Duration())) { debugPrint( - '[WatchSync] Active iPhone token access is expired, forwarding token to Watch for recovery'); + '[WatchSync] Active iPhone token access is expired, forwarding token to Watch for recovery', + ); } final tokenData = _buildTokenSyncPayload(token, includeSentAt: true); @@ -571,7 +577,8 @@ class WatchSyncHelper { } static Future> _processTokenFromWatch( - dynamic arguments) async { + dynamic arguments, + ) async { if (!initDone) { debugPrint('[WatchSync] Cannot process Watch token: app not initialized'); return {'success': false, 'error': 'not_initialized'}; @@ -604,7 +611,8 @@ class WatchSyncHelper { final watchExpiryDate = DateTime.fromMillisecondsSinceEpoch(watchExpiry); if (!_isAccessTokenUsable(watchExpiryDate, skew: const Duration())) { debugPrint( - '[WatchSync] Rejecting expired token from Watch, expiry: $watchExpiryDate'); + '[WatchSync] Rejecting expired token from Watch, expiry: $watchExpiryDate', + ); return {'success': false, 'error': 'expired_token'}; } final watchTokenVersion = _resolveIncomingTokenVersion(tokenData); @@ -612,7 +620,8 @@ class WatchSyncHelper { final watchIdToken = tokenData['idToken'] as String?; final watchRefreshToken = tokenData['refreshToken'] as String?; - final isForActiveAccount = expectedStudentIdNorm == null || + final isForActiveAccount = + expectedStudentIdNorm == null || watchStudentIdNorm == expectedStudentIdNorm; if (isForActiveAccount && currentToken != null && @@ -626,13 +635,15 @@ class WatchSyncHelper { currentToken: currentToken, )) { debugPrint( - '[WatchSync] Ignoring stale token from Watch for active account. Incoming expiry: $watchExpiryDate, incomingVersion: $watchTokenVersion'); + '[WatchSync] Ignoring stale token from Watch for active account. Incoming expiry: $watchExpiryDate, incomingVersion: $watchTokenVersion', + ); return {'success': false, 'error': 'stale_token'}; } } debugPrint( - '[WatchSync] Accepting token from Watch, expiry: $watchExpiryDate (expired: ${watchExpiryDate.isBefore(DateTime.now())})'); + '[WatchSync] Accepting token from Watch, expiry: $watchExpiryDate (expired: ${watchExpiryDate.isBefore(DateTime.now())})', + ); final newToken = TokenModel.fromValues( watchStudentIdNorm, @@ -656,7 +667,8 @@ class WatchSyncHelper { KretaClient.clearReauthFlag(); } else { debugPrint( - '[WatchSync] Stored token for inactive account ($watchStudentIdNorm), active is $expectedStudentIdNorm'); + '[WatchSync] Stored token for inactive account ($watchStudentIdNorm), active is $expectedStudentIdNorm', + ); } debugPrint('[WatchSync] Token from Watch saved successfully'); @@ -685,15 +697,18 @@ class WatchSyncHelper { return; } - final accessExpired = - !_isAccessTokenUsable(token.expiryDate, skew: const Duration()); + final accessExpired = !_isAccessTokenUsable( + token.expiryDate, + skew: const Duration(), + ); if (accessExpired && !allowExpiredAccessToken) { debugPrint('[WatchSync] Token expired, not sending to Watch'); return; } if (accessExpired && allowExpiredAccessToken) { debugPrint( - '[WatchSync] Sending expired-access token to Watch for account-switch recovery'); + '[WatchSync] Sending expired-access token to Watch for account-switch recovery', + ); } final tokenData = _buildTokenSyncPayload(token, includeSentAt: true); @@ -705,7 +720,8 @@ class WatchSyncHelper { static String? _getLanguageForWatch() { if (!initDone) { debugPrint( - '[WatchSync] App not initialized yet, language unavailable for Watch'); + '[WatchSync] App not initialized yet, language unavailable for Watch', + ); return null; } @@ -727,7 +743,8 @@ class WatchSyncHelper { await _invokeMethodWithTimeout('sendLanguageToWatch', languageCode); debugPrint( - '[WatchSync] Language sent to Watch: $languageCode (or timeout)'); + '[WatchSync] Language sent to Watch: $languageCode (or timeout)', + ); } /// Check iCloud for a fresher token and update local storage if found. @@ -757,7 +774,10 @@ class WatchSyncHelper { try { debugPrint('[WatchSync] Checking iCloud for fresher token...'); final result = await _invokeMethodWithTimeout( - 'checkiCloudToken', null, const Duration(seconds: 5)); + 'checkiCloudToken', + null, + const Duration(seconds: 5), + ); if (result == null) { debugPrint('[WatchSync] No response from native (timeout or error)'); @@ -779,7 +799,8 @@ class WatchSyncHelper { if (expectedStudentIdNorm != null && iCloudStudentIdNorm != expectedStudentIdNorm) { debugPrint( - '[WatchSync] iCloud token belongs to different account ($iCloudStudentIdNorm), active is $expectedStudentIdNorm - ignoring'); + '[WatchSync] iCloud token belongs to different account ($iCloudStudentIdNorm), active is $expectedStudentIdNorm - ignoring', + ); return false; } @@ -789,13 +810,17 @@ class WatchSyncHelper { return false; } - final iCloudExpiryDate = - DateTime.fromMillisecondsSinceEpoch(iCloudExpiry); - final iCloudAccessExpired = - !_isAccessTokenUsable(iCloudExpiryDate, skew: const Duration()); + final iCloudExpiryDate = DateTime.fromMillisecondsSinceEpoch( + iCloudExpiry, + ); + final iCloudAccessExpired = !_isAccessTokenUsable( + iCloudExpiryDate, + skew: const Duration(), + ); if (iCloudAccessExpired && !allowExpiredAccessToken) { debugPrint( - '[WatchSync] iCloud token access is expired (expiry: $iCloudExpiryDate), skipping direct apply'); + '[WatchSync] iCloud token access is expired (expiry: $iCloudExpiryDate), skipping direct apply', + ); return false; } final iCloudTokenVersion = _resolveIncomingTokenVersion(tokenData); @@ -821,7 +846,8 @@ class WatchSyncHelper { if (shouldAccept) { debugPrint( - '[WatchSync] iCloud has fresher token! iCloud: $iCloudExpiryDate, Local: $localExpiry, iCloudVersion: $iCloudTokenVersion'); + '[WatchSync] iCloud has fresher token! iCloud: $iCloudExpiryDate, Local: $localExpiry, iCloudVersion: $iCloudTokenVersion', + ); final newToken = TokenModel.fromValues( (tokenData['studentIdNorm'] as int?) ?? 0, @@ -852,7 +878,8 @@ class WatchSyncHelper { effectiveClient.model = newToken; } - final shouldClearReauth = !iCloudAccessExpired && + final shouldClearReauth = + !iCloudAccessExpired && (expectedStudentIdNorm == null || newToken.studentIdNorm == expectedStudentIdNorm); if (shouldClearReauth) { @@ -860,11 +887,13 @@ class WatchSyncHelper { } debugPrint( - '[WatchSync] Token recovered from iCloud! New expiry: $iCloudExpiryDate'); + '[WatchSync] Token recovered from iCloud! New expiry: $iCloudExpiryDate', + ); return true; } else { debugPrint( - '[WatchSync] Local token is same or fresher. Local: $localExpiry, iCloud: $iCloudExpiryDate'); + '[WatchSync] Local token is same or fresher. Local: $localExpiry, iCloud: $iCloudExpiryDate', + ); return false; } } catch (e) { @@ -890,7 +919,8 @@ class WatchSyncHelper { final watchInstalled = await isWatchAppInstalled(); if (!watchInstalled) { debugPrint( - '[WatchSync] Skipping iCloud token save because no paired Watch app is installed'); + '[WatchSync] Skipping iCloud token save because no paired Watch app is installed', + ); return; } @@ -898,7 +928,10 @@ class WatchSyncHelper { tokenData['forceAccountSwitch'] = forceAccountSwitch; await _invokeMethodWithTimeout( - 'saveTokeToniCloud', tokenData, const Duration(seconds: 5)); + 'saveTokeToniCloud', + tokenData, + const Duration(seconds: 5), + ); debugPrint('[WatchSync] Token saved to iCloud (or timeout)'); } @@ -921,7 +954,10 @@ class WatchSyncHelper { try { debugPrint('[WatchSync] Requesting token from Watch...'); final result = await _invokeMethodWithTimeout( - 'requestTokenFromWatch', null, const Duration(seconds: 10)); + 'requestTokenFromWatch', + null, + const Duration(seconds: 10), + ); final expectedStudentIdNorm = _resolveExpectedStudentIdNorm( tokens: effectiveTokens, client: effectiveClient, @@ -956,7 +992,8 @@ class WatchSyncHelper { currentToken.expiryDate != null && !KretaClient.needsReauth) { debugPrint( - '[WatchSync] Sending iPhone token to Watch (Watch has no token)'); + '[WatchSync] Sending iPhone token to Watch (Watch has no token)', + ); await _sendTokenToWatchInternal( currentToken, allowExpiredAccessToken: true, @@ -980,7 +1017,8 @@ class WatchSyncHelper { if (expectedStudentIdNorm != null && watchStudentIdNorm != expectedStudentIdNorm) { debugPrint( - '[WatchSync] Watch token belongs to different account ($watchStudentIdNorm), active is $expectedStudentIdNorm - keeping active account'); + '[WatchSync] Watch token belongs to different account ($watchStudentIdNorm), active is $expectedStudentIdNorm - keeping active account', + ); if (currentToken != null && currentToken.accessToken != null && currentToken.refreshToken != null && @@ -997,13 +1035,16 @@ class WatchSyncHelper { final watchExpiryDate = DateTime.fromMillisecondsSinceEpoch(watchExpiry); if (!_isAccessTokenUsable(watchExpiryDate, skew: const Duration())) { debugPrint( - '[WatchSync] Watch provided expired token, ignoring and keeping iPhone token'); + '[WatchSync] Watch provided expired token, ignoring and keeping iPhone token', + ); if (currentToken != null && currentToken.accessToken != null && currentToken.refreshToken != null && currentToken.expiryDate != null && - _isAccessTokenUsable(currentToken.expiryDate, - skew: const Duration()) && + _isAccessTokenUsable( + currentToken.expiryDate, + skew: const Duration(), + ) && !KretaClient.needsReauth) { await _sendTokenToWatchInternal( currentToken, @@ -1063,10 +1104,12 @@ class WatchSyncHelper { } debugPrint( - '[WatchSync] Token updated from Watch. New expiry: $watchExpiryDate'); + '[WatchSync] Token updated from Watch. New expiry: $watchExpiryDate', + ); } else { debugPrint( - '[WatchSync] iPhone token is same or newer, sending to Watch'); + '[WatchSync] iPhone token is same or newer, sending to Watch', + ); await _sendTokenToWatchInternal( currentToken, allowExpiredAccessToken: true, diff --git a/firka/lib/main.dart b/firka/lib/main.dart index 16a0257..546f364 100644 --- a/firka/lib/main.dart +++ b/firka/lib/main.dart @@ -131,7 +131,7 @@ Future initLang(AppInitialization data) async { languageCode = 'de'; break; default: // auto - switch (ui.window.locale.languageCode) { + switch (ui.PlatformDispatcher.instance.locale.languageCode) { case 'hu': data.l10n = AppLocalizationsHu(); languageCode = 'hu'; @@ -218,7 +218,8 @@ Future _initData(AppInitialization init) async { final nextLocale = init.l10n.localeName; if (previousLocale != nextLocale) { logger.info( - "[Init] System locale changed in auto mode: $previousLocale -> $nextLocale"); + "[Init] System locale changed in auto mode: $previousLocale -> $nextLocale", + ); } globalUpdate.update(); }()); @@ -234,7 +235,8 @@ Future _initData(AppInitialization init) async { await WatchSyncHelper.runFreshInstallCleanupIfNeeded(isar: init.isar); if (didRunFreshInstallCleanup) { logger.info( - '[Init] Fresh-install cleanup completed; skipping startup iCloud recovery on this launch'); + '[Init] Fresh-install cleanup completed; skipping startup iCloud recovery on this launch', + ); } else { await WatchSyncHelper.checkAndRecoverFromiCloud( isar: init.isar, @@ -253,7 +255,8 @@ Future _initData(AppInitialization init) async { final token = pickActiveToken(tokens: allTokens, settings: init.settings); if (token == null) { logger.warning( - "[Init] Tokens disappeared during initialization; skipping client setup"); + "[Init] Tokens disappeared during initialization; skipping client setup", + ); return; } logger.fine("Initializing kréta client as: ${token.studentId}"); @@ -305,8 +308,8 @@ Future initializeApp() async { try { if (Platform.isAndroid) { const channel = MethodChannel("firka.app/main"); - final rawInfo = - ((await channel.invokeMethod("get_info")) as String).split(";"); + final rawInfo = ((await channel.invokeMethod("get_info")) as String) + .split(";"); devInfo = DeviceInfo(rawInfo[0], rawInfo[1], rawInfo[2]); devInfoFetched = true; @@ -335,8 +338,9 @@ Future initializeApp() async { if (Platform.isIOS) { try { - await LiveActivityService.initialize() - .timeout(const Duration(seconds: 8)); + await LiveActivityService.initialize().timeout( + const Duration(seconds: 8), + ); } on TimeoutException catch (e, st) { logger.warning('LiveActivity init timed out: $e', e, st); } catch (e, st) { @@ -359,131 +363,142 @@ void main() async { dio.options.receiveTimeout = Duration(seconds: 3); dio.options.validateStatus = (status) => status != null && status < 500; - runZonedGuarded(() async { - logger.finest("Initializing app"); - WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); - FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); + runZonedGuarded( + () async { + logger.finest("Initializing app"); + WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); - try { - await dotenv.load(fileName: ".env"); - logger.info("Environment variables loaded"); - } catch (e, st) { - logger.severe("Failed to load .env: $e", e, st); - } + try { + await dotenv.load(fileName: ".env"); + logger.info("Environment variables loaded"); + } catch (e, st) { + logger.severe("Failed to load .env: $e", e, st); + } - { - final jwtPattern = - RegExp(r'([A-Za-z0-9-_]+)\.([A-Za-z0-9-_]+)\.([A-Za-z0-9-_]+)'); - final omPattern = RegExp(r'(\d{3})(\d{6})([A-Za-z0-9]?)'); - final refreshTokenPattern = - RegExp(r'"(?=.{21,}$)([A-Z0-9]+-[A-Z0-9_\-.~+]*)"'); + { + final jwtPattern = RegExp( + r'([A-Za-z0-9-_]+)\.([A-Za-z0-9-_]+)\.([A-Za-z0-9-_]+)', + ); + final omPattern = RegExp(r'(\d{3})(\d{6})([A-Za-z0-9]?)'); + final refreshTokenPattern = RegExp( + r'"(?=.{21,}$)([A-Z0-9]+-[A-Z0-9_\-.~+]*)"', + ); - final docs = await getApplicationDocumentsDirectory(); - - Future deleteOldLogFiles() async { final docs = await getApplicationDocumentsDirectory(); - final dir = Directory(docs.path); - if (!dir.existsSync()) return; - final now = DateTime.now(); - final cutoff = now.subtract(Duration(days: 30)); + Future deleteOldLogFiles() async { + final docs = await getApplicationDocumentsDirectory(); + final dir = Directory(docs.path); + if (!dir.existsSync()) return; - final logFileRegex = RegExp(r'^(\d{4})_(\d{2})_(\d{2})\.log$'); + final now = DateTime.now(); + final cutoff = now.subtract(Duration(days: 30)); - for (final entity in dir.listSync()) { - if (entity is! File) continue; - final name = entity.uri.pathSegments.last; - final m = logFileRegex.firstMatch(name); - if (m == null) continue; + final logFileRegex = RegExp(r'^(\d{4})_(\d{2})_(\d{2})\.log$'); - try { - final y = int.parse(m.group(1)!); - final mo = int.parse(m.group(2)!); - final d = int.parse(m.group(3)!); - final fileDate = DateTime(y, mo, d); - if (fileDate - .isBefore(DateTime(cutoff.year, cutoff.month, cutoff.day))) { - logger.info("Removing old log file: $name"); - await entity.delete(); + for (final entity in dir.listSync()) { + if (entity is! File) continue; + final name = entity.uri.pathSegments.last; + final m = logFileRegex.firstMatch(name); + if (m == null) continue; + + try { + final y = int.parse(m.group(1)!); + final mo = int.parse(m.group(2)!); + final d = int.parse(m.group(3)!); + final fileDate = DateTime(y, mo, d); + if (fileDate.isBefore( + DateTime(cutoff.year, cutoff.month, cutoff.day), + )) { + logger.info("Removing old log file: $name"); + await entity.delete(); + } + } catch (_) { + // ignore parse/delete errors } - } catch (_) { - // ignore parse/delete errors } } - } - String logFilePathForDate(DateTime dt) { - final fileName = "${DateFormat("yyyy_MM_dd").format(dt)}.log"; - return Directory(docs.path).uri.resolve(fileName).toFilePath(); - } - - File fileForDate(DateTime dt) { - final path = logFilePathForDate(dt); - final file = File(path); - if (!file.existsSync()) file.createSync(recursive: true); - return file; - } - - String censorLog(String msg) { - return msg.replaceAll(jwtPattern, '***').replaceAllMapped(omPattern, - (match) { - return "${match.group(1)}******${match.group(3)}"; - }).replaceAll(refreshTokenPattern, '"***"'); - } - - hierarchicalLoggingEnabled = true; - logger.level = Level.ALL; - - DateTime currentDate = DateTime.now(); - IOSink sink = fileForDate(currentDate).openWrite(mode: FileMode.append); - - logger.onRecord.listen((record) { - final now = DateTime.now(); - if (now.year != currentDate.year || - now.month != currentDate.month || - now.day != currentDate.day) { - sink.flush(); - sink.close(); - currentDate = now; - sink = fileForDate(currentDate).openWrite(mode: FileMode.append); + String logFilePathForDate(DateTime dt) { + final fileName = "${DateFormat("yyyy_MM_dd").format(dt)}.log"; + return Directory(docs.path).uri.resolve(fileName).toFilePath(); } - final censored = censorLog(record.message); - final timestamp = DateFormat('yyyy-MM-dd HH:mm:ss.SSS').format(now); - final level = record.level.name; - final line = '[$timestamp] [$level] [$censored]'; - sink.writeln(line); + File fileForDate(DateTime dt) { + final path = logFilePathForDate(dt); + final file = File(path); + if (!file.existsSync()) file.createSync(recursive: true); + return file; + } - debugPrint( - "[Firka] [${record.level.name}] ${kDebugMode ? record.message : censored}"); - }); + String censorLog(String msg) { + return msg + .replaceAll(jwtPattern, '***') + .replaceAllMapped(omPattern, (match) { + return "${match.group(1)}******${match.group(3)}"; + }) + .replaceAll(refreshTokenPattern, '"***"'); + } - (() async { - await deleteOldLogFiles(); - })(); - } + hierarchicalLoggingEnabled = true; + logger.level = Level.ALL; - try { - logger.finest('loading dirty words'); - await loadDirtyWords(); - logger.finest('loaded dirty words'); - } catch (e, st) { - logger.severe('Failed to load dirty words: $e', e, st); - } + DateTime currentDate = DateTime.now(); + IOSink sink = fileForDate(currentDate).openWrite(mode: FileMode.append); - // Run App Initialization - runApp(InitializationScreen()); - }, (error, stackTrace) { - logger.shout('Caught error: $error'); - logger.shout('Stack trace: $stackTrace'); + logger.onRecord.listen((record) { + final now = DateTime.now(); + if (now.year != currentDate.year || + now.month != currentDate.month || + now.day != currentDate.day) { + sink.flush(); + sink.close(); + currentDate = now; + sink = fileForDate(currentDate).openWrite(mode: FileMode.append); + } - navigatorKey.currentState?.push( - MaterialPageRoute( - builder: (context) => - ErrorPage(key: ValueKey('errorPage'), exception: error.toString()), - ), - ); - }); + final censored = censorLog(record.message); + final timestamp = DateFormat('yyyy-MM-dd HH:mm:ss.SSS').format(now); + final level = record.level.name; + final line = '[$timestamp] [$level] [$censored]'; + sink.writeln(line); + + debugPrint( + "[Firka] [${record.level.name}] ${kDebugMode ? record.message : censored}", + ); + }); + + (() async { + await deleteOldLogFiles(); + })(); + } + + try { + logger.finest('loading dirty words'); + await loadDirtyWords(); + logger.finest('loaded dirty words'); + } catch (e, st) { + logger.severe('Failed to load dirty words: $e', e, st); + } + + // Run App Initialization + runApp(InitializationScreen()); + }, + (error, stackTrace) { + logger.shout('Caught error: $error'); + logger.shout('Stack trace: $stackTrace'); + + navigatorKey.currentState?.push( + MaterialPageRoute( + builder: (context) => ErrorPage( + key: ValueKey('errorPage'), + exception: error.toString(), + ), + ), + ); + }, + ); } final ValueNotifier isLightMode = ValueNotifier(true); @@ -492,8 +507,9 @@ final UpdateNotifier globalUpdate = UpdateNotifier(); class InitializationScreen extends StatelessWidget { InitializationScreen({super.key}); - final Future _init = - initializeApp().timeout(const Duration(seconds: 20)); + final Future _init = initializeApp().timeout( + const Duration(seconds: 20), + ); @override Widget build(BuildContext context) { @@ -503,8 +519,11 @@ class InitializationScreen extends StatelessWidget { // Check if initialization is complete if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasError) { - logger.shout("Error in InitializationScreen", - snapshot.error.toString(), snapshot.stackTrace); + logger.shout( + "Error in InitializationScreen", + snapshot.error.toString(), + snapshot.stackTrace, + ); FlutterNativeSplash.remove(); @@ -512,15 +531,16 @@ class InitializationScreen extends StatelessWidget { return MaterialApp( key: ValueKey('errorPage'), home: DefaultAssetBundle( - bundle: FirkaBundle(), - child: Scaffold( - body: Center( - child: Text( - 'Error initializing app: ${snapshot.error}', - style: TextStyle(color: Colors.red), - ), + bundle: FirkaBundle(), + child: Scaffold( + body: Center( + child: Text( + 'Error initializing app: ${snapshot.error}', + style: TextStyle(color: Colors.red), ), - )), + ), + ), + ), ); } @@ -540,7 +560,8 @@ class InitializationScreen extends StatelessWidget { await WatchSyncHelper.sendLanguageToWatch(); } catch (e) { logger.warning( - '[Init] Failed to publish language to Watch after sync init: $e'); + '[Init] Failed to publish language to Watch after sync init: $e', + ); } }()); } @@ -562,8 +583,11 @@ class InitializationScreen extends StatelessWidget { watch.sendMessage({"id": "pong"}); navigatorKey.currentState?.push( MaterialPageRoute( - builder: (context) => HomeScreen(initData, true, - model: msg["model"] as String), + builder: (context) => HomeScreen( + initData, + true, + model: msg["model"] as String, + ), ), ); } @@ -572,16 +596,9 @@ class InitializationScreen extends StatelessWidget { } if (snapshot.data!.tokens.isEmpty) { - screen = LoginScreen( - initData, - key: ValueKey('loginScreen'), - ); + screen = LoginScreen(initData, key: ValueKey('loginScreen')); } else { - screen = HomeScreen( - initData, - false, - key: ValueKey('homeScreen'), - ); + screen = HomeScreen(initData, false, key: ValueKey('homeScreen')); } return MaterialApp( @@ -606,10 +623,12 @@ class InitializationScreen extends StatelessWidget { builder: (context, isLight, _) { final overlay = SystemUiOverlayStyle( statusBarColor: Colors.transparent, - statusBarIconBrightness: - isLight ? Brightness.dark : Brightness.light, - statusBarBrightness: - isLight ? Brightness.light : Brightness.dark, + statusBarIconBrightness: isLight + ? Brightness.dark + : Brightness.light, + statusBarBrightness: isLight + ? Brightness.light + : Brightness.dark, systemStatusBarContrastEnforced: false, ); @@ -625,27 +644,17 @@ class InitializationScreen extends StatelessWidget { ), routes: { '/login': (context) => DefaultAssetBundle( - bundle: FirkaBundle(), - child: LoginScreen( - initData, - key: ValueKey('loginScreen'), - ), - ), + bundle: FirkaBundle(), + child: LoginScreen(initData, key: ValueKey('loginScreen')), + ), '/home': (context) => DefaultAssetBundle( - bundle: FirkaBundle(), - child: HomeScreen( - initData, - false, - key: ValueKey('homeScreen'), - ), - ), + bundle: FirkaBundle(), + child: HomeScreen(initData, false, key: ValueKey('homeScreen')), + ), '/debug': (context) => DefaultAssetBundle( - bundle: FirkaBundle(), - child: DebugScreen( - initData, - key: ValueKey('debugScreen'), - ), - ), + bundle: FirkaBundle(), + child: DebugScreen(initData, key: ValueKey('debugScreen')), + ), }, ); } diff --git a/firka/lib/ui/model/style.dart b/firka/lib/ui/model/style.dart index 7334e0f..691bbfd 100644 --- a/firka/lib/ui/model/style.dart +++ b/firka/lib/ui/model/style.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +// Design system token names; ignore non_constant_identifier_names for consistency with design specs +// ignore_for_file: non_constant_identifier_names + class FirkaFonts { TextStyle H_H1; TextStyle H_18px; @@ -126,8 +129,11 @@ class FirkaStyle { FirkaFonts fonts; bool isLight; - FirkaStyle( - {required this.isLight, required this.colors, required this.fonts}); + FirkaStyle({ + required this.isLight, + required this.colors, + required this.fonts, + }); } final _defaultFonts = FirkaFonts( @@ -222,80 +228,82 @@ final _defaultFonts = FirkaFonts( ); final FirkaStyle lightStyle = FirkaStyle( - isLight: true, - colors: FirkaColors( - background: Color(0xFFFAFFF0), - backgroundAmoled: Colors.black, - background0p: Color(0x00fafff0), - success: Color(0xFF92EA3B), - shadowBlur: 2, - textPrimary: Color(0xFF394C0A), - textSecondary: Color(0xCC394C0A), - textTertiary: Color(0x80394C0A), - textPrimaryLight: Color(0xFF394C0A), - textSecondaryLight: Color(0xCC394C0A), - textTertiaryLight: Color(0x80394C0A), - card: Color(0xFFF3FBDE), - cardTranslucent: Color(0x80F3FBDE), - buttonSecondaryFill: Color(0xFFFEFFFD), - accent: Color(0xFFA7DC22), - secondary: Color(0xFF6E8F1B), - shadowColor: Color(0x33647e22), - a10p: Color(0x1aa7dc22), - a15p: Color(0x26a7dc22), - warningAccent: Color(0xFFFFA046), - warningText: Color(0xFF8F531B), - warning15p: Color(0x26FFA046), - warningCard: Color(0xFFFAEBDC), - errorAccent: Color(0xFFFF54A1), - errorText: Color(0xFF8F1B4F), - error15p: Color(0x26FF54A1), - errorCard: Color(0xFFFADCE9), - grade5: Color(0xFF22CCAD), - grade4: Color(0xFF92EA3B), - grade3: Color(0xFFF9CF00), - grade2: Color(0xFFFFA046), - grade1: Color(0xFFFF54A1), - ), - fonts: _defaultFonts); + isLight: true, + colors: FirkaColors( + background: Color(0xFFFAFFF0), + backgroundAmoled: Colors.black, + background0p: Color(0x00fafff0), + success: Color(0xFF92EA3B), + shadowBlur: 2, + textPrimary: Color(0xFF394C0A), + textSecondary: Color(0xCC394C0A), + textTertiary: Color(0x80394C0A), + textPrimaryLight: Color(0xFF394C0A), + textSecondaryLight: Color(0xCC394C0A), + textTertiaryLight: Color(0x80394C0A), + card: Color(0xFFF3FBDE), + cardTranslucent: Color(0x80F3FBDE), + buttonSecondaryFill: Color(0xFFFEFFFD), + accent: Color(0xFFA7DC22), + secondary: Color(0xFF6E8F1B), + shadowColor: Color(0x33647e22), + a10p: Color(0x1aa7dc22), + a15p: Color(0x26a7dc22), + warningAccent: Color(0xFFFFA046), + warningText: Color(0xFF8F531B), + warning15p: Color(0x26FFA046), + warningCard: Color(0xFFFAEBDC), + errorAccent: Color(0xFFFF54A1), + errorText: Color(0xFF8F1B4F), + error15p: Color(0x26FF54A1), + errorCard: Color(0xFFFADCE9), + grade5: Color(0xFF22CCAD), + grade4: Color(0xFF92EA3B), + grade3: Color(0xFFF9CF00), + grade2: Color(0xFFFFA046), + grade1: Color(0xFFFF54A1), + ), + fonts: _defaultFonts, +); final FirkaStyle darkStyle = FirkaStyle( - isLight: false, - colors: FirkaColors( - background: Color(0xFF0D1202), - backgroundAmoled: Colors.black, - background0p: Color(0x00fafff0), - success: Color(0xFF92EA3B), - shadowBlur: 0, - textPrimary: Color(0xFFEAF7CC), - textSecondary: Color(0xB3EAF7CC), - textTertiary: Color(0x80EAF7CC), - textPrimaryLight: Color(0xFF394C0A), - textSecondaryLight: Color(0xCC394C0A), - textTertiaryLight: Color(0x80394C0A), - card: Color(0xFF141905), - cardTranslucent: Color(0x80141905), - buttonSecondaryFill: Color(0xFF20290B), - accent: Color(0xFFA7DC22), - secondary: Color(0xFFCBEE71), - shadowColor: Color(0x26CBEE71), - a10p: Color(0x1AA7DC22), - a15p: Color(0x26A7DC22), - warningAccent: Color(0xFFFFA046), - warningText: Color(0xFFF0B37A), - warning15p: Color(0x26FFA046), - warningCard: Color(0xFF201203), - errorAccent: Color(0xFFFF54A1), - errorText: Color(0xFFF59EC5), - error15p: Color(0x26FF54A1), - errorCard: Color(0xFF1E030F), - grade5: Color(0xFF22CCAD), - grade4: Color(0xFF92EA3B), - grade3: Color(0xFFF9CF00), - grade2: Color(0xFFFFA046), - grade1: Color(0xFFFF54A1), - ), - fonts: _defaultFonts); + isLight: false, + colors: FirkaColors( + background: Color(0xFF0D1202), + backgroundAmoled: Colors.black, + background0p: Color(0x00fafff0), + success: Color(0xFF92EA3B), + shadowBlur: 0, + textPrimary: Color(0xFFEAF7CC), + textSecondary: Color(0xB3EAF7CC), + textTertiary: Color(0x80EAF7CC), + textPrimaryLight: Color(0xFF394C0A), + textSecondaryLight: Color(0xCC394C0A), + textTertiaryLight: Color(0x80394C0A), + card: Color(0xFF141905), + cardTranslucent: Color(0x80141905), + buttonSecondaryFill: Color(0xFF20290B), + accent: Color(0xFFA7DC22), + secondary: Color(0xFFCBEE71), + shadowColor: Color(0x26CBEE71), + a10p: Color(0x1AA7DC22), + a15p: Color(0x26A7DC22), + warningAccent: Color(0xFFFFA046), + warningText: Color(0xFFF0B37A), + warning15p: Color(0x26FFA046), + warningCard: Color(0xFF201203), + errorAccent: Color(0xFFFF54A1), + errorText: Color(0xFFF59EC5), + error15p: Color(0x26FF54A1), + errorCard: Color(0xFF1E030F), + grade5: Color(0xFF22CCAD), + grade4: Color(0xFF92EA3B), + grade3: Color(0xFFF9CF00), + grade2: Color(0xFFFFA046), + grade1: Color(0xFFFF54A1), + ), + fonts: _defaultFonts, +); FirkaStyle appStyle = lightStyle; FirkaStyle wearStyle = darkStyle; diff --git a/firka/lib/ui/phone/pages/error/error_page.dart b/firka/lib/ui/phone/pages/error/error_page.dart index c693e6e..d3ea7a5 100644 --- a/firka/lib/ui/phone/pages/error/error_page.dart +++ b/firka/lib/ui/phone/pages/error/error_page.dart @@ -12,167 +12,147 @@ class ErrorPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: appStyle.colors.background, - body: Padding( - padding: const EdgeInsets.symmetric(vertical: 64, horizontal: 24), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column(children: [ - SizedBox(height: 48), - Container( - width: 50, - height: 50, - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - image: DecorationImage( - image: PreloadedImageProvider(FirkaBundle(), - ('assets/images/logos/dave_error.png')), - fit: BoxFit.cover, - ), - shape: ContinuousRectangleBorder(), + backgroundColor: appStyle.colors.background, + body: Padding( + padding: const EdgeInsets.symmetric(vertical: 64, horizontal: 24), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + SizedBox(height: 48), + Container( + width: 50, + height: 50, + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + image: DecorationImage( + image: PreloadedImageProvider( + FirkaBundle(), + ('assets/images/logos/dave_error.png'), ), + fit: BoxFit.cover, ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 24, horizontal: 32), - child: Column(children: [ - Text( - 'e-Kréta, te', - style: appStyle.fonts.H_16px - .copyWith(color: appStyle.colors.textSecondary), - textAlign: TextAlign.center, - ), - Text( - //'a,', - generateSwearSentence(), - style: appStyle.fonts.H_H2.copyWith( - color: appStyle.colors.textPrimary, - fontSize: 24), - textAlign: TextAlign.center, - ), - ])), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Text( - "Valami probléma történt, ez természetesen az EduDev Zrt. hibája minden esetben", - style: appStyle.fonts.B_14R.copyWith( + shape: ContinuousRectangleBorder(), + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 24, + horizontal: 32, + ), + child: Column( + children: [ + Text( + 'e-Kréta, te', + style: appStyle.fonts.H_16px.copyWith( color: appStyle.colors.textSecondary, ), textAlign: TextAlign.center, ), - ), - ]), - Column( - children: [ - Stack(children: [ - Container( - height: 300, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - shape: BoxShape.rectangle, - color: appStyle.colors.card, - borderRadius: BorderRadius.circular(12)), - child: Text(exception, - style: appStyle.fonts.B_14R.copyWith( - color: appStyle.colors.textPrimary, - fontFamily: 'RobotoMono'))), - Positioned( - bottom: 0, - right: 16, - child: Container( - width: 60, - height: 76, - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - image: DecorationImage( - image: PreloadedImageProvider(FirkaBundle(), - ('assets/images/cactus_error_screen.png')), - fit: BoxFit.cover, - colorFilter: ColorFilter.srgbToLinearGamma()), - shape: ContinuousRectangleBorder(), - ), - ), - ) - ]), - SizedBox( - height: 20, + Text( + //'a,', + generateSwearSentence(), + style: appStyle.fonts.H_H2.copyWith( + color: appStyle.colors.textPrimary, + fontSize: 24, + ), + textAlign: TextAlign.center, ), - ElevatedButton( - // TODO: report bug - onPressed: () => Navigator.pop(context), - style: ElevatedButton.styleFrom( - foregroundColor: appStyle.colors.textPrimary, - backgroundColor: appStyle.colors.accent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), - elevation: 1, - shadowColor: appStyle.colors.shadowColor, - minimumSize: Size.fromHeight(48)), - child: Text("Hiba jelentése", - style: appStyle.fonts.H_18px - .copyWith(fontWeight: FontWeight.w700))), - SizedBox( - height: 8, - ), - ElevatedButton( - onPressed: () => Navigator.pop(context), - style: ElevatedButton.styleFrom( - foregroundColor: appStyle.colors.textSecondary, - backgroundColor: - appStyle.colors.buttonSecondaryFill, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), - elevation: 1, - shadowColor: appStyle.colors.shadowColor, - minimumSize: Size.fromHeight(48)), - child: Text("Vissza", style: appStyle.fonts.B_16R)) ], - ) - ]))); - - return Scaffold( - appBar: AppBar( - title: Text('Error Occurred'), - backgroundColor: Colors.red, - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.error, - size: 80, - color: Colors.red, - ), - const SizedBox(height: 20), - Text( - 'An error occurred!', - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.red, ), - ), - const SizedBox(height: 10), - Text( - 'Details:', - style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Text( + "Valami probléma történt, ez természetesen az EduDev Zrt. hibája minden esetben", + style: appStyle.fonts.B_14R.copyWith( + color: appStyle.colors.textSecondary, + ), + textAlign: TextAlign.center, ), + ), + ], ), - const SizedBox(height: 10), - Text( - exception, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.redAccent, + Column( + children: [ + Stack( + children: [ + Container( + height: 300, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + color: appStyle.colors.card, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + exception, + style: appStyle.fonts.B_14R.copyWith( + color: appStyle.colors.textPrimary, + fontFamily: 'RobotoMono', + ), + ), + ), + Positioned( + bottom: 0, + right: 16, + child: Container( + width: 60, + height: 76, + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + image: DecorationImage( + image: PreloadedImageProvider( + FirkaBundle(), + ('assets/images/cactus_error_screen.png'), + ), + fit: BoxFit.cover, + colorFilter: ColorFilter.srgbToLinearGamma(), + ), + shape: ContinuousRectangleBorder(), + ), + ), + ), + ], + ), + SizedBox(height: 20), + ElevatedButton( + // TODO: report bug + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + foregroundColor: appStyle.colors.textPrimary, + backgroundColor: appStyle.colors.accent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 1, + shadowColor: appStyle.colors.shadowColor, + minimumSize: Size.fromHeight(48), ), - ), - const SizedBox(height: 30), - ElevatedButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Go Back'), + child: Text( + "Hiba jelentése", + style: appStyle.fonts.H_18px.copyWith( + fontWeight: FontWeight.w700, + ), + ), + ), + SizedBox(height: 8), + ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + foregroundColor: appStyle.colors.textSecondary, + backgroundColor: appStyle.colors.buttonSecondaryFill, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 1, + shadowColor: appStyle.colors.shadowColor, + minimumSize: Size.fromHeight(48), + ), + child: Text("Vissza", style: appStyle.fonts.B_16R), + ), + ], ), ], ), diff --git a/firka/lib/ui/phone/pages/error/wear_error_page.dart b/firka/lib/ui/phone/pages/error/wear_error_page.dart index 5e92345..d230471 100644 --- a/firka/lib/ui/phone/pages/error/wear_error_page.dart +++ b/firka/lib/ui/phone/pages/error/wear_error_page.dart @@ -17,31 +17,27 @@ class WearErrorPage extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.error, - size: 80, - color: Colors.red, - ), + Icon(Icons.error, size: 80, color: Colors.red), const SizedBox(height: 20), Text( 'An error occurred!', - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.red, - ), + style: Theme.of( + context, + ).textTheme.headlineMedium?.copyWith(color: Colors.red), ), const SizedBox(height: 10), Text( 'Details:', - style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - ), + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 10), Text( exception, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.redAccent, - ), + style: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(color: Colors.redAccent), ), const SizedBox(height: 30), ElevatedButton( diff --git a/firka/lib/ui/phone/pages/extras/extras.dart b/firka/lib/ui/phone/pages/extras/extras.dart index 5ae83fd..5253956 100644 --- a/firka/lib/ui/phone/pages/extras/extras.dart +++ b/firka/lib/ui/phone/pages/extras/extras.dart @@ -18,14 +18,19 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { logger.finest("showExtrasBottomSheet() developer mode: ${isDeveloper()}"); if (isDeveloper()) { - debugBtn = (double itemWidth) => GestureDetector( // Fejlesztői menü + debugBtn = (double itemWidth) => GestureDetector( + // Fejlesztői menü onTap: () => { Navigator.pop(context), Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DefaultAssetBundle( - bundle: FirkaBundle(), child: DebugScreen(data)))) + context, + MaterialPageRoute( + builder: (context) => DefaultAssetBundle( + bundle: FirkaBundle(), + child: DebugScreen(data), + ), + ), + ), }, child: SizedBox( height: 60, @@ -51,7 +56,9 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { Text( data.l10n.debug_screen, textAlign: TextAlign.right, - style: appStyle.fonts.B_16R.apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), ), ], ), @@ -97,34 +104,44 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { child: Stack( children: [ Column( - children: [ - Align( - alignment: Alignment.centerLeft, - child: Text( + children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( data.l10n.other, - style: appStyle.fonts.H_H2.apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.H_H2.apply( + color: appStyle.colors.textPrimary, ), ), - SizedBox(height: 8), - LayoutBuilder(builder: (context, constraints) { + ), + SizedBox(height: 8), + LayoutBuilder( + builder: (context, constraints) { final itemWidth = (constraints.maxWidth - 8) / 2; return Wrap( spacing: 2, runSpacing: 2, children: [ debugBtn(itemWidth), - GestureDetector( // Fiókod + GestureDetector( + // Fiókod onTap: () { Navigator.pop(context); Navigator.push( context, MaterialPageRoute( - builder: (context) => DefaultAssetBundle( - bundle: FirkaBundle(), - child: SettingsScreen( - data, - data.settings.items - .group("profile_settings"))))); + builder: (context) => + DefaultAssetBundle( + bundle: FirkaBundle(), + child: SettingsScreen( + data, + data.settings.items.group( + "profile_settings", + ), + ), + ), + ), + ); }, child: SizedBox( height: 60, @@ -133,11 +150,15 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { shadow: true, child: Card( color: appStyle.colors.card, - shadowColor: isLightMode.value ? null : Colors.transparent, + shadowColor: isLightMode.value + ? null + : Colors.transparent, child: Align( alignment: Alignment.centerLeft, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), child: Row( children: [ FirkaIconWidget( @@ -150,7 +171,12 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { Text( data.l10n.s_your_account, textAlign: TextAlign.right, - style: appStyle.fonts.B_16R.apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R + .apply( + color: appStyle + .colors + .textPrimary, + ), ), ], ), @@ -160,16 +186,23 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { ), ), ), - GestureDetector( // Beállítás + GestureDetector( + // Beállítás onTap: () { Navigator.pop(context); Navigator.push( context, MaterialPageRoute( - builder: (context) => DefaultAssetBundle( - bundle: FirkaBundle(), - child: SettingsScreen( - data, data.settings.items)))); + builder: (context) => + DefaultAssetBundle( + bundle: FirkaBundle(), + child: SettingsScreen( + data, + data.settings.items, + ), + ), + ), + ); }, child: SizedBox( height: 60, @@ -178,11 +211,15 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { shadow: true, child: Card( color: appStyle.colors.card, - shadowColor: isLightMode.value ? null : Colors.transparent, + shadowColor: isLightMode.value + ? null + : Colors.transparent, child: Align( alignment: Alignment.centerLeft, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), child: Row( children: [ FirkaIconWidget( @@ -195,7 +232,12 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { Text( data.l10n.settings_screen, textAlign: TextAlign.right, - style: appStyle.fonts.B_16R.apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R + .apply( + color: appStyle + .colors + .textPrimary, + ), ), ], ), @@ -208,7 +250,8 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { // Ide jön a többi gomb majd ], ); - }), + }, + ), ], ), Padding( @@ -219,17 +262,22 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { SizedBox(), GestureDetector( child: Text( - "v${data.packageInfo.version} ${isBeta ? "beta" : ""}", - style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textTertiary)), + "v${data.packageInfo.version} ${isBeta ? "beta" : ""}", + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textTertiary, + ), + ), onTap: () async { if (isDebug()) return; if (debugCounter == 10) { - data.settings.group("settings").setBoolean( - "developer_enabled", - !data.settings - .group("settings") - .boolean("developer_enabled")); + data.settings + .group("settings") + .setBoolean( + "developer_enabled", + !data.settings + .group("settings") + .boolean("developer_enabled"), + ); await data.isar.writeTxn(() async { await data.settings @@ -241,18 +289,21 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { .group("settings")["developer_enabled"]! .postUpdate(); - Navigator.of(navigatorKey.currentContext!) - .popUntil((route) => false); + Navigator.of( + navigatorKey.currentContext!, + ).popUntil((route) => false); Navigator.push( navigatorKey.currentContext!, MaterialPageRoute( - builder: (context) => DefaultAssetBundle( - bundle: FirkaBundle(), - child: HomeScreen( - data, - false, - key: ValueKey('homeScreen'), - ))), + builder: (context) => DefaultAssetBundle( + bundle: FirkaBundle(), + child: HomeScreen( + data, + false, + key: ValueKey('homeScreen'), + ), + ), + ), ); } else if (debugCounter < 10) { debugCounter++; diff --git a/firka/lib/ui/phone/pages/extras/main_error.dart b/firka/lib/ui/phone/pages/extras/main_error.dart index dbf4d60..dbc1028 100644 --- a/firka/lib/ui/phone/pages/extras/main_error.dart +++ b/firka/lib/ui/phone/pages/extras/main_error.dart @@ -31,12 +31,7 @@ void showErrorBottomSheet(BuildContext context, String err) { ), child: Padding( padding: const EdgeInsets.all(16.0), - child: Center( - child: Text( - err, - style: appStyle.fonts.B_16R, - ), - ), + child: Center(child: Text(err, style: appStyle.fonts.B_16R)), ), ), ), diff --git a/firka/lib/ui/phone/pages/extras/main_reauth.dart b/firka/lib/ui/phone/pages/extras/main_reauth.dart index 53331d9..d6f3d66 100644 --- a/firka/lib/ui/phone/pages/extras/main_reauth.dart +++ b/firka/lib/ui/phone/pages/extras/main_reauth.dart @@ -4,12 +4,17 @@ import 'package:firka/ui/model/style.dart'; import 'package:firka/ui/phone/widgets/login_webview.dart'; import 'package:flutter/material.dart'; -void showReauthBottomSheet(BuildContext context, AppInitialization data, String message) { - final accountPicker = (data.settings - .group("profile_settings")["e_kreta_account_picker"] - as SettingsKretenAccountPicker); +void showReauthBottomSheet( + BuildContext context, + AppInitialization data, + String message, +) { + final accountPicker = + (data.settings.group("profile_settings")["e_kreta_account_picker"] + as SettingsKretenAccountPicker); - final currentToken = data.tokens.isNotEmpty && accountPicker.accountIndex < data.tokens.length + final currentToken = + data.tokens.isNotEmpty && accountPicker.accountIndex < data.tokens.length ? data.tokens[accountPicker.accountIndex] : null; diff --git a/firka/lib/ui/phone/pages/extras/main_wear_pair.dart b/firka/lib/ui/phone/pages/extras/main_wear_pair.dart index bbc40b3..f532e3d 100644 --- a/firka/lib/ui/phone/pages/extras/main_wear_pair.dart +++ b/firka/lib/ui/phone/pages/extras/main_wear_pair.dart @@ -8,14 +8,20 @@ import 'package:watch_connectivity/watch_connectivity.dart'; import '../../../model/style.dart'; void showWearBottomSheet( - BuildContext context, AppInitialization data, String model) async { + BuildContext context, + AppInitialization data, + String model, +) async { final watch = WatchConnectivity(); - final timetable = await data.client - .getTimeTable(timeNow(), timeNow().add(Duration(days: 7))); + final timetable = await data.client.getTimeTable( + timeNow(), + timeNow().add(Duration(days: 7)), + ); if (timetable.err != null) { return; } + if (!context.mounted) return; List> timetableArray = List.empty(growable: true); @@ -58,22 +64,29 @@ void showWearBottomSheet( SvgPicture.asset("assets/images/wear_pair.svg"), SizedBox(height: 32), Center( - child: Text(data.l10n.pairing, - style: appStyle.fonts.H_14px - .apply(color: appStyle.colors.secondary)), + child: Text( + data.l10n.pairing, + style: appStyle.fonts.H_14px.apply( + color: appStyle.colors.secondary, + ), + ), ), Center( - child: Text(model, - style: appStyle.fonts.H_H2 - .apply(color: appStyle.colors.textPrimary)), + child: Text( + model, + style: appStyle.fonts.H_H2.apply( + color: appStyle.colors.textPrimary, + ), + ), ), Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6), child: Text( data.l10n.pairing_description, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), textAlign: TextAlign.center, ), ), @@ -87,9 +100,10 @@ void showWearBottomSheet( center: [ Text( data.l10n.pair, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), - ) + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), ], color: appStyle.colors.accent, ), @@ -104,7 +118,10 @@ void showWearBottomSheet( "idToken": data.client.model.idToken, "accessToken": data.client.model.accessToken, "refreshToken": data.client.model.refreshToken, - "expiryDate": data.client.model.expiryDate! + "expiryDate": data + .client + .model + .expiryDate! .millisecondsSinceEpoch, }, }); @@ -120,9 +137,10 @@ void showWearBottomSheet( center: [ Text( data.l10n.cancel, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), - ) + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), ], color: appStyle.colors.buttonSecondaryFill, ), diff --git a/firka/lib/ui/phone/pages/extras/reauth_toast.dart b/firka/lib/ui/phone/pages/extras/reauth_toast.dart index 27b1dc0..183677f 100644 --- a/firka/lib/ui/phone/pages/extras/reauth_toast.dart +++ b/firka/lib/ui/phone/pages/extras/reauth_toast.dart @@ -10,10 +10,10 @@ class ReauthToastWidget extends StatefulWidget { final Function() onDismiss; const ReauthToastWidget({ - Key? key, + super.key, required this.data, required this.onDismiss, - }) : super(key: key); + }); @override State createState() => _ReauthToastWidgetState(); @@ -32,7 +32,11 @@ class _ReauthToastWidgetState extends State { child: Center( child: GestureDetector( onTap: () { - showReauthBottomSheet(context, widget.data, widget.data.l10n.reauth); + showReauthBottomSheet( + context, + widget.data, + widget.data.l10n.reauth, + ); }, onVerticalDragUpdate: (details) { setState(() { @@ -62,8 +66,9 @@ class _ReauthToastWidgetState extends State { children: [ Text( widget.data.l10n.reauth, - style: appStyle.fonts.B_16SB - .copyWith(color: appStyle.colors.errorText), + style: appStyle.fonts.B_16SB.copyWith( + color: appStyle.colors.errorText, + ), ), SizedBox(width: 8), FirkaIconWidget( @@ -82,9 +87,10 @@ class _ReauthToastWidgetState extends State { } } -Widget buildReauthToast(BuildContext context, AppInitialization data, Function() onDismiss) { - return ReauthToastWidget( - data: data, - onDismiss: onDismiss, - ); +Widget buildReauthToast( + BuildContext context, + AppInitialization data, + Function() onDismiss, +) { + return ReauthToastWidget(data: data, onDismiss: onDismiss); } diff --git a/firka/lib/ui/phone/pages/home/home_grades.dart b/firka/lib/ui/phone/pages/home/home_grades.dart index dc5c9f2..34f5325 100644 --- a/firka/lib/ui/phone/pages/home/home_grades.dart +++ b/firka/lib/ui/phone/pages/home/home_grades.dart @@ -26,8 +26,12 @@ class HomeGradesScreen extends StatefulWidget { final void Function(int) pageController; const HomeGradesScreen( - this.data, this.updateNotifier, this.finishNotifier, this.pageController, - {super.key}); + this.data, + this.updateNotifier, + this.finishNotifier, + this.pageController, { + super.key, + }); @override State createState() => _HomeGradesScreen(); @@ -53,23 +57,25 @@ class _HomeGradesScreen extends FirkaState { widget.updateNotifier.addListener(updateListener); } + void updateListener() async { + var now = timeNow(); + var start = now.subtract(Duration(days: now.weekday - 1)); + var end = start.add(Duration(days: 6)); -void updateListener() async { - var now = timeNow(); - var start = now.subtract(Duration(days: now.weekday - 1)); - var end = start.add(Duration(days: 6)); - - grades = await widget.data.client.getGrades(forceCache: false); - week = await widget.data.client.getTimeTable(start, end, forceCache: false); - classGroups = await widget.data.client.getClassGroups(forceCache: false); - if (classGroups?.response?.isNotEmpty ?? false) { - var group = classGroups!.response!.first; - lessons = await widget.data.client.getSubjectAverage(group, forceCache: false); - await Future.delayed(Duration(milliseconds: 100)); + grades = await widget.data.client.getGrades(forceCache: false); + week = await widget.data.client.getTimeTable(start, end, forceCache: false); + classGroups = await widget.data.client.getClassGroups(forceCache: false); + if (classGroups?.response?.isNotEmpty ?? false) { + var group = classGroups!.response!.first; + lessons = await widget.data.client.getSubjectAverage( + group, + forceCache: false, + ); + await Future.delayed(Duration(milliseconds: 100)); + } + if (mounted) setState(() {}); + widget.finishNotifier.update(); } - if (mounted) setState(() {}); - widget.finishNotifier.update(); -} @override void initState() { @@ -89,7 +95,7 @@ void updateListener() async { var group = classGroups!.response!.first; lessons = await widget.data.client.getSubjectAverage(group); await Future.delayed(Duration(milliseconds: 100)); - } + } if (mounted) setState(() {}); })(); } @@ -107,11 +113,7 @@ void updateListener() async { height: MediaQuery.of(context).size.height / 1.35, child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - DelayedSpinnerWidget(), - SizedBox(), - ], + children: [SizedBox(), DelayedSpinnerWidget(), SizedBox()], ), ); } else { @@ -130,10 +132,9 @@ void updateListener() async { if (lessons != null && lessons!.response != null) { for (var lesson in lessons!.response!) { - if (subjects - .where((s) => s.uid == lesson.uid) - .isEmpty) { - subjects.add(Subject( + if (subjects.where((s) => s.uid == lesson.uid).isEmpty) { + subjects.add( + Subject( uid: lesson.uid, name: lesson.name, category: NameUidDesc( @@ -142,7 +143,8 @@ void updateListener() async { description: lesson.subjectCategoryDescription, ), sortIndex: lesson.sortIndex, - )); + ), + ); } } } @@ -159,7 +161,10 @@ void updateListener() async { for (var grade in subjectGrades) { if (grade.valueType.name == "Szazalekos") { grade.valueType = NameUidDesc( - uid: "1,Osztalyzat", name: "Osztalyzat", description: ""); + uid: "1,Osztalyzat", + name: "Osztalyzat", + description: "", + ); if (grade.numericValue != null) { grade.numericValue = percentageToGrade(grade.numericValue!); } @@ -169,29 +174,37 @@ void updateListener() async { } if (avg.isNaN) { - gradeCards.add(GestureDetector( - child: GradeSmallCard(grades!.response!, subject), - onTap: () { - activeSubjectUid = subject.uid; - subjectName = subject.name; - subjectId = subject.uid; - subjectCategory = subject.category.name!; - subjectInfo = subjects.where((s) => s.uid == subject.uid).toList(); - widget.pageController(1); - }, - )); + gradeCards.add( + GestureDetector( + child: GradeSmallCard(grades!.response!, subject), + onTap: () { + activeSubjectUid = subject.uid; + subjectName = subject.name; + subjectId = subject.uid; + subjectCategory = subject.category.name!; + subjectInfo = subjects + .where((s) => s.uid == subject.uid) + .toList(); + widget.pageController(1); + }, + ), + ); } else { - gradeCards.add(GestureDetector( - child: GradeSmallCard(grades!.response!, subject), - onTap: () { - activeSubjectUid = subject.uid; - subjectName = subject.name; - subjectId = subject.uid; - subjectCategory = subject.category.name!; - subjectInfo = subjects.where((s) => s.uid == subject.uid).toList(); - widget.pageController(1); - }, - )); + gradeCards.add( + GestureDetector( + child: GradeSmallCard(grades!.response!, subject), + onTap: () { + activeSubjectUid = subject.uid; + subjectName = subject.name; + subjectId = subject.uid; + subjectCategory = subject.category.name!; + subjectInfo = subjects + .where((s) => s.uid == subject.uid) + .toList(); + widget.pageController(1); + }, + ), + ); } if (!avg.isNaN) { @@ -212,11 +225,7 @@ void updateListener() async { var subjectAvgColor = getGradeColor(subjectAvg); return Padding( - padding: const EdgeInsets.only( - left: 20.0, - right: 20.0, - top: 12.0, - ), + padding: const EdgeInsets.only(left: 20.0, right: 20.0, top: 12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -224,9 +233,10 @@ void updateListener() async { children: [ Text( widget.data.l10n.subjects, - style: appStyle.fonts.H_H2 - .apply(color: appStyle.colors.textPrimary), - ) + style: appStyle.fonts.H_H2.apply( + color: appStyle.colors.textPrimary, + ), + ), ], ), // SizedBox(height: 16), // TODO: Add graphs here @@ -237,24 +247,27 @@ void updateListener() async { children: [ Text( widget.data.l10n.your_subjects, - style: appStyle.fonts.H_14px - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.H_14px.apply( + color: appStyle.colors.textSecondary, + ), ), SizedBox(height: 16), ...gradeCards, SizedBox(height: 16), Text( widget.data.l10n.data, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textSecondary, + ), ), SizedBox(height: 16), FirkaCard( left: [ Text( widget.data.l10n.subject_avg, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), ), ], right: [ @@ -263,11 +276,16 @@ void updateListener() async { color: subjectAvgColor.withAlpha(38), child: Padding( padding: EdgeInsets.only( - left: 8, right: 8, top: 4, bottom: 4), + left: 8, + right: 8, + top: 4, + bottom: 4, + ), child: Text( subjectAvg.toStringAsFixed(2), - style: appStyle.fonts.B_16SB - .apply(color: subjectAvgColor), + style: appStyle.fonts.B_16SB.apply( + color: subjectAvgColor, + ), ), ), ), @@ -277,8 +295,9 @@ void updateListener() async { left: [ Text( widget.data.l10n.subject_avg_rounded, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), ), ], right: [ @@ -287,11 +306,16 @@ void updateListener() async { color: subjectAvgColor.withAlpha(38), child: Padding( padding: EdgeInsets.only( - left: 8, right: 8, top: 4, bottom: 4), + left: 8, + right: 8, + top: 4, + bottom: 4, + ), child: Text( subjectAvgRounded.toStringAsFixed(2), - style: appStyle.fonts.B_16SB - .apply(color: subjectAvgColor), + style: appStyle.fonts.B_16SB.apply( + color: subjectAvgColor, + ), ), ), ), @@ -301,8 +325,9 @@ void updateListener() async { left: [ Text( "Összesített átlag", - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), ), ], right: [ @@ -311,40 +336,52 @@ void updateListener() async { color: subjectAvgColor.withAlpha(38), child: Padding( padding: EdgeInsets.only( - left: 8, right: 8, top: 4, bottom: 4), + left: 8, + right: 8, + top: 4, + bottom: 4, + ), child: Text( summaryAvg2.toStringAsFixed(2), - style: appStyle.fonts.B_16SB - .apply(color: subjectAvgColor), + style: appStyle.fonts.B_16SB.apply( + color: subjectAvgColor, + ), ), ), ), ], ), - FirkaCard(left: [ - Text( - widget.data.l10n.class_avg, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary), - ), - ]), + FirkaCard( + left: [ + Text( + widget.data.l10n.class_avg, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), FirkaCard( left: [ Text( widget.data.l10n.class_n, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), ), ], right: [ Text( week!.response! - .where((lesson) => - lesson.type.name != TimetableConsts.event) + .where( + (lesson) => + lesson.type.name != TimetableConsts.event, + ) .length .toString(), - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), ), ], ), diff --git a/firka/lib/ui/phone/pages/home/home_grades_subject.dart b/firka/lib/ui/phone/pages/home/home_grades_subject.dart index 4f589cd..437816f 100644 --- a/firka/lib/ui/phone/pages/home/home_grades_subject.dart +++ b/firka/lib/ui/phone/pages/home/home_grades_subject.dart @@ -22,8 +22,12 @@ class HomeGradesSubjectScreen extends StatefulWidget { final void Function(int) pageController; const HomeGradesSubjectScreen( - this.data, this.updateNotifier, this.finishNotifier, this.pageController, - {super.key}); + this.data, + this.updateNotifier, + this.finishNotifier, + this.pageController, { + super.key, + }); @override State createState() => _HomeGradesSubjectScreen(); @@ -41,8 +45,7 @@ class _HomeGradesSubjectScreen extends FirkaState { } void updateListener() async { - grades = (await widget.data.client.getGrades(forceCache: false)) - .response! + grades = (await widget.data.client.getGrades(forceCache: false)).response! .where((grade) => grade.subject.uid == activeSubjectUid) .where((grade) => grade.type.name != "felevi_jegy_ertekeles"); @@ -58,8 +61,7 @@ class _HomeGradesSubjectScreen extends FirkaState { widget.updateNotifier.addListener(updateListener); (() async { - grades = (await widget.data.client.getGrades()) - .response! + grades = (await widget.data.client.getGrades()).response! .where((grade) => grade.subject.uid == activeSubjectUid) .where((grade) => grade.type.name != "felevi_jegy_ertekeles"); @@ -82,64 +84,66 @@ class _HomeGradesSubjectScreen extends FirkaState { var gradeWidgets = List.empty(growable: true); for (var group in groups.entries) { - gradeWidgets.add(SizedBox( - height: 8, - )); - gradeWidgets.add(Text( - group.key.format(widget.data.l10n, FormatMode.grades), - style: - appStyle.fonts.H_14px.apply(color: appStyle.colors.textPrimary), - )); - gradeWidgets.add(SizedBox( - height: 8, - )); + gradeWidgets.add(SizedBox(height: 8)); + gradeWidgets.add( + Text( + group.key.format(widget.data.l10n, FormatMode.grades), + style: appStyle.fonts.H_14px.apply( + color: appStyle.colors.textPrimary, + ), + ), + ); + gradeWidgets.add(SizedBox(height: 8)); for (var grade in group.value) { - gradeWidgets.add(GestureDetector( - child: FirkaCard( - left: [ - Row( - children: [ - GradeWidget(grade), - SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width / 1.45, - child: Text( + gradeWidgets.add( + GestureDetector( + child: FirkaCard( + left: [ + Row( + children: [ + GradeWidget(grade), + SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width / 1.45, + child: Text( (grade.topic ?? grade.type.description!) .firstUpper(), - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)), - ), - grade.mode?.description != null - ? SizedBox( - width: MediaQuery.of(context).size.width / 1.45, - child: Text( - grade.mode!.description!.firstUpper(), - style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textSecondary), - ), - ) - : SizedBox(), - ], - ) - ], - ) - ], + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), + ), + grade.mode?.description != null + ? SizedBox( + width: + MediaQuery.of(context).size.width / 1.45, + child: Text( + grade.mode!.description!.firstUpper(), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), + ) + : SizedBox(), + ], + ), + ], + ), + ], + ), + onTap: () { + showGradeBottomSheet(context, widget.data, grade); + }, ), - onTap: () { - showGradeBottomSheet(context, widget.data, grade); - }, - )); + ); } } return Padding( - padding: const EdgeInsets.only( - left: 16.0, - right: 16.0, - ), + padding: const EdgeInsets.only(left: 16.0, right: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -149,52 +153,54 @@ class _HomeGradesSubjectScreen extends FirkaState { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - Transform.translate( - offset: const Offset(-4, 0), - child: GestureDetector( - child: FirkaIconWidget( - FirkaIconType.majesticons, Majesticon.chevronLeftLine, - color: appStyle.colors.textSecondary + Row( + children: [ + Transform.translate( + offset: const Offset(-4, 0), + child: GestureDetector( + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.chevronLeftLine, + color: appStyle.colors.textSecondary, + ), + onTap: () { + widget.pageController(0); + }, ), - onTap: () { - widget.pageController(0); - } ), - ), - Transform.translate( - offset: const Offset(-4, 0), - child: Text( - widget.data.l10n.subjects, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), + Transform.translate( + offset: const Offset(-4, 0), + child: Text( + widget.data.l10n.subjects, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), ), - ), - ], - ), - GestureDetector( - child: Card( - color: appStyle.colors.buttonSecondaryFill, - child: Padding( - padding: const EdgeInsets.all(4), - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.menuSolid, - size: 26.0, - color: appStyle.colors.accent, - ), - ), + ], + ), + GestureDetector( + child: Card( + color: appStyle.colors.buttonSecondaryFill, + child: Padding( + padding: const EdgeInsets.all(4), + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.menuSolid, + size: 26.0, + color: appStyle.colors.accent, + ), + ), + ), + onTap: () { + // Navigator.push(context, Settings) + // showSubjectBottomSheetSettings( + // context, + // widget.data, + // aGrade.subject, + // ); + }, ), - onTap: () { - // Navigator.push(context, Settings) - // showSubjectBottomSheetSettings( - // context, - // widget.data, - // aGrade.subject, - // ); - }, - ) ], ), ], @@ -202,7 +208,8 @@ class _HomeGradesSubjectScreen extends FirkaState { // SizedBox(height: 16), // GradeChart(grades: grades?.toList() ?? []), SizedBox( - height: MediaQuery.of(context).size.height - + height: + MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top - 230, child: ListView( @@ -214,7 +221,8 @@ class _HomeGradesSubjectScreen extends FirkaState { shadowColor: const Color.fromRGBO(0, 0, 0, 0), color: appStyle.colors.a15p, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16)), + borderRadius: BorderRadius.circular(16), + ), child: Padding( padding: EdgeInsetsGeometry.all(6), child: ClassIconWidget( @@ -228,24 +236,27 @@ class _HomeGradesSubjectScreen extends FirkaState { SizedBox(height: 8), Text( aGrade.subject.name, - style: appStyle.fonts.H_H2 - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.H_H2.apply( + color: appStyle.colors.textPrimary, + ), ), SizedBox(height: 2), Text( aGrade.teacher, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), ), - SizedBox(height: 15) - ]), + SizedBox(height: 15), + ], + ), Padding( padding: EdgeInsets.only(left: 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: gradeWidgets, ), - ) + ), ], ), ), @@ -254,10 +265,7 @@ class _HomeGradesSubjectScreen extends FirkaState { ); } else { return Padding( - padding: const EdgeInsets.only( - left: 16.0, - right: 16.0, - ), + padding: const EdgeInsets.only(left: 16.0, right: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -269,8 +277,10 @@ class _HomeGradesSubjectScreen extends FirkaState { offset: const Offset(-4, 0), child: GestureDetector( child: FirkaIconWidget( - FirkaIconType.majesticons, Majesticon.chevronLeftLine, - color: appStyle.colors.textSecondary), + FirkaIconType.majesticons, + Majesticon.chevronLeftLine, + color: appStyle.colors.textSecondary, + ), onTap: () { widget.pageController(0); }, @@ -280,17 +290,19 @@ class _HomeGradesSubjectScreen extends FirkaState { offset: const Offset(-4, 1), child: Text( widget.data.l10n.subjects, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), ), - ) + ), ], ), ], ), SizedBox(height: 16), SizedBox( - height: MediaQuery.of(context).size.height - + height: + MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top - 230, child: ListView( @@ -302,7 +314,8 @@ class _HomeGradesSubjectScreen extends FirkaState { shadowColor: const Color.fromRGBO(0, 0, 0, 0), color: appStyle.colors.a15p, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16)), + borderRadius: BorderRadius.circular(16), + ), child: Padding( padding: EdgeInsetsGeometry.all(6), child: ClassIconWidget( @@ -316,28 +329,40 @@ class _HomeGradesSubjectScreen extends FirkaState { SizedBox(height: 8), Text( subjectName, - style: appStyle.fonts.H_H2 - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.H_H2.apply( + color: appStyle.colors.textPrimary, + ), ), SizedBox(height: 2), Text( widget.data.l10n.unknown_teacher, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), ), - ]), + ], + ), SizedBox( - height: MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top - 320, + height: + MediaQuery.of(context).size.height - + MediaQuery.of(context).padding.top - + 320, child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ - SvgPicture.asset("assets/images/logos/dave.svg", - width: 48, height: 48), - SizedBox(height: 12), - Text(widget.data.l10n.no_grades, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary)), + SvgPicture.asset( + "assets/images/logos/dave.svg", + width: 48, + height: 48, + ), + SizedBox(height: 12), + Text( + widget.data.l10n.no_grades, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), ], ), ), diff --git a/firka/lib/ui/phone/pages/home/home_main.dart b/firka/lib/ui/phone/pages/home/home_main.dart index d809b0a..dd816a5 100644 --- a/firka/lib/ui/phone/pages/home/home_main.dart +++ b/firka/lib/ui/phone/pages/home/home_main.dart @@ -34,8 +34,12 @@ class HomeMainScreen extends StatefulWidget { final UpdateNotifier updateNotifier; final UpdateNotifier finishNotifier; - const HomeMainScreen(this.data, this.updateNotifier, this.finishNotifier, - {super.key}); + const HomeMainScreen( + this.data, + this.updateNotifier, + this.finishNotifier, { + super.key, + }); @override State createState() => _HomeMainScreen(); @@ -81,21 +85,23 @@ class _HomeMainScreen extends FirkaState { widget.data.client .getTimeTableStream( - midnight, midnight.add(Duration(hours: 23, minutes: 59)), - cacheOnly: cacheOnly) + midnight, + midnight.add(Duration(hours: 23, minutes: 59)), + cacheOnly: cacheOnly, + ) .forEach((lessons) { - lessonsFetched++; + lessonsFetched++; - if (mounted) { - setState(() { - this.lessons = lessons.response; + if (mounted) { + setState(() { + this.lessons = lessons.response; + }); + } }); - } - }); - widget.data.client - .getNoticeBoardStream(cacheOnly: cacheOnly) - .forEach((items) { + widget.data.client.getNoticeBoardStream(cacheOnly: cacheOnly).forEach(( + items, + ) { noticeBoardFetched++; if (mounted) { @@ -105,9 +111,9 @@ class _HomeMainScreen extends FirkaState { } }); - widget.data.client - .getInfoBoardStream(cacheOnly: cacheOnly) - .forEach((items) { + widget.data.client.getInfoBoardStream(cacheOnly: cacheOnly).forEach(( + items, + ) { infoBoardFetched++; if (mounted) { @@ -117,9 +123,9 @@ class _HomeMainScreen extends FirkaState { } }); - widget.data.client - .getStudentStream(cacheOnly: cacheOnly) - .forEach((student) { + widget.data.client.getStudentStream(cacheOnly: cacheOnly).forEach(( + student, + ) { studentFetched++; if (mounted) { @@ -149,9 +155,9 @@ class _HomeMainScreen extends FirkaState { } }); - widget.data.client - .getHomeworkStream(cacheOnly: cacheOnly) - .forEach((homework) { + widget.data.client.getHomeworkStream(cacheOnly: cacheOnly).forEach(( + homework, + ) { homeworkFetched++; if (mounted) { @@ -222,7 +228,8 @@ class _HomeMainScreen extends FirkaState { welcomeWidget = StartingSoonWidget(widget.data.l10n, now, lessons!); } else { var currentLesson = lessons!.firstWhereOrNull( - (lesson) => now.isAfter(lesson.start) && now.isBefore(lesson.end)); + (lesson) => now.isAfter(lesson.start) && now.isBefore(lesson.end), + ); // "fun" fact if your clock was exactly when the class ends then isBefore // and isAfter would fail, so to work around that we just add 1ms to the // current time @@ -235,26 +242,43 @@ class _HomeMainScreen extends FirkaState { lessonActive = true; } - welcomeWidget = LessonBigWidget(widget.data.l10n, now, lessonIndex, - currentLesson, prevLesson, nextLesson, lessons!, tests!); + welcomeWidget = LessonBigWidget( + widget.data.l10n, + now, + lessonIndex, + currentLesson, + prevLesson, + nextLesson, + lessons!, + tests!, + ); } } if (lessons != null && lessons!.isNotEmpty) { var nextLesson = lessons!.getNextLesson(now); if (nextLesson != null) { - nextClass = - LessonSmallWidget(widget.data.l10n, nextLesson, lessonActive); + nextClass = LessonSmallWidget( + widget.data.l10n, + nextLesson, + lessonActive, + ); if (tests != null) { final testsOnDate = tests! - .where((test) => - test.date.isAfter(nextLesson.start - .getMidnight() - .subtract(Duration(seconds: 1))) && - test.date.isBefore(nextLesson.end - .getMidnight() - .add(Duration(hours: 23, minutes: 59))) && - test.subject.uid == nextLesson.subject?.uid) + .where( + (test) => + test.date.isAfter( + nextLesson.start.getMidnight().subtract( + Duration(seconds: 1), + ), + ) && + test.date.isBefore( + nextLesson.end.getMidnight().add( + Duration(hours: 23, minutes: 59), + ), + ) && + test.subject.uid == nextLesson.subject?.uid, + ) .toList(); if (testsOnDate.isNotEmpty) { @@ -268,14 +292,20 @@ class _HomeMainScreen extends FirkaState { color: appStyle.colors.accent, ), SizedBox(width: 6), - Text(test.theme, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textSecondary)) + Text( + test.theme, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textSecondary, + ), + ), ], right: [ - Text(test.method.description ?? "N/A", - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textTertiary)) + Text( + test.method.description ?? "N/A", + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textTertiary, + ), + ), ], ); } @@ -296,11 +326,14 @@ class _HomeMainScreen extends FirkaState { GestureDetector( child: InfoBoardItemWidget(item), onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => MessageScreen(widget.data, item))); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => MessageScreen(widget.data, item), + ), + ); }, ), - item.date + item.date, )); } @@ -319,10 +352,12 @@ class _HomeMainScreen extends FirkaState { SizedBox( width: MediaQuery.of(context).size.width / 1.45, child: Text( - (grade.topic ?? grade.type.description!) - .firstUpper(), - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)), + (grade.topic ?? grade.type.description!) + .firstUpper(), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), ), grade.mode?.description != null ? SizedBox( @@ -330,38 +365,38 @@ class _HomeMainScreen extends FirkaState { child: Text( grade.mode!.description!.firstUpper(), style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textSecondary), + color: appStyle.colors.textSecondary, + ), ), ) : SizedBox(), ], - ) + ), ], - ) + ), ], ), onTap: () { showGradeBottomSheet(context, widget.data, grade); }, ), - grade.recordDate + grade.recordDate, )); } for (final entry in homework!) { - noticeBoardWidgets - .add((HomeworkWidget(widget.data, entry), entry.creationDate)); + noticeBoardWidgets.add(( + HomeworkWidget(widget.data, entry), + entry.creationDate, + )); } - noticeBoardWidgets - .sort((item1, item2) => item2.$2.difference(item1.$2).inMilliseconds); + noticeBoardWidgets.sort( + (item1, item2) => item2.$2.difference(item1.$2).inMilliseconds, + ); return Padding( - padding: const EdgeInsets.only( - left: 20.0, - top: 24.0, - right: 20.0, - ), + padding: const EdgeInsets.only(left: 20.0, top: 24.0, right: 20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -377,7 +412,7 @@ class _HomeMainScreen extends FirkaState { child: ListView( children: noticeBoardWidgets.map((e) => e.$1).toList(), ), - ) + ), ], ), ); @@ -390,7 +425,7 @@ class _HomeMainScreen extends FirkaState { Row( mainAxisAlignment: MainAxisAlignment.center, children: [DelayedSpinnerWidget()], - ) + ), ], ), ); diff --git a/firka/lib/ui/phone/pages/home/home_subpage.dart b/firka/lib/ui/phone/pages/home/home_subpage.dart index a8a3e9b..63b0cd1 100644 --- a/firka/lib/ui/phone/pages/home/home_subpage.dart +++ b/firka/lib/ui/phone/pages/home/home_subpage.dart @@ -11,15 +11,19 @@ class PageWithSubPages extends StatefulWidget { final ValueNotifier subPageActive; final UpdateNotifier back; - const PageWithSubPages(this.subPages, this.subPageActive, this.back, - {Key? key, required this.pageIndex}) - : super(key: key); + const PageWithSubPages( + this.subPages, + this.subPageActive, + this.back, { + super.key, + required this.pageIndex, + }); @override - _PageWithSubPagesState createState() => _PageWithSubPagesState(); + PageWithSubPagesState createState() => PageWithSubPagesState(); } -class _PageWithSubPagesState extends FirkaState { +class PageWithSubPagesState extends FirkaState { int _currentSubPage = 0; @override diff --git a/firka/lib/ui/phone/pages/home/home_timetable.dart b/firka/lib/ui/phone/pages/home/home_timetable.dart index 0363b0a..9f2ade8 100644 --- a/firka/lib/ui/phone/pages/home/home_timetable.dart +++ b/firka/lib/ui/phone/pages/home/home_timetable.dart @@ -32,8 +32,12 @@ class HomeTimetableScreen extends StatefulWidget { final void Function(int) pageController; const HomeTimetableScreen( - this.data, this.updateNotifier, this.finishNotifier, this.pageController, - {super.key}); + this.data, + this.updateNotifier, + this.finishNotifier, + this.pageController, { + super.key, + }); @override State createState() => _HomeTimetableScreen(); @@ -81,19 +85,24 @@ class _HomeTimetableScreen extends FirkaState void setActiveToToday() { final todayMid = now!.getMidnight(); - int idx = dates!.indexWhere((d) => - d.year == todayMid.year && - d.month == todayMid.month && - d.day == todayMid.day); + int idx = dates!.indexWhere( + (d) => + d.year == todayMid.year && + d.month == todayMid.month && + d.day == todayMid.day, + ); if (idx >= 0) { - final todaysLessons = lessons?.where((lesson) => - lesson.start.isAfter(todayMid) && - lesson.end.isBefore(todayMid.add(Duration(hours: 23, minutes: 59)))); + final todaysLessons = lessons?.where( + (lesson) => + lesson.start.isAfter(todayMid) && + lesson.end.isBefore(todayMid.add(Duration(hours: 23, minutes: 59))), + ); if (todaysLessons != null && todaysLessons.isNotEmpty) { - final lastLessonToday = - todaysLessons.reduce((a, b) => a.end.isAfter(b.end) ? a : b); + final lastLessonToday = todaysLessons.reduce( + (a, b) => a.end.isAfter(b.end) ? a : b, + ); if (now!.isAfter(lastLessonToday.end)) { int nextIdx = idx + 1; @@ -146,8 +155,11 @@ class _HomeTimetableScreen extends FirkaState await widget.data.client.getTests(); } - Future _updateState(DateTime now, ApiResponse> lessonsResp, - ApiResponse> testsResp) async { + Future _updateState( + DateTime now, + ApiResponse> lessonsResp, + ApiResponse> testsResp, + ) async { var monday = now.getMonday().getMidnight(); List dates = List.empty(growable: true); @@ -164,7 +176,8 @@ class _HomeTimetableScreen extends FirkaState for (var i = 0; i < 7; i++) { var t = monday.add(Duration(days: i)); - var hasLessons = i < 5 || + var hasLessons = + i < 5 || lessons!.firstWhereOrNull((lesson) { return lesson.start.getMidnight().millisecondsSinceEpoch == t.getMidnight().millisecondsSinceEpoch; @@ -204,9 +217,9 @@ class _HomeTimetableScreen extends FirkaState widget.data.client .getTimeTableStream(monday, sunday, cacheOnly: forceCache) .forEach((lessons) { - lessonsResp = lessons; - lessonsFetched++; - }); + lessonsResp = lessons; + lessonsFetched++; + }); widget.data.client.getTestsStream(cacheOnly: forceCache).forEach((tests) { testsResp = tests; @@ -278,8 +291,9 @@ class _HomeTimetableScreen extends FirkaState // If the target is not adjacent, create temporary order if ((targetIndex - oldIndex).abs() > 1) { // Determine the temporary target position next to the current position - int tempTargetIndex = - oldIndex < targetIndex ? oldIndex + 1 : oldIndex - 1; + int tempTargetIndex = oldIndex < targetIndex + ? oldIndex + 1 + : oldIndex - 1; // Create a new order where target day is next to current day List reorderedDates = List.from(_animationDates!); @@ -307,13 +321,13 @@ class _HomeTimetableScreen extends FirkaState final double end = targetDisplayIndex * totalCardWidth; _cardAnimationController!.reset(); - _cardOffsetAnimation = Tween( - begin: Offset(start, 0), - end: Offset(end, 0), - ).animate(CurvedAnimation( - parent: _cardAnimationController!, - curve: Curves.easeInOut, - )); + _cardOffsetAnimation = + Tween(begin: Offset(start, 0), end: Offset(end, 0)).animate( + CurvedAnimation( + parent: _cardAnimationController!, + curve: Curves.easeInOut, + ), + ); setState(() { _showAnimatedCard = true; @@ -371,27 +385,45 @@ class _HomeTimetableScreen extends FirkaState final realIndex = i; // Always use real index for nav icons final testsOnDate = tests! - .where((test) => - test.date.isAfter(date.subtract(Duration(seconds: 1))) && - test.date.isBefore(date.add(Duration(hours: 23, minutes: 59)))) + .where( + (test) => + test.date.isAfter(date.subtract(Duration(seconds: 1))) && + test.date.isBefore( + date.add(Duration(hours: 23, minutes: 59)), + ), + ) .toList(); if (testsOnDate.isNotEmpty) { - ttWidgets.add(Stack( - children: [ - BottomTimeTableNavIconWidget(widget.data.l10n, () { - _handleNavTap(active, realIndex); - }, active == i, date), - Transform.translate( - offset: Offset(38, -10), - child: BubbleTest(), - ), - ], - )); + ttWidgets.add( + Stack( + children: [ + BottomTimeTableNavIconWidget( + widget.data.l10n, + () { + _handleNavTap(active, realIndex); + }, + active == i, + date, + ), + Transform.translate( + offset: Offset(38, -10), + child: BubbleTest(), + ), + ], + ), + ); } else { - ttWidgets.add(BottomTimeTableNavIconWidget(widget.data.l10n, () { - _handleNavTap(active, realIndex); - }, active == i, date)); + ttWidgets.add( + BottomTimeTableNavIconWidget( + widget.data.l10n, + () { + _handleNavTap(active, realIndex); + }, + active == i, + date, + ), + ); } } @@ -400,28 +432,49 @@ class _HomeTimetableScreen extends FirkaState final date = _animationDates![i]; final lessonsOnDate = lessons! - .where((lesson) => - lesson.start.isAfter(date) && - lesson.end.isBefore(date.add(Duration(hours: 24)))) + .where( + (lesson) => + lesson.start.isAfter(date) && + lesson.end.isBefore(date.add(Duration(hours: 24))), + ) .toList(); final lessonsOnDay = lessons! - .where((lesson) => - lesson.start.getMidnight().millisecondsSinceEpoch == - date.getMidnight().millisecondsSinceEpoch) + .where( + (lesson) => + lesson.start.getMidnight().millisecondsSinceEpoch == + date.getMidnight().millisecondsSinceEpoch, + ) .toList(); final eventsOnDate = events! - .where((lesson) => - lesson.start.isAfter(date.subtract(Duration(seconds: 1))) && - lesson.end.isBefore(date.add(Duration(hours: 23, minutes: 59)))) + .where( + (lesson) => + lesson.start.isAfter(date.subtract(Duration(seconds: 1))) && + lesson.end.isBefore( + date.add(Duration(hours: 23, minutes: 59)), + ), + ) .toList(); final testsOnDate = tests! - .where((test) => - test.date.isAfter(date.subtract(Duration(seconds: 1))) && - test.date.isBefore(date.add(Duration(hours: 23, minutes: 59)))) + .where( + (test) => + test.date.isAfter(date.subtract(Duration(seconds: 1))) && + test.date.isBefore( + date.add(Duration(hours: 23, minutes: 59)), + ), + ) .toList(); - ttDays.add(TimeTableDayWidget(widget.data, date, lessons!, - lessonsOnDate, eventsOnDate, testsOnDate, lessonsOnDay)); + ttDays.add( + TimeTableDayWidget( + widget.data, + date, + lessons!, + lessonsOnDate, + eventsOnDate, + testsOnDate, + lessonsOnDay, + ), + ); } List ttEmptyCards = List.empty(growable: true); @@ -436,70 +489,76 @@ class _HomeTimetableScreen extends FirkaState ); if (_showAnimatedCard && _cardOffsetAnimation != null) { - ttEmptyCards.add(AnimatedBuilder( - animation: _cardOffsetAnimation!, - builder: (context, child) { - return Transform.translate( - offset: _cardOffsetAnimation!.value, - child: cardWidget, - ); - }, - )); + ttEmptyCards.add( + AnimatedBuilder( + animation: _cardOffsetAnimation!, + builder: (context, child) { + return Transform.translate( + offset: _cardOffsetAnimation!.value, + child: cardWidget, + ); + }, + ), + ); } else { - ttEmptyCards.add(Transform.translate( - offset: Offset(0, 0), - child: cardWidget, - )); + ttEmptyCards.add( + Transform.translate(offset: Offset(0, 0), child: cardWidget), + ); } } else { - ttEmptyCards.add(Card( - color: Colors.transparent, - shadowColor: Colors.transparent, - child: SizedBox(width: 40, height: 54), - )); + ttEmptyCards.add( + Card( + color: Colors.transparent, + shadowColor: Colors.transparent, + child: SizedBox(width: 40, height: 54), + ), + ); } } } else { ttEmptyCards.clear(); } - return Stack(children: [ - Column(children: [ - SizedBox( - width: MediaQuery.of(context).size.width, - height: 74 + 16, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + return Stack( + children: [ + Column( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width, + height: 74 + 16, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( children: [ - Text( - widget.data.l10n.timetable, - style: appStyle.fonts.H_H2 - .apply(color: appStyle.colors.textPrimary), - ), Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - GestureDetector( - child: Card( - color: appStyle.colors.buttonSecondaryFill, - child: Padding( - padding: const EdgeInsets.all(8), - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.tableSolid, - size: 26.0, - color: appStyle.colors.accent, - ), - ), + Text( + widget.data.l10n.timetable, + style: appStyle.fonts.H_H2.apply( + color: appStyle.colors.textPrimary, ), - onTap: () { - widget.pageController(1); - }, ), - /* TODO: 1.1.0 + Row( + children: [ + GestureDetector( + child: Card( + color: appStyle.colors.buttonSecondaryFill, + child: Padding( + padding: const EdgeInsets.all(8), + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.tableSolid, + size: 26.0, + color: appStyle.colors.accent, + ), + ), + ), + onTap: () { + widget.pageController(1); + }, + ), + /* TODO: 1.1.0 Card( color: appStyle.colors.buttonSecondaryFill, @@ -514,179 +573,195 @@ class _HomeTimetableScreen extends FirkaState ), ), */ - GestureDetector( - child: Card( - color: appStyle.colors.buttonSecondaryFill, - child: Padding( - padding: const EdgeInsets.all(8), - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.settingsCogSolid, - size: 26.0, - color: appStyle.colors.accent, + GestureDetector( + child: Card( + color: appStyle.colors.buttonSecondaryFill, + child: Padding( + padding: const EdgeInsets.all(8), + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.settingsCogSolid, + size: 26.0, + color: appStyle.colors.accent, + ), + ), ), + onTap: () { + showSettingsSheet( + context, + MediaQuery.of(context).size.height * 0.4, + widget.data, + widget.data.settings + .group("settings") + .subGroup("timetable_toast"), + ); + }, + ), + ], + ), + ], + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + behavior: HitTestBehavior.translucent, + child: SizedBox( + width: 24, + height: 24, + child: FirkaIconWidget( + FirkaIconType.icons, + "dropdownLeft", + size: 24, + color: appStyle.colors.accent, ), ), - onTap: () { - showSettingsSheet( - context, - MediaQuery.of(context).size.height * 0.4, - widget.data, - widget.data.settings - .group("settings") - .subGroup("timetable_toast")); + onTap: () async { + var newNow = now!.subtract(Duration(days: 7)); + if (!mounted) return; + setState(() { + now = newNow; + lessons = null; + dates = null; + }); + await initForWeek(newNow); + setState(() { + now = newNow; + }); }, - ) + ), + GestureDetector( + child: Row( + children: [ + Text( + now!.format( + widget.data.l10n, + FormatMode.yyyymmddwedd, + ), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + SizedBox(width: 4), + if (showABTimetable) ...[ + Text( + "•", + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.accent, + ), + ), + SizedBox(width: 4), + Text( + now!.isAWeek() + ? widget.data.l10n.a_week + : widget.data.l10n.b_week, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ], + ), + onTap: () { + now = timeNow(); + setActiveToToday(); + _controller.jumpToPage(active); + }, + ), + GestureDetector( + behavior: HitTestBehavior.translucent, + child: SizedBox( + width: 24, + height: 24, + child: FirkaIconWidget( + FirkaIconType.icons, + "dropdownRight", + size: 24, + color: appStyle.colors.accent, + ), + ), + onTap: () async { + var newNow = now!.add(Duration(days: 7)); + if (!mounted) return; + setState(() { + now = newNow; + lessons = null; + dates = null; + }); + await initForWeek(newNow); + setState(() { + now = newNow; + }); + }, + ), ], ), ], ), - SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - behavior: HitTestBehavior.translucent, - child: SizedBox( - width: 24, - height: 24, - child: FirkaIconWidget( - FirkaIconType.icons, - "dropdownLeft", - size: 24, - color: appStyle.colors.accent, - ), - ), - onTap: () async { - var newNow = now!.subtract(Duration(days: 7)); - if (!mounted) return; - setState(() { - now = newNow; - lessons = null; - dates = null; - }); - await initForWeek(newNow); - setState(() { - now = newNow; - }); - }, - ), - GestureDetector( - child: Row( - children: [ - Text( - now!.format( - widget.data.l10n, FormatMode.yyyymmddwedd), - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)), - SizedBox(width: 4), - if (showABTimetable) ...[ - Text("•", - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.accent)), - SizedBox(width: 4), - Text( - now!.isAWeek() - ? widget.data.l10n.a_week - : widget.data.l10n.b_week, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)), - ] - ], - ), - onTap: () { - now = timeNow(); - setActiveToToday(); - _controller.jumpToPage(active); - }, - ), - GestureDetector( - behavior: HitTestBehavior.translucent, - child: SizedBox( - width: 24, - height: 24, - child: FirkaIconWidget( - FirkaIconType.icons, - "dropdownRight", - size: 24, - color: appStyle.colors.accent, - ), - ), - onTap: () async { - var newNow = now!.add(Duration(days: 7)); - if (!mounted) return; - setState(() { - now = newNow; - lessons = null; - dates = null; - }); - await initForWeek(newNow); - setState(() { - now = newNow; - }); - }, - ), - ], - ) - ], - ), - ), - ), - ]), - Column(mainAxisAlignment: MainAxisAlignment.end, children: [ - Expanded( - child: TransparentPointer( - child: CarouselSlider( - items: ttDays, - carouselController: _controller, - options: CarouselOptions( - height: MediaQuery.of(context).size.height, - viewportFraction: 1, - enableInfiniteScroll: false, - initialPage: active, - onPageChanged: (i, _) { - if (animating || !mounted) return; - - HapticFeedback.mediumImpact(); - - // Convert animation index to display index - final displayIndex = dates!.indexOf(_animationDates![i]); - maybeCacheNextWeek(displayIndex); - maybeCachePreviousWeek(displayIndex); - setState(() { - active = displayIndex; - }); - }), - ))), - Container( - padding: EdgeInsets.only(bottom: 12), - decoration: ShapeDecoration( - color: Colors.transparent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(0), ), - shadows: [ - BoxShadow( + ), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: TransparentPointer( + child: CarouselSlider( + items: ttDays, + carouselController: _controller, + options: CarouselOptions( + height: MediaQuery.of(context).size.height, + viewportFraction: 1, + enableInfiniteScroll: false, + initialPage: active, + onPageChanged: (i, _) { + if (animating || !mounted) return; + + HapticFeedback.mediumImpact(); + + // Convert animation index to display index + final displayIndex = dates!.indexOf( + _animationDates![i], + ); + maybeCacheNextWeek(displayIndex); + maybeCachePreviousWeek(displayIndex); + setState(() { + active = displayIndex; + }); + }, + ), + ), + ), + ), + Container( + padding: EdgeInsets.only(bottom: 12), + decoration: ShapeDecoration( + color: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + shadows: [ + BoxShadow( color: appStyle.colors.background, blurRadius: 30, spreadRadius: 20, - offset: Offset(0, -25)), - ], + offset: Offset(0, -25), + ), + ], + ), + child: Stack( + children: [ + Wrap(spacing: 10, children: ttEmptyCards), + Wrap(spacing: 10, children: ttWidgets), + ], + ), ), - child: Stack( - children: [ - Wrap( - spacing: 10, - children: ttEmptyCards, - ), - Wrap( - spacing: 10, - children: ttWidgets, - ), - ], - )), - ]) - ]); + ], + ), + ], + ); } else { return Column( mainAxisAlignment: MainAxisAlignment.center, @@ -694,7 +769,7 @@ class _HomeTimetableScreen extends FirkaState Row( mainAxisAlignment: MainAxisAlignment.center, children: [DelayedSpinnerWidget()], - ) + ), ], ); } diff --git a/firka/lib/ui/phone/pages/home/home_timetable_mo.dart b/firka/lib/ui/phone/pages/home/home_timetable_mo.dart index 285e91e..095331f 100644 --- a/firka/lib/ui/phone/pages/home/home_timetable_mo.dart +++ b/firka/lib/ui/phone/pages/home/home_timetable_mo.dart @@ -25,8 +25,12 @@ class HomeTimetableMonthlyScreen extends StatefulWidget { final void Function(int) pageController; const HomeTimetableMonthlyScreen( - this.data, this.updateNotifier, this.finishNotifier, this.pageController, - {super.key}); + this.data, + this.updateNotifier, + this.finishNotifier, + this.pageController, { + super.key, + }); @override State createState() => @@ -49,20 +53,28 @@ class _HomeTimetableMonthlyScreen Future initForMonth(DateTime now, {bool forceCache = true}) async { final monthStart = DateTime.utc(now.year, now.month, 1); - final monthEnd = - DateTime.utc(now.year, now.month + 1).subtract(Duration(days: 1)); + final monthEnd = DateTime.utc( + now.year, + now.month + 1, + ).subtract(Duration(days: 1)); final start = monthStart.subtract(Duration(days: 7)).getMonday(); - var end = - monthEnd.add(Duration(days: 7)).getMonday().add(Duration(days: 7)); + var end = monthEnd + .add(Duration(days: 7)) + .getMonday() + .add(Duration(days: 7)); var days = end.difference(start).inDays; - var lessonsResp = await widget.data.client - .getTimeTable(monthStart, monthEnd, forceCache: forceCache); + var lessonsResp = await widget.data.client.getTimeTable( + monthStart, + monthEnd, + forceCache: forceCache, + ); var testsResp = await widget.data.client.getTests(forceCache: forceCache); - var omissionsResp = - await widget.data.client.getOmissions(forceCache: forceCache); + var omissionsResp = await widget.data.client.getOmissions( + forceCache: forceCache, + ); List dates = List.empty(growable: true); for (var i = 0; i < days; i++) { @@ -127,8 +139,10 @@ class _HomeTimetableMonthlyScreen final meow = dates![20]; final currentMonthStart = DateTime.utc(meow.year, meow.month, 1); - final currentMonthEnd = - DateTime.utc(meow.year, meow.month + 1).subtract(Duration(days: 1)); + final currentMonthEnd = DateTime.utc( + meow.year, + meow.month + 1, + ).subtract(Duration(days: 1)); // column-major -> row-major for (var day = 0; day < 7; day++) { @@ -136,56 +150,73 @@ class _HomeTimetableMonthlyScreen final d = dates![week * 7 + day]; if (d.isBefore(currentMonthStart) || d.isAfter(currentMonthEnd)) { - ttDays.add(Column( - children: [ - Container( + ttDays.add( + Column( + children: [ + Container( width: 40, height: 40, clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( color: appStyle.colors.cardTranslucent, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), - )), - SizedBox(height: 4), - Text(d.format(widget.data.l10n, FormatMode.d), + borderRadius: BorderRadius.circular(12), + ), + ), + ), + SizedBox(height: 4), + Text( + d.format(widget.data.l10n, FormatMode.d), style: appStyle.fonts.B_16R.apply( - color: (d.weekday == DateTime.saturday || - d.weekday == DateTime.sunday) - ? appStyle.colors.errorText - : appStyle.colors.textTertiary)), - ], - )); + color: + (d.weekday == DateTime.saturday || + d.weekday == DateTime.sunday) + ? appStyle.colors.errorText + : appStyle.colors.textTertiary, + ), + ), + ], + ), + ); } else { Widget body = SizedBox(); Color bodyBgColor = appStyle.colors.a15p; - var lessonsToday = lessons!.where((lesson) => - lesson.start.isAfter(d.getMidnight()) && - lesson.end.isBefore( - d.getMidnight().add(Duration(hours: 23, minutes: 59)))); + var lessonsToday = lessons!.where( + (lesson) => + lesson.start.isAfter(d.getMidnight()) && + lesson.end.isBefore( + d.getMidnight().add(Duration(hours: 23, minutes: 59)), + ), + ); - var omissionType = lessonsToday.firstWhereOrNull((lesson) => - lesson.studentPresence != null && - lesson.studentPresence?.name != OmissionConsts.na && - lesson.studentPresence?.name != OmissionConsts.present); + var omissionType = lessonsToday.firstWhereOrNull( + (lesson) => + lesson.studentPresence != null && + lesson.studentPresence?.name != OmissionConsts.na && + lesson.studentPresence?.name != OmissionConsts.present, + ); switch (activeFilter) { case ActiveFilter.lessonNo: if (lessonsToday.isNotEmpty) { body = Center( - child: Text(lessonsToday.length.toString(), - style: appStyle.fonts.H_16px.apply( - color: omissionType != null && - (omissionType.studentPresence!.name == - OmissionConsts.absence || - omissionType.studentPresence!.name == - OmissionConsts.na) - ? appStyle.colors.errorText - : timeNow().day == d.day && - timeNow().month == d.month - ? appStyle.colors.accent - : appStyle.colors.secondary)), + child: Text( + lessonsToday.length.toString(), + style: appStyle.fonts.H_16px.apply( + color: + omissionType != null && + (omissionType.studentPresence!.name == + OmissionConsts.absence || + omissionType.studentPresence!.name == + OmissionConsts.na) + ? appStyle.colors.errorText + : timeNow().day == d.day && + timeNow().month == d.month + ? appStyle.colors.accent + : appStyle.colors.secondary, + ), + ), ); if (omissionType != null && @@ -198,13 +229,18 @@ class _HomeTimetableMonthlyScreen } break; case ActiveFilter.tests: - if (lessonsToday.firstWhereOrNull((lesson) => tests!.any( + if (lessonsToday.firstWhereOrNull( + (lesson) => tests!.any( (test) => test.lessonNumber == lesson.lessonNumber && lesson.start.isAfter(test.date.getMidnight()) && - lesson.end.isBefore(test.date - .getMidnight() - .add(Duration(hours: 23, minutes: 59))))) != + lesson.end.isBefore( + test.date.getMidnight().add( + Duration(hours: 23, minutes: 59), + ), + ), + ), + ) != null) { body = Center( child: FirkaIconWidget( @@ -265,7 +301,8 @@ class _HomeTimetableMonthlyScreen break; default: logger.fine( - "omission: ${omissionType.studentPresence!.name}"); + "omission: ${omissionType.studentPresence!.name}", + ); body = Center( child: FirkaIconWidget( FirkaIconType.majesticons, @@ -279,30 +316,37 @@ class _HomeTimetableMonthlyScreen break; } - ttDays.add(Column( - children: [ - Container( - width: 40, - height: 40, - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - color: bodyBgColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), + ttDays.add( + Column( + children: [ + Container( + width: 40, + height: 40, + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: bodyBgColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: body, ), - child: body, - ), - SizedBox(height: 4), - Text(d.format(widget.data.l10n, FormatMode.d), + SizedBox(height: 4), + Text( + d.format(widget.data.l10n, FormatMode.d), style: appStyle.fonts.B_16R.apply( - color: (d.weekday == DateTime.saturday || - d.weekday == DateTime.sunday) && - lessonsToday.isEmpty - ? appStyle.colors.errorText - : appStyle.colors.textSecondary)), - SizedBox(height: 12), - ], - )); + color: + (d.weekday == DateTime.saturday || + d.weekday == DateTime.sunday) && + lessonsToday.isEmpty + ? appStyle.colors.errorText + : appStyle.colors.textSecondary, + ), + ), + SizedBox(height: 12), + ], + ), + ); if (timeNow().getMidnight().millisecondsSinceEpoch == d.toLocal().getMidnight().millisecondsSinceEpoch) { @@ -313,8 +357,9 @@ class _HomeTimetableMonthlyScreen } return Scaffold( - backgroundColor: appStyle.colors.background, - body: Stack(children: [ + backgroundColor: appStyle.colors.background, + body: Stack( + children: [ SizedBox( width: MediaQuery.of(context).size.width, height: 74 + 16, @@ -327,8 +372,9 @@ class _HomeTimetableMonthlyScreen children: [ Text( widget.data.l10n.timetable, - style: appStyle.fonts.H_H2 - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.H_H2.apply( + color: appStyle.colors.textPrimary, + ), ), Row( children: [ @@ -377,12 +423,13 @@ class _HomeTimetableMonthlyScreen ), onTap: () { showSettingsSheet( - context, - MediaQuery.of(context).size.height * 0.4, - widget.data, - widget.data.settings - .group("settings") - .subGroup("timetable_toast")); + context, + MediaQuery.of(context).size.height * 0.4, + widget.data, + widget.data.settings + .group("settings") + .subGroup("timetable_toast"), + ); }, ), ], @@ -419,11 +466,13 @@ class _HomeTimetableMonthlyScreen }, ), Text( - now! - .format(widget.data.l10n, FormatMode.yyyymmmm) - .toLowerCase(), - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)), + now! + .format(widget.data.l10n, FormatMode.yyyymmmm) + .toLowerCase(), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), GestureDetector( child: FirkaIconWidget( FirkaIconType.icons, @@ -445,31 +494,30 @@ class _HomeTimetableMonthlyScreen }, ), ], - ) + ), ], ), ), ), TransparentPointer( - child: Padding( - padding: const EdgeInsets.only(top: 74 + 16 + 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - StaggeredGrid.count( - crossAxisCount: 7, - mainAxisSpacing: 8, - children: ttDays, - ) - ], + child: Padding( + padding: const EdgeInsets.only(top: 74 + 16 + 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + StaggeredGrid.count( + crossAxisCount: 7, + mainAxisSpacing: 8, + children: ttDays, + ), + ], + ), ), - )), + ), TransparentPointer( child: Column( children: [ - SizedBox( - height: MediaQuery.of(context).size.height / 1.3, - ), + SizedBox(height: MediaQuery.of(context).size.height / 1.3), SizedBox( width: MediaQuery.of(context).size.width, child: Row( @@ -479,70 +527,99 @@ class _HomeTimetableMonthlyScreen Row( children: [ _StatusToast( - FirkaIconWidget(FirkaIconType.majesticons, - Majesticon.clockSolid, - color: appStyle.colors.accent, size: 16), - lessons! - .where((lesson) => - lesson.start - .isAfter(currentMonthStart) && - lesson.end.isBefore(currentMonthEnd)) - .length, - activeFilter == ActiveFilter.lessonNo, () { - setState(() { - activeFilter = ActiveFilter.lessonNo; - }); - }), + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.clockSolid, + color: appStyle.colors.accent, + size: 16, + ), + lessons! + .where( + (lesson) => + lesson.start.isAfter( + currentMonthStart, + ) && + lesson.end.isBefore(currentMonthEnd), + ) + .length, + activeFilter == ActiveFilter.lessonNo, + () { + setState(() { + activeFilter = ActiveFilter.lessonNo; + }); + }, + ), _StatusToast( - FirkaIconWidget(FirkaIconType.majesticons, - Majesticon.editPen4Solid, - color: appStyle.colors.accent, size: 16), - lessons! - .where((lesson) => tests!.any((test) => - test.lessonNumber == - lesson.lessonNumber && - lesson.start - .isAfter(test.date.getMidnight()) && - lesson.end.isBefore(test.date - .getMidnight() - .add(Duration( - hours: 23, minutes: 59))))) - .length, - activeFilter == ActiveFilter.tests, () { - setState(() { - activeFilter = ActiveFilter.tests; - }); - }), + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.editPen4Solid, + color: appStyle.colors.accent, + size: 16, + ), + lessons! + .where( + (lesson) => tests!.any( + (test) => + test.lessonNumber == + lesson.lessonNumber && + lesson.start.isAfter( + test.date.getMidnight(), + ) && + lesson.end.isBefore( + test.date.getMidnight().add( + Duration(hours: 23, minutes: 59), + ), + ), + ), + ) + .length, + activeFilter == ActiveFilter.tests, + () { + setState(() { + activeFilter = ActiveFilter.tests; + }); + }, + ), _StatusToast( - FirkaIconWidget(FirkaIconType.majesticons, - Majesticon.timerLine, - color: appStyle.colors.accent, size: 16), - lessons! - .where((lesson) => - lesson.start - .isAfter(currentMonthStart) && + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.timerLine, + color: appStyle.colors.accent, + size: 16, + ), + lessons! + .where( + (lesson) => + lesson.start.isAfter( + currentMonthStart, + ) && lesson.end.isBefore(currentMonthEnd) && lesson.studentPresence != null && lesson.studentPresence?.name != OmissionConsts.na && lesson.studentPresence?.name != - OmissionConsts.present) - .length, - activeFilter == ActiveFilter.omissions, () { - setState(() { - activeFilter = ActiveFilter.omissions; - }); - }), + OmissionConsts.present, + ) + .length, + activeFilter == ActiveFilter.omissions, + () { + setState(() { + activeFilter = ActiveFilter.omissions; + }); + }, + ), ], ), - SizedBox() + SizedBox(), ], ), - ) + ), ], ), ), - ])); + ], + ), + ); } else { return Scaffold( backgroundColor: appStyle.colors.background, @@ -552,7 +629,7 @@ class _HomeTimetableMonthlyScreen Row( mainAxisAlignment: MainAxisAlignment.center, children: [DelayedSpinnerWidget()], - ) + ), ], ), ); @@ -578,8 +655,9 @@ class _StatusToast extends StatelessWidget { color: _active ? appStyle.colors.buttonSecondaryFill : appStyle.colors.cardTranslucent, - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), @@ -587,9 +665,12 @@ class _StatusToast extends StatelessWidget { children: [ _icon, SizedBox(width: 6), - Text(_count.toString(), - style: appStyle.fonts.H_16px - .apply(color: appStyle.colors.textPrimary)) + Text( + _count.toString(), + style: appStyle.fonts.H_16px.apply( + color: appStyle.colors.textPrimary, + ), + ), ], ), ), diff --git a/firka/lib/ui/phone/screens/debug/debug_screen.dart b/firka/lib/ui/phone/screens/debug/debug_screen.dart index ccb8eba..0789eb6 100644 --- a/firka/lib/ui/phone/screens/debug/debug_screen.dart +++ b/firka/lib/ui/phone/screens/debug/debug_screen.dart @@ -49,10 +49,7 @@ class _DebugScreen extends FirkaState { } return Scaffold( - appBar: AppBar( - title: const Text('Debug'), - centerTitle: true, - ), + appBar: AppBar(title: const Text('Debug'), centerTitle: true), body: Center( child: SingleChildScrollView( child: Column( @@ -60,10 +57,7 @@ class _DebugScreen extends FirkaState { children: [ const Text( 'Debug Screen', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 20), Row( @@ -76,7 +70,7 @@ class _DebugScreen extends FirkaState { useCache = value; }); }, - ) + ), ], ), const SizedBox(height: 5), @@ -90,7 +84,7 @@ class _DebugScreen extends FirkaState { debugTimeAdvance = value; }); }, - ) + ), ], ), const SizedBox(height: 20), @@ -111,17 +105,22 @@ class _DebugScreen extends FirkaState { ElevatedButton( onPressed: () async { var d = await showDatePicker( - context: context, - firstDate: DateTime.now().subtract(Duration(days: 365)), - lastDate: DateTime.now().add(Duration(days: 365))); + context: context, + firstDate: DateTime.now().subtract(Duration(days: 365)), + lastDate: DateTime.now().add(Duration(days: 365)), + ); + if (!context.mounted) return; var t = await showTimePicker( - context: context, initialTime: TimeOfDay.now()); + context: context, + initialTime: TimeOfDay.now(), + ); + if (!context.mounted) return; if (d != null && t != null) { - debugFakeTime = d - .getMidnight() - .add(Duration(hours: t.hour, minutes: t.minute)); + debugFakeTime = d.getMidnight().add( + Duration(hours: t.hour, minutes: t.minute), + ); debugSetAt = DateTime.now(); } @@ -140,21 +139,24 @@ class _DebugScreen extends FirkaState { ElevatedButton( onPressed: () async { logger.finest( - "getStudent(): ${await widget.data.client.getStudent(forceCache: useCache)}"); + "getStudent(): ${await widget.data.client.getStudent(forceCache: useCache)}", + ); }, child: const Text('getStudent()'), ), ElevatedButton( onPressed: () async { logger.finest( - "getNoticeBoard(): ${await widget.data.client.getNoticeBoard(forceCache: useCache)}"); + "getNoticeBoard(): ${await widget.data.client.getNoticeBoard(forceCache: useCache)}", + ); }, child: const Text('getNoticeBoard()'), ), ElevatedButton( onPressed: () async { logger.finest( - "getGrades(): ${await widget.data.client.getGrades(forceCache: useCache)}"); + "getGrades(): ${await widget.data.client.getGrades(forceCache: useCache)}", + ); }, child: const Text('getGrades()'), ), @@ -166,28 +168,32 @@ class _DebugScreen extends FirkaState { var end = now.add(Duration(days: 7)); logger.finest( - "getLessons(): ${await widget.data.client.getTimeTable(start, end, forceCache: useCache)}"); + "getLessons(): ${await widget.data.client.getTimeTable(start, end, forceCache: useCache)}", + ); }, child: const Text('getLessons()'), ), ElevatedButton( onPressed: () async { logger.finest( - "getHomework(): ${await widget.data.client.getHomework(forceCache: useCache)}"); + "getHomework(): ${await widget.data.client.getHomework(forceCache: useCache)}", + ); }, child: const Text('getHomework()'), ), ElevatedButton( onPressed: () async { logger.finest( - "getTests(): ${await widget.data.client.getTests(forceCache: useCache)}"); + "getTests(): ${await widget.data.client.getTests(forceCache: useCache)}", + ); }, child: const Text('getTests()'), ), ElevatedButton( onPressed: () async { logger.finest( - "getOmissions(): ${await widget.data.client.getOmissions(forceCache: useCache)}"); + "getOmissions(): ${await widget.data.client.getOmissions(forceCache: useCache)}", + ); }, child: const Text('getOmissions()'), ), @@ -199,10 +205,11 @@ class _DebugScreen extends FirkaState { ), ElevatedButton( style: ButtonStyle( - backgroundColor: WidgetStateProperty< - Color>.fromMap({ - WidgetState.any: Colors.red, - }), + backgroundColor: WidgetStateProperty.fromMap( + { + WidgetState.any: Colors.red, + }, + ), ), onPressed: () async { var isar = widget.data.isar; @@ -213,12 +220,16 @@ class _DebugScreen extends FirkaState { widget.data.tokens = List.empty(growable: true); + if (!context.mounted) return; Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DefaultAssetBundle( - bundle: FirkaBundle(), - child: LoginScreen(widget.data)))); + context, + MaterialPageRoute( + builder: (context) => DefaultAssetBundle( + bundle: FirkaBundle(), + child: LoginScreen(widget.data), + ), + ), + ); }, child: const Text('wipe users'), ), @@ -237,9 +248,11 @@ class _DebugScreen extends FirkaState { ), Center( child: FirkaIconWidget( - FirkaIconType.majesticons, getIconData(e), - color: Colors.black), - ) + FirkaIconType.majesticons, + getIconData(e), + color: Colors.black, + ), + ), ], ); }).toList(), diff --git a/firka/lib/ui/phone/screens/home/beta_screen.dart b/firka/lib/ui/phone/screens/home/beta_screen.dart index 8dd25fb..0d9c82a 100644 --- a/firka/lib/ui/phone/screens/home/beta_screen.dart +++ b/firka/lib/ui/phone/screens/home/beta_screen.dart @@ -45,28 +45,32 @@ class _BetaScreenState extends FirkaState { return Scaffold( backgroundColor: appStyle.colors.background, body: SafeArea( - child: Column( - children: [ - Spacer(), - Center( - child: Text(widget.data.l10n.beta_title, - style: appStyle.fonts.H_H1 - .apply(color: appStyle.colors.textPrimary)), - ), - SizedBox(height: 32), - Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + Spacer(), + Center( child: Text( - widget.data.l10n.beta_body, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), - textAlign: TextAlign.center, + widget.data.l10n.beta_title, + style: appStyle.fonts.H_H1.apply( + color: appStyle.colors.textPrimary, + ), ), ), - ), - Spacer(), - Padding( + SizedBox(height: 32), + Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Text( + widget.data.l10n.beta_body, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + textAlign: TextAlign.center, + ), + ), + ), + Spacer(), + Padding( padding: const EdgeInsets.only(left: 32, right: 32, bottom: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -75,10 +79,13 @@ class _BetaScreenState extends FirkaState { child: FirkaButton( text: widget.data.l10n.cancel, bgColor: appStyle.colors.buttonSecondaryFill, - fontStyle: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimaryLight), - icon: Icon(Icons.close, - color: appStyle.colors.textPrimaryLight), + fontStyle: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimaryLight, + ), + icon: Icon( + Icons.close, + color: appStyle.colors.textPrimaryLight, + ), ), onTap: () { exit(0); @@ -92,10 +99,13 @@ class _BetaScreenState extends FirkaState { bgColor: counter == 0 ? appStyle.colors.accent : appStyle.colors.secondary, - fontStyle: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimaryLight), - icon: Icon(Icons.check, - color: appStyle.colors.textPrimaryLight), + fontStyle: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimaryLight, + ), + icon: Icon( + Icons.check, + color: appStyle.colors.textPrimaryLight, + ), ), onTap: () async { if (counter != 0) return; @@ -112,18 +122,21 @@ class _BetaScreenState extends FirkaState { await widget.data.settings .group("settings")["beta_warning"]! .postUpdate(); + if (!context.mounted) return; Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( - builder: (context) => - HomeScreen(widget.data, false)), + builder: (context) => HomeScreen(widget.data, false), + ), (route) => false, ); }, ), ], - )), - ], - )), + ), + ), + ], + ), + ), ); } diff --git a/firka/lib/ui/phone/screens/home/home_bottom_nav.dart b/firka/lib/ui/phone/screens/home/home_bottom_nav.dart index e69de29..8b13789 100644 --- a/firka/lib/ui/phone/screens/home/home_bottom_nav.dart +++ b/firka/lib/ui/phone/screens/home/home_bottom_nav.dart @@ -0,0 +1 @@ + diff --git a/firka/lib/ui/phone/screens/home/home_screen.dart b/firka/lib/ui/phone/screens/home/home_screen.dart index 39bcc81..547ecd3 100644 --- a/firka/lib/ui/phone/screens/home/home_screen.dart +++ b/firka/lib/ui/phone/screens/home/home_screen.dart @@ -44,11 +44,18 @@ class PageNavData { String subjectName; String? subjectId; String? subjectCategory; - PageNavData(this.page, this.subPageParams, this.subjectName, {this.subjectId, this.subjectCategory}); + PageNavData( + this.page, + this.subPageParams, + this.subjectName, { + this.subjectId, + this.subjectCategory, + }); } -final ValueNotifier pageNavNotifier = - ValueNotifier(PageNavData(HomePage.home, null, "")); +final ValueNotifier pageNavNotifier = ValueNotifier( + PageNavData(HomePage.home, null, ""), +); bool forcedNavPage = false; // TODO: this is a hack! class HomeScreen extends StatefulWidget { @@ -89,8 +96,9 @@ class _HomeScreenState extends FirkaState { bool _preloadDone = false; int forcedNav = 0; bool _didRunSecondaryICloudRecovery = false; - final RefreshController _refreshController = - RefreshController(initialRefresh: false); + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); ActiveToastType activeToast = ActiveToastType.none; @@ -214,7 +222,8 @@ class _HomeScreenState extends FirkaState { ); final now = DateTime.now(); - final shouldRunRecovery = KretaClient.needsReauth || + final shouldRunRecovery = + KretaClient.needsReauth || activeToken == null || activeToken.expiryDate == null || activeToken.expiryDate!.isBefore(now.add(const Duration(seconds: 60))); @@ -224,7 +233,8 @@ class _HomeScreenState extends FirkaState { } logger.info( - '[Home] Secondary iCloud recovery scheduled (5s delay, startup safety pass)'); + '[Home] Secondary iCloud recovery scheduled (5s delay, startup safety pass)', + ); await Future.delayed(const Duration(seconds: 5)); if (_disposed) return; @@ -282,12 +292,15 @@ class _HomeScreenState extends FirkaState { if (Platform.isAndroid) { await WidgetCacheHelper.updateWidgetCache(appStyle, widget.data.client); await HomeWidget.updateWidget( - qualifiedAndroidName: "app.firka.naplo.glance.TimetableWidget"); + qualifiedAndroidName: "app.firka.naplo.glance.TimetableWidget", + ); } if (Platform.isIOS) { await WidgetCacheHelper.refreshIOSWidgets( - widget.data.client, widget.data.settings); + widget.data.client, + widget.data.settings, + ); final token = pickActiveToken( tokens: widget.data.tokens, @@ -372,14 +385,18 @@ class _HomeScreenState extends FirkaState { children: [ Text( widget.data.l10n.api_error, - style: appStyle.fonts.B_16SB - .copyWith(color: appStyle.colors.errorText), + style: appStyle.fonts.B_16SB.copyWith( + color: appStyle.colors.errorText, + ), ), SizedBox(width: 8), GestureDetector( - child: FirkaIconWidget(FirkaIconType.majesticons, - Majesticon.questionCircleSolid, - color: appStyle.colors.errorAccent, size: 24), + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.questionCircleSolid, + color: appStyle.colors.errorAccent, + size: 24, + ), onTap: () { var stackTrace = ""; if (e is Error && e.stackTrace != null) { @@ -419,11 +436,13 @@ class _HomeScreenState extends FirkaState { widget.data.client .getTimeTableStream( - midnight, midnight.add(Duration(hours: 23, minutes: 59)), - cacheOnly: false) + midnight, + midnight.add(Duration(hours: 23, minutes: 59)), + cacheOnly: false, + ) .forEach((lessons) { - lessonsFetched++; - }); + lessonsFetched++; + }); widget.data.client.getNoticeBoardStream(cacheOnly: false).forEach((items) { noticeBoardFetched++; @@ -617,9 +636,10 @@ class _HomeScreenState extends FirkaState { SizedBox(width: 16), Text( widget.data.l10n.refreshing, - style: appStyle.fonts.B_16SB - .copyWith(color: appStyle.colors.textPrimary), - ) + style: appStyle.fonts.B_16SB.copyWith( + color: appStyle.colors.textPrimary, + ), + ), ], ), ), @@ -631,111 +651,118 @@ class _HomeScreenState extends FirkaState { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); return DefaultAssetBundle( - bundle: FirkaBundle(), - child: PopScope( - canPop: canPop || subPageActive.value, - child: Scaffold( - backgroundColor: appStyle.colors.background, - body: SafeArea( - child: SizedBox( - height: MediaQuery.of(context).size.height, - child: Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: RefreshConfiguration( - springDescription: SpringDescription( - mass: 1.9, stiffness: 85, damping: 16), - child: SmartScroll( - controller: _refreshController, - onLoading: _onLoading, - onRefresh: _onRefresh, - header: MaterialClassicHeader( - color: appStyle.colors.accent, - backgroundColor: appStyle.colors.background, - offset: 24, - ), - physics: ClampingScrollPhysics(), - child: PageView( - controller: _pageController, - physics: ClampingScrollPhysics(), - onPageChanged: (index) { - HapticFeedback.heavyImpact(); + bundle: FirkaBundle(), + child: PopScope( + canPop: canPop || subPageActive.value, + child: Scaffold( + backgroundColor: appStyle.colors.background, + body: SafeArea( + child: SizedBox( + height: MediaQuery.of(context).size.height, + child: Stack( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: RefreshConfiguration( + springDescription: SpringDescription( + mass: 1.9, + stiffness: 85, + damping: 16, + ), + child: SmartScroll( + controller: _refreshController, + onLoading: _onLoading, + onRefresh: _onRefresh, + header: MaterialClassicHeader( + color: appStyle.colors.accent, + backgroundColor: appStyle.colors.background, + offset: 24, + ), + physics: ClampingScrollPhysics(), + child: PageView( + controller: _pageController, + physics: ClampingScrollPhysics(), + onPageChanged: (index) { + HapticFeedback.heavyImpact(); - if (forcedNav > 0) { - forcedNav--; + if (forcedNav > 0) { + forcedNav--; - if (previousPages.isEmpty) { - canPop = true; - } - return; - } + if (previousPages.isEmpty) { + canPop = true; + } + return; + } - setState(() { - previousPages.add(homeScreenPage); - canPop = false; - homeScreenPage = HomePage.values[index]; - }); - }, - children: [ - HomeSubPage( - HomePage.home, - widget.data, - widget.updateNotifier, - widget.updateFinishedNotifier, - ), - HomeSubPage( - HomePage.grades, - widget.data, - widget.updateNotifier, - widget.updateFinishedNotifier, - ), - HomeSubPage( - HomePage.timetable, - widget.data, - widget.updateNotifier, - widget.updateFinishedNotifier, - ), - ], - ))), - ), - Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - colors: [ - appStyle.colors.background, - appStyle.colors.background - .withValues(alpha: 0.0), + setState(() { + previousPages.add(homeScreenPage); + canPop = false; + homeScreenPage = HomePage.values[index]; + }); + }, + children: [ + HomeSubPage( + HomePage.home, + widget.data, + widget.updateNotifier, + widget.updateFinishedNotifier, + ), + HomeSubPage( + HomePage.grades, + widget.data, + widget.updateNotifier, + widget.updateFinishedNotifier, + ), + HomeSubPage( + HomePage.timetable, + widget.data, + widget.updateNotifier, + widget.updateFinishedNotifier, + ), ], - stops: const [0.0, 1.0], ), ), - width: MediaQuery.of(context).size.width, - child: Padding( - padding: const EdgeInsets.fromLTRB(55, 0, 55, 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // Home Button - BottomNavIconWidget(() { + ), + ), + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [ + appStyle.colors.background, + appStyle.colors.background.withValues(alpha: 0.0), + ], + stops: const [0.0, 1.0], + ), + ), + width: MediaQuery.of(context).size.width, + child: Padding( + padding: const EdgeInsets.fromLTRB(55, 0, 55, 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Home Button + BottomNavIconWidget( + () { if (homeScreenPage != HomePage.home) { if (previousPages.length > 1 && forcedNavPage) { forcedNavPage = false; - _pageController.jumpToPage(previousPages[ - previousPages.length - 2] - .index); + _pageController.jumpToPage( + previousPages[previousPages.length - 2] + .index, + ); } if (previousPages.length > 1 && forcedNavPage) { forcedNavPage = false; - _pageController.jumpToPage(previousPages[ - previousPages.length - 2] - .index); + _pageController.jumpToPage( + previousPages[previousPages.length - 2] + .index, + ); } _pageController.animateToPage( HomePage.home.index, @@ -744,24 +771,27 @@ class _HomeScreenState extends FirkaState { ); } }, - homeScreenPage == HomePage.home, - homeScreenPage == HomePage.home - ? Majesticon.homeSolid - : Majesticon.homeLine, - widget.data.l10n.home, - homeScreenPage == HomePage.home - ? appStyle.colors.accent - : appStyle.colors.secondary, - appStyle.colors.textPrimary), - // Grades Button - BottomNavIconWidget(() { + homeScreenPage == HomePage.home, + homeScreenPage == HomePage.home + ? Majesticon.homeSolid + : Majesticon.homeLine, + widget.data.l10n.home, + homeScreenPage == HomePage.home + ? appStyle.colors.accent + : appStyle.colors.secondary, + appStyle.colors.textPrimary, + ), + // Grades Button + BottomNavIconWidget( + () { if (homeScreenPage != HomePage.grades) { if (previousPages.length > 1 && forcedNavPage) { forcedNavPage = false; - _pageController.jumpToPage(previousPages[ - previousPages.length - 2] - .index); + _pageController.jumpToPage( + previousPages[previousPages.length - 2] + .index, + ); } _pageController.animateToPage( HomePage.grades.index, @@ -770,24 +800,27 @@ class _HomeScreenState extends FirkaState { ); } }, - homeScreenPage == HomePage.grades, - homeScreenPage == HomePage.grades - ? Majesticon.bookmarkSolid - : Majesticon.bookmarkLine, - widget.data.l10n.grades, - homeScreenPage == HomePage.grades - ? appStyle.colors.accent - : appStyle.colors.secondary, - appStyle.colors.textPrimary), - // Timetable Button - BottomNavIconWidget(() { + homeScreenPage == HomePage.grades, + homeScreenPage == HomePage.grades + ? Majesticon.bookmarkSolid + : Majesticon.bookmarkLine, + widget.data.l10n.grades, + homeScreenPage == HomePage.grades + ? appStyle.colors.accent + : appStyle.colors.secondary, + appStyle.colors.textPrimary, + ), + // Timetable Button + BottomNavIconWidget( + () { if (homeScreenPage != HomePage.timetable) { if (previousPages.length > 1 && forcedNavPage) { forcedNavPage = false; - _pageController.jumpToPage(previousPages[ - previousPages.length - 2] - .index); + _pageController.jumpToPage( + previousPages[previousPages.length - 2] + .index, + ); } _pageController.animateToPage( HomePage.timetable.index, @@ -796,73 +829,76 @@ class _HomeScreenState extends FirkaState { ); } }, - homeScreenPage == HomePage.timetable, - homeScreenPage == HomePage.timetable - ? Majesticon.calendarSolid - : Majesticon.calendarLine, - widget.data.l10n.timetable, - homeScreenPage == HomePage.timetable - ? appStyle.colors.accent - : appStyle.colors.secondary, - appStyle.colors.textPrimary), - // More Button - BottomNavIconWidget( - () { - HapticFeedback.lightImpact(); - showExtrasBottomSheet(context, widget.data); - }, - false, - widget.data.profilePicture != null - ? widget.data.profilePicture! - : Majesticon.menuLine, - widget.data.l10n.other, - appStyle.colors.secondary, - appStyle.colors.textPrimary, - isProfilePicture: - widget.data.profilePicture != null, - ), - ], - ), + homeScreenPage == HomePage.timetable, + homeScreenPage == HomePage.timetable + ? Majesticon.calendarSolid + : Majesticon.calendarLine, + widget.data.l10n.timetable, + homeScreenPage == HomePage.timetable + ? appStyle.colors.accent + : appStyle.colors.secondary, + appStyle.colors.textPrimary, + ), + // More Button + BottomNavIconWidget( + () { + HapticFeedback.lightImpact(); + showExtrasBottomSheet(context, widget.data); + }, + false, + widget.data.profilePicture != null + ? widget.data.profilePicture! + : Majesticon.menuLine, + widget.data.l10n.other, + appStyle.colors.secondary, + appStyle.colors.textPrimary, + isProfilePicture: + widget.data.profilePicture != null, + ), + ], ), ), - ], - ), - toast ?? SizedBox(), - ], - ), + ), + ], + ), + toast ?? SizedBox(), + ], ), ), ), - onPopInvokedWithResult: (_, __) { - if (subPageActive.value) { - subPageBack.update(); - return; - } + ), + onPopInvokedWithResult: (bool didPop, dynamic result) { + if (subPageActive.value) { + subPageBack.update(); + return; + } - if (previousPages.isNotEmpty && - homeScreenPage != previousPages.last) { - setState(() { - homeScreenPage = previousPages.removeLast(); + if (previousPages.isNotEmpty && + homeScreenPage != previousPages.last) { + setState(() { + homeScreenPage = previousPages.removeLast(); - forcedNav++; - _pageController.animateToPage( - homeScreenPage.index, - duration: Duration(milliseconds: 175), - curve: Curves.easeInOut, - ); - canPop = previousPages.isEmpty; - }); - } - }, - )); + forcedNav++; + _pageController.animateToPage( + homeScreenPage.index, + duration: Duration(milliseconds: 175), + curve: Curves.easeInOut, + ); + canPop = previousPages.isEmpty; + }); + } + }, + ), + ); } @override void dispose() { _pageController.dispose(); widget.data.settingsUpdateNotifier.removeListener(settingsUpdateListener); - widget.data.profilePictureUpdateNotifier - .removeListener(settingsUpdateListener); + widget.data.profilePictureUpdateNotifier.removeListener( + settingsUpdateListener, + ); widget.data.profilePictureUpdateNotifier.removeListener(() { if (mounted) setState(() {}); @@ -886,8 +922,12 @@ class HomeSubPage extends StatefulWidget { final UpdateNotifier _updateFinishNotifier; const HomeSubPage( - this.page, this.data, this._updateNotifier, this._updateFinishNotifier, - {super.key}); + this.page, + this.data, + this._updateNotifier, + this._updateFinishNotifier, { + super.key, + }); @override State createState() => _HomeSubPage(); @@ -910,43 +950,101 @@ class _HomeSubPage extends State { if (subPageData == null) { switch (p) { case HomePage.home: - return HomeMainScreen(widget.data, widget._updateNotifier, - widget._updateFinishNotifier); + return HomeMainScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + ); case HomePage.grades: - return PageWithSubPages([ - (cb) => HomeGradesScreen(widget.data, widget._updateNotifier, - widget._updateFinishNotifier, cb), - (cb) => HomeGradesSubjectScreen(widget.data, widget._updateNotifier, - widget._updateFinishNotifier, cb), - ], subPageActive, subPageBack, pageIndex: 0); + return PageWithSubPages( + [ + (cb) => HomeGradesScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + (cb) => HomeGradesSubjectScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + ], + subPageActive, + subPageBack, + pageIndex: 0, + ); case HomePage.timetable: - return PageWithSubPages([ - (cb) => HomeTimetableScreen(widget.data, widget._updateNotifier, - widget._updateFinishNotifier, cb), - (cb) => HomeTimetableMonthlyScreen(widget.data, - widget._updateNotifier, widget._updateFinishNotifier, cb) - ], subPageActive, subPageBack, pageIndex: 0); + return PageWithSubPages( + [ + (cb) => HomeTimetableScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + (cb) => HomeTimetableMonthlyScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + ], + subPageActive, + subPageBack, + pageIndex: 0, + ); } } else { switch (p) { case HomePage.home: - return HomeMainScreen(widget.data, widget._updateNotifier, - widget._updateFinishNotifier); + return HomeMainScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + ); case HomePage.grades: activeSubjectUid = subPageData!; - return PageWithSubPages([ - (cb) => HomeGradesSubjectScreen(widget.data, widget._updateNotifier, - widget._updateFinishNotifier, cb), - (cb) => HomeGradesScreen(widget.data, widget._updateNotifier, - widget._updateFinishNotifier, cb), - ], subPageActive, subPageBack, pageIndex: 0); + return PageWithSubPages( + [ + (cb) => HomeGradesSubjectScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + (cb) => HomeGradesScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + ], + subPageActive, + subPageBack, + pageIndex: 0, + ); case HomePage.timetable: - return PageWithSubPages([ - (cb) => HomeTimetableMonthlyScreen(widget.data, - widget._updateNotifier, widget._updateFinishNotifier, cb), - (cb) => HomeTimetableScreen(widget.data, widget._updateNotifier, - widget._updateFinishNotifier, cb) - ], subPageActive, subPageBack, pageIndex: 0); + return PageWithSubPages( + [ + (cb) => HomeTimetableMonthlyScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + (cb) => HomeTimetableScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + ], + subPageActive, + subPageBack, + pageIndex: 0, + ); } } } @@ -954,21 +1052,50 @@ class _HomeSubPage extends State { switch (widget.page) { case HomePage.home: return HomeMainScreen( - widget.data, widget._updateNotifier, widget._updateFinishNotifier); + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + ); case HomePage.grades: - return PageWithSubPages([ - (cb) => HomeGradesScreen(widget.data, widget._updateNotifier, - widget._updateFinishNotifier, cb), - (cb) => HomeGradesSubjectScreen(widget.data, widget._updateNotifier, - widget._updateFinishNotifier, cb), - ], subPageActive, subPageBack, pageIndex: 0); + return PageWithSubPages( + [ + (cb) => HomeGradesScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + (cb) => HomeGradesSubjectScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + ], + subPageActive, + subPageBack, + pageIndex: 0, + ); case HomePage.timetable: - return PageWithSubPages([ - (cb) => HomeTimetableScreen(widget.data, widget._updateNotifier, - widget._updateFinishNotifier, cb), - (cb) => HomeTimetableMonthlyScreen(widget.data, - widget._updateNotifier, widget._updateFinishNotifier, cb) - ], subPageActive, subPageBack, pageIndex: 0); + return PageWithSubPages( + [ + (cb) => HomeTimetableScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + (cb) => HomeTimetableMonthlyScreen( + widget.data, + widget._updateNotifier, + widget._updateFinishNotifier, + cb, + ), + ], + subPageActive, + subPageBack, + pageIndex: 0, + ); } } @@ -986,7 +1113,7 @@ class _HomeSubPage extends State { forcedHomePage = pageNavNotifier.value.page; subPageData = pageNavNotifier.value.subPageParams; subjectName = pageNavNotifier.value.subjectName; - subjectId = pageNavNotifier.value.subjectId ?? ""; + subjectId = pageNavNotifier.value.subjectId ?? ""; subjectCategory = pageNavNotifier.value.subjectCategory ?? ""; previousPages.add(homeScreenPage); homeScreenPage = forcedHomePage!; diff --git a/firka/lib/ui/phone/screens/live_activity/full_privacy_policy_screen.dart b/firka/lib/ui/phone/screens/live_activity/full_privacy_policy_screen.dart index e7c8110..eee8430 100644 --- a/firka/lib/ui/phone/screens/live_activity/full_privacy_policy_screen.dart +++ b/firka/lib/ui/phone/screens/live_activity/full_privacy_policy_screen.dart @@ -12,10 +12,12 @@ class FullPrivacyPolicyScreen extends StatefulWidget { const FullPrivacyPolicyScreen({required this.data, super.key}); @override - State createState() => _FullPrivacyPolicyScreenState(); + State createState() => + _FullPrivacyPolicyScreenState(); } -class _FullPrivacyPolicyScreenState extends FirkaState { +class _FullPrivacyPolicyScreenState + extends FirkaState { @override Widget build(BuildContext context) { return Scaffold( @@ -41,8 +43,9 @@ class _FullPrivacyPolicyScreenState extends FirkaState Expanded( child: Text( widget.data.l10n.la_privacy_header, - style: appStyle.fonts.H_H2 - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.H_H2.apply( + color: appStyle.colors.textPrimary, + ), ), ), ], @@ -58,8 +61,9 @@ class _FullPrivacyPolicyScreenState extends FirkaState children: [ Text( widget.data.l10n.la_privacy_intro, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), ), SizedBox(height: 24), _buildPrivacySection( @@ -85,14 +89,16 @@ class _FullPrivacyPolicyScreenState extends FirkaState SizedBox(height: 16), Text( widget.data.l10n.la_privacy_footer, - style: appStyle.fonts.B_12R - .apply(color: appStyle.colors.textTertiary), + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.textTertiary, + ), ), SizedBox(height: 8), Text( widget.data.l10n.la_privacy_contact, - style: appStyle.fonts.B_12R - .apply(color: appStyle.colors.textTertiary), + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.textTertiary, + ), ), SizedBox(height: 24), ], @@ -114,14 +120,16 @@ class _FullPrivacyPolicyScreenState extends FirkaState children: [ Text( title, - style: - appStyle.fonts.H_16px.apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.H_16px.apply( + color: appStyle.colors.textPrimary, + ), ), SizedBox(height: 10), Text( body, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), ), ], ), diff --git a/firka/lib/ui/phone/screens/live_activity/live_activity_consent_screen.dart b/firka/lib/ui/phone/screens/live_activity/live_activity_consent_screen.dart index 2720d57..ab9a166 100644 --- a/firka/lib/ui/phone/screens/live_activity/live_activity_consent_screen.dart +++ b/firka/lib/ui/phone/screens/live_activity/live_activity_consent_screen.dart @@ -14,10 +14,7 @@ import '../../../../helpers/firka_state.dart'; class LiveActivityConsentScreen extends StatefulWidget { final AppInitialization data; - const LiveActivityConsentScreen({ - required this.data, - super.key, - }); + const LiveActivityConsentScreen({required this.data, super.key}); @override State createState() => @@ -48,8 +45,9 @@ class _LiveActivityConsentScreenState Expanded( child: Text( widget.data.l10n.la_title, - style: appStyle.fonts.H_H1 - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.H_H1.apply( + color: appStyle.colors.textPrimary, + ), ), ), ], @@ -59,8 +57,9 @@ class _LiveActivityConsentScreenState width: double.infinity, child: Text( widget.data.l10n.la_subtitle, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), maxLines: null, softWrap: true, ), @@ -89,8 +88,9 @@ class _LiveActivityConsentScreenState Expanded( child: Text( widget.data.l10n.la_privacy_title, - style: appStyle.fonts.H_16px - .apply(color: appStyle.colors.warningText), + style: appStyle.fonts.H_16px.apply( + color: appStyle.colors.warningText, + ), ), ), ], @@ -98,8 +98,9 @@ class _LiveActivityConsentScreenState SizedBox(height: 8), Text( widget.data.l10n.la_privacy_required, - style: appStyle.fonts.B_14R - .apply(color: appStyle.colors.warningText), + style: appStyle.fonts.B_14R.apply( + color: appStyle.colors.warningText, + ), softWrap: true, ), ], @@ -115,8 +116,9 @@ class _LiveActivityConsentScreenState children: [ Text( widget.data.l10n.la_privacy_intro, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), ), SizedBox(height: 20), _buildPrivacySummaryItem( @@ -138,10 +140,12 @@ class _LiveActivityConsentScreenState GestureDetector( onTap: () { Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - FullPrivacyPolicyScreen(data: widget.data))); + context, + MaterialPageRoute( + builder: (context) => + FullPrivacyPolicyScreen(data: widget.data), + ), + ); }, child: FirkaCard( color: appStyle.colors.buttonSecondaryFill, @@ -149,8 +153,9 @@ class _LiveActivityConsentScreenState center: [ Text( widget.data.l10n.la_learn_more, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textSecondary, + ), ), SizedBox(width: 8), FirkaIconWidget( @@ -179,7 +184,8 @@ class _LiveActivityConsentScreenState text: widget.data.l10n.la_accept, bgColor: appStyle.colors.accent, fontStyle: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textPrimary), + color: appStyle.colors.textPrimary, + ), ), ), SizedBox(height: 12), @@ -190,8 +196,9 @@ class _LiveActivityConsentScreenState child: FirkaButton( text: widget.data.l10n.la_decline, bgColor: appStyle.colors.buttonSecondaryFill, - fontStyle: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), + fontStyle: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), ), ), ], @@ -226,14 +233,16 @@ class _LiveActivityConsentScreenState children: [ Text( title, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), ), SizedBox(height: 6), Text( description, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), ), ], ), diff --git a/firka/lib/ui/phone/screens/login/login_screen.dart b/firka/lib/ui/phone/screens/login/login_screen.dart index fd4699c..4c8cd30 100644 --- a/firka/lib/ui/phone/screens/login/login_screen.dart +++ b/firka/lib/ui/phone/screens/login/login_screen.dart @@ -12,8 +12,10 @@ import '../../../../helpers/firka_state.dart'; import '../../../../helpers/image_preloader.dart'; import '../../../model/style.dart'; import '../../../widget/delayed_spinner.dart'; + // TODO: Replace these with actual privacy policy URLs -const String _privacyUrlHungarian = 'https://github.com/QwIT-Development/privacy-policy/blob/master/README.md'; +const String _privacyUrlHungarian = + 'https://github.com/QwIT-Development/privacy-policy/blob/master/README.md'; const String _privacyUrlOther = 'https://firka.app/privacy'; class LoginScreen extends StatefulWidget { @@ -107,7 +109,8 @@ class _LoginScreenState extends FirkaState { final carousel = isLightMode.value ? "carousel" : "carousel_dark"; - final paddingWidthHorizontal = MediaQuery.of(context).size.width - + final paddingWidthHorizontal = + MediaQuery.of(context).size.width - MediaQuery.of(context).size.width * 0.95; List> slides = [ { @@ -155,7 +158,7 @@ class _LoginScreenState extends FirkaState { 'scale': 1.35, 'x': -5.00, 'y': 80.00, - } + }, //TODO: implement simulated physics so that the little boxes can move like the phone moves ]; @@ -163,260 +166,286 @@ class _LoginScreenState extends FirkaState { home: Scaffold( backgroundColor: appStyle.colors.background, body: SafeArea( - child: Stack( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 16), - Padding( - padding: EdgeInsets.only(left: paddingWidthHorizontal), - child: Row( - children: [ - Container( - width: 30, - height: 30, - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - image: DecorationImage( - image: PreloadedImageProvider( - DefaultAssetBundle.of(context), - 'assets/images/logos/colored_logo.webp'), - fit: BoxFit.cover, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10)), - ), - ), - const SizedBox(width: 8), - Text( - 'Firka Napló', - style: appStyle.fonts.H_18px - .copyWith(color: appStyle.colors.textPrimary), - ), - ], - ), - ), - const SizedBox(height: 16), - Expanded( - child: CarouselSlider.builder( - itemCount: slides.length, - itemBuilder: (context, index, realIndex) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + Padding( + padding: EdgeInsets.only(left: paddingWidthHorizontal), + child: Row( children: [ - Padding( - padding: EdgeInsetsGeometry.symmetric( - horizontal: paddingWidthHorizontal), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - slides[index]['title']! as String, - style: appStyle.fonts.H_18px.copyWith( - color: appStyle.colors.textPrimary), - softWrap: true, - overflow: TextOverflow.visible, + Container( + width: 30, + height: 30, + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + image: DecorationImage( + image: PreloadedImageProvider( + DefaultAssetBundle.of(context), + 'assets/images/logos/colored_logo.webp', ), - const SizedBox(height: 8), - Text( - slides[index]['subtitle']! as String, - style: appStyle.fonts.B_16R.copyWith( - color: appStyle.colors.textPrimary), - softWrap: true, - overflow: TextOverflow.visible, - ), - ], + fit: BoxFit.cover, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), ), ), - Stack( - children: [ - slides[index]['background']! == '' - ? SizedBox() - : ClipRect( - clipper: - ImageClipper(MediaQuery.of(context)), - child: Transform.rotate( - angle: -math.pi / - (slides[index]['rotation']! - as double), - child: Transform.translate( - offset: Offset( + const SizedBox(width: 8), + Text( + 'Firka Napló', + style: appStyle.fonts.H_18px.copyWith( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + ), + const SizedBox(height: 16), + Expanded( + child: CarouselSlider.builder( + itemCount: slides.length, + itemBuilder: (context, index, realIndex) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsGeometry.symmetric( + horizontal: paddingWidthHorizontal, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + slides[index]['title']! as String, + style: appStyle.fonts.H_18px.copyWith( + color: appStyle.colors.textPrimary, + ), + softWrap: true, + overflow: TextOverflow.visible, + ), + const SizedBox(height: 8), + Text( + slides[index]['subtitle']! as String, + style: appStyle.fonts.B_16R.copyWith( + color: appStyle.colors.textPrimary, + ), + softWrap: true, + overflow: TextOverflow.visible, + ), + ], + ), + ), + Stack( + children: [ + slides[index]['background']! == '' + ? SizedBox() + : ClipRect( + clipper: ImageClipper( + MediaQuery.of(context), + ), + child: Transform.rotate( + angle: + -math.pi / + (slides[index]['rotation']! + as double), + child: Transform.translate( + offset: Offset( slides[index]['x'] as double, - slides[index]['y'] as double), - child: SizedBox( - width: - MediaQuery.of(context).size.width, - child: Transform.scale( - scale: slides[index]['scale'] - as double, + slides[index]['y'] as double, + ), + child: SizedBox( + width: MediaQuery.of( + context, + ).size.width, + child: Transform.scale( + scale: + slides[index]['scale'] + as double, child: Image.asset( slides[index]['background']! as String, bundle: DefaultAssetBundle.of( - context), + context, + ), fit: BoxFit.contain, width: double.infinity, - )), + ), + ), + ), ), ), - )), - Column( - children: [ - SizedBox(height: 73), - Padding( - padding: EdgeInsetsGeometry.symmetric( - horizontal: 18), - child: SizedBox( - width: MediaQuery.of(context).size.width, - child: Image( - image: PreloadedImageProvider( + ), + Column( + children: [ + SizedBox(height: 73), + Padding( + padding: EdgeInsetsGeometry.symmetric( + horizontal: 18, + ), + child: SizedBox( + width: MediaQuery.of(context).size.width, + child: Image( + image: PreloadedImageProvider( DefaultAssetBundle.of(context), - slides[index]['picture']! as String), - fit: BoxFit.cover, - width: double.infinity, - alignment: Alignment.center, + slides[index]['picture']! as String, + ), + fit: BoxFit.cover, + width: double.infinity, + alignment: Alignment.center, + ), ), ), - ), - ], - ), - slides[index]['foreground']! == '' - ? SizedBox() - : SizedBox( - width: MediaQuery.of(context).size.width, - child: ClipRect( - clipBehavior: Clip.none, - child: Transform.rotate( - angle: -math.pi / + ], + ), + slides[index]['foreground']! == '' + ? SizedBox() + : SizedBox( + width: MediaQuery.of(context).size.width, + child: ClipRect( + clipBehavior: Clip.none, + child: Transform.rotate( + angle: + -math.pi / (slides[index]['rotation']! as double), child: Transform.translate( offset: Offset( - slides[index]['x'] as double, - slides[index]['y'] as double), + slides[index]['x'] as double, + slides[index]['y'] as double, + ), child: Transform.scale( - scale: slides[index]['scale'] - as double, + scale: + slides[index]['scale'] + as double, child: Image.asset( slides[index]['foreground']! as String, bundle: DefaultAssetBundle.of( - context), + context, + ), fit: BoxFit.cover, - width: MediaQuery.of(context) - .size - .width, + width: MediaQuery.of( + context, + ).size.width, alignment: Alignment.center, ), ), - )), + ), + ), + ), ), - ), - ], - ) - ], - ), - options: CarouselOptions( - height: double.infinity, - autoPlay: false, - autoPlayInterval: const Duration(milliseconds: 3000), - viewportFraction: 1.0, - enableInfiniteScroll: true, - ), - ), - ), - ], - ), - Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Container( - width: double.infinity, - height: 200, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - appStyle.colors.background.withAlpha(0), - appStyle.colors.background, - ], // customize colors - stops: [0.0, 0.5], // percentages (0% → 50% → 100%) - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - ), - ), - ) - ], - ), - Positioned( - bottom: 10, - left: 0, - right: 0, - child: Column( - children: [ - Center( - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: paddingWidthHorizontal), - child: InkWell( - onTap: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (BuildContext context) { - return _loginWebView; - }, - ); - }, - borderRadius: BorderRadius.circular(12), - child: Container( - width: double.infinity, - height: 48, - padding: const EdgeInsets.symmetric(vertical: 8), - decoration: ShapeDecoration( - color: appStyle.colors.accent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - shadows: [ - BoxShadow( - color: - appStyle.colors.textPrimary.withAlpha(13), - blurRadius: 2, - offset: Offset(0, 1), - spreadRadius: 0, - ) ], ), - child: Center( - child: Text( - widget.data.l10n.loginBtn, - textAlign: TextAlign.center, - style: appStyle.fonts.H_16px.copyWith( + ], + ), + options: CarouselOptions( + height: double.infinity, + autoPlay: false, + autoPlayInterval: const Duration(milliseconds: 3000), + viewportFraction: 1.0, + enableInfiniteScroll: true, + ), + ), + ), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + width: double.infinity, + height: 200, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + appStyle.colors.background.withAlpha(0), + appStyle.colors.background, + ], // customize colors + stops: [0.0, 0.5], // percentages (0% → 50% → 100%) + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + ), + ], + ), + Positioned( + bottom: 10, + left: 0, + right: 0, + child: Column( + children: [ + Center( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: paddingWidthHorizontal, + ), + child: InkWell( + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (BuildContext context) { + return _loginWebView; + }, + ); + }, + borderRadius: BorderRadius.circular(12), + child: Container( + width: double.infinity, + height: 48, + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: ShapeDecoration( + color: appStyle.colors.accent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + shadows: [ + BoxShadow( + color: appStyle.colors.textPrimary.withAlpha( + 13, + ), + blurRadius: 2, + offset: Offset(0, 1), + spreadRadius: 0, + ), + ], + ), + child: Center( + child: Text( + widget.data.l10n.loginBtn, + textAlign: TextAlign.center, + style: appStyle.fonts.H_16px.copyWith( color: appStyle.colors.textPrimaryLight, - fontVariations: [FontVariation("wght", 800)]), + fontVariations: [FontVariation("wght", 800)], + ), + ), ), ), ), ), ), - ), - const SizedBox(height: 20), - GestureDetector( - child: Text( - widget.data.l10n.privacyLabel, - textAlign: TextAlign.center, - style: appStyle.fonts.H_12px - .copyWith(color: appStyle.colors.textTertiary), + const SizedBox(height: 20), + GestureDetector( + onTap: _launchPrivacyPolicy, + child: Text( + widget.data.l10n.privacyLabel, + textAlign: TextAlign.center, + style: appStyle.fonts.H_12px.copyWith( + color: appStyle.colors.textTertiary, + ), + ), ), - onTap: _launchPrivacyPolicy, - ) - ], + ], + ), ), - ), - ], - )), + ], + ), + ), ), ); } @@ -431,7 +460,11 @@ class ImageClipper extends CustomClipper { @override Rect getClip(Size size) { return Rect.fromLTWH( - 0, -70, _mediaQuery.size.width, _mediaQuery.size.height); + 0, + -70, + _mediaQuery.size.width, + _mediaQuery.size.height, + ); } @override diff --git a/firka/lib/ui/phone/screens/message/message_screen.dart b/firka/lib/ui/phone/screens/message/message_screen.dart index ec70bda..1de523a 100644 --- a/firka/lib/ui/phone/screens/message/message_screen.dart +++ b/firka/lib/ui/phone/screens/message/message_screen.dart @@ -17,149 +17,152 @@ class MessageScreen extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultAssetBundle( - bundle: FirkaBundle(), - child: Scaffold( - backgroundColor: appStyle.colors.background, - body: SafeArea( - child: SizedBox( - height: MediaQuery.of(context).size.height, - child: Align( - alignment: Alignment.bottomCenter, - child: Container( - decoration: BoxDecoration( - color: appStyle.colors.background, - borderRadius: - BorderRadius.vertical(top: Radius.circular(16)), - ), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + bundle: FirkaBundle(), + child: Scaffold( + backgroundColor: appStyle.colors.background, + body: SafeArea( + child: SizedBox( + height: MediaQuery.of(context).size.height, + child: Align( + alignment: Alignment.bottomCenter, + child: Container( + decoration: BoxDecoration( + color: appStyle.colors.background, + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( children: [ - Row( - children: [ - Transform.translate( - offset: const Offset(-4, 0), - child: GestureDetector( - child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.chevronLeftLine, - color: appStyle.colors.textSecondary), - onTap: () { - Navigator.of(context).pop(); - }, - ), + Transform.translate( + offset: const Offset(-4, 0), + child: GestureDetector( + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.chevronLeftLine, + color: appStyle.colors.textSecondary, ), - Transform.translate( - offset: const Offset(-4, 1), - child: Text( - data.l10n.s_a, - style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textPrimary), - ), - ) - ], + onTap: () { + Navigator.of(context).pop(); + }, + ), ), - SizedBox(height: 56), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width * 0.85, - child: Text( - info.title, - textAlign: TextAlign.center, - style: appStyle.fonts.H_H2.apply( - color: appStyle.colors.textPrimary), - ), - ), - ], - ), - SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - info.date - .format(data.l10n, FormatMode.yyyymmdd), - textAlign: TextAlign.center, - style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textSecondary), - ), - ], - ), - SizedBox(height: 56), - Row( - children: [ - Container( - decoration: ShapeDecoration( - color: appStyle.colors.accent, - shape: CircleBorder( - eccentricity: 1, - )), - child: SizedBox( - width: 28, - height: 28, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: - const EdgeInsets.only(bottom: 6), - child: Text( - info.author[0], - style: appStyle.fonts.H_18px.copyWith( - fontSize: 20, - color: - appStyle.colors.textPrimary), - ), - ) - ], - ), - ), - ), - SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: - MediaQuery.of(context).size.width / 1.4, - child: Text( - info.author, - style: appStyle.fonts.B_16SB.apply( - color: appStyle.colors.textPrimary), - ), - ) - ], - ), - ], - ), - SizedBox(height: 20), - Padding( - padding: const EdgeInsets.all(4), - child: Container( - decoration: BoxDecoration( - color: appStyle.colors.card, - borderRadius: - BorderRadius.all(Radius.circular(16))), - child: Padding( - padding: const EdgeInsets.all(12), - child: Text( - info.contentText, - style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textPrimary), - textAlign: TextAlign.start, - ), + Transform.translate( + offset: const Offset(-4, 1), + child: Text( + data.l10n.s_a, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, ), ), ), ], ), - ), + SizedBox(height: 56), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.85, + child: Text( + info.title, + textAlign: TextAlign.center, + style: appStyle.fonts.H_H2.apply( + color: appStyle.colors.textPrimary, + ), + ), + ), + ], + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + info.date.format(data.l10n, FormatMode.yyyymmdd), + textAlign: TextAlign.center, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), + ], + ), + SizedBox(height: 56), + Row( + children: [ + Container( + decoration: ShapeDecoration( + color: appStyle.colors.accent, + shape: CircleBorder(eccentricity: 1), + ), + child: SizedBox( + width: 28, + height: 28, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 6), + child: Text( + info.author[0], + style: appStyle.fonts.H_18px.copyWith( + fontSize: 20, + color: appStyle.colors.textPrimary, + ), + ), + ), + ], + ), + ), + ), + SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width / 1.4, + child: Text( + info.author, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), + ), + ], + ), + ], + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(4), + child: Container( + decoration: BoxDecoration( + color: appStyle.colors.card, + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + info.contentText, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + textAlign: TextAlign.start, + ), + ), + ), + ), + ], ), - )), + ), + ), + ), ), - )); + ), + ), + ); } } diff --git a/firka/lib/ui/phone/screens/settings/settings_screen.dart b/firka/lib/ui/phone/screens/settings/settings_screen.dart index dbf3868..890504d 100644 --- a/firka/lib/ui/phone/screens/settings/settings_screen.dart +++ b/firka/lib/ui/phone/screens/settings/settings_screen.dart @@ -57,8 +57,10 @@ class _SettingsScreenState extends FirkaState { } List createWidgetTree( - Iterable items, SettingsStore settings, - {bool forceRender = false}) { + Iterable items, + SettingsStore settings, { + bool forceRender = false, + }) { var widgets = List.empty(growable: true); for (var item in items) { @@ -69,67 +71,80 @@ class _SettingsScreenState extends FirkaState { continue; } if (item is SettingsPadding) { - widgets.add(SizedBox( - width: item.padding, - height: item.padding, - )); + widgets.add(SizedBox(width: item.padding, height: item.padding)); continue; } if (item is SettingsBackHeader) { - widgets.add(Column( - children: [ - Row( - children: [ - Transform.translate( - offset: const Offset(-4, 0), - child: GestureDetector( - child: FirkaIconWidget( - FirkaIconType.majesticons, Majesticon.chevronLeftLine, - color: appStyle.colors.textSecondary), - onTap: () { - Navigator.of(context).pop(); - }, + widgets.add( + Column( + children: [ + Row( + children: [ + Transform.translate( + offset: const Offset(-4, 0), + child: GestureDetector( + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.chevronLeftLine, + color: appStyle.colors.textSecondary, + ), + onTap: () { + Navigator.of(context).pop(); + }, + ), ), - ), - Transform.translate( - offset: const Offset(-4, 1), - child: Text( - item.title, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), + Transform.translate( + offset: const Offset(-4, 1), + child: Text( + item.title, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), ), - ) - ], - ), - SizedBox(height: 13), - ], - )); + ], + ), + SizedBox(height: 13), + ], + ), + ); continue; } if (item is SettingsHeader) { - widgets.add(Text( - item.title, - style: appStyle.fonts.H_H1.apply(color: appStyle.colors.textPrimary), - )); + widgets.add( + Text( + item.title, + style: appStyle.fonts.H_H1.apply( + color: appStyle.colors.textPrimary, + ), + ), + ); continue; } if (item is SettingsMediumHeader) { - widgets.add(Text( - item.title, - style: appStyle.fonts.H_H2.apply(color: appStyle.colors.textPrimary), - )); + widgets.add( + Text( + item.title, + style: appStyle.fonts.H_H2.apply( + color: appStyle.colors.textPrimary, + ), + ), + ); continue; } if (item is SettingsHeaderSmall) { - widgets.add(Text( - item.title, - style: - appStyle.fonts.H_14px.apply(color: appStyle.colors.textPrimary), - )); + widgets.add( + Text( + item.title, + style: appStyle.fonts.H_14px.apply( + color: appStyle.colors.textPrimary, + ), + ), + ); continue; } @@ -137,50 +152,67 @@ class _SettingsScreenState extends FirkaState { List cardWidgets = []; if (item.iconType != null && item.iconData != null) { - cardWidgets.add(FirkaIconWidget( - item.iconType!, - item.iconData!, - color: appStyle.colors.accent, - )); + cardWidgets.add( + FirkaIconWidget( + item.iconType!, + item.iconData!, + color: appStyle.colors.accent, + ), + ); cardWidgets.add(SizedBox(width: 8)); } - cardWidgets.add(Text(item.title, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary))); + cardWidgets.add( + Text( + item.title, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), + ); - widgets.add(GestureDetector( - onTap: () { - if (item.redirectTo != null && item.redirectTo == "discord") { - launchUrlString( - "https://discord.com/invite/firka-1111649116020285532"); - return; - } else if (item.redirectTo != null && - item.redirectTo == "privacy") { - launchUrlString("https://firka.app/privacy"); - return; - } else { - Navigator.push( + widgets.add( + GestureDetector( + onTap: () { + if (item.redirectTo != null && item.redirectTo == "discord") { + launchUrlString( + "https://discord.com/invite/firka-1111649116020285532", + ); + return; + } else if (item.redirectTo != null && + item.redirectTo == "privacy") { + launchUrlString("https://firka.app/privacy"); + return; + } else { + Navigator.push( context, MaterialPageRoute( - builder: (context) => DefaultAssetBundle( - bundle: FirkaBundle(), - child: SettingsScreen(widget.data, item.children)))); - } - }, - child: item.redirectTo != null - ? FirkaCard( - left: cardWidgets, - right: [ - RotationTransition( + builder: (context) => DefaultAssetBundle( + bundle: FirkaBundle(), + child: SettingsScreen(widget.data, item.children), + ), + ), + ); + } + }, + child: item.redirectTo != null + ? FirkaCard( + left: cardWidgets, + right: [ + RotationTransition( turns: AlwaysStoppedAnimation(-45 / 360), - child: FirkaIconWidget(FirkaIconType.majesticons, - Majesticon.arrowRightSolid, - size: 24, color: appStyle.colors.textSecondary)) - ], - ) - : FirkaCard(left: cardWidgets), - )); + child: FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.arrowRightSolid, + size: 24, + color: appStyle.colors.textSecondary, + ), + ), + ], + ) + : FirkaCard(left: cardWidgets), + ), + ); continue; } @@ -188,64 +220,86 @@ class _SettingsScreenState extends FirkaState { if (item is SettingsDouble) { var v = item.toRoundedString(); - widgets.add(GestureDetector( - child: FirkaCard(height: 52 + 12, left: [ - item.iconType != null - ? Row( - children: [ - FirkaIconWidget(item.iconType!, item.iconData!, - color: appStyle.colors.accent), - SizedBox(width: 4), - ], - ) - : SizedBox(), - Text(item.title, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)) - ], right: [ - Text(v == "0.0" ? "0" : v, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)) - ]), - onTap: () async { - showSetDoubleSheet(context, item, widget.data, setState); - }, - )); + widgets.add( + GestureDetector( + child: FirkaCard( + height: 52 + 12, + left: [ + item.iconType != null + ? Row( + children: [ + FirkaIconWidget( + item.iconType!, + item.iconData!, + color: appStyle.colors.accent, + ), + SizedBox(width: 4), + ], + ) + : SizedBox(), + Text( + item.title, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + right: [ + Text( + v == "0.0" ? "0" : v, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + onTap: () async { + showSetDoubleSheet(context, item, widget.data, setState); + }, + ), + ); continue; } if (item is SettingsBoolean) { - widgets.add(FirkaCard( - height: 52 + 12, - left: [ - item.iconType != null - ? Row( - children: [ - FirkaIconWidget(item.iconType!, item.iconData!, - color: appStyle.colors.accent), - SizedBox(width: 4), - ], - ) - : SizedBox(), - Text(item.title, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)) - ], - right: [ - Switch( + widgets.add( + FirkaCard( + height: 52 + 12, + left: [ + item.iconType != null + ? Row( + children: [ + FirkaIconWidget( + item.iconType!, + item.iconData!, + color: appStyle.colors.accent, + ), + SizedBox(width: 4), + ], + ) + : SizedBox(), + Text( + item.title, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + right: [ + Switch( value: item.value, // activeColor: appStyle.colors.accent, thumbColor: WidgetStateProperty.fromMap({ WidgetState.selected: appStyle.colors.buttonSecondaryFill, - WidgetState.any: appStyle.colors.accent + WidgetState.any: appStyle.colors.accent, }), trackColor: WidgetStateProperty.fromMap({ WidgetState.selected: appStyle.colors.accent, - WidgetState.any: appStyle.colors.a10p + WidgetState.any: appStyle.colors.a10p, }), trackOutlineColor: WidgetStateProperty.fromMap({ WidgetState.selected: appStyle.colors.accent, - WidgetState.any: appStyle.colors.a15p + WidgetState.any: appStyle.colors.a15p, }), onChanged: (v) async { setState(() { @@ -257,9 +311,11 @@ class _SettingsScreenState extends FirkaState { }); await item.postUpdate(); - }) - ], - )); + }, + ), + ], + ), + ); continue; } @@ -268,56 +324,74 @@ class _SettingsScreenState extends FirkaState { var k = item.values[i]; if (item.values[item.activeIndex] == k) { - widgets.add(FirkaCard(height: 52 + 12, left: [ - Text(k, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)) - ], right: [ - SizedBox( - width: 16, - height: 16, - child: Checkbox( - value: true, - fillColor: WidgetStateProperty.resolveWith( - (Set states) { - return appStyle.colors.secondary; - }), - onChanged: (_) async { - setState(() { - item.activeIndex = i; - }); + widgets.add( + FirkaCard( + height: 52 + 12, + left: [ + Text( + k, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + right: [ + SizedBox( + width: 16, + height: 16, + child: Checkbox( + value: true, + fillColor: WidgetStateProperty.resolveWith(( + Set states, + ) { + return appStyle.colors.secondary; + }), + onChanged: (_) async { + setState(() { + item.activeIndex = i; + }); - await widget.data.isar.writeTxn(() async { - await item.save(widget.data.isar.appSettingsModels); - }); + await widget.data.isar.writeTxn(() async { + await item.save(widget.data.isar.appSettingsModels); + }); - await item.postUpdate(); - logger.finest('Settings saved'); - }), + await item.postUpdate(); + logger.finest('Settings saved'); + }, + ), + ), + SizedBox(width: 8), + ], ), - SizedBox(width: 8), - ])); + ); } else { - widgets.add(GestureDetector( - child: FirkaCard(height: 52 + 12, left: [ - Text(k, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)) - ], right: [ - SizedBox(height: 16 + 8), - ]), - onTap: () async { - setState(() { - item.activeIndex = i; - }); + widgets.add( + GestureDetector( + child: FirkaCard( + height: 52 + 12, + left: [ + Text( + k, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + right: [SizedBox(height: 16 + 8)], + ), + onTap: () async { + setState(() { + item.activeIndex = i; + }); - await widget.data.isar.writeTxn(() async { - await item.save(widget.data.isar.appSettingsModels); - }); + await widget.data.isar.writeTxn(() async { + await item.save(widget.data.isar.appSettingsModels); + }); - await item.postUpdate(); - }, - )); + await item.postUpdate(); + }, + ), + ); } } @@ -327,127 +401,157 @@ class _SettingsScreenState extends FirkaState { widgets.add( FutureBuilder>( future: LicenseRegistry.licenses.toList(), - builder: (BuildContext context, - AsyncSnapshot> snapshot) { - if (!snapshot.hasData) { - return Center( - child: CircularProgressIndicator( - color: appStyle.colors.accent)); - } + builder: + ( + BuildContext context, + AsyncSnapshot> snapshot, + ) { + if (!snapshot.hasData) { + return Center( + child: CircularProgressIndicator( + color: appStyle.colors.accent, + ), + ); + } - final licenses = snapshot.data!; - final shownPackages = {}; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: licenses - .where((license) => license.packages - .any((pkg) => !shownPackages.contains(pkg))) - .map((license) { - final packageName = license.packages.firstWhere( - (pkg) => !shownPackages.contains(pkg), - orElse: () => license.packages.first, - ); - shownPackages.add(packageName); - final paragraphs = - license.paragraphs.map((p) => p.text).join('\n\n'); + final licenses = snapshot.data!; + final shownPackages = {}; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: licenses + .where( + (license) => license.packages.any( + (pkg) => !shownPackages.contains(pkg), + ), + ) + .map((license) { + final packageName = license.packages.firstWhere( + (pkg) => !shownPackages.contains(pkg), + orElse: () => license.packages.first, + ); + shownPackages.add(packageName); + final paragraphs = license.paragraphs + .map((p) => p.text) + .join('\n\n'); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - backgroundColor: appStyle.colors.card, - title: Text( - packageName, - style: const TextStyle( - fontWeight: FontWeight.bold), - ), - content: SingleChildScrollView( - child: Text( - paragraphs, - style: appStyle.fonts.B_15SB.apply( - color: appStyle.colors.textPrimary), - ), - ), - actions: [ - TextButton( - child: Text( - widget.data.l10n.close, - style: appStyle.fonts.B_14R.apply( - color: - appStyle.colors.textSecondary), - ), - onPressed: () { - Navigator.of(context).pop(); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: appStyle.colors.card, + title: Text( + packageName, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + content: SingleChildScrollView( + child: Text( + paragraphs, + style: appStyle.fonts.B_15SB + .apply( + color: appStyle + .colors + .textPrimary, + ), + ), + ), + actions: [ + TextButton( + child: Text( + widget.data.l10n.close, + style: appStyle.fonts.B_14R + .apply( + color: appStyle + .colors + .textSecondary, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); }, - ), - ], - ); - }, - ); - }, - child: FirkaCard(left: [ - Text( - packageName, - style: appStyle.fonts.B_14R - .apply(color: appStyle.colors.textPrimary), + ); + }, + child: FirkaCard( + left: [ + Text( + packageName, + style: appStyle.fonts.B_14R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + ), + ], ), - ]), - ), - ], - ), + ); + }) + .toList(), ); - }).toList(), - ); - }, + }, ), ); continue; } if (item is SettingsAppIconPreview) { - widgets.add(Container( - decoration: BoxDecoration( - image: DecorationImage( + widgets.add( + Container( + decoration: BoxDecoration( + image: DecorationImage( image: PreloadedImageProvider( - FirkaBundle(), ('assets/images/background.webp')), - fit: BoxFit.cover), - borderRadius: BorderRadius.all(Radius.circular(16)), - ), - width: MediaQuery.of(context).size.width, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - children: [ - Column( - children: [ - ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(16.0)), - child: Image( - image: PreloadedImageProvider(FirkaBundle(), - "assets/images/icons/$activeIcon.webp"), - width: 74, - height: 74, + FirkaBundle(), + ('assets/images/background.webp'), + ), + fit: BoxFit.cover, + ), + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + width: MediaQuery.of(context).size.width, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + Column( + children: [ + ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(16.0), + ), + child: Image( + image: PreloadedImageProvider( + FirkaBundle(), + "assets/images/icons/$activeIcon.webp", + ), + width: 74, + height: 74, + ), ), - ), - Text( - settings.appIcons[activeIcon]!, - style: appStyle.fonts.H_12px - .apply(color: appStyle.colors.card), - ) - ], - ) - ], + Text( + settings.appIcons[activeIcon]!, + style: appStyle.fonts.H_12px.apply( + color: appStyle.colors.card, + ), + ), + ], + ), + ], + ), ), ), - )); + ); continue; } @@ -472,80 +576,103 @@ class _SettingsScreenState extends FirkaState { for (var icon in item.iconGroups[group]!) { var active = icon == activeIcon; - groupIcons.add(Column( - children: [ - GestureDetector( - child: active - ? Container( - decoration: BoxDecoration( - color: appStyle.colors.accent, - borderRadius: BorderRadius.all(Radius.circular(16)), - ), - child: Padding( - padding: const EdgeInsets.all(4), - child: ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(12.0)), - child: Image( - image: PreloadedImageProvider(FirkaBundle(), - "assets/images/icons/$icon.webp"), - width: 48, - height: 48, + groupIcons.add( + Column( + children: [ + GestureDetector( + child: active + ? Container( + decoration: BoxDecoration( + color: appStyle.colors.accent, + borderRadius: BorderRadius.all( + Radius.circular(16), ), ), + child: Padding( + padding: const EdgeInsets.all(4), + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(12.0), + ), + child: Image( + image: PreloadedImageProvider( + FirkaBundle(), + "assets/images/icons/$icon.webp", + ), + width: 48, + height: 48, + ), + ), + ), + ) + : ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(16.0), + ), + child: Image( + image: PreloadedImageProvider( + FirkaBundle(), + "assets/images/icons/$icon.webp", + ), + width: 54, + height: 54, + ), ), - ) - : ClipRRect( - borderRadius: - const BorderRadius.all(Radius.circular(16.0)), - child: Image( - image: PreloadedImageProvider(FirkaBundle(), - "assets/images/icons/$icon.webp"), - width: 54, - height: 54, - ), - ), - onTap: () { - if (settingAppIcon) return; + onTap: () { + if (settingAppIcon) return; - setState(() { - activeIcon = icon; - }); - }, - ), - Text( - settings.appIcons[icon]!, - style: appStyle.fonts.B_12R.apply( + setState(() { + activeIcon = icon; + }); + }, + ), + Text( + settings.appIcons[icon]!, + style: appStyle.fonts.B_12R.apply( color: active ? appStyle.colors.textPrimary - : appStyle.colors.textSecondary), - textAlign: TextAlign.center, - ), - ], - )); + : appStyle.colors.textSecondary, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); } - pWidgets.add(Text( - group, - style: - appStyle.fonts.H_14px.apply(color: appStyle.colors.textPrimary), - )); + pWidgets.add( + Text( + group, + style: appStyle.fonts.H_14px.apply( + color: appStyle.colors.textPrimary, + ), + ), + ); if (group == widget.data.l10n.s_ci_icon_g6) { - pWidgets.add(Text(widget.data.l10n.s_ci_icon_g6_desc, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary))); + pWidgets.add( + Text( + widget.data.l10n.s_ci_icon_g6_desc, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), + ); } if (group == widget.data.l10n.s_ci_icon_g7 || group == widget.data.l10n.s_ci_icon_g8) { - var settingsWidgets = createWidgetTree([ - widget.data.settings - .group("settings") - .subGroup("customization") - .subGroup("icon_picker")["child_protection"] - as SettingsBoolean - ], settings, forceRender: true); + var settingsWidgets = createWidgetTree( + [ + widget.data.settings + .group("settings") + .subGroup("customization") + .subGroup("icon_picker")["child_protection"] + as SettingsBoolean, + ], + settings, + forceRender: true, + ); pWidgets.add(SizedBox(height: 12)); for (var w in settingsWidgets) { @@ -554,72 +681,84 @@ class _SettingsScreenState extends FirkaState { } pWidgets.add(SizedBox(height: 12)); - pWidgets.add(SizedBox( - height: (groupIcons.length / 4).ceil() * 100, - child: GridView.count( - crossAxisCount: 4, - physics: NeverScrollableScrollPhysics(), - children: groupIcons, + pWidgets.add( + SizedBox( + height: (groupIcons.length / 4).ceil() * 100, + child: GridView.count( + crossAxisCount: 4, + physics: NeverScrollableScrollPhysics(), + children: groupIcons, + ), ), - )); + ); } - widgets.add(SizedBox( - height: MediaQuery.of(context).size.height / 1.7, - child: SingleChildScrollView( + widgets.add( + SizedBox( + height: MediaQuery.of(context).size.height / 1.7, + child: SingleChildScrollView( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: pWidgets, - )), - )); + crossAxisAlignment: CrossAxisAlignment.start, + children: pWidgets, + ), + ), + ), + ); - widgets.add(Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - child: FirkaButton( + widgets.add( + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + child: FirkaButton( text: widget.data.l10n.cancel, bgColor: appStyle.colors.buttonSecondaryFill, - fontStyle: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary)), - onTap: () { - Navigator.pop(context); - }, - ), - GestureDetector( - child: FirkaButton( + fontStyle: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), + onTap: () { + Navigator.pop(context); + }, + ), + GestureDetector( + child: FirkaButton( text: widget.data.l10n.save, bgColor: appStyle.colors.accent, - fontStyle: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondaryLight)), - onTap: () async { - if (settingAppIcon) return; - settingAppIcon = true; + fontStyle: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondaryLight, + ), + ), + onTap: () async { + if (settingAppIcon) return; + settingAppIcon = true; - widget.data.settings - .group("settings") - .subGroup("customization") - .subGroup("icon_picker") - .setIconString("icon_picker", activeIcon); + widget.data.settings + .group("settings") + .subGroup("customization") + .subGroup("icon_picker") + .setIconString("icon_picker", activeIcon); - await widget.data.isar.writeTxn(() async { - await widget.data.settings - .save(widget.data.isar.appSettingsModels); - }); + await widget.data.isar.writeTxn(() async { + await widget.data.settings.save( + widget.data.isar.appSettingsModels, + ); + }); - await Future.delayed(Duration(seconds: 1)); + await Future.delayed(Duration(seconds: 1)); - const channel = MethodChannel('firka.app/main'); - await channel.invokeMethod('set_icon', { - "icon": activeIcon == "original" ? null : activeIcon, - "icons": settings.appIcons.keys - .where((e) => e != "original") - .join(",") - }); - }, - ) - ], - )); + const channel = MethodChannel('firka.app/main'); + await channel.invokeMethod('set_icon', { + "icon": activeIcon == "original" ? null : activeIcon, + "icons": settings.appIcons.keys + .where((e) => e != "original") + .join(","), + }); + }, + ), + ], + ), + ); continue; } @@ -634,79 +773,236 @@ class _SettingsScreenState extends FirkaState { } else { studentRole = payload["role"]; } - widgets.add(GestureDetector( - child: SizedBox( - height: 52, - child: FirkaCard( - left: [ - Text( - payload["name"], - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), - ), - SizedBox(width: 8), - Text( - studentRole, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textTertiary), - ) - ], - right: [ - i != item.accountIndex - ? SizedBox() - : Checkbox( - value: true, - fillColor: WidgetStateProperty.resolveWith( - (Set states) { - return appStyle.colors.secondary; - }), - onChanged: (_) async { - setState(() { - // item.activeIndex = i; - }); + widgets.add( + GestureDetector( + child: SizedBox( + height: 52, + child: FirkaCard( + left: [ + Text( + payload["name"], + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + SizedBox(width: 8), + Text( + studentRole, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textTertiary, + ), + ), + ], + right: [ + i != item.accountIndex + ? SizedBox() + : Checkbox( + value: true, + fillColor: WidgetStateProperty.resolveWith(( + Set states, + ) { + return appStyle.colors.secondary; + }), + onChanged: (_) async { + setState(() { + // item.activeIndex = i; + }); - await widget.data.isar.writeTxn(() async { - await item - .save(widget.data.isar.appSettingsModels); - }); + await widget.data.isar.writeTxn(() async { + await item.save( + widget.data.isar.appSettingsModels, + ); + }); - await item.postUpdate(); - logger.finest('Settings saved'); - }) - ], + await item.postUpdate(); + logger.finest('Settings saved'); + }, + ), + ], + ), ), - ), - onTap: () async { - if (i != item.accountIndex) { - final previousAccountId = widget.data.client.model.studentIdNorm; - if (Platform.isIOS) { - await LiveActivityService.onUserLogout(); - try { - await WatchSyncHelper.clearSharedLanguageState(); - } catch (e) { - logger.warning( - '[Settings] Failed to clear shared language state on account switch: $e'); - } - if (previousAccountId != null) { + onTap: () async { + if (i != item.accountIndex) { + final previousAccountId = + widget.data.client.model.studentIdNorm; + if (Platform.isIOS) { + await LiveActivityService.onUserLogout(); try { - await WatchSyncHelper.clearRefreshLeaseForAccount( - previousAccountId); + await WatchSyncHelper.clearSharedLanguageState(); } catch (e) { logger.warning( - '[Settings] Failed to clear refresh lease on account switch: $e'); + '[Settings] Failed to clear shared language state on account switch: $e', + ); + } + if (previousAccountId != null) { + try { + await WatchSyncHelper.clearRefreshLeaseForAccount( + previousAccountId, + ); + } catch (e) { + logger.warning( + '[Settings] Failed to clear refresh lease on account switch: $e', + ); + } } } + + await widget.data.isar.writeTxn(() async { + item.accountIndex = i; + + await item.save(widget.data.isar.appSettingsModels); + }); + + await item.postUpdate(); + + if (Platform.isIOS) { + var watchReachable = false; + try { + watchReachable = await WatchSyncHelper.isWatchReachable( + forceRefreshInstall: true, + ); + } catch (e) { + logger.warning( + '[Settings] Failed to query Watch reachability on account switch: $e', + ); + } + + if (watchReachable) { + try { + await WatchSyncHelper.sendTokenModelToWatch( + token, + allowExpiredAccessToken: true, + ); + } catch (e) { + logger.warning( + '[Settings] Failed to send switched account token to reachable Watch: $e', + ); + } + } else { + try { + await WatchSyncHelper.saveTokenToiCloud( + token, + forceAccountSwitch: true, + ); + } catch (e) { + logger.warning( + '[Settings] Failed to sync switched account token to iCloud: $e', + ); + } + } + } + + runApp(InitializationScreen()); } + }, + ), + ); + widgets.add(SizedBox(height: 8)); + } - await widget.data.isar.writeTxn(() async { - item.accountIndex = i; + widgets.add( + GestureDetector( + child: FirkaCard( + left: [ + Text( + widget.data.l10n.s_acc_add, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (BuildContext context) { + return LoginWebviewWidget(widget.data); + }, + ); + }, + ), + ); + widgets.add(SizedBox(height: 20)); + widgets.add( + GestureDetector( + child: FirkaCard( + left: [ + Row( + children: [ + FirkaIconWidget( + FirkaIconType.icons, + "group", + color: appStyle.colors.accent, + ), + SizedBox(width: 8), + Text( + widget.data.l10n.s_acc_logout, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + ], + ), + onTap: () async { + if (Platform.isIOS) { + await LiveActivityService.onUserLogout(); + await WidgetCacheHelper.clearIOSWidgets(); + } - await item.save(widget.data.isar.appSettingsModels); - }); + final active = widget.data.client.model.studentIdNorm!; + if (Platform.isIOS) { + try { + await WatchSyncHelper.clearRefreshLeaseForAccount(active); + } catch (e) { + logger.warning( + '[Settings] Failed to clear refresh lease for active account: $e', + ); + } + try { + await WatchSyncHelper.clearSharedLanguageState(); + } catch (e) { + logger.warning( + '[Settings] Failed to clear shared language state on logout: $e', + ); + } + } - await item.postUpdate(); + await widget.data.isar.writeTxn(() async { + await widget.data.isar.tokenModels.delete(active); + item.accountIndex = 0; + await item.save(widget.data.isar.appSettingsModels); + }); + + final accounts = await widget.data.isar.tokenModels + .where() + .findAll(); + + if (accounts.isEmpty) { if (Platform.isIOS) { + try { + await WatchSyncHelper.clearICloudToken(notifyWatch: true); + await WatchSyncHelper.clearAllRefreshLeases(); + } catch (e) { + logger.warning( + '[Settings] Failed to clear iCloud token: $e', + ); + } + KretaClient.clearReauthFlag(); + } + if (!mounted) return; + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => LoginScreen(widget.data), + ), + (route) => false, + ); + } else { + if (Platform.isIOS) { + final nextToken = accounts.first; var watchReachable = false; try { watchReachable = await WatchSyncHelper.isWatchReachable( @@ -714,189 +1010,73 @@ class _SettingsScreenState extends FirkaState { ); } catch (e) { logger.warning( - '[Settings] Failed to query Watch reachability on account switch: $e'); + '[Settings] Failed to query Watch reachability after logout: $e', + ); } if (watchReachable) { try { await WatchSyncHelper.sendTokenModelToWatch( - token, + nextToken, allowExpiredAccessToken: true, ); } catch (e) { logger.warning( - '[Settings] Failed to send switched account token to reachable Watch: $e'); + '[Settings] Failed to send next account token to reachable Watch after logout: $e', + ); } } else { try { await WatchSyncHelper.saveTokenToiCloud( - token, + nextToken, forceAccountSwitch: true, ); } catch (e) { logger.warning( - '[Settings] Failed to sync switched account token to iCloud: $e'); + '[Settings] Failed to sync next account token to iCloud after logout: $e', + ); } } } + widget.data.tokens = accounts; runApp(InitializationScreen()); } }, - )); - widgets.add(SizedBox(height: 8)); - } - - widgets.add(GestureDetector( - child: FirkaCard(left: [ - Text( - widget.data.l10n.s_acc_add, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), - ) - ]), - onTap: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (BuildContext context) { - return LoginWebviewWidget(widget.data); - }, - ); - }, - )); - widgets.add(SizedBox(height: 20)); - widgets.add(GestureDetector( - child: FirkaCard(left: [ - Row( - children: [ - FirkaIconWidget( - FirkaIconType.icons, - "group", - color: appStyle.colors.accent, - ), - SizedBox(width: 8), - Text( - widget.data.l10n.s_acc_logout, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), - ), - ], - ) - ]), - onTap: () async { - if (Platform.isIOS) { - await LiveActivityService.onUserLogout(); - await WidgetCacheHelper.clearIOSWidgets(); - } - - final active = widget.data.client.model.studentIdNorm!; - if (Platform.isIOS) { - try { - await WatchSyncHelper.clearRefreshLeaseForAccount(active); - } catch (e) { - logger.warning( - '[Settings] Failed to clear refresh lease for active account: $e'); - } - try { - await WatchSyncHelper.clearSharedLanguageState(); - } catch (e) { - logger.warning( - '[Settings] Failed to clear shared language state on logout: $e'); - } - } - - await widget.data.isar.writeTxn(() async { - await widget.data.isar.tokenModels.delete(active); - - item.accountIndex = 0; - await item.save(widget.data.isar.appSettingsModels); - }); - - final accounts = - await widget.data.isar.tokenModels.where().findAll(); - - if (accounts.isEmpty) { - if (Platform.isIOS) { - try { - await WatchSyncHelper.clearICloudToken(notifyWatch: true); - await WatchSyncHelper.clearAllRefreshLeases(); - } catch (e) { - logger.warning('[Settings] Failed to clear iCloud token: $e'); - } - KretaClient.clearReauthFlag(); - } - if (!mounted) return; - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (context) => LoginScreen(widget.data)), - (route) => false, - ); - } else { - if (Platform.isIOS) { - final nextToken = accounts.first; - var watchReachable = false; - try { - watchReachable = await WatchSyncHelper.isWatchReachable( - forceRefreshInstall: true, - ); - } catch (e) { - logger.warning( - '[Settings] Failed to query Watch reachability after logout: $e'); - } - - if (watchReachable) { - try { - await WatchSyncHelper.sendTokenModelToWatch( - nextToken, - allowExpiredAccessToken: true, - ); - } catch (e) { - logger.warning( - '[Settings] Failed to send next account token to reachable Watch after logout: $e'); - } - } else { - try { - await WatchSyncHelper.saveTokenToiCloud( - nextToken, - forceAccountSwitch: true, - ); - } catch (e) { - logger.warning( - '[Settings] Failed to sync next account token to iCloud after logout: $e'); - } - } - } - - widget.data.tokens = accounts; - runApp(InitializationScreen()); - } - }, - )); + ), + ); continue; } if (item is SettingsButton) { - widgets.add(GestureDetector( - child: FirkaCard( - left: [ - item.iconType != null - ? Row( - children: [ - FirkaIconWidget(item.iconType!, item.iconData!, - color: appStyle.colors.accent), - SizedBox(width: 8), - ], - ) - : SizedBox(), - Text(item.title, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)) - ], + widgets.add( + GestureDetector( + child: FirkaCard( + left: [ + item.iconType != null + ? Row( + children: [ + FirkaIconWidget( + item.iconType!, + item.iconData!, + color: appStyle.colors.accent, + ), + SizedBox(width: 8), + ], + ) + : SizedBox(), + Text( + item.title, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + onTap: () async { + await item.onTap(); + }, ), - onTap: () async { - await item.onTap(); - }, - )); + ); continue; } @@ -909,63 +1089,76 @@ class _SettingsScreenState extends FirkaState { final m = logFileRegex.firstMatch(name); if (m == null) continue; - widgets.add(GestureDetector( - child: SizedBox( - height: 52, - child: FirkaCard( - left: [ - FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.noteTextSolid, - color: appStyle.colors.accent, - ), - Text( - name, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), - ), - ], + widgets.add( + GestureDetector( + child: SizedBox( + height: 52, + child: FirkaCard( + left: [ + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.noteTextSolid, + color: appStyle.colors.accent, + ), + Text( + name, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), ), - ), - onTap: () async { - try { - logger.info("Compressing log file: ${entity.path}"); - final original = File(entity.path); - final originalBytes = await original.readAsBytes(); - final gzBytes = GZipCodec().encode(originalBytes); - final tempDir = await Directory.systemTemp.createTemp('firka'); - final gzPath = - p.join(tempDir.path, '${p.basename(entity.path)}.gz'); - final gzFile = - await File(gzPath).writeAsBytes(gzBytes, flush: true); + onTap: () async { + try { + logger.info("Compressing log file: ${entity.path}"); + final original = File(entity.path); + final originalBytes = await original.readAsBytes(); + final gzBytes = GZipCodec().encode(originalBytes); + final tempDir = await Directory.systemTemp.createTemp( + 'firka', + ); + final gzPath = p.join( + tempDir.path, + '${p.basename(entity.path)}.gz', + ); + final gzFile = await File( + gzPath, + ).writeAsBytes(gzBytes, flush: true); - final params = ShareParams( - text: name, - files: [XFile(gzFile.path, mimeType: 'application/gzip')], - ); + final params = ShareParams( + text: name, + files: [XFile(gzFile.path, mimeType: 'application/gzip')], + ); - await SharePlus.instance.share(params); + await SharePlus.instance.share(params); - await gzFile.delete(); - await tempDir.delete(); - } catch (ex) { - if (ex is Error) { - logger.shout("Failed to compress log file", ex.toString(), - ex.stackTrace); - } else { - logger.shout("Failed to compress log file", ex.toString()); + await gzFile.delete(); + await tempDir.delete(); + } catch (ex) { + if (ex is Error) { + logger.shout( + "Failed to compress log file", + ex.toString(), + ex.stackTrace, + ); + } else { + logger.shout("Failed to compress log file", ex.toString()); + } + + logger.info( + "Sharing regular log file instead: ${entity.path}", + ); + final params = ShareParams( + text: name, + files: [XFile(entity.path, mimeType: 'text/plain')], + ); + + await SharePlus.instance.share(params); } - - logger.info("Sharing regular log file instead: ${entity.path}"); - final params = ShareParams( - text: name, - files: [XFile(entity.path, mimeType: 'text/plain')], - ); - - await SharePlus.instance.share(params); - } - }, - )); + }, + ), + ); widgets.add(SizedBox(height: 8)); } continue; @@ -980,31 +1173,38 @@ class _SettingsScreenState extends FirkaState { var body = createWidgetTree(widget.items.values, widget.data.settings); return DefaultAssetBundle( - bundle: FirkaBundle(), - child: Scaffold( - backgroundColor: appStyle.colors.background, - body: SafeArea( - child: SizedBox( - height: MediaQuery.of(context).size.height, - child: Stack( - children: [ - Padding( - padding: EdgeInsetsGeometry.all(20), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: body)), - ) - ], - ), + bundle: FirkaBundle(), + child: Scaffold( + backgroundColor: appStyle.colors.background, + body: SafeArea( + child: SizedBox( + height: MediaQuery.of(context).size.height, + child: Stack( + children: [ + Padding( + padding: EdgeInsetsGeometry.all(20), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: body, + ), + ), + ), + ], ), ), - )); + ), + ), + ); } } -void showSetDoubleSheet(BuildContext context, SettingsDouble setting, - AppInitialization data, void Function(VoidCallback fn) setStateOuter) { +void showSetDoubleSheet( + BuildContext context, + SettingsDouble setting, + AppInitialization data, + void Function(VoidCallback fn) setStateOuter, +) { showModalBottomSheet( context: context, elevation: 100, @@ -1017,96 +1217,111 @@ void showSetDoubleSheet(BuildContext context, SettingsDouble setting, ), builder: (BuildContext context) { return StatefulBuilder( - builder: (BuildContext context, setState) => Stack( - children: [ - Positioned.fill( - child: GestureDetector( - onTap: () => Navigator.pop(context), - behavior: HitTestBehavior.opaque, - child: Container(color: Colors.transparent), - ), + builder: (BuildContext context, setState) => Stack( + children: [ + Positioned.fill( + child: GestureDetector( + onTap: () => Navigator.pop(context), + behavior: HitTestBehavior.opaque, + child: Container(color: Colors.transparent), + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + decoration: BoxDecoration( + color: appStyle.colors.card, + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + child: Padding( + padding: const EdgeInsets.only( + left: 18.0, + right: 16.0, + bottom: 30.0, + top: 20.0, ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - decoration: BoxDecoration( - color: appStyle.colors.card, - borderRadius: - BorderRadius.vertical(top: Radius.circular(16)), + child: Column( + children: [ + Center( + child: Text( + setting.title, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), ), - child: Padding( - padding: const EdgeInsets.only( - left: 18.0, right: 16.0, bottom: 30.0, top: 20.0), - child: Column( + Padding( + padding: const EdgeInsets.symmetric( + vertical: 0, + horizontal: 40, + ), + child: Row( + mainAxisSize: MainAxisSize.max, children: [ - Center( - child: Text( - setting.title, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), - )), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 0, horizontal: 40), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - // TODO: Make a firka slider - child: Slider( - min: setting.minValue, - value: setting.value, - max: setting.maxValue, - divisions: setting.step != null - ? ((setting.maxValue - - setting.minValue) / - setting.step!) - .round() - : null, - thumbColor: appStyle.colors.accent, - activeColor: appStyle.colors.secondary, - inactiveColor: appStyle.colors.a15p, - onChanged: (v) async { - setState(() { - if (setting.step != null) { - setting.value = - (v / setting.step!).round() * - setting.step!; - } else { - setting.value = v; - } - setting.value = - setting.toRoundedDouble(); - }); + Expanded( + // TODO: Make a firka slider + child: Slider( + min: setting.minValue, + value: setting.value, + max: setting.maxValue, + divisions: setting.step != null + ? ((setting.maxValue - setting.minValue) / + setting.step!) + .round() + : null, + thumbColor: appStyle.colors.accent, + activeColor: appStyle.colors.secondary, + inactiveColor: appStyle.colors.a15p, + onChanged: (v) async { + setState(() { + if (setting.step != null) { + setting.value = + (v / setting.step!).round() * + setting.step!; + } else { + setting.value = v; + } + setting.value = setting.toRoundedDouble(); + }); - await data.isar.writeTxn(() async { - await setting.save( - data.isar.appSettingsModels); + await data.isar.writeTxn(() async { + await setting.save( + data.isar.appSettingsModels, + ); - setStateOuter(() {}); - }); - await setting.postUpdate(); - }), - ), - Text(setting.toRoundedString(), - style: appStyle.fonts.B_16R.apply( - color: appStyle.colors.textPrimary)) - ], + setStateOuter(() {}); + }); + await setting.postUpdate(); + }, + ), + ), + Text( + setting.toRoundedString(), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, ), ), ], ), ), - ), + ], ), - ], - )); + ), + ), + ), + ], + ), + ); }, ); } -void showSettingsSheet(BuildContext context, double height, - AppInitialization data, LinkedHashMap items) { +void showSettingsSheet( + BuildContext context, + double height, + AppInitialization data, + LinkedHashMap items, +) { showModalBottomSheet( context: context, elevation: 100, @@ -1114,9 +1329,7 @@ void showSettingsSheet(BuildContext context, double height, enableDrag: true, backgroundColor: Colors.transparent, barrierColor: appStyle.colors.a15p, - constraints: BoxConstraints( - maxHeight: height, - ), + constraints: BoxConstraints(maxHeight: height), builder: (BuildContext context) { return Stack( children: [ diff --git a/firka/lib/ui/phone/widgets/bottom_nav_icon.dart b/firka/lib/ui/phone/widgets/bottom_nav_icon.dart index e2d3b19..ed4ecef 100644 --- a/firka/lib/ui/phone/widgets/bottom_nav_icon.dart +++ b/firka/lib/ui/phone/widgets/bottom_nav_icon.dart @@ -14,9 +14,16 @@ class BottomNavIconWidget extends StatelessWidget { final Color textColor; final bool isProfilePicture; - const BottomNavIconWidget(this.onTap, this.active, this.icon, this.text, - this.iconColor, this.textColor, - {this.isProfilePicture = false, super.key}); + const BottomNavIconWidget( + this.onTap, + this.active, + this.icon, + this.text, + this.iconColor, + this.textColor, { + this.isProfilePicture = false, + super.key, + }); @override Widget build(BuildContext context) { @@ -44,17 +51,22 @@ class BottomNavIconWidget extends StatelessWidget { ), ) else - FirkaIconWidget(FirkaIconType.majesticons, icon as Uint8List, - color: iconColor, size: 24) - .build(context), + FirkaIconWidget( + FirkaIconType.majesticons, + icon as Uint8List, + color: iconColor, + size: 24, + ).build(context), const SizedBox(height: 4), Text( text, style: active - ? appStyle.fonts.B_12SB - .apply(color: appStyle.colors.textPrimary) - : appStyle.fonts.B_12R - .apply(color: appStyle.colors.textSecondary), + ? appStyle.fonts.B_12SB.apply( + color: appStyle.colors.textPrimary, + ) + : appStyle.fonts.B_12R.apply( + color: appStyle.colors.textSecondary, + ), ), ], ), diff --git a/firka/lib/ui/phone/widgets/bottom_tt_icon.dart b/firka/lib/ui/phone/widgets/bottom_tt_icon.dart index 236cf2a..fc226d0 100644 --- a/firka/lib/ui/phone/widgets/bottom_tt_icon.dart +++ b/firka/lib/ui/phone/widgets/bottom_tt_icon.dart @@ -11,8 +11,12 @@ class BottomTimeTableNavIconWidget extends StatelessWidget { final DateTime date; const BottomTimeTableNavIconWidget( - this.l10n, this.onTap, this.active, this.date, - {super.key}); + this.l10n, + this.onTap, + this.active, + this.date, { + super.key, + }); @override Widget build(BuildContext context) { @@ -22,25 +26,31 @@ class BottomTimeTableNavIconWidget extends StatelessWidget { onTap(); }, child: Card( - color: - active ? appStyle.colors.buttonSecondaryFill : Colors.transparent, + color: active + ? appStyle.colors.buttonSecondaryFill + : Colors.transparent, shadowColor: active ? appStyle.colors.shadowColor : Colors.transparent, child: SizedBox( - width: 40, - height: 54, - child: Column( - children: [ - SizedBox(height: 6), - Text(date.format(l10n, FormatMode.da), - style: appStyle.fonts.H_16px - .apply(color: appStyle.colors.textPrimary)), - Text( - date.format(l10n, FormatMode.dd), - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), - ) - ], - )), + width: 40, + height: 54, + child: Column( + children: [ + SizedBox(height: 6), + Text( + date.format(l10n, FormatMode.da), + style: appStyle.fonts.H_16px.apply( + color: appStyle.colors.textPrimary, + ), + ), + Text( + date.format(l10n, FormatMode.dd), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), + ], + ), + ), ), ); } diff --git a/firka/lib/ui/phone/widgets/grade_chart.dart b/firka/lib/ui/phone/widgets/grade_chart.dart index 693c48f..8788c0a 100644 --- a/firka/lib/ui/phone/widgets/grade_chart.dart +++ b/firka/lib/ui/phone/widgets/grade_chart.dart @@ -1,5 +1,4 @@ import 'package:firka/helpers/api/model/grade.dart'; -import 'package:firka/helpers/average_helper.dart'; import 'package:firka/helpers/ui/grade_helpers.dart'; import 'package:firka/ui/model/style.dart'; import 'package:fl_chart/fl_chart.dart'; @@ -25,7 +24,7 @@ class _GradeChartState extends State { appStyle.colors.grade2, appStyle.colors.grade1, ]; - + late final List spots; @override @@ -60,7 +59,6 @@ class _GradeChartState extends State { spots.add(FlSpot(9, 4.00)); spots.add(FlSpot(10, 4.89)); - if (spots.isEmpty) { spots = [const FlSpot(0, 0)]; } @@ -71,9 +69,7 @@ class _GradeChartState extends State { return ClipRRect( borderRadius: BorderRadius.circular(12), child: DecoratedBox( - decoration: BoxDecoration( - color: appStyle.colors.card, - ), + decoration: BoxDecoration(color: appStyle.colors.card), child: AspectRatio( aspectRatio: 1.90, child: Padding( @@ -114,11 +110,12 @@ class _GradeChartState extends State { child: Text(text, style: style), ); } + Widget buildCircle({ required String text, required Color bgColor, required Color textColor, - }) { + }) { return SizedBox( width: 24, height: 24, @@ -140,8 +137,8 @@ class _GradeChartState extends State { ), ); } - Widget leftTitleWidgets(double value, TitleMeta meta) { + Widget leftTitleWidgets(double value, TitleMeta meta) { String text = switch (value.toInt()) { 1 => '1', 2 => '2', @@ -151,7 +148,7 @@ class _GradeChartState extends State { _ => '', }; Color gradeColor; - if (text != ""){ + if (text != "") { gradeColor = getGradeColor(int.parse(text).toDouble()); } else { gradeColor = getGradeColor(0); @@ -233,14 +230,14 @@ class _GradeChartState extends State { getTooltipColor: (touchedSpot) => appStyle.colors.buttonSecondaryFill, tooltipBorderRadius: BorderRadius.circular(90), fitInsideVertically: true, - + showOnTopOfTheChartBoxArea: true, getTooltipItems: (touchedSpots) { return touchedSpots.map((LineBarSpot touchedSpot) { final textStyle = TextStyle( color: colorForY(touchedSpot.y), fontWeight: FontWeight.bold, - fontSize: 14 + fontSize: 14, ); return LineTooltipItem(touchedSpot.y.toString(), textStyle); }).toList(); @@ -248,15 +245,10 @@ class _GradeChartState extends State { ), getTouchedSpotIndicator: (barData, spotIndexes) { return spotIndexes.map((index) { - final touchedSpot = barData.spots[index]; + final touchedSpot = barData.spots[index]; return TouchedSpotIndicatorData( - FlLine( - color: colorForY(touchedSpot.y), - strokeWidth: 3, - ), - FlDotData( - show: false, - ), + FlLine(color: colorForY(touchedSpot.y), strokeWidth: 3), + FlDotData(show: false), ); }).toList(); }, @@ -282,18 +274,18 @@ class _GradeChartState extends State { // color: const Color(0xFFC8C8C8), // strokeWidth: 1.2, // ); - return FlLine( + return FlLine( color: const Color(0xFFC8C8C8), strokeWidth: 1.0, dashArray: [8, 12], ); } return FlLine( - color: const Color(0xFFC8C8C8), - strokeWidth: 1.0, - dashArray: [8, 12], - ); - } + color: const Color(0xFFC8C8C8), + strokeWidth: 1.0, + dashArray: [8, 12], + ); + }, ), titlesData: FlTitlesData( show: true, @@ -313,9 +305,7 @@ class _GradeChartState extends State { interval: 1, ), ), - topTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), + topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), @@ -331,9 +321,8 @@ class _GradeChartState extends State { LineChartBarData( spots: spots, isCurved: false, - - showingIndicators: - _touchedIndex != null ? [_touchedIndex!] : [], + + showingIndicators: _touchedIndex != null ? [_touchedIndex!] : [], gradient: LinearGradient( colors: [for (final s in spots) colorForY(s.y)], ), @@ -344,8 +333,7 @@ class _GradeChartState extends State { show: true, gradient: LinearGradient( colors: [ - for (final s in spots) - colorForY(s.y).withValues(alpha: 0.1), + for (final s in spots) colorForY(s.y).withValues(alpha: 0.1), ], ), ), diff --git a/firka/lib/ui/phone/widgets/home_bottom_nav.dart b/firka/lib/ui/phone/widgets/home_bottom_nav.dart index e69de29..8b13789 100644 --- a/firka/lib/ui/phone/widgets/home_bottom_nav.dart +++ b/firka/lib/ui/phone/widgets/home_bottom_nav.dart @@ -0,0 +1 @@ + diff --git a/firka/lib/ui/phone/widgets/home_main_starting_soon.dart b/firka/lib/ui/phone/widgets/home_main_starting_soon.dart index ee85313..e6f9235 100644 --- a/firka/lib/ui/phone/widgets/home_main_starting_soon.dart +++ b/firka/lib/ui/phone/widgets/home_main_starting_soon.dart @@ -38,60 +38,74 @@ class StartingSoonWidget extends StatelessWidget { SizedBox(width: 6), Text( l10n.starting_soon, - style: appStyle.fonts.H_16px - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.H_16px.apply( + color: appStyle.colors.textPrimary, + ), ), ], ), Row( children: [ CounterDigitWidget( - hour.toString(), - appStyle.fonts.H_16px - .apply(color: appStyle.colors.textPrimary)), + hour.toString(), + appStyle.fonts.H_16px.apply( + color: appStyle.colors.textPrimary, + ), + ), SizedBox(width: 2), Text( hourTxt, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), ), SizedBox(width: 4), CounterDigitWidget( - (min / 10).floor().toString(), - appStyle.fonts.H_16px - .apply(color: appStyle.colors.textPrimary)), + (min / 10).floor().toString(), + appStyle.fonts.H_16px.apply( + color: appStyle.colors.textPrimary, + ), + ), CounterDigitWidget( - ((min % 10)).toString(), - appStyle.fonts.H_16px - .apply(color: appStyle.colors.textPrimary)), + ((min % 10)).toString(), + appStyle.fonts.H_16px.apply( + color: appStyle.colors.textPrimary, + ), + ), SizedBox(width: 2), Text( minTxt, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), ), SizedBox(width: 4), CounterDigitWidget( - (sec / 10).floor().toString(), - appStyle.fonts.H_16px - .apply(color: appStyle.colors.textPrimary)), + (sec / 10).floor().toString(), + appStyle.fonts.H_16px.apply( + color: appStyle.colors.textPrimary, + ), + ), CounterDigitWidget( - ((sec % 10)).toString(), - appStyle.fonts.H_16px - .apply(color: appStyle.colors.textPrimary)), + ((sec % 10)).toString(), + appStyle.fonts.H_16px.apply( + color: appStyle.colors.textPrimary, + ), + ), SizedBox(width: 2), Text( secTxt, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), ), ], - ) + ), ], - ) + ), ], right: [], - ) + ), ], ); } diff --git a/firka/lib/ui/phone/widgets/home_main_welcome.dart b/firka/lib/ui/phone/widgets/home_main_welcome.dart index 13d4aee..85ec365 100644 --- a/firka/lib/ui/phone/widgets/home_main_welcome.dart +++ b/firka/lib/ui/phone/widgets/home_main_welcome.dart @@ -16,8 +16,13 @@ class WelcomeWidget extends StatefulWidget { final List lessons; final DateTime now; - const WelcomeWidget(this.l10n, this.now, this.student, this.lessons, - {super.key}); + const WelcomeWidget( + this.l10n, + this.now, + this.student, + this.lessons, { + super.key, + }); @override State createState() => _WelcomeWidgetState(); @@ -29,7 +34,9 @@ class _WelcomeWidgetState extends State { @override void initState() { super.initState(); - _controllerCenter = ConfettiController(duration: const Duration(seconds: 2)); + _controllerCenter = ConfettiController( + duration: const Duration(seconds: 2), + ); final birthDate = DateFormat("MM-dd").format(widget.student.birthdate); if (birthDate == DateFormat("MM-dd").format(widget.now)) { @@ -37,28 +44,38 @@ class _WelcomeWidgetState extends State { } } - @override void dispose() { _controllerCenter.dispose(); super.dispose(); } - getIconForCycle(Cycle dayCycle) { + Widget getIconForCycle(Cycle dayCycle) { switch (dayCycle) { case Cycle.morning: - return FirkaIconWidget(FirkaIconType.majesticonsLocal, "sunSolid", - color: appStyle.colors.accent); + return FirkaIconWidget( + FirkaIconType.majesticonsLocal, + "sunSolid", + color: appStyle.colors.accent, + ); case Cycle.day: return FirkaIconWidget( - FirkaIconType.majesticonsLocal, "parkSolidSchool", - color: appStyle.colors.accent); + FirkaIconType.majesticonsLocal, + "parkSolidSchool", + color: appStyle.colors.accent, + ); case Cycle.afternoon: - return FirkaIconWidget(FirkaIconType.majesticons, Majesticon.moonSolid, - color: appStyle.colors.accent); + return FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.moonSolid, + color: appStyle.colors.accent, + ); case Cycle.night: - return FirkaIconWidget(FirkaIconType.majesticons, Majesticon.moonSolid, - color: appStyle.colors.accent); + return FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.moonSolid, + color: appStyle.colors.accent, + ); } } @@ -87,8 +104,9 @@ class _WelcomeWidgetState extends State { final birthDate = DateFormat("MM-dd").format(widget.student.birthdate); if (birthDate == DateFormat("MM-dd").format(widget.now)) { return widget.l10n.happy_birthday(name); - } else if (widget.lessons.length > 1 &&widget.now.isBefore(widget.lessons.first.start)) { - return getRawTitle(name, dayCycle); + } else if (widget.lessons.length > 1 && + widget.now.isBefore(widget.lessons.first.start)) { + return getRawTitle(name, dayCycle); } else { return getRawTitle(name, dayCycle); } @@ -105,8 +123,9 @@ class _WelcomeWidgetState extends State { if (now.isBefore(lessons.first.start)) { return now.format(l10n, FormatMode.welcome); } - var lessonsLeft = - lessons.where((lesson) => lesson.end.isAfter(now)).length; + var lessonsLeft = lessons + .where((lesson) => lesson.end.isAfter(now)) + .length; if (lessonsLeft < 1) { return l10n.tomorrow_subtitle; } @@ -140,13 +159,19 @@ class _WelcomeWidgetState extends State { children: [ getIconForCycle(dayCycle), const SizedBox(height: 16.0), - Text(getTitle(dayCycle), - style: appStyle.fonts.H_H2 - .copyWith(color: appStyle.colors.textPrimary)), + Text( + getTitle(dayCycle), + style: appStyle.fonts.H_H2.copyWith( + color: appStyle.colors.textPrimary, + ), + ), const SizedBox(height: 2.0), - Text(getSubtitle(dayCycle), - style: appStyle.fonts.B_16R - .copyWith(color: appStyle.colors.textSecondary)), + Text( + getSubtitle(dayCycle), + style: appStyle.fonts.B_16R.copyWith( + color: appStyle.colors.textSecondary, + ), + ), ], ), ], diff --git a/firka/lib/ui/phone/widgets/homework.dart b/firka/lib/ui/phone/widgets/homework.dart index e02d2dd..572801f 100644 --- a/firka/lib/ui/phone/widgets/homework.dart +++ b/firka/lib/ui/phone/widgets/homework.dart @@ -41,40 +41,45 @@ class HomeworkWidget extends StatelessWidget { color: appStyle.colors.accent, size: 24, ); - } - ), - SizedBox( - width: 8, + }, ), + SizedBox(width: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - FutureBuilder( - future: isHomeworkDone(data.isar, item.uid), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return SizedBox(); - } - final done = snapshot.data!; - return done - ? Text(data.l10n.homework, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary, decoration: TextDecoration.lineThrough)) - : Text(data.l10n.homework, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)); - }, + FutureBuilder( + future: isHomeworkDone(data.isar, item.uid), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return SizedBox(); + } + final done = snapshot.data!; + return done + ? Text( + data.l10n.homework, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + decoration: TextDecoration.lineThrough, + ), + ) + : Text( + data.l10n.homework, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ); + }, + ), + Text( + item.subjectName, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, ), - Text( - item.subjectName, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary), - ) - - ], + ), + ], ), ], - ) + ), ], ), onTap: () { diff --git a/firka/lib/ui/phone/widgets/info_board_item.dart b/firka/lib/ui/phone/widgets/info_board_item.dart index 050504c..fe87084 100644 --- a/firka/lib/ui/phone/widgets/info_board_item.dart +++ b/firka/lib/ui/phone/widgets/info_board_item.dart @@ -12,55 +12,62 @@ class InfoBoardItemWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return FirkaCard(left: [ - Row( - children: [ - Container( - decoration: ShapeDecoration( + return FirkaCard( + left: [ + Row( + children: [ + Container( + decoration: ShapeDecoration( color: appStyle.colors.accent, shape: CircleBorder( eccentricity: 1, // borderRadius: BorderRadius.circular(6)), - )), - child: SizedBox( - width: 28, - height: 28, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 6), - child: Text( - item.author[0], - style: appStyle.fonts.H_18px.copyWith( - fontSize: 20, color: appStyle.colors.textPrimary), - ), - ) - ], - ), - ), - ), - SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width / 1.4, - child: Text( - item.title, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary), ), ), - Text( - item.author, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary), - ) - ], - ) - ], - ) - ]); + child: SizedBox( + width: 28, + height: 28, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 6), + child: Text( + item.author[0], + style: appStyle.fonts.H_18px.copyWith( + fontSize: 20, + color: appStyle.colors.textPrimary, + ), + ), + ), + ], + ), + ), + ), + SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width / 1.4, + child: Text( + item.title, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), + ), + Text( + item.author, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), + ], + ), + ], + ), + ], + ); } } diff --git a/firka/lib/ui/phone/widgets/lesson.dart b/firka/lib/ui/phone/widgets/lesson.dart index 7603ab3..bccf87c 100644 --- a/firka/lib/ui/phone/widgets/lesson.dart +++ b/firka/lib/ui/phone/widgets/lesson.dart @@ -25,9 +25,17 @@ class LessonWidget extends StatelessWidget { final Lesson? nextLesson; final bool? placeholderMode; - const LessonWidget(this.data, this.week, this.day, this.lessonNo, this.lesson, - this.test, this.nextLesson, - {super.key, this.placeholderMode}); + const LessonWidget( + this.data, + this.week, + this.day, + this.lessonNo, + this.lesson, + this.test, + this.nextLesson, { + super.key, + this.placeholderMode, + }); @override Widget build(BuildContext context) { @@ -50,8 +58,10 @@ class LessonWidget extends StatelessWidget { if (week.isNotEmpty) { showBreak = timeNow().isAfter(lesson.start) && timeNow().isBefore(lesson.end) || - timeNow().isAfter(week.last.end) || - lesson.start.getMidnight() != timeNow().getMidnight() || timeNow().isAfter(day.last.end) || timeNow().isBefore(day.first.start); + timeNow().isAfter(week.last.end) || + lesson.start.getMidnight() != timeNow().getMidnight() || + timeNow().isAfter(day.last.end) || + timeNow().isBefore(day.first.start); } var accent = appStyle.colors.accent; var secondary = appStyle.colors.secondary; @@ -81,117 +91,146 @@ class LessonWidget extends StatelessWidget { roomName = "${roomName.substring(0, 11 - 3)}..."; } - elements.add(GestureDetector( - - onTap: () { - if (lessonNo == null) return; - showLessonBottomSheet( - context, data, lesson, lessonNo, accent, secondary, bgColor, test); - }, - child: FirkaCard( - color: isDismissed - ? appStyle.colors.cardTranslucent - : appStyle.colors.card, - shadow: !isDismissed, - left: [ - showLessonNos == false || lessonNo == null - ? SizedBox() - : SizedBox( - width: 18, - height: 18, - child: Stack( - alignment: Alignment.center, - children: [ - SvgPicture.asset( - "assets/icons/subtract.svg", - color: bgColor, - width: 18, - height: 18, - ), - Text( - lessonNo.toString(), - style: appStyle.fonts.B_12R.apply(color: secondary), - textAlign: TextAlign.center, - ), - ], + elements.add( + GestureDetector( + onTap: () { + if (lessonNo == null) return; + showLessonBottomSheet( + context, + data, + lesson, + lessonNo, + accent, + secondary, + bgColor, + test, + ); + }, + child: FirkaCard( + color: isDismissed + ? appStyle.colors.cardTranslucent + : appStyle.colors.card, + shadow: !isDismissed, + left: [ + showLessonNos == false || lessonNo == null + ? SizedBox() + : SizedBox( + width: 18, + height: 18, + child: Stack( + alignment: Alignment.center, + children: [ + SvgPicture.asset( + "assets/icons/subtract.svg", + color: bgColor, + width: 18, + height: 18, + ), + Text( + lessonNo.toString(), + style: appStyle.fonts.B_12R.apply(color: secondary), + textAlign: TextAlign.center, + ), + ], + ), ), + Transform.translate( + offset: Offset(-4, 0), + child: Card( + shadowColor: Colors.transparent, + color: bgColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), ), - Transform.translate( - offset: Offset(-4, 0), - child: Card( - shadowColor: Colors.transparent, - color: bgColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16)), - child: Stack(children: [ - Padding( - padding: EdgeInsetsGeometry.all(4), - child: ClassIconWidget( - color: accent, - size: 20, - uid: lesson.uid, - className: lesson.name, - category: lesson.subject?.name != null - ? lesson.subject!.name.firstUpper() - : '', - ), - ), - !showTests && test != null - ? Transform.translate( - offset: Offset(26, -18), - child: BubbleTest(), - ) - : SizedBox(), - ]), - ), - ), - SizedBox(width: !showTests && test != null ? 16 : 8), - Text(subjectName, - style: appStyle.fonts.B_15SB - .apply(color: appStyle.colors.textPrimary)), - ], - right: [ - placeholderMode == true - ? SizedBox() - : Text( - isDismissed - ? data.l10n.class_dismissed - : lesson.start - .toLocal() - .format(data.l10n, FormatMode.hmm), - style: appStyle.fonts.B_14R - .apply(color: appStyle.colors.textPrimary)), - placeholderMode == true - ? SizedBox() - : isDismissed - ? SizedBox() - : Card( - shadowColor: Colors.transparent, - color: appStyle.colors.a15p, - child: Padding( - padding: EdgeInsets.all(5), - child: Text(roomName, - style: appStyle.fonts.B_12R - .apply(color: appStyle.colors.secondary)), + child: Stack( + children: [ + Padding( + padding: EdgeInsetsGeometry.all(4), + child: ClassIconWidget( + color: accent, + size: 20, + uid: lesson.uid, + className: lesson.name, + category: lesson.subject?.name != null + ? lesson.subject!.name.firstUpper() + : '', ), ), - ], + !showTests && test != null + ? Transform.translate( + offset: Offset(26, -18), + child: BubbleTest(), + ) + : SizedBox(), + ], + ), + ), + ), + SizedBox(width: !showTests && test != null ? 16 : 8), + Text( + subjectName, + style: appStyle.fonts.B_15SB.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + right: [ + placeholderMode == true + ? SizedBox() + : Text( + isDismissed + ? data.l10n.class_dismissed + : lesson.start.toLocal().format( + data.l10n, + FormatMode.hmm, + ), + style: appStyle.fonts.B_14R.apply( + color: appStyle.colors.textPrimary, + ), + ), + placeholderMode == true + ? SizedBox() + : isDismissed + ? SizedBox() + : Card( + shadowColor: Colors.transparent, + color: appStyle.colors.a15p, + child: Padding( + padding: EdgeInsets.all(5), + child: Text( + roomName, + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.secondary, + ), + ), + ), + ), + ], + ), ), - )); + ); if (isSubstituted && showSubstitutions) { - elements.add(FirkaCard( - left: [ - Text(data.l10n.class_substitution, - style: appStyle.fonts.H_16px - .apply(color: appStyle.colors.textPrimary)) - ], - right: [ - Text(lesson.substituteTeacher!, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary)) - ], - )); + elements.add( + FirkaCard( + left: [ + Text( + data.l10n.class_substitution, + style: appStyle.fonts.H_16px.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + right: [ + Text( + lesson.substituteTeacher!, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), + ], + ), + ); } if (test != null && showTests) { @@ -206,29 +245,46 @@ class LessonWidget extends StatelessWidget { theme = theme.firstUpper(); method = method.firstUpper(); - elements.add(GestureDetector( - onTap: () { - showTestBottomSheet( - context, data, lesson, lessonNo, accent, secondary, bgColor, test); - }, - child: FirkaCard( - left: [ - FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.editPen4Solid, - color: appStyle.colors.accent, + elements.add( + GestureDetector( + onTap: () { + showTestBottomSheet( + context, + data, + lesson, + lessonNo, + accent, + secondary, + bgColor, + test, + ); + }, + child: FirkaCard( + left: [ + FirkaIconWidget( + FirkaIconType.majesticons, + Majesticon.editPen4Solid, + color: appStyle.colors.accent, + ), + SizedBox(width: 6), + Text( + theme, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textSecondary, + ), + ), + ], + right: [ + Text( + method, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textTertiary, + ), + ), + ], ), - SizedBox(width: 6), - Text(theme, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textSecondary)) - ], - right: [ - Text(method, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textTertiary)) - ], - ))); + ), + ); } if (nextLesson != null) { @@ -237,9 +293,11 @@ class LessonWidget extends StatelessWidget { if (breakMins > 45) { final breakEnd = lesson.end.add(Duration(minutes: breakMins)); - final emptyClass = seqSchedule.firstWhereOrNull((lesson2) => - lesson2.start.isAfter(lesson.end) && - lesson2.end.isBefore(breakEnd)); + final emptyClass = seqSchedule.firstWhereOrNull( + (lesson2) => + lesson2.start.isAfter(lesson.end) && + lesson2.end.isBefore(breakEnd), + ); if (emptyClass != null) { final preBreak = emptyClass.start.difference(lesson.end).inMinutes; @@ -250,136 +308,177 @@ class LessonWidget extends StatelessWidget { .subGroup("timetable_toast") .boolean("breaks") && showBreak) { - elements.add(FirkaCard( - color: appStyle.colors.cardTranslucent, - shadow: false, + elements.add( + FirkaCard( + color: appStyle.colors.cardTranslucent, + shadow: false, + left: [ + Text( + data.l10n.breakTxt, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textSecondary, + ), + ), + ], + right: [ + Text( + "$preBreak ${preBreak == 1 ? data.l10n.starting_min : data.l10n.starting_min_plural}", + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textTertiary, + ), + ), + ], + ), + ); + } + + elements.add( + FirkaCard( left: [ - Text(data.l10n.breakTxt, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textSecondary)) + SizedBox( + width: 18, + height: 18, + child: Stack( + children: [ + SvgPicture.asset( + "assets/icons/subtract.svg", + color: bgColor, + width: 18, + height: 18, + ), + Padding( + padding: EdgeInsets.only(left: 5), + child: Text( + emptyClass.lessonNumber.toString(), + style: appStyle.fonts.B_12R.apply(color: secondary), + ), + ), + ], + ), + ), + Transform.translate( + offset: Offset(-4, 0), + child: Card( + shadowColor: Colors.transparent, + color: bgColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: EdgeInsetsGeometry.all(4), + child: FirkaIconWidget( + FirkaIconType.majesticonsLocal, + 'cupFilled', + color: appStyle.colors.accent, + size: 24, + ), + ), + ), + ), + SizedBox(width: 8), + Text( + data.l10n.empty_class, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), ], right: [ Text( - "$preBreak ${preBreak == 1 ? data.l10n.starting_min : data.l10n.starting_min_plural}", - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textTertiary)) - ], - )); - } - - elements.add(FirkaCard( - left: [ - SizedBox( - width: 18, - height: 18, - child: Stack( - children: [ - SvgPicture.asset( - "assets/icons/subtract.svg", - color: bgColor, - width: 18, - height: 18, - ), - Padding( - padding: EdgeInsets.only(left: 5), - child: Text(emptyClass.lessonNumber.toString(), - style: appStyle.fonts.B_12R.apply(color: secondary)), - ) - ], - ), - ), - Transform.translate( - offset: Offset(-4, 0), - child: Card( - shadowColor: Colors.transparent, - color: bgColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16)), - child: Padding( - padding: EdgeInsetsGeometry.all(4), - child: FirkaIconWidget( - FirkaIconType.majesticonsLocal, 'cupFilled', - color: appStyle.colors.accent, size: 24), - ), - ), - ), - SizedBox(width: 8), - Text(data.l10n.empty_class, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)), - ], - right: [ - Text( isDismissed ? data.l10n.class_dismissed : "${emptyClass.start.toLocal().format(data.l10n, FormatMode.hmm)} - ${emptyClass.end.toLocal().format(data.l10n, FormatMode.hmm)}", - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)) - ], - )); + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + ); if (data.settings .group("settings") .subGroup("timetable_toast") .boolean("breaks") && showBreak) { - elements.add(FirkaCard( - color: appStyle.colors.cardTranslucent, - shadow: false, - left: [ - Text(data.l10n.breakTxt, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textSecondary)) - ], - right: [ - Text( + elements.add( + FirkaCard( + color: appStyle.colors.cardTranslucent, + shadow: false, + left: [ + Text( + data.l10n.breakTxt, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textSecondary, + ), + ), + ], + right: [ + Text( "$postBreak ${postBreak == 1 ? data.l10n.starting_min : data.l10n.starting_min_plural}", - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textTertiary)) - ], - )); + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textTertiary, + ), + ), + ], + ), + ); } } else if (data.settings .group("settings") .subGroup("timetable_toast") .boolean("breaks") && showBreak) { - elements.add(FirkaCard( - color: appStyle.colors.cardTranslucent, - shadow: false, - left: [ - Text(data.l10n.breakTxt, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textSecondary)) - ], - right: [ - Text( + elements.add( + FirkaCard( + color: appStyle.colors.cardTranslucent, + shadow: false, + left: [ + Text( + data.l10n.breakTxt, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textSecondary, + ), + ), + ], + right: [ + Text( "$breakMins ${breakMins == 1 ? data.l10n.starting_min : data.l10n.starting_min_plural}", - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textTertiary)) - ], - )); + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textTertiary, + ), + ), + ], + ), + ); } } else if (data.settings .group("settings") .subGroup("timetable_toast") .boolean("breaks") && showBreak) { - elements.add(FirkaCard( - color: appStyle.colors.cardTranslucent, - shadow: false, - left: [ - Text(data.l10n.breakTxt, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textSecondary)) - ], - right: [ - Text( + elements.add( + FirkaCard( + color: appStyle.colors.cardTranslucent, + shadow: false, + left: [ + Text( + data.l10n.breakTxt, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textSecondary, + ), + ), + ], + right: [ + Text( "$breakMins ${breakMins == 1 ? data.l10n.starting_min : data.l10n.starting_min_plural}", - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textTertiary)) - ], - )); + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textTertiary, + ), + ), + ], + ), + ); } } diff --git a/firka/lib/ui/phone/widgets/lesson_big.dart b/firka/lib/ui/phone/widgets/lesson_big.dart index 2f3fc3f..7bfd626 100644 --- a/firka/lib/ui/phone/widgets/lesson_big.dart +++ b/firka/lib/ui/phone/widgets/lesson_big.dart @@ -21,25 +21,36 @@ class LessonBigWidget extends StatelessWidget { final List lessons; final List tests; - const LessonBigWidget(this.l10n, this.now, this.lessonNo, this.lesson, - this.prevLesson, this.nextLesson, this.lessons, this.tests, - {super.key}); + const LessonBigWidget( + this.l10n, + this.now, + this.lessonNo, + this.lesson, + this.prevLesson, + this.nextLesson, + this.lessons, + this.tests, { + super.key, + }); @override Widget build(BuildContext context) { var hasLesson = lesson != null; - var lessonsLeft = - lessons.where((lesson) => lesson.end.isAfter(now)).length; + var lessonsLeft = lessons.where((lesson) => lesson.end.isAfter(now)).length; var hasPrevLesson = prevLesson != null; var hasNextLesson = nextLesson != null; // TODO: holnapi órák száma kiszámolás var lessonsTomorrow = 0; - - var testsTomorrow = tests.where((test) => - test.date.isAfter(now) && - test.date.isBefore(DateTime(now.year, now.month, now.day + 2))).length; - - if (lessonsLeft < 1){ + + var testsTomorrow = tests + .where( + (test) => + test.date.isAfter(now) && + test.date.isBefore(DateTime(now.year, now.month, now.day + 2)), + ) + .length; + + if (lessonsLeft < 1) { return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -58,7 +69,9 @@ class LessonBigWidget extends StatelessWidget { Card( shadowColor: Colors.transparent, color: appStyle.colors.a15p, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), child: Padding( padding: EdgeInsets.all(6), child: FirkaIconWidget( @@ -74,14 +87,16 @@ class LessonBigWidget extends StatelessWidget { ), Text( testsTomorrow == 0 - ? l10n.tt_no_classes_l2 - : l10n.get_ready, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)), + ? l10n.tt_no_classes_l2 + : l10n.get_ready, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), ], - ) + ), ], - ) + ), ], extra: Column( children: [ @@ -102,10 +117,10 @@ class LessonBigWidget extends StatelessWidget { Padding( padding: EdgeInsets.all(2), child: FirkaIconWidget( - FirkaIconType.majesticons, - Majesticon.editPen4Solid, - size: 32.0, - color: appStyle.colors.accent, + FirkaIconType.majesticons, + Majesticon.editPen4Solid, + size: 32.0, + color: appStyle.colors.accent, ), ), ], @@ -113,24 +128,27 @@ class LessonBigWidget extends StatelessWidget { ), SizedBox(width: 8), Text( - (lessonsTomorrow == 0 && testsTomorrow == 0) - ? l10n.no_tests_tomorrow - : (testsTomorrow > 1) + (lessonsTomorrow == 0 && testsTomorrow == 0) + ? l10n.no_tests_tomorrow + : (testsTomorrow > 1) ? l10n.tests_tomorrow(testsTomorrow.toString()) : (testsTomorrow < 1 && lessonsTomorrow > 0) - ? l10n.lessons_tomorrow(lessonsTomorrow.toString()) - : l10n.tests_tomorrow(testsTomorrow.toString()), - textAlign: TextAlign.left, - style: appStyle.fonts.B_16R.apply( + ? l10n.lessons_tomorrow( + lessonsTomorrow.toString(), + ) + : l10n.tests_tomorrow(testsTomorrow.toString()), + textAlign: TextAlign.left, + style: appStyle.fonts.B_16R.apply( color: appStyle.colors.textPrimary, ), ), - ]) + ], + ), ), ), ], - ) - ) + ), + ), ], ); } @@ -152,36 +170,52 @@ class LessonBigWidget extends StatelessWidget { child: Padding( padding: EdgeInsets.all(4), child: FirkaIconWidget( - FirkaIconType.majesticons, 'cupFilled', - color: appStyle.colors.accent, size: 24), + FirkaIconType.majesticons, + 'cupFilled', + color: appStyle.colors.accent, + size: 24, + ), + ), + ), + Text( + l10n.breakTxt, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, ), ), - Text(l10n.breakTxt, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)), ], ), ], - ) + ), ], right: [ Column( children: [ - Row(children: [ - Text('-', - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)) - ]), - Row(children: [ - Text('-', - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)) - ]) + Row( + children: [ + Text( + '-', + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + Row( + children: [ + Text( + '-', + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), ], - ) + ), ], extra: SizedBox.shrink(), - ) + ), ], ); } @@ -208,46 +242,65 @@ class LessonBigWidget extends StatelessWidget { child: Padding( padding: EdgeInsets.all(4), child: FirkaIconWidget( - FirkaIconType.majesticonsLocal, 'cupFilled', - color: appStyle.colors.accent, size: 24), + FirkaIconType.majesticonsLocal, + 'cupFilled', + color: appStyle.colors.accent, + size: 24, + ), + ), + ), + Text( + l10n.breakTxt, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, ), ), - Text(l10n.breakTxt, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)), ], ), Row( children: [ - Text(timeLeftStr, - style: appStyle.fonts.B_12R - .apply(color: appStyle.colors.textSecondary)), + Text( + timeLeftStr, + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.textSecondary, + ), + ), ], ), ], - ) + ), ], right: [ Column( children: [ - Row(children: [ - Text('-', - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)) - ]), - Row(children: [ - Text( - nextLesson!.start - .toLocal() - .format(l10n, FormatMode.hmm), - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)) - ]) + Row( + children: [ + Text( + '-', + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + Row( + children: [ + Text( + nextLesson!.start.toLocal().format( + l10n, + FormatMode.hmm, + ), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), ], - ) + ), ], extra: SizedBox.shrink(), - ) + ), ], ); } @@ -302,76 +355,96 @@ class LessonBigWidget extends StatelessWidget { ), Padding( padding: EdgeInsets.only(left: 5), - child: Text(lessonNo.toString(), - style: appStyle.fonts.B_12R - .apply(color: appStyle.colors.secondary)), - ) + child: Text( + lessonNo.toString(), + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.secondary, + ), + ), + ), ], ), ), Transform.translate( - offset: Offset(-4, 0), - child: Card( - shadowColor: Colors.transparent, - color: appStyle.colors.a15p, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16)), - child: Padding( - padding: EdgeInsets.all(4), - child: ClassIconWidget( - color: appStyle.colors.accent, - size: 24, - uid: lesson!.uid, - className: lesson!.name, - category: lesson!.subject?.name ?? '', - ), + offset: Offset(-4, 0), + child: Card( + shadowColor: Colors.transparent, + color: appStyle.colors.a15p, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: EdgeInsets.all(4), + child: ClassIconWidget( + color: appStyle.colors.accent, + size: 24, + uid: lesson!.uid, + className: lesson!.name, + category: lesson!.subject?.name ?? '', ), - )), - Text(lesson!.subject?.name ?? 'N/A', - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)), + ), + ), + ), + Text( + lesson!.subject?.name ?? 'N/A', + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), ], ), Row( children: [ - Text(timeLeftStr, - style: appStyle.fonts.B_12R - .apply(color: appStyle.colors.textSecondary)), + Text( + timeLeftStr, + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.textSecondary, + ), + ), ], ), ], - ) + ), ], right: [ Column( children: [ Row( children: [ - Text(lesson!.start.toLocal().format(l10n, FormatMode.hmm), - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)), + Text( + lesson!.start.toLocal().format(l10n, FormatMode.hmm), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), Card( shadowColor: Colors.transparent, color: appStyle.colors.a15p, child: Padding( padding: EdgeInsets.all(4), - child: Text(lesson!.roomName ?? '?', - style: appStyle.fonts.B_12R - .apply(color: appStyle.colors.secondary)), + child: Text( + lesson!.roomName ?? '?', + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.secondary, + ), + ), ), - ) + ), ], ), Row( children: [ SizedBox(width: 18), - Text(lesson!.end.toLocal().format(l10n, FormatMode.hmm), - style: appStyle.fonts.B_12R - .apply(color: appStyle.colors.textSecondary)), + Text( + lesson!.end.toLocal().format(l10n, FormatMode.hmm), + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.textSecondary, + ), + ), ], - ) + ), ], - ) + ), ], extra: ClipRRect( borderRadius: BorderRadius.circular(8), @@ -382,12 +455,13 @@ class LessonBigWidget extends StatelessWidget { minHeight: 8, ), ), - ) + ), ], ); } else { - var duration = - nextLesson!.start.difference(prevLesson!.end).inMilliseconds; + var duration = nextLesson!.start + .difference(prevLesson!.end) + .inMilliseconds; var progress = duration - nextLesson!.start.difference(now).inMilliseconds; var timeLeft = nextLesson!.start.difference(now); @@ -410,24 +484,33 @@ class LessonBigWidget extends StatelessWidget { child: Padding( padding: EdgeInsets.all(4), child: FirkaIconWidget( - FirkaIconType.majesticonsLocal, 'cupFilled', - color: appStyle.colors.accent, size: 24), + FirkaIconType.majesticonsLocal, + 'cupFilled', + color: appStyle.colors.accent, + size: 24, + ), + ), + ), + Text( + l10n.breakTxt, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, ), ), - Text(l10n.breakTxt, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)), ], ), Row( children: [ - Text(timeLeftStr, - style: appStyle.fonts.B_12R - .apply(color: appStyle.colors.textSecondary)), + Text( + timeLeftStr, + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.textSecondary, + ), + ), ], ), ], - ) + ), ], right: [ Column( @@ -435,25 +518,28 @@ class LessonBigWidget extends StatelessWidget { Row( children: [ Text( - prevLesson!.end - .toLocal() - .format(l10n, FormatMode.hmm), - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)), + prevLesson!.end.toLocal().format(l10n, FormatMode.hmm), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), ], ), Row( children: [ Text( - nextLesson!.start - .toLocal() - .format(l10n, FormatMode.hmm), - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)), + nextLesson!.start.toLocal().format( + l10n, + FormatMode.hmm, + ), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), ], - ) + ), ], - ) + ), ], extra: LinearProgressIndicator( // TODO: Make this rounded @@ -461,7 +547,7 @@ class LessonBigWidget extends StatelessWidget { backgroundColor: appStyle.colors.a15p, color: appStyle.colors.accent, ), - ) + ), ], ); } diff --git a/firka/lib/ui/phone/widgets/lesson_small.dart b/firka/lib/ui/phone/widgets/lesson_small.dart index 6e0d4dd..0042c17 100644 --- a/firka/lib/ui/phone/widgets/lesson_small.dart +++ b/firka/lib/ui/phone/widgets/lesson_small.dart @@ -12,8 +12,12 @@ class LessonSmallWidget extends StatelessWidget { final Lesson lesson; final bool lessonActive; - const LessonSmallWidget(this.l10n, this.lesson, this.lessonActive, - {super.key}); + const LessonSmallWidget( + this.l10n, + this.lesson, + this.lessonActive, { + super.key, + }); @override Widget build(BuildContext context) { @@ -44,9 +48,12 @@ class LessonSmallWidget extends StatelessWidget { : '', ), SizedBox(width: 8), - Text(subjectName, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary)), + Text( + subjectName, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), ], right: [ Card( @@ -54,17 +61,22 @@ class LessonSmallWidget extends StatelessWidget { color: appStyle.colors.a15p, child: Padding( padding: EdgeInsets.all(4), - child: Text(roomName, - style: appStyle.fonts.B_12R - .apply(color: appStyle.colors.secondary)), + child: Text( + roomName, + style: appStyle.fonts.B_12R.apply( + color: appStyle.colors.secondary, + ), + ), ), ), Text( - "${lesson.start.toLocal().format(l10n, FormatMode.hmm)} - ${lesson.end.toLocal().format(l10n, FormatMode.hmm)}", - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)), + "${lesson.start.toLocal().format(l10n, FormatMode.hmm)} - ${lesson.end.toLocal().format(l10n, FormatMode.hmm)}", + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), ], - ) + ), ], ); } diff --git a/firka/lib/ui/phone/widgets/login_webview.dart b/firka/lib/ui/phone/widgets/login_webview.dart index 370fdbb..5c0be80 100644 --- a/firka/lib/ui/phone/widgets/login_webview.dart +++ b/firka/lib/ui/phone/widgets/login_webview.dart @@ -5,7 +5,6 @@ import 'package:firka/helpers/db/models/app_settings_model.dart'; import 'package:firka/helpers/live_activity_service.dart'; import 'package:firka/main.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:isar_community/isar.dart'; import 'package:webview_flutter/webview_flutter.dart'; @@ -25,8 +24,12 @@ class LoginWebviewWidget extends StatefulWidget { final String? username; final String? schoolId; - const LoginWebviewWidget(this.data, - {super.key, this.username, this.schoolId}); + const LoginWebviewWidget( + this.data, { + super.key, + this.username, + this.schoolId, + }); @override State createState() => _LoginWebviewWidgetState(); @@ -48,14 +51,18 @@ class _LoginWebviewWidgetState extends FirkaState vsync: this, ); - _fadeAnimation = - Tween(begin: 1.0, end: 0.0).animate(_fadeAnimationController!); + _fadeAnimation = Tween( + begin: 1.0, + end: 0.0, + ).animate(_fadeAnimationController!); var loginUrl = KretaEndpoints.kretaLoginUrl; if (widget.username != null && widget.schoolId != null) { loginUrl = KretaEndpoints.kretaLoginUrlRefresh( - widget.username!, widget.schoolId!); + widget.username!, + widget.schoolId!, + ); } logger.info("Using loginUrl: $loginUrl"); @@ -63,7 +70,8 @@ class _LoginWebviewWidgetState extends FirkaState _webViewController = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..loadRequest(Uri.parse(loginUrl)) - ..setNavigationDelegate(NavigationDelegate( + ..setNavigationDelegate( + NavigationDelegate( onPageFinished: (String url) { Timer(const Duration(milliseconds: 500), () { if (mounted) { @@ -83,90 +91,98 @@ class _LoginWebviewWidgetState extends FirkaState _fadeAnimationController?.reset(); }, onNavigationRequest: (NavigationRequest request) async { - var uri = Uri.parse(request.url); + var uri = Uri.parse(request.url); - if (uri.path == "/ellenorzo-student/prod/oauthredirect") { - var code = uri.queryParameters["code"]!; + if (uri.path == "/ellenorzo-student/prod/oauthredirect") { + var code = uri.queryParameters["code"]!; - try { - var isar = widget.data.isar; - var resp = await getAccessToken(code); + try { + var isar = widget.data.isar; + var resp = await getAccessToken(code); - logger.info("getAccessToken(): $resp"); + logger.info("getAccessToken(): $resp"); - var tokenModel = TokenModel.fromResp(resp); + var tokenModel = TokenModel.fromResp(resp); - final accountPicker = (widget.data.settings - .group("profile_settings")["e_kreta_account_picker"] - as SettingsKretenAccountPicker); + final accountPicker = + (widget.data.settings.group( + "profile_settings", + )["e_kreta_account_picker"] + as SettingsKretenAccountPicker); - var tokenId = 0; - var om = 0; - await isar.writeTxn(() async { - om = await isar.tokenModels.put(tokenModel); - }); + var tokenId = 0; + var om = 0; + await isar.writeTxn(() async { + om = await isar.tokenModels.put(tokenModel); + }); - widget.data.tokens = await isar.tokenModels.where().findAll(); - for (var i = 0; i < widget.data.tokens.length; i++) { - if (widget.data.tokens[i].studentIdNorm == om) { - tokenId = i; - break; - } - } - - await isar.writeTxn(() async { - accountPicker.accountIndex = tokenId; - await accountPicker.save(widget.data.isar.appSettingsModels); - }); - - await accountPicker.postUpdate(); - - if (Platform.isIOS) { - final watchInstalled = - await WatchSyncHelper.isWatchAppInstalled(); - if (watchInstalled) { - try { - await WatchSyncHelper.saveTokenToiCloud(tokenModel); - } catch (_) {} - - try { - await WatchSyncHelper.sendTokenToWatch(); - } catch (_) { - // Watch may be unavailable, ignore + widget.data.tokens = await isar.tokenModels.where().findAll(); + for (var i = 0; i < widget.data.tokens.length; i++) { + if (widget.data.tokens[i].studentIdNorm == om) { + tokenId = i; + break; + } } - } - } - if (!mounted) return NavigationDecision.prevent; + await isar.writeTxn(() async { + accountPicker.accountIndex = tokenId; + await accountPicker.save(widget.data.isar.appSettingsModels); + }); - KretaClient.clearReauthFlag(); - if (Platform.isIOS) { - LiveActivityService.clearTokenExpiration(); - } + await accountPicker.postUpdate(); - runApp(InitializationScreen()); - } catch (ex) { - if (ex is Error) { - logger.shout( - "oauthredirect failed:", ex.toString(), ex.stackTrace); - } else { - logger.shout("oauthredirect failed:", ex.toString()); - } - Navigator.push( - context, - MaterialPageRoute( + if (Platform.isIOS) { + final watchInstalled = + await WatchSyncHelper.isWatchAppInstalled(); + if (watchInstalled) { + try { + await WatchSyncHelper.saveTokenToiCloud(tokenModel); + } catch (_) {} + + try { + await WatchSyncHelper.sendTokenToWatch(); + } catch (_) { + // Watch may be unavailable, ignore + } + } + } + + if (!mounted) return NavigationDecision.prevent; + + KretaClient.clearReauthFlag(); + if (Platform.isIOS) { + LiveActivityService.clearTokenExpiration(); + } + + runApp(InitializationScreen()); + } catch (ex) { + if (ex is Error) { + logger.shout( + "oauthredirect failed:", + ex.toString(), + ex.stackTrace, + ); + } else { + logger.shout("oauthredirect failed:", ex.toString()); + } + Navigator.push( + context, + MaterialPageRoute( builder: (context) => DefaultAssetBundle( - bundle: FirkaBundle(), - child: ErrorPage( - exception: ex.toString(), - )))); - } + bundle: FirkaBundle(), + child: ErrorPage(exception: ex.toString()), + ), + ), + ); + } - return NavigationDecision.prevent; - } + return NavigationDecision.prevent; + } - return NavigationDecision.navigate; - })); + return NavigationDecision.navigate; + }, + ), + ); } @override @@ -175,14 +191,14 @@ class _LoginWebviewWidgetState extends FirkaState super.dispose(); } - @override Widget build(BuildContext context) { return Material( color: appStyle.colors.card, child: Padding( - padding: - EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), child: FractionallySizedBox( heightFactor: 0.90, child: Center( @@ -198,7 +214,9 @@ class _LoginWebviewWidgetState extends FirkaState padding: const EdgeInsets.symmetric(vertical: 16), child: Container( decoration: BoxDecoration( - color: appStyle.colors.secondary.withValues(alpha: 0.5), + color: appStyle.colors.secondary.withValues( + alpha: 0.5, + ), borderRadius: BorderRadius.all(Radius.circular(2)), ), width: 40, @@ -216,18 +234,17 @@ class _LoginWebviewWidgetState extends FirkaState borderRadius: BorderRadius.circular(20), child: Stack( children: [ - WebViewWidget( - controller: _webViewController, - ), - if (_fadeAnimationController != null && _fadeAnimation != null) + WebViewWidget(controller: _webViewController), + if (_fadeAnimationController != null && + _fadeAnimation != null) AnimatedBuilder( animation: _fadeAnimationController!, builder: (context, child) => AnimatedOpacity( opacity: _isLoading ? 1.0 : _fadeAnimationController!.isAnimating - ? _fadeAnimation!.value - : 0.0, + ? _fadeAnimation!.value + : 0.0, duration: const Duration(milliseconds: 500), child: Container( color: appStyle.colors.background, diff --git a/firka/lib/ui/phone/widgets/tt_day.dart b/firka/lib/ui/phone/widgets/tt_day.dart index bcc7db1..27a5210 100644 --- a/firka/lib/ui/phone/widgets/tt_day.dart +++ b/firka/lib/ui/phone/widgets/tt_day.dart @@ -16,11 +16,18 @@ class TimeTableDayWidget extends StatelessWidget { final List lessons; final List events; final List tests; - final List day; + final List day; const TimeTableDayWidget( - this.data, this.date, this.week, this.lessons, this.events, this.tests, this.day, - {super.key}); + this.data, + this.date, + this.week, + this.lessons, + this.events, + this.tests, + this.day, { + super.key, + }); @override Widget build(BuildContext context) { @@ -29,47 +36,72 @@ class TimeTableDayWidget extends StatelessWidget { if (lessons.isEmpty) { noLessonsWidget = Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset("assets/images/logos/dave.svg", - width: 48, height: 48), - SizedBox(height: 12), - Text(data.l10n.tt_no_classes_l1, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary)), - Text(data.l10n.tt_no_classes_l2, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary)), - if (events.isNotEmpty) - ...events.map((event) => - Center( - child: Text(event.name.replaceAll(" (Nem órarendi nap)", ""), - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textSecondary)), - )) - ]); + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset( + "assets/images/logos/dave.svg", + width: 48, + height: 48, + ), + SizedBox(height: 12), + Text( + data.l10n.tt_no_classes_l1, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), + Text( + data.l10n.tt_no_classes_l2, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), + if (events.isNotEmpty) + ...events.map( + (event) => Center( + child: Text( + event.name.replaceAll(" (Nem órarendi nap)", ""), + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textSecondary, + ), + ), + ), + ), + ], + ); } else { for (var i = 0; i < events.length; i++) { var event = events[i]; - ttBody.add(FirkaCard(left: [ - Text(event.name, - style: appStyle.fonts.B_16R - .apply(color: appStyle.colors.textPrimary)) - ])); + ttBody.add( + FirkaCard( + left: [ + Text( + event.name, + style: appStyle.fonts.B_16R.apply( + color: appStyle.colors.textPrimary, + ), + ), + ], + ), + ); } for (var i = 0; i < lessons.length; i++) { var lesson = lessons[i]; Lesson? nextLesson = lessons.length > i + 1 ? lessons[i + 1] : null; - ttBody.add(LessonWidget( + ttBody.add( + LessonWidget( data, week, day, lessons.getLessonNo(lesson), lesson, tests.firstWhereOrNull( - (test) => test.lessonNumber == lesson.lessonNumber), - nextLesson)); + (test) => test.lessonNumber == lesson.lessonNumber, + ), + nextLesson, + ), + ); } } @@ -78,18 +110,16 @@ class TimeTableDayWidget extends StatelessWidget { child: ttBody.isEmpty ? noLessonsWidget : Padding( - padding: - const EdgeInsets.only(top: 70 + 16 + 20, left: 4, right: 4), + padding: const EdgeInsets.only( + top: 70 + 16 + 20, + left: 4, + right: 4, + ), child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...ttBody, - SizedBox( - height: 24, - ) - ], + children: [...ttBody, SizedBox(height: 24)], ), ), ), diff --git a/firka/lib/ui/widget/class_icon.dart b/firka/lib/ui/widget/class_icon.dart index 2f22256..80f6178 100644 --- a/firka/lib/ui/widget/class_icon.dart +++ b/firka/lib/ui/widget/class_icon.dart @@ -10,22 +10,26 @@ class ClassIconWidget extends StatelessWidget { final Color color; final double? size; - const ClassIconWidget( - {super.key, - required String uid, - required String className, - required String category, - this.color = Colors.white, - this.size}) - : _className = className, - _uid = uid, - _category = category; + const ClassIconWidget({ + super.key, + required String uid, + required String className, + required String category, + this.color = Colors.white, + this.size, + }) : _className = className, + _uid = uid, + _category = category; @override Widget build(BuildContext context) { var iconCategory = getIconType(_uid, _className, _category); - return FirkaIconWidget(FirkaIconType.majesticons, getIconData(iconCategory), - color: color, size: size); + return FirkaIconWidget( + FirkaIconType.majesticons, + getIconData(iconCategory), + color: color, + size: size, + ); } } diff --git a/firka/lib/ui/widget/counter_digit.dart b/firka/lib/ui/widget/counter_digit.dart index e6e77a5..85133b4 100644 --- a/firka/lib/ui/widget/counter_digit.dart +++ b/firka/lib/ui/widget/counter_digit.dart @@ -14,10 +14,7 @@ class CounterDigitWidget extends StatelessWidget { color: appStyle.colors.buttonSecondaryFill, child: Padding( padding: EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4), - child: Text( - c, - style: style, - ), + child: Text(c, style: style), ), ); } diff --git a/firka/lib/ui/widget/delayed_spinner.dart b/firka/lib/ui/widget/delayed_spinner.dart index 5ae90c5..4701470 100644 --- a/firka/lib/ui/widget/delayed_spinner.dart +++ b/firka/lib/ui/widget/delayed_spinner.dart @@ -30,9 +30,7 @@ class _DelayedSpinner extends FirkaState { @override Widget build(BuildContext context) { if (showSpinner) { - return CircularProgressIndicator( - color: appStyle.colors.accent, - ); + return CircularProgressIndicator(color: appStyle.colors.accent); } else { return SizedBox(); } diff --git a/firka/lib/ui/widget/firka_icon.dart b/firka/lib/ui/widget/firka_icon.dart index beffbbe..6bc8118 100644 --- a/firka/lib/ui/widget/firka_icon.dart +++ b/firka/lib/ui/widget/firka_icon.dart @@ -4,11 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:majesticons_flutter/majesticons_flutter.dart'; -enum FirkaIconType { - icons, - majesticons, - majesticonsLocal, -} +enum FirkaIconType { icons, majesticons, majesticonsLocal } class FirkaIconWidget extends StatelessWidget { final FirkaIconType iconType; @@ -16,8 +12,13 @@ class FirkaIconWidget extends StatelessWidget { final Color color; final double? size; - const FirkaIconWidget(this.iconType, this.iconData, - {super.key, this.color = Colors.white, this.size}); + const FirkaIconWidget( + this.iconType, + this.iconData, { + super.key, + this.color = Colors.white, + this.size, + }); @override Widget build(BuildContext context) { diff --git a/firka/lib/ui/widget/grade_small_card.dart b/firka/lib/ui/widget/grade_small_card.dart index b070d83..27cf773 100644 --- a/firka/lib/ui/widget/grade_small_card.dart +++ b/firka/lib/ui/widget/grade_small_card.dart @@ -12,41 +12,50 @@ class GradeSmallCard extends FirkaCard { final Subject subject; GradeSmallCard(this.grades, this.subject, {super.key}) - : super(left: [ + : super( + left: [ ClassIconWidget( uid: subject.uid, className: subject.name, category: subject.category.name!, color: appStyle.colors.accent, ), - SizedBox( - width: 4, - ), + SizedBox(width: 4), SizedBox( width: 200, child: Text( subject.name, - style: appStyle.fonts.B_16SB - .apply(color: appStyle.colors.textPrimary), + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), ), ), - ], right: [ + ], + right: [ grades.getAverageBySubject(subject).isNaN ? SizedBox() : Card( shadowColor: Colors.transparent, - color: getGradeColor(grades.getAverageBySubject(subject)) - .withAlpha(38), + color: getGradeColor( + grades.getAverageBySubject(subject), + ).withAlpha(38), child: Padding( - padding: - EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4), + padding: EdgeInsets.only( + left: 8, + right: 8, + top: 4, + bottom: 4, + ), child: Text( grades.getAverageBySubject(subject).toStringAsFixed(2), style: appStyle.fonts.B_16SB.apply( - color: getGradeColor( - grades.getAverageBySubject(subject))), + color: getGradeColor( + grades.getAverageBySubject(subject), + ), + ), ), ), ), - ]); + ], + ); } diff --git a/firka/pubspec.yaml b/firka/pubspec.yaml index 773e925..f69c7a8 100644 --- a/firka/pubspec.yaml +++ b/firka/pubspec.yaml @@ -50,6 +50,7 @@ dependencies: confetti: ^0.8.0 flutter_html: ^3.0.0 fl_chart: ^1.1.1 + flutter_native_splash: ^2.4.7 dev_dependencies: flutter_test: @@ -59,7 +60,6 @@ dev_dependencies: android_notification_icons: ^0.0.1 integration_test: sdk: flutter - flutter_native_splash: ^2.4.7 android_notification_icons: image_path: 'assets/images/logos/dave_monochrome.png'