forked from firka/firka
fix warnings, reformat files
This commit is contained in:
@@ -8,8 +8,7 @@ int resolveActiveAccountIndex(dynamic settings) {
|
||||
if (accountIndex is int && accountIndex >= 0) {
|
||||
return accountIndex;
|
||||
}
|
||||
} catch (_) {
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -42,12 +42,7 @@ class ApiResponse<T> {
|
||||
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<TokenModel> _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<dynamic> 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<List<ClassGroup>>? classGroupCache;
|
||||
|
||||
Future<ApiResponse<List<ClassGroup>>> getClassGroups(
|
||||
{bool forceCache = true}) async {
|
||||
Future<ApiResponse<List<ClassGroup>>> 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<ClassGroup>.empty(growable: true);
|
||||
String? err;
|
||||
@@ -548,15 +585,20 @@ class KretaClient {
|
||||
|
||||
ApiResponse<List<NoticeBoardItem>>? noticeBoardCache;
|
||||
|
||||
Future<ApiResponse<List<NoticeBoardItem>>> getNoticeBoard(
|
||||
{bool forceCache = true}) async {
|
||||
Future<ApiResponse<List<NoticeBoardItem>>> 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<NoticeBoardItem>.empty(growable: true);
|
||||
String? err;
|
||||
@@ -580,11 +622,16 @@ class KretaClient {
|
||||
|
||||
ApiResponse<List<InfoBoardItem>>? infoBoardCache;
|
||||
|
||||
Future<ApiResponse<List<InfoBoardItem>>> getInfoBoard(
|
||||
{bool forceCache = true}) async {
|
||||
Future<ApiResponse<List<InfoBoardItem>>> 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<InfoBoardItem>.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<Grade>.empty(growable: true);
|
||||
String? err;
|
||||
@@ -642,14 +693,19 @@ class KretaClient {
|
||||
ApiResponse<List<SubjectAverage>>? subjectAverageCache;
|
||||
|
||||
Future<ApiResponse<List<SubjectAverage>>> 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<SubjectAverage>.empty(growable: true), 0, err, false);
|
||||
List<SubjectAverage>.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<SubjectAverage>.empty(growable: true);
|
||||
try {
|
||||
@@ -679,14 +739,15 @@ class KretaClient {
|
||||
}
|
||||
|
||||
Future<(List<dynamic>, int, Object?, bool)>
|
||||
_timedCachingGet<T extends DatedCacheEntry>(
|
||||
IsarCollection<T> cacheModel,
|
||||
String endpoint,
|
||||
DateTime from,
|
||||
DateTime? to,
|
||||
bool forceCache,
|
||||
int counter,
|
||||
Future<void> Function(dynamic, int) storeCache) async {
|
||||
_timedCachingGet<T extends DatedCacheEntry>(
|
||||
IsarCollection<T> cacheModel,
|
||||
String endpoint,
|
||||
DateTime from,
|
||||
DateTime? to,
|
||||
bool forceCache,
|
||||
int counter,
|
||||
Future<void> 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<ApiResponse<List<Lesson>>> _getTimeTable(
|
||||
DateTime from, DateTime to, bool forceCache) async {
|
||||
var (resp, status, ex, cached) =
|
||||
await _timedCachingGet<TimetableCacheModel>(
|
||||
isar.timetableCacheModels,
|
||||
KretaEndpoints.getTimeTable(model.iss!),
|
||||
from,
|
||||
to,
|
||||
forceCache,
|
||||
0, (dynamic resp, int cacheKey) async {
|
||||
TimetableCacheModel cache = TimetableCacheModel();
|
||||
var rawClasses = List<String>.empty(growable: true);
|
||||
DateTime from,
|
||||
DateTime to,
|
||||
bool forceCache,
|
||||
) async {
|
||||
var (
|
||||
resp,
|
||||
status,
|
||||
ex,
|
||||
cached,
|
||||
) = await _timedCachingGet<TimetableCacheModel>(
|
||||
isar.timetableCacheModels,
|
||||
KretaEndpoints.getTimeTable(model.iss!),
|
||||
from,
|
||||
to,
|
||||
forceCache,
|
||||
0,
|
||||
(dynamic resp, int cacheKey) async {
|
||||
TimetableCacheModel cache = TimetableCacheModel();
|
||||
var rawClasses = List<String>.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<Lesson>.empty(growable: true);
|
||||
String? err;
|
||||
@@ -819,16 +900,18 @@ class KretaClient {
|
||||
return ApiResponse(items, status, err, cached);
|
||||
}
|
||||
|
||||
Future<ApiResponse<List<Homework>>> getHomework(
|
||||
{bool forceCache = true}) async {
|
||||
Future<ApiResponse<List<Homework>>> 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<Homework>.empty(growable: true);
|
||||
String? err;
|
||||
@@ -851,15 +934,20 @@ class KretaClient {
|
||||
}
|
||||
|
||||
/// Automatically aligns requests to start at Monday and end at Sunday
|
||||
Future<ApiResponse<List<Lesson>>> getTimeTable(DateTime from, DateTime to,
|
||||
{bool forceCache = true}) async {
|
||||
Future<ApiResponse<List<Lesson>>> getTimeTable(
|
||||
DateTime from,
|
||||
DateTime to, {
|
||||
bool forceCache = true,
|
||||
}) async {
|
||||
var lessons = List<Lesson>.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<ApiResponse<List<AllLessons>>> getLessons(
|
||||
{bool forceCache = true}) async {
|
||||
Future<ApiResponse<List<AllLessons>>> 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<ApiResponse<List<Test>>> 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<Test>.empty(growable: true);
|
||||
String? err;
|
||||
@@ -949,15 +1043,20 @@ class KretaClient {
|
||||
|
||||
ApiResponse<List<Omission>>? omissionsCache;
|
||||
|
||||
Future<ApiResponse<List<Omission>>> getOmissions(
|
||||
{bool forceCache = true}) async {
|
||||
Future<ApiResponse<List<Omission>>> 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<Omission>.empty(growable: true);
|
||||
String? err;
|
||||
|
||||
@@ -21,8 +21,9 @@ bool getTestsStreamFL = false;
|
||||
bool getOmissionsStreamFL = false;
|
||||
|
||||
extension KretaStream on KretaClient {
|
||||
Stream<ApiResponse<Student>> getStudentStream(
|
||||
{bool cacheOnly = true}) async* {
|
||||
Stream<ApiResponse<Student>> getStudentStream({
|
||||
bool cacheOnly = true,
|
||||
}) async* {
|
||||
while (getStudentFL) {
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
}
|
||||
@@ -35,8 +36,9 @@ extension KretaStream on KretaClient {
|
||||
getStudentFL = false;
|
||||
}
|
||||
|
||||
Stream<ApiResponse<List<ClassGroup>>> getClassGroupsStream(
|
||||
{bool cacheOnly = true}) async* {
|
||||
Stream<ApiResponse<List<ClassGroup>>> getClassGroupsStream({
|
||||
bool cacheOnly = true,
|
||||
}) async* {
|
||||
while (getClassGroupsFL) {
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
}
|
||||
@@ -49,8 +51,9 @@ extension KretaStream on KretaClient {
|
||||
getClassGroupsFL = false;
|
||||
}
|
||||
|
||||
Stream<ApiResponse<List<NoticeBoardItem>>> getNoticeBoardStream(
|
||||
{bool cacheOnly = true}) async* {
|
||||
Stream<ApiResponse<List<NoticeBoardItem>>> getNoticeBoardStream({
|
||||
bool cacheOnly = true,
|
||||
}) async* {
|
||||
while (getNoticeBoardStreamFL) {
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
}
|
||||
@@ -63,8 +66,9 @@ extension KretaStream on KretaClient {
|
||||
getNoticeBoardStreamFL = false;
|
||||
}
|
||||
|
||||
Stream<ApiResponse<List<InfoBoardItem>>> getInfoBoardStream(
|
||||
{bool cacheOnly = true}) async* {
|
||||
Stream<ApiResponse<List<InfoBoardItem>>> getInfoBoardStream({
|
||||
bool cacheOnly = true,
|
||||
}) async* {
|
||||
while (getInfoBoardStreamFL) {
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
}
|
||||
@@ -77,8 +81,9 @@ extension KretaStream on KretaClient {
|
||||
getInfoBoardStreamFL = false;
|
||||
}
|
||||
|
||||
Stream<ApiResponse<List<Grade>>> getGradesStream(
|
||||
{bool cacheOnly = true}) async* {
|
||||
Stream<ApiResponse<List<Grade>>> getGradesStream({
|
||||
bool cacheOnly = true,
|
||||
}) async* {
|
||||
while (getGradesStreamFL) {
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
}
|
||||
@@ -92,8 +97,9 @@ extension KretaStream on KretaClient {
|
||||
}
|
||||
|
||||
Stream<ApiResponse<List<SubjectAverage>>> 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<ApiResponse<List<Homework>>> getHomeworkStream(
|
||||
{bool cacheOnly = true}) async* {
|
||||
Stream<ApiResponse<List<Homework>>> getHomeworkStream({
|
||||
bool cacheOnly = true,
|
||||
}) async* {
|
||||
while (getHomeworkStreamFL) {
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
}
|
||||
@@ -121,8 +128,10 @@ extension KretaStream on KretaClient {
|
||||
}
|
||||
|
||||
Stream<ApiResponse<List<Lesson>>> 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<ApiResponse<List<Test>>> getTestsStream(
|
||||
{bool cacheOnly = true}) async* {
|
||||
Stream<ApiResponse<List<Test>>> getTestsStream({
|
||||
bool cacheOnly = true,
|
||||
}) async* {
|
||||
while (getTestsStreamFL) {
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
}
|
||||
@@ -149,8 +159,9 @@ extension KretaStream on KretaClient {
|
||||
getTestsStreamFL = false;
|
||||
}
|
||||
|
||||
Stream<ApiResponse<List<Omission>>> getOmissionsStream(
|
||||
{bool cacheOnly = true}) async* {
|
||||
Stream<ApiResponse<List<Omission>>> getOmissionsStream({
|
||||
bool cacheOnly = true,
|
||||
}) async* {
|
||||
while (getOmissionsStreamFL) {
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
}
|
||||
|
||||
@@ -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<bool> unregisterDevice({
|
||||
required String deviceToken,
|
||||
}) async {
|
||||
Future<bool> 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<List<Lesson>?> getTimetable({
|
||||
required String deviceToken,
|
||||
}) async {
|
||||
Future<List<Lesson>?> 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<bool> sendTestNotification({
|
||||
required String deviceToken,
|
||||
}) async {
|
||||
Future<bool> 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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,67 +62,66 @@ class AllLessons {
|
||||
});
|
||||
|
||||
factory AllLessons.fromJson(Map<String, dynamic> 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<String, dynamic> 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<AllLessons> lessonsFromJson(String str) =>
|
||||
|
||||
@@ -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<String, dynamic> 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
|
||||
|
||||
@@ -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<String, dynamic> json) {
|
||||
return NameUidDesc(
|
||||
uid: json['Uid'], name: json['Nev'], description: json['Leiras']);
|
||||
uid: json['Uid'],
|
||||
name: json['Nev'],
|
||||
description: json['Leiras'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> 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<String, dynamic> json) {
|
||||
return NameUid(
|
||||
uid: json['Uid'],
|
||||
name: json['Nev'],
|
||||
);
|
||||
return NameUid(uid: json['Uid'], name: json['Nev']);
|
||||
}
|
||||
|
||||
Map<String, dynamic> 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<String, dynamic> json) {
|
||||
return UidObj(
|
||||
uid: json['Uid'],
|
||||
);
|
||||
return UidObj(uid: json['Uid']);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -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<String, dynamic> json) {
|
||||
return Grade(
|
||||
|
||||
@@ -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<String, dynamic> 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
|
||||
|
||||
@@ -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<String, dynamic> 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
|
||||
|
||||
@@ -4,11 +4,12 @@ class Institution {
|
||||
final List<SystemModule> 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<String, dynamic> json) {
|
||||
var systemModuleList = List<SystemModule>.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<String, dynamic> 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<String, dynamic> json) {
|
||||
return SystemModule(
|
||||
isActive: json['IsAktiv'], type: json['Tipus'], url: json['Url']);
|
||||
isActive: json['IsAktiv'],
|
||||
type: json['Tipus'],
|
||||
url: json['Url'],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -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<String, dynamic> 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<String, dynamic> 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
|
||||
|
||||
@@ -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<String, dynamic> json) {
|
||||
return Class(
|
||||
|
||||
@@ -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<String, dynamic> json) {
|
||||
var guardianList = List<Guardian>.empty(growable: true);
|
||||
@@ -50,19 +51,21 @@ class Student {
|
||||
}
|
||||
|
||||
return Student(
|
||||
addressDataList: listToTyped<String>(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<String>(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<String, dynamic> 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
|
||||
|
||||
@@ -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<String>.from(json['DigitalisTamogatoEszkozTipusList'])
|
||||
: List<String>.empty(),
|
||||
? List<String>.from(json['DigitalisTamogatoEszkozTipusList'])
|
||||
: List<String>.empty(),
|
||||
createdAt: DateTime.parse(json['Letrehozas']).toLocal(),
|
||||
lastModifiedAt: DateTime.parse(json['UtolsoModositas']).toLocal(),
|
||||
);
|
||||
|
||||
@@ -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<String, dynamic> 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
|
||||
|
||||
@@ -23,8 +23,11 @@ Future<TokenGrantResponse> 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<TokenGrantResponse> 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<TokenGrantResponse> 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 = <String, String>{
|
||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
@@ -66,30 +71,38 @@ Future<TokenGrantResponse> 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<TokenGrantResponse> 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<TokenGrantResponse> 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");
|
||||
}
|
||||
|
||||
@@ -20,4 +20,4 @@ double calculateAverage(List<Grade> sortedGrades) {
|
||||
|
||||
final avg = weightedSum / totalWeight;
|
||||
return double.parse(avg.toStringAsFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ class IOSWidgetHelper {
|
||||
if (!Platform.isIOS) return null;
|
||||
|
||||
try {
|
||||
final result = await _channel.invokeMethod<String>('getAppGroupDirectory');
|
||||
final result = await _channel.invokeMethod<String>(
|
||||
'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,
|
||||
|
||||
@@ -12,7 +12,7 @@ enum CacheId {
|
||||
getClassGroup,
|
||||
getSubjectAvg,
|
||||
getLessons,
|
||||
getHomework
|
||||
getHomework,
|
||||
}
|
||||
|
||||
@collection
|
||||
|
||||
@@ -28,7 +28,7 @@ Future<void> resetOldHomeworkCache(Isar isar) async {
|
||||
});
|
||||
}
|
||||
|
||||
@collection
|
||||
@collection
|
||||
class HomeworkDoneModel {
|
||||
Id? id;
|
||||
|
||||
@@ -36,13 +36,15 @@ class HomeworkDoneModel {
|
||||
late DateTime doneAt;
|
||||
|
||||
HomeworkDoneModel();
|
||||
|
||||
}
|
||||
|
||||
Future<void> 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<bool> isHomeworkDone(Isar isar, String homeWorkUid) async {
|
||||
.homeworkIdEqualTo(homeWorkUid)
|
||||
.findFirst();
|
||||
return existing != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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])) >>>
|
||||
|
||||
@@ -57,7 +57,9 @@ class WidgetCacheHelper {
|
||||
}
|
||||
|
||||
static Future<void> 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<void> refreshIOSWidgets(KretaClient client, SettingsStore settings) async {
|
||||
static Future<void> 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<Lesson> 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<String, double> subjectAverages = {};
|
||||
final Set<String> 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;
|
||||
|
||||
@@ -12,29 +12,43 @@ extension TimetableExtension on Iterable<Lesson> {
|
||||
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<T> on Iterable<T> {
|
||||
Map<DateTime, List<T>> 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> {
|
||||
|
||||
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))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,9 @@ class FirkaBundle extends CachingAssetBundle {
|
||||
@override
|
||||
Future<ByteData> 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));
|
||||
|
||||
@@ -34,7 +34,7 @@ enum ClassIcon {
|
||||
linux,
|
||||
database,
|
||||
applications,
|
||||
project
|
||||
project,
|
||||
}
|
||||
|
||||
Map<ClassIcon, RegExp> _descriptors = {
|
||||
@@ -49,8 +49,9 @@ Map<ClassIcon, RegExp> _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<ClassIcon, RegExp> _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<ClassIcon, Uint8List> _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;
|
||||
|
||||
@@ -10,7 +10,9 @@ class ImagePreloader {
|
||||
static final Map<String, Future<ui.Image>> _loadingFutures = {};
|
||||
|
||||
static Future<ui.Image> 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<List<ui.Image>> preloadMultipleAssets(
|
||||
AssetBundle bundle, List<String> assetPaths) async {
|
||||
final futures =
|
||||
assetPaths.map((path) => preloadAssetImage(bundle, path)).toList();
|
||||
AssetBundle bundle,
|
||||
List<String> assetPaths,
|
||||
) async {
|
||||
final futures = assetPaths
|
||||
.map((path) => preloadAssetImage(bundle, path))
|
||||
.toList();
|
||||
return await Future.wait(futures);
|
||||
}
|
||||
|
||||
@@ -91,7 +96,9 @@ class ImagePreloader {
|
||||
}
|
||||
|
||||
static Future<ui.Image> _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<PreloadedImageProvider> {
|
||||
|
||||
@override
|
||||
ImageStreamCompleter loadImage(
|
||||
PreloadedImageProvider key, ImageDecoderCallback decode) {
|
||||
PreloadedImageProvider key,
|
||||
ImageDecoderCallback decode,
|
||||
) {
|
||||
return OneFrameImageStreamCompleter(_loadAsync(key));
|
||||
}
|
||||
|
||||
@@ -133,8 +142,10 @@ class PreloadedImageProvider extends ImageProvider<PreloadedImageProvider> {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,9 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
Future<void> pickProfilePicture(
|
||||
AppInitialization data, ImagePicker picker) async {
|
||||
AppInitialization data,
|
||||
ImagePicker picker,
|
||||
) async {
|
||||
var imageFile = await picker.pickImage(source: ImageSource.gallery);
|
||||
if (imageFile == null) return;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@ Future<void> 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<void> 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');
|
||||
}
|
||||
|
||||
@@ -25,25 +25,24 @@ import 'grade.dart';
|
||||
import '../../helpers/api/model/test.dart';
|
||||
|
||||
Future<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> showLessonBottomSheet(
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 1.1,
|
||||
@@ -265,15 +287,20 @@ Future<void> 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<void> showLessonBottomSheet(
|
||||
}
|
||||
|
||||
Future<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> showTestBottomSheet(
|
||||
}
|
||||
|
||||
Future<void> 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<void> 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<void> 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<void> 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<void> 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<void> showGradeBottomSheet(
|
||||
}
|
||||
|
||||
Future<void> 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<void> 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<void> 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<void> 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<void> 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<void> showHomeworkBottomSheet(
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
FutureBuilder<bool>(
|
||||
future: isHomeworkDone(data.isar, homework.uid),
|
||||
@@ -805,8 +901,9 @@ Future<void> 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<void> 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<void> 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<void> showHomeworkBottomSheet(
|
||||
}
|
||||
|
||||
Future<void> 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<void> showSubjectBottomSheetSettings(
|
||||
backgroundColor: Colors.transparent,
|
||||
barrierColor: appStyle.colors.a15p,
|
||||
builder: (BuildContext context) {
|
||||
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Align(
|
||||
@@ -904,13 +1008,16 @@ Future<void> 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<void> 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<void> showSubjectBottomSheetSettings(
|
||||
logger.finest("Tantárgy átnevezése");
|
||||
// Navigator.pop(context);
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,15 @@ class WatchSyncHelper {
|
||||
try {
|
||||
return await _watchChannel
|
||||
.invokeMethod<T>(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<bool> isWatchAppInstalled({bool forceRefresh = false}) async {
|
||||
@@ -262,11 +270,14 @@ class WatchSyncHelper {
|
||||
return _watchAppInstalledCache;
|
||||
}
|
||||
|
||||
static Future<bool> isWatchReachable({bool forceRefreshInstall = false}) async {
|
||||
static Future<bool> 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<bool>(
|
||||
@@ -309,16 +320,13 @@ class WatchSyncHelper {
|
||||
if (!watchInstalled) return true;
|
||||
|
||||
final timeout = maxWait + const Duration(seconds: 5);
|
||||
final result = await _invokeMethodWithTimeout<dynamic>(
|
||||
'waitForPeerRefreshLease',
|
||||
{
|
||||
'owner': _leaseOwnerIPhone,
|
||||
'studentIdNorm': studentIdNorm,
|
||||
'maxWaitMs': maxWait.inMilliseconds,
|
||||
'pollIntervalMs': _leasePollInterval.inMilliseconds,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
final result =
|
||||
await _invokeMethodWithTimeout<dynamic>('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<dynamic>(
|
||||
'acquireRefreshLease',
|
||||
{
|
||||
'owner': _leaseOwnerIPhone,
|
||||
'studentIdNorm': studentIdNorm,
|
||||
'ttlMs': ttl.inMilliseconds,
|
||||
},
|
||||
const Duration(seconds: 5),
|
||||
);
|
||||
final result =
|
||||
await _invokeMethodWithTimeout<dynamic>('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<void> 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<void> 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<void> _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<Map<String, dynamic>> _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,
|
||||
|
||||
@@ -131,7 +131,7 @@ Future<void> 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<void> _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<void> _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<void> _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<AppInitialization> 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<AppInitialization> 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<void> 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<void> 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<bool> isLightMode = ValueNotifier<bool>(true);
|
||||
@@ -492,8 +507,9 @@ final UpdateNotifier globalUpdate = UpdateNotifier();
|
||||
class InitializationScreen extends StatelessWidget {
|
||||
InitializationScreen({super.key});
|
||||
|
||||
final Future<AppInitialization> _init =
|
||||
initializeApp().timeout(const Duration(seconds: 20));
|
||||
final Future<AppInitialization> _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')),
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<Map<String, dynamic>> 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,
|
||||
),
|
||||
|
||||
@@ -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<ReauthToastWidget> createState() => _ReauthToastWidgetState();
|
||||
@@ -32,7 +32,11 @@ class _ReauthToastWidgetState extends State<ReauthToastWidget> {
|
||||
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<ReauthToastWidget> {
|
||||
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<ReauthToastWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<StatefulWidget> createState() => _HomeGradesScreen();
|
||||
@@ -53,23 +57,25 @@ class _HomeGradesScreen extends FirkaState<HomeGradesScreen> {
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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<StatefulWidget> createState() => _HomeGradesSubjectScreen();
|
||||
@@ -41,8 +45,7 @@ class _HomeGradesSubjectScreen extends FirkaState<HomeGradesSubjectScreen> {
|
||||
}
|
||||
|
||||
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<HomeGradesSubjectScreen> {
|
||||
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<HomeGradesSubjectScreen> {
|
||||
var gradeWidgets = List<Widget>.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<HomeGradesSubjectScreen> {
|
||||
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<HomeGradesSubjectScreen> {
|
||||
// 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<HomeGradesSubjectScreen> {
|
||||
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<HomeGradesSubjectScreen> {
|
||||
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<HomeGradesSubjectScreen> {
|
||||
);
|
||||
} 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<HomeGradesSubjectScreen> {
|
||||
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<HomeGradesSubjectScreen> {
|
||||
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<HomeGradesSubjectScreen> {
|
||||
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<HomeGradesSubjectScreen> {
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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<HomeMainScreen> createState() => _HomeMainScreen();
|
||||
@@ -81,21 +85,23 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
|
||||
|
||||
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<HomeMainScreen> {
|
||||
}
|
||||
});
|
||||
|
||||
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<HomeMainScreen> {
|
||||
}
|
||||
});
|
||||
|
||||
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<HomeMainScreen> {
|
||||
}
|
||||
});
|
||||
|
||||
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<HomeMainScreen> {
|
||||
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<HomeMainScreen> {
|
||||
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<HomeMainScreen> {
|
||||
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<HomeMainScreen> {
|
||||
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<HomeMainScreen> {
|
||||
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<HomeMainScreen> {
|
||||
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<HomeMainScreen> {
|
||||
child: ListView(
|
||||
children: noticeBoardWidgets.map((e) => e.$1).toList(),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -390,7 +425,7 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [DelayedSpinnerWidget()],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -11,15 +11,19 @@ class PageWithSubPages extends StatefulWidget {
|
||||
final ValueNotifier<bool> 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<PageWithSubPages> {
|
||||
class PageWithSubPagesState extends FirkaState<PageWithSubPages> {
|
||||
int _currentSubPage = 0;
|
||||
|
||||
@override
|
||||
|
||||
@@ -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<HomeTimetableScreen> createState() => _HomeTimetableScreen();
|
||||
@@ -81,19 +85,24 @@ class _HomeTimetableScreen extends FirkaState<HomeTimetableScreen>
|
||||
|
||||
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<HomeTimetableScreen>
|
||||
await widget.data.client.getTests();
|
||||
}
|
||||
|
||||
Future<void> _updateState(DateTime now, ApiResponse<List<Lesson>> lessonsResp,
|
||||
ApiResponse<List<Test>> testsResp) async {
|
||||
Future<void> _updateState(
|
||||
DateTime now,
|
||||
ApiResponse<List<Lesson>> lessonsResp,
|
||||
ApiResponse<List<Test>> testsResp,
|
||||
) async {
|
||||
var monday = now.getMonday().getMidnight();
|
||||
|
||||
List<DateTime> dates = List.empty(growable: true);
|
||||
@@ -164,7 +176,8 @@ class _HomeTimetableScreen extends FirkaState<HomeTimetableScreen>
|
||||
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<HomeTimetableScreen>
|
||||
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<HomeTimetableScreen>
|
||||
// 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<DateTime> reorderedDates = List.from(_animationDates!);
|
||||
@@ -307,13 +321,13 @@ class _HomeTimetableScreen extends FirkaState<HomeTimetableScreen>
|
||||
final double end = targetDisplayIndex * totalCardWidth;
|
||||
|
||||
_cardAnimationController!.reset();
|
||||
_cardOffsetAnimation = Tween<Offset>(
|
||||
begin: Offset(start, 0),
|
||||
end: Offset(end, 0),
|
||||
).animate(CurvedAnimation(
|
||||
parent: _cardAnimationController!,
|
||||
curve: Curves.easeInOut,
|
||||
));
|
||||
_cardOffsetAnimation =
|
||||
Tween<Offset>(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<HomeTimetableScreen>
|
||||
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<HomeTimetableScreen>
|
||||
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<Widget> ttEmptyCards = List.empty(growable: true);
|
||||
@@ -436,70 +489,76 @@ class _HomeTimetableScreen extends FirkaState<HomeTimetableScreen>
|
||||
);
|
||||
|
||||
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<HomeTimetableScreen>
|
||||
),
|
||||
),
|
||||
*/
|
||||
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<HomeTimetableScreen>
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [DelayedSpinnerWidget()],
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<HomeTimetableMonthlyScreen> createState() =>
|
||||
@@ -49,20 +53,28 @@ class _HomeTimetableMonthlyScreen
|
||||
|
||||
Future<void> 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<DateTime> 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -49,10 +49,7 @@ class _DebugScreen extends FirkaState<DebugScreen> {
|
||||
}
|
||||
|
||||
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<DebugScreen> {
|
||||
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<DebugScreen> {
|
||||
useCache = value;
|
||||
});
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
@@ -90,7 +84,7 @@ class _DebugScreen extends FirkaState<DebugScreen> {
|
||||
debugTimeAdvance = value;
|
||||
});
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
@@ -111,17 +105,22 @@ class _DebugScreen extends FirkaState<DebugScreen> {
|
||||
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<DebugScreen> {
|
||||
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<DebugScreen> {
|
||||
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<DebugScreen> {
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty<
|
||||
Color>.fromMap(<WidgetStatesConstraint, Color>{
|
||||
WidgetState.any: Colors.red,
|
||||
}),
|
||||
backgroundColor: WidgetStateProperty<Color>.fromMap(
|
||||
<WidgetStatesConstraint, Color>{
|
||||
WidgetState.any: Colors.red,
|
||||
},
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
var isar = widget.data.isar;
|
||||
@@ -213,12 +220,16 @@ class _DebugScreen extends FirkaState<DebugScreen> {
|
||||
|
||||
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<DebugScreen> {
|
||||
),
|
||||
Center(
|
||||
child: FirkaIconWidget(
|
||||
FirkaIconType.majesticons, getIconData(e),
|
||||
color: Colors.black),
|
||||
)
|
||||
FirkaIconType.majesticons,
|
||||
getIconData(e),
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
|
||||
@@ -45,28 +45,32 @@ class _BetaScreenState extends FirkaState<BetaScreen> {
|
||||
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<BetaScreen> {
|
||||
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<BetaScreen> {
|
||||
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<BetaScreen> {
|
||||
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,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
@@ -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<PageNavData> pageNavNotifier =
|
||||
ValueNotifier(PageNavData(HomePage.home, null, ""));
|
||||
final ValueNotifier<PageNavData> 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<HomeScreen> {
|
||||
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<HomeScreen> {
|
||||
);
|
||||
|
||||
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<HomeScreen> {
|
||||
}
|
||||
|
||||
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<HomeScreen> {
|
||||
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<HomeScreen> {
|
||||
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<HomeScreen> {
|
||||
|
||||
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<HomeScreen> {
|
||||
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<HomeScreen> {
|
||||
|
||||
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<HomeScreen> {
|
||||
);
|
||||
}
|
||||
},
|
||||
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<HomeScreen> {
|
||||
);
|
||||
}
|
||||
},
|
||||
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<HomeScreen> {
|
||||
);
|
||||
}
|
||||
},
|
||||
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<HomeSubPage> createState() => _HomeSubPage();
|
||||
@@ -910,43 +950,101 @@ class _HomeSubPage extends State<HomeSubPage> {
|
||||
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<HomeSubPage> {
|
||||
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<HomeSubPage> {
|
||||
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!;
|
||||
|
||||
@@ -12,10 +12,12 @@ class FullPrivacyPolicyScreen extends StatefulWidget {
|
||||
const FullPrivacyPolicyScreen({required this.data, super.key});
|
||||
|
||||
@override
|
||||
State<FullPrivacyPolicyScreen> createState() => _FullPrivacyPolicyScreenState();
|
||||
State<FullPrivacyPolicyScreen> createState() =>
|
||||
_FullPrivacyPolicyScreenState();
|
||||
}
|
||||
|
||||
class _FullPrivacyPolicyScreenState extends FirkaState<FullPrivacyPolicyScreen> {
|
||||
class _FullPrivacyPolicyScreenState
|
||||
extends FirkaState<FullPrivacyPolicyScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -41,8 +43,9 @@ class _FullPrivacyPolicyScreenState extends FirkaState<FullPrivacyPolicyScreen>
|
||||
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<FullPrivacyPolicyScreen>
|
||||
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<FullPrivacyPolicyScreen>
|
||||
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<FullPrivacyPolicyScreen>
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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<LiveActivityConsentScreen> 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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<LoginScreen> {
|
||||
|
||||
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<Map<String, Object>> slides = [
|
||||
{
|
||||
@@ -155,7 +158,7 @@ class _LoginScreenState extends FirkaState<LoginScreen> {
|
||||
'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<LoginScreen> {
|
||||
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<void>(
|
||||
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<void>(
|
||||
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<Rect> {
|
||||
@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
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<GradeChart> {
|
||||
appStyle.colors.grade2,
|
||||
appStyle.colors.grade1,
|
||||
];
|
||||
|
||||
|
||||
late final List<FlSpot> spots;
|
||||
|
||||
@override
|
||||
@@ -60,7 +59,6 @@ class _GradeChartState extends State<GradeChart> {
|
||||
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<GradeChart> {
|
||||
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<GradeChart> {
|
||||
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<GradeChart> {
|
||||
),
|
||||
);
|
||||
}
|
||||
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<GradeChart> {
|
||||
_ => '',
|
||||
};
|
||||
Color gradeColor;
|
||||
if (text != ""){
|
||||
if (text != "") {
|
||||
gradeColor = getGradeColor(int.parse(text).toDouble());
|
||||
} else {
|
||||
gradeColor = getGradeColor(0);
|
||||
@@ -233,14 +230,14 @@ class _GradeChartState extends State<GradeChart> {
|
||||
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<GradeChart> {
|
||||
),
|
||||
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<GradeChart> {
|
||||
// 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<GradeChart> {
|
||||
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<GradeChart> {
|
||||
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<GradeChart> {
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
@@ -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: [],
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,13 @@ class WelcomeWidget extends StatefulWidget {
|
||||
final List<Lesson> 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<WelcomeWidget> createState() => _WelcomeWidgetState();
|
||||
@@ -29,7 +34,9 @@ class _WelcomeWidgetState extends State<WelcomeWidget> {
|
||||
@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<WelcomeWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@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<WelcomeWidget> {
|
||||
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<WelcomeWidget> {
|
||||
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<WelcomeWidget> {
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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<bool>(
|
||||
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<bool>(
|
||||
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: () {
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,25 +21,36 @@ class LessonBigWidget extends StatelessWidget {
|
||||
final List<Lesson> lessons;
|
||||
final List<Test> 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,
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<LoginWebviewWidget> createState() => _LoginWebviewWidgetState();
|
||||
@@ -48,14 +51,18 @@ class _LoginWebviewWidgetState extends FirkaState<LoginWebviewWidget>
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_fadeAnimation =
|
||||
Tween<double>(begin: 1.0, end: 0.0).animate(_fadeAnimationController!);
|
||||
_fadeAnimation = Tween<double>(
|
||||
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<LoginWebviewWidget>
|
||||
_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<LoginWebviewWidget>
|
||||
_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<LoginWebviewWidget>
|
||||
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<LoginWebviewWidget>
|
||||
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<LoginWebviewWidget>
|
||||
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,
|
||||
|
||||
@@ -16,11 +16,18 @@ class TimeTableDayWidget extends StatelessWidget {
|
||||
final List<Lesson> lessons;
|
||||
final List<Lesson> events;
|
||||
final List<Test> tests;
|
||||
final List<Lesson> day;
|
||||
final List<Lesson> 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)],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,9 +30,7 @@ class _DelayedSpinner extends FirkaState<DelayedSpinnerWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (showSpinner) {
|
||||
return CircularProgressIndicator(
|
||||
color: appStyle.colors.accent,
|
||||
);
|
||||
return CircularProgressIndicator(color: appStyle.colors.accent);
|
||||
} else {
|
||||
return SizedBox();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
]);
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user