From 32936c2aa5b712013299fba661b118d7474ce268 Mon Sep 17 00:00:00 2001 From: Armand <4831c0@proton.me> Date: Mon, 2 Mar 2026 23:26:59 +0100 Subject: [PATCH] firka: extract firka_common package with shared widgets (Isar kept separate) - Create firka_common package with core helpers (debug, json, icon), theme, and shared widgets (FirkaCard, FirkaShadow, GradeWidget, GradeSmallCard, ClassIconWidget, FirkaIconWidget, DelayedSpinnerWidget, CounterDigitWidget) - Keep Isar models (GenericCacheModel, TimetableCacheModel, HomeworkCacheModel, DatedCacheEntry, util) in firka and firka_wear - not moved to firka_common - Update firka and firka_wear to depend on firka_common for shared UI only - Add configurable roundGrade thresholds for firka settings - Add package param to FirkaIconWidget for app asset paths --- firka/lib/api/client/kreta_client.dart | 2 +- firka/lib/core/debug_helper.dart | 18 +- firka/lib/core/extensions.dart | 27 +- firka/lib/core/icon_helper.dart | 153 +-------- firka/lib/core/json_helper.dart | 10 +- firka/lib/data/models/token_model.dart | 2 +- firka/lib/ui/components/firka_card.dart | 135 +------- firka/lib/ui/components/firka_shadow.dart | 46 +-- firka/lib/ui/components/grade.dart | 98 +----- firka/lib/ui/components/grade_helpers.dart | 96 +----- .../lib/ui/phone/pages/home/home_grades.dart | 25 +- .../ui/phone/pages/home/home_timetable.dart | 2 + .../phone/pages/home/home_timetable_mo.dart | 2 + .../screens/settings/settings_screen.dart | 24 ++ .../ui/phone/widgets/home_main_welcome.dart | 2 + firka/lib/ui/phone/widgets/homework.dart | 1 + firka/lib/ui/phone/widgets/lesson.dart | 1 + firka/lib/ui/phone/widgets/lesson_big.dart | 2 + firka/lib/ui/shared/class_icon.dart | 36 +- firka/lib/ui/shared/counter_digit.dart | 22 +- firka/lib/ui/shared/delayed_spinner.dart | 46 +-- firka/lib/ui/shared/firka_icon.dart | 43 +-- firka/lib/ui/shared/grade_small_card.dart | 61 +--- firka/lib/ui/theme/style.dart | 310 +----------------- firka/pubspec.yaml | 2 + firka_common/lib/core/debug_helper.dart | 17 + firka_common/lib/core/extensions.dart | 19 ++ firka_common/lib/core/icon_helper.dart | 135 ++++++++ firka_common/lib/core/json_helper.dart | 9 + firka_common/lib/firka_common.dart | 16 + .../lib/ui/components/firka_card.dart | 137 ++++++++ .../lib/ui/components/firka_shadow.dart | 52 +++ firka_common/lib/ui/components/grade.dart | 97 ++++++ .../lib/ui/components/grade_helpers.dart | 100 ++++++ firka_common/lib/ui/shared/class_icon.dart | 35 ++ firka_common/lib/ui/shared/counter_digit.dart | 22 ++ .../lib/ui/shared/delayed_spinner.dart | 48 +++ firka_common/lib/ui/shared/firka_icon.dart | 46 +++ .../lib/ui/shared/grade_small_card.dart | 60 ++++ firka_common/lib/ui/theme/style.dart | 309 +++++++++++++++++ firka_common/pubspec.yaml | 21 ++ firka_wear/lib/core/debug_helper.dart | 18 +- firka_wear/lib/core/extensions.dart | 21 +- firka_wear/lib/core/icon_helper.dart | 153 +-------- firka_wear/lib/core/json_helper.dart | 10 +- .../lib/data/models/homework_cache_model.dart | 2 +- .../data/models/timetable_cache_model.dart | 2 +- firka_wear/lib/ui/components/firka_card.dart | 58 +--- .../lib/ui/components/firka_shadow.dart | 45 +-- firka_wear/lib/ui/components/grade.dart | 88 +---- .../lib/ui/components/grade_helpers.dart | 76 +---- firka_wear/lib/ui/shared/class_icon.dart | 37 +-- firka_wear/lib/ui/shared/counter_digit.dart | 23 +- firka_wear/lib/ui/shared/delayed_spinner.dart | 43 +-- firka_wear/lib/ui/shared/firka_icon.dart | 40 +-- .../lib/ui/shared/grade_small_card.dart | 61 +--- firka_wear/lib/ui/theme/style.dart | 272 +-------------- firka_wear/pubspec.yaml | 2 + 58 files changed, 1221 insertions(+), 2019 deletions(-) create mode 100644 firka_common/lib/core/debug_helper.dart create mode 100644 firka_common/lib/core/extensions.dart create mode 100644 firka_common/lib/core/icon_helper.dart create mode 100644 firka_common/lib/core/json_helper.dart create mode 100644 firka_common/lib/firka_common.dart create mode 100644 firka_common/lib/ui/components/firka_card.dart create mode 100644 firka_common/lib/ui/components/firka_shadow.dart create mode 100644 firka_common/lib/ui/components/grade.dart create mode 100644 firka_common/lib/ui/components/grade_helpers.dart create mode 100644 firka_common/lib/ui/shared/class_icon.dart create mode 100644 firka_common/lib/ui/shared/counter_digit.dart create mode 100644 firka_common/lib/ui/shared/delayed_spinner.dart create mode 100644 firka_common/lib/ui/shared/firka_icon.dart create mode 100644 firka_common/lib/ui/shared/grade_small_card.dart create mode 100644 firka_common/lib/ui/theme/style.dart create mode 100644 firka_common/pubspec.yaml diff --git a/firka/lib/api/client/kreta_client.dart b/firka/lib/api/client/kreta_client.dart index 6b3435d..e76cd23 100644 --- a/firka/lib/api/client/kreta_client.dart +++ b/firka/lib/api/client/kreta_client.dart @@ -12,8 +12,8 @@ import 'package:kreta_api/kreta_api.dart' hide KretaEndpoints; import 'package:firka/app/app_state.dart'; import 'package:firka/core/bloc/reauth_cubit.dart'; import 'package:firka/data/models/token_model.dart'; -import 'package:firka/data/util.dart'; import 'package:firka/core/debug_helper.dart'; +import 'package:firka/data/util.dart'; import 'package:firka/services/active_account_helper.dart'; import 'package:firka/services/watch_sync_helper.dart'; import '../consts.dart'; diff --git a/firka/lib/core/debug_helper.dart b/firka/lib/core/debug_helper.dart index 027d8e8..d374108 100644 --- a/firka/lib/core/debug_helper.dart +++ b/firka/lib/core/debug_helper.dart @@ -1,17 +1 @@ -DateTime? debugFakeTime; -DateTime? debugSetAt; -var debugTimeAdvance = false; - -DateTime timeNow() { - if (debugFakeTime != null) { - if (debugTimeAdvance && debugSetAt != null) { - var diff = DateTime.now().difference(debugSetAt!); - - return debugFakeTime!.add(diff); - } else { - return debugFakeTime!; - } - } else { - return DateTime.now(); - } -} +export 'package:firka_common/core/debug_helper.dart'; diff --git a/firka/lib/core/extensions.dart b/firka/lib/core/extensions.dart index 8eb59a5..e837e7f 100644 --- a/firka/lib/core/extensions.dart +++ b/firka/lib/core/extensions.dart @@ -1,8 +1,11 @@ import 'package:intl/intl.dart'; -import 'package:kreta_api/kreta_api.dart'; -import 'package:firka/core/debug_helper.dart'; import 'package:firka/l10n/app_localizations.dart'; +import 'package:firka_common/core/debug_helper.dart'; +import 'package:firka_common/core/extensions.dart'; +import 'package:kreta_api/kreta_api.dart'; + +export 'package:firka_common/core/extensions.dart'; extension TimetableExtension on Iterable { List getAllSeqs(Lesson reference) { @@ -59,26 +62,6 @@ extension TimetableExtension on Iterable { } } -extension IterableExtensionMap on Iterable> { - Map toMap() { - var map = {}; - for (var item in this) { - map[item.key] = item.value; - } - - return map; - } -} - -extension IterableExtension on Iterable { - T? firstWhereOrNull(bool Function(T element) test) { - for (var element in this) { - if (test(element)) return element; - } - return null; - } -} - extension DurationExtension on Duration { String formatDuration() { String hours = inHours.toString().padLeft(2, '0'); diff --git a/firka/lib/core/icon_helper.dart b/firka/lib/core/icon_helper.dart index 34d5343..dbf6f91 100644 --- a/firka/lib/core/icon_helper.dart +++ b/firka/lib/core/icon_helper.dart @@ -1,152 +1 @@ -import 'dart:typed_data'; - -import 'package:majesticons_flutter/majesticons_flutter.dart'; - -enum ClassIcon { - mathematics, - grammar, - literature, - history, - geography, - art, - physics, - music, - pe, - chemistry, - biology, - env, - religion, - economics, - it, - code, - networking, - theatre, - film, - electricalEngineering, - mechanicalEngineering, - technika, - dance, - philosophy, - ofo, - diligence, - attitude, - language, - linux, - database, - applications, - project, -} - -Map _descriptors = { - ClassIcon.mathematics: RegExp(r'mate(k|matika)'), - ClassIcon.grammar: RegExp(r'magyar nyelv|nyelvtan'), - ClassIcon.literature: RegExp(r'irodalom'), - ClassIcon.history: RegExp(r'tor(i|tenelem)'), - ClassIcon.geography: RegExp(r'foldrajz'), - ClassIcon.art: RegExp(r'rajz|muvtori|muveszet|vizualis'), - ClassIcon.physics: RegExp(r'fizika'), - ClassIcon.music: RegExp(r'^enek|zene|szolfezs|zongora|korus'), - 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.religion: RegExp(r'(hit|erkolcs)tan|vallas|etika|bibliaismeret'), - ClassIcon.economics: RegExp(r'penzugy|gazdasag'), - ClassIcon.it: RegExp(r'informatika|szoftver|iroda|digitalis'), - ClassIcon.code: RegExp(r'prog|alkalmazas'), - ClassIcon.networking: RegExp(r'halozat'), - ClassIcon.theatre: RegExp(r'szinhaz'), - ClassIcon.film: RegExp(r'film|media'), - ClassIcon.electricalEngineering: RegExp(r'elektro(tech)?nika'), - ClassIcon.mechanicalEngineering: RegExp(r'gepesz|mernok|ipar'), - ClassIcon.technika: RegExp(r'technika'), - ClassIcon.dance: RegExp(r'tanc'), - ClassIcon.philosophy: RegExp(r'filozofia'), - 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.linux: RegExp(r'linux'), - ClassIcon.database: RegExp(r'adatbazis.*'), - ClassIcon.applications: RegExp(r'asztali alkalmazasok'), - ClassIcon.project: RegExp(r'projekt'), -}; - -Map _iconMap = { - ClassIcon.mathematics: Majesticon.calculatorSolid, - ClassIcon.grammar: Majesticon.bookSolid, - ClassIcon.literature: Majesticon.bookOpenSolid, - ClassIcon.history: Majesticon.compass2Solid, - ClassIcon.geography: Majesticon.globeEarth2Solid, - ClassIcon.art: Majesticon.editPen2Solid, - // ClassIcon.physics: , - ClassIcon.music: Majesticon.musicNoteSolid, - // ClassIcon.pe: , - ClassIcon.chemistry: Majesticon.testTubeFilledSolid, - ClassIcon.biology: Majesticon.covidSolid, - // ClassIcon.env: , - // ClassIcon.religion: , - // ClassIcon.economics: , - ClassIcon.it: Majesticon.laptopSolid, - ClassIcon.code: Majesticon.curlyBracesSolid, - ClassIcon.networking: Majesticon.cloudSolid, - // ClassIcon.theatre: , - // ClassIcon.film: , - // ClassIcon.electricalEngineering: , - // ClassIcon.mechanicalEngineering: , - ClassIcon.technika: Majesticon.ruler2Solid, - // ClassIcon.dance: , - // ClassIcon.philosophy: , - // ClassIcon.ofo: , - // ClassIcon.diligence: , - // ClassIcon.attitude: , - ClassIcon.language: Majesticon.tooltipsSolid, - // ClassIcon.linux: , - ClassIcon.database: Majesticon.dataSolid, - // ClassIcon.applications: , - // ClassIcon.project: , -}; - -ClassIcon? getIconType(String uid, String className, String category) { - ClassIcon? icon; - if (category.toLowerCase() == "matematika") { - icon = ClassIcon.mathematics; - } - - 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(), - )) { - icon = desc.key; - - break; - } - } - } - - return icon; -} - -Uint8List getIconData(ClassIcon? icon) { - if (icon == null) return Majesticon.alertCircleSolid; - - var iconData = _iconMap[icon]; - iconData ??= Majesticon.alertCircleSolid; - - return iconData; -} +export 'package:firka_common/core/icon_helper.dart'; diff --git a/firka/lib/core/json_helper.dart b/firka/lib/core/json_helper.dart index 4e07713..cd391aa 100644 --- a/firka/lib/core/json_helper.dart +++ b/firka/lib/core/json_helper.dart @@ -1,9 +1 @@ -List listToTyped(List dynamicList) { - var newList = List.empty(growable: true); - - for (var item in dynamicList) { - newList.add(item as T); - } - - return newList; -} +export 'package:firka_common/core/json_helper.dart'; diff --git a/firka/lib/data/models/token_model.dart b/firka/lib/data/models/token_model.dart index 01c0523..974a830 100644 --- a/firka/lib/data/models/token_model.dart +++ b/firka/lib/data/models/token_model.dart @@ -3,8 +3,8 @@ import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:kreta_api/kreta_api.dart'; -import 'package:firka/core/debug_helper.dart'; import 'package:firka/core/extensions.dart'; +import 'package:firka_common/core/debug_helper.dart'; import 'package:isar_community/isar.dart'; part 'token_model.g.dart'; diff --git a/firka/lib/ui/components/firka_card.dart b/firka/lib/ui/components/firka_card.dart index 02ba429..d732b90 100644 --- a/firka/lib/ui/components/firka_card.dart +++ b/firka/lib/ui/components/firka_card.dart @@ -1,134 +1 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:firka/core/bloc/theme_cubit.dart'; -import 'package:firka/ui/components/firka_shadow.dart'; -import 'package:firka/ui/theme/style.dart'; - -enum Attach { none, bottom, top } - -class FirkaCard extends StatelessWidget { - final List left; - final List? center; - final double? height; - final List? right; - final bool shadow; - final Widget? extra; - 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, - }); - - @override - Widget build(BuildContext context) { - var right = this.right ?? []; - - var attached = this.attached != null ? this.attached! : Attach.none; - final defaultRounding = 16.0; - final attachedRounding = 8.0; - final isLight = context.watch().state.isLightMode; - - if (extra != null) { - return SizedBox( - width: MediaQuery.of(context).size.width, - height: height, - child: FirkaShadow( - shadow: shadow, - child: Card( - color: color ?? appStyle.colors.card, - shadowColor: isLight && 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: isLight && 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), - ], - ), - ), - ), - ), - ); - } - } -} +export 'package:firka_common/ui/components/firka_card.dart'; diff --git a/firka/lib/ui/components/firka_shadow.dart b/firka/lib/ui/components/firka_shadow.dart index f3e7e13..29a96dc 100644 --- a/firka/lib/ui/components/firka_shadow.dart +++ b/firka/lib/ui/components/firka_shadow.dart @@ -1,45 +1 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:firka/core/bloc/theme_cubit.dart'; -import 'package:firka/ui/theme/style.dart'; - -class FirkaShadow extends StatelessWidget { - final Widget child; - final bool shadow; - - const FirkaShadow({required this.shadow, required this.child, super.key}); - - @override - Widget build(BuildContext context) { - 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)), - ); - - if (!shadow) { - return ClipRRect(borderRadius: borderRadius, child: child); - } - - final isLight = context.watch().state.isLightMode; - if (isLight) { - return child; - } else { - return Container( - decoration: shadowBox, - child: ClipRRect(borderRadius: borderRadius, child: child), - ); - } - } -} +export 'package:firka_common/ui/components/firka_shadow.dart'; diff --git a/firka/lib/ui/components/grade.dart b/firka/lib/ui/components/grade.dart index a83e4c3..fdc5b4a 100644 --- a/firka/lib/ui/components/grade.dart +++ b/firka/lib/ui/components/grade.dart @@ -1,97 +1 @@ -import 'package:kreta_api/kreta_api.dart'; -import 'package:flutter/material.dart'; - -import 'package:firka/ui/theme/style.dart'; -import 'package:firka/ui/components/grade_helpers.dart'; - -class GradeWidget extends StatelessWidget { - const GradeWidget(this.grade, {super.key}) - : gradeValue = null, - _fromValue = false; - - const GradeWidget.gradeValue(int value, {super.key}) - : grade = null, - gradeValue = value, - _fromValue = true; - - final Grade? grade; - final int? gradeValue; - final bool _fromValue; - - @override - Widget build(BuildContext context) { - if (_fromValue && gradeValue != null) { - return _buildNumericCircle( - gradeValue!, - getGradeColor(gradeValue!.toDouble()), - ); - } - - final g = grade!; - Color gradeColor = appStyle.colors.grade1; - final gradeStr = g.numericValue?.toString() ?? '0'; - - if (g.valueType.name == 'Szazalekos') { - if (g.numericValue != null) { - gradeColor = getGradeColor( - percentageToGrade(g.numericValue!).toDouble(), - ); - } - - final str = g.strValue.replaceAll('%', ''); - return Card( - shape: const CircleBorder(), - shadowColor: Colors.transparent, - color: gradeColor.withAlpha(38), - child: Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(str, style: appStyle.fonts.P_14.copyWith(color: gradeColor)), - Text('%', style: appStyle.fonts.P_12.copyWith(color: gradeColor)), - ], - ), - ), - ); - } - - if (g.numericValue != null) { - gradeColor = getGradeColor(g.numericValue!.toDouble()); - } - - if (gradeStr == '0') { - return Card( - shadowColor: Colors.transparent, - color: gradeColor.withAlpha(38), - child: Padding( - padding: const EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2), - child: Text( - g.strValue, - style: appStyle.fonts.H_H1.copyWith( - fontSize: 16, - color: gradeColor, - ), - ), - ), - ); - } - - return _buildNumericCircle(g.numericValue!, gradeColor); - } - - Widget _buildNumericCircle(int value, Color gradeColor) { - return Card( - shape: const CircleBorder(), - shadowColor: Colors.transparent, - color: gradeColor.withAlpha(38), - child: Padding( - padding: const EdgeInsets.only(left: 8, right: 8), - child: Text( - value.toString(), - style: appStyle.fonts.H_H1.copyWith(fontSize: 24, color: gradeColor), - ), - ), - ); - } -} +export 'package:firka_common/ui/components/grade.dart'; diff --git a/firka/lib/ui/components/grade_helpers.dart b/firka/lib/ui/components/grade_helpers.dart index caaea9b..cfa04d2 100644 --- a/firka/lib/ui/components/grade_helpers.dart +++ b/firka/lib/ui/components/grade_helpers.dart @@ -1,95 +1 @@ -import 'dart:ui'; - -import 'package:firka/core/settings.dart'; -import 'package:firka/app/app_state.dart'; - -import 'package:firka/ui/theme/style.dart'; -import 'package:kreta_api/kreta_api.dart'; - -int roundGrade(double grade) { - final rounding = initData.settings - .group("settings") - .subGroup("application") - .subGroup("rounding"); - if (grade < 1 + rounding.dbl("1")) { - return 1; - } - if (grade < 2 + rounding.dbl("2")) { - return 2; - } - if (grade < 3 + rounding.dbl("3")) { - return 3; - } - if (grade < 4 + rounding.dbl("4")) { - return 4; - } - - return 5; -} - -int percentageToGrade(int grade) { - if (grade < 50) { - return 1; - } - if (grade < 60) { - return 2; - } - if (grade < 70) { - return 3; - } - if (grade < 80) { - return 4; - } - - return 5; -} - -Color getGradeColor(double grade) { - switch (roundGrade(grade)) { - case 2: - return appStyle.colors.grade2; - case 3: - return appStyle.colors.grade3; - case 4: - return appStyle.colors.grade4; - case 5: - return appStyle.colors.grade5; - default: - return appStyle.colors.grade1; - } -} - -(int total, List countsByGrade) getGradeDistribution(List grades) { - final filtered = grades - .where((g) => g.type.name != "felevi_jegy_ertekeles") - .toList(); - final counts = [0, 0, 0, 0, 0]; - for (final g in filtered) { - if (g.numericValue == null) continue; - final value = g.valueType.name == "Szazalekos" - ? percentageToGrade(g.numericValue!.round()) - : g.numericValue!.round().clamp(1, 5); - counts[value - 1]++; - } - return (filtered.length, counts); -} - -extension GradeListExtension on List { - double getAverageBySubject(Subject subject) { - var weightTotal = 0.00; - var sum = 0.00; - - for (var grade in this) { - if (grade.subject.uid == subject.uid) { - if (grade.numericValue != null) { - var weight = (grade.weightPercentage ?? 100) / 100.0; - weightTotal += weight; - - sum += grade.numericValue! * weight; - } - } - } - - return sum / weightTotal; - } -} +export 'package:firka_common/ui/components/grade_helpers.dart'; diff --git a/firka/lib/ui/phone/pages/home/home_grades.dart b/firka/lib/ui/phone/pages/home/home_grades.dart index 7b9abd3..35e98ba 100644 --- a/firka/lib/ui/phone/pages/home/home_grades.dart +++ b/firka/lib/ui/phone/pages/home/home_grades.dart @@ -14,6 +14,7 @@ import 'package:firka/core/debug_helper.dart'; import 'package:firka/core/state/firka_state.dart'; import 'package:firka/app/app_state.dart'; import 'package:firka/core/bloc/home_refresh_cubit.dart'; +import 'package:firka/core/settings.dart'; import 'package:firka/ui/theme/style.dart'; import 'package:firka/ui/shared/delayed_spinner.dart'; @@ -197,7 +198,17 @@ class _HomeGradesScreen extends FirkaState { if (!avg.isNaN) { subjectCount++; subjectAvg += avg; - subjectAvgRounded += roundGrade(avg); + final rounding = widget.data.settings + .group("settings") + .subGroup("application") + .subGroup("rounding"); + subjectAvgRounded += roundGrade( + avg, + t1: rounding.dbl("1"), + t2: rounding.dbl("2"), + t3: rounding.dbl("3"), + t4: rounding.dbl("4"), + ); } } @@ -209,7 +220,17 @@ class _HomeGradesScreen extends FirkaState { subjectAvgRounded = 0.00; } - var subjectAvgColor = getGradeColor(subjectAvg); + final rounding = widget.data.settings + .group("settings") + .subGroup("application") + .subGroup("rounding"); + var subjectAvgColor = getGradeColor( + subjectAvg, + t1: rounding.dbl("1"), + t2: rounding.dbl("2"), + t3: rounding.dbl("3"), + t4: rounding.dbl("4"), + ); return Padding( padding: const EdgeInsets.only(left: 20.0, right: 20.0, top: 12.0), diff --git a/firka/lib/ui/phone/pages/home/home_timetable.dart b/firka/lib/ui/phone/pages/home/home_timetable.dart index 0062cfc..e306136 100644 --- a/firka/lib/ui/phone/pages/home/home_timetable.dart +++ b/firka/lib/ui/phone/pages/home/home_timetable.dart @@ -608,6 +608,7 @@ class _HomeTimetableScreen extends FirkaState "dropdownLeft", size: 24, color: appStyle.colors.accent, + package: 'firka', ), ), onTap: () async { @@ -672,6 +673,7 @@ class _HomeTimetableScreen extends FirkaState "dropdownRight", size: 24, color: appStyle.colors.accent, + package: 'firka', ), ), onTap: () async { diff --git a/firka/lib/ui/phone/pages/home/home_timetable_mo.dart b/firka/lib/ui/phone/pages/home/home_timetable_mo.dart index e438f2f..f451096 100644 --- a/firka/lib/ui/phone/pages/home/home_timetable_mo.dart +++ b/firka/lib/ui/phone/pages/home/home_timetable_mo.dart @@ -438,6 +438,7 @@ class _HomeTimetableMonthlyScreen "dropdownLeft", size: 24, color: appStyle.colors.accent, + package: 'firka', ), ), onTap: () async { @@ -467,6 +468,7 @@ class _HomeTimetableMonthlyScreen "dropdownRight", size: 24, color: appStyle.colors.accent, + package: 'firka', ), onTap: () async { var newNow = DateTime(now!.year, now!.month + 1); diff --git a/firka/lib/ui/phone/screens/settings/settings_screen.dart b/firka/lib/ui/phone/screens/settings/settings_screen.dart index 02e3d36..bfc83a2 100644 --- a/firka/lib/ui/phone/screens/settings/settings_screen.dart +++ b/firka/lib/ui/phone/screens/settings/settings_screen.dart @@ -157,6 +157,11 @@ class _SettingsScreenState extends FirkaState { item.iconType!, item.iconData!, color: appStyle.colors.accent, + package: + item.iconType == FirkaIconType.icons || + item.iconType == FirkaIconType.majesticonsLocal + ? 'firka' + : null, ), ); cardWidgets.add(SizedBox(width: 8)); @@ -224,6 +229,12 @@ class _SettingsScreenState extends FirkaState { item.iconType!, item.iconData!, color: appStyle.colors.accent, + package: + item.iconType == FirkaIconType.icons || + item.iconType == + FirkaIconType.majesticonsLocal + ? 'firka' + : null, ), SizedBox(width: 4), ], @@ -265,6 +276,12 @@ class _SettingsScreenState extends FirkaState { item.iconType!, item.iconData!, color: appStyle.colors.accent, + package: + item.iconType == FirkaIconType.icons || + item.iconType == + FirkaIconType.majesticonsLocal + ? 'firka' + : null, ), SizedBox(width: 4), ], @@ -930,6 +947,7 @@ class _SettingsScreenState extends FirkaState { FirkaIconType.icons, "group", color: appStyle.colors.accent, + package: 'firka', ), SizedBox(width: 8), Text( @@ -1067,6 +1085,12 @@ class _SettingsScreenState extends FirkaState { item.iconType!, item.iconData!, color: appStyle.colors.accent, + package: + item.iconType == FirkaIconType.icons || + item.iconType == + FirkaIconType.majesticonsLocal + ? 'firka' + : null, ), SizedBox(width: 8), ], diff --git a/firka/lib/ui/phone/widgets/home_main_welcome.dart b/firka/lib/ui/phone/widgets/home_main_welcome.dart index cdc2170..4b90323 100644 --- a/firka/lib/ui/phone/widgets/home_main_welcome.dart +++ b/firka/lib/ui/phone/widgets/home_main_welcome.dart @@ -56,12 +56,14 @@ class _WelcomeWidgetState extends State { FirkaIconType.majesticonsLocal, "sunSolid", color: appStyle.colors.accent, + package: 'firka', ); case Cycle.day: return FirkaIconWidget( FirkaIconType.majesticonsLocal, "parkSolidSchool", color: appStyle.colors.accent, + package: 'firka', ); case Cycle.afternoon: return FirkaIconWidget( diff --git a/firka/lib/ui/phone/widgets/homework.dart b/firka/lib/ui/phone/widgets/homework.dart index 18634d8..956b650 100644 --- a/firka/lib/ui/phone/widgets/homework.dart +++ b/firka/lib/ui/phone/widgets/homework.dart @@ -34,6 +34,7 @@ class HomeworkWidget extends StatelessWidget { "homeWithMark", color: appStyle.colors.accent, size: 24, + package: 'firka', ) : FirkaIconWidget( FirkaIconType.majesticons, diff --git a/firka/lib/ui/phone/widgets/lesson.dart b/firka/lib/ui/phone/widgets/lesson.dart index a7a04fc..b9a795f 100644 --- a/firka/lib/ui/phone/widgets/lesson.dart +++ b/firka/lib/ui/phone/widgets/lesson.dart @@ -370,6 +370,7 @@ class LessonWidget extends StatelessWidget { 'cupFilled', color: appStyle.colors.accent, size: 24, + package: 'firka', ), ), ), diff --git a/firka/lib/ui/phone/widgets/lesson_big.dart b/firka/lib/ui/phone/widgets/lesson_big.dart index d3c5654..28acf96 100644 --- a/firka/lib/ui/phone/widgets/lesson_big.dart +++ b/firka/lib/ui/phone/widgets/lesson_big.dart @@ -245,6 +245,7 @@ class LessonBigWidget extends StatelessWidget { 'cupFilled', color: appStyle.colors.accent, size: 24, + package: 'firka', ), ), ), @@ -487,6 +488,7 @@ class LessonBigWidget extends StatelessWidget { 'cupFilled', color: appStyle.colors.accent, size: 24, + package: 'firka', ), ), ), diff --git a/firka/lib/ui/shared/class_icon.dart b/firka/lib/ui/shared/class_icon.dart index b689ed3..c650795 100644 --- a/firka/lib/ui/shared/class_icon.dart +++ b/firka/lib/ui/shared/class_icon.dart @@ -1,35 +1 @@ -import 'package:firka/core/icon_helper.dart'; -import 'package:flutter/material.dart'; - -import 'package:firka/ui/shared/firka_icon.dart'; - -class ClassIconWidget extends StatelessWidget { - final String _uid; - final String _className; - final String _category; - 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; - - @override - Widget build(BuildContext context) { - var iconCategory = getIconType(_uid, _className, _category); - - return FirkaIconWidget( - FirkaIconType.majesticons, - getIconData(iconCategory), - color: color, - size: size, - ); - } -} +export 'package:firka_common/ui/shared/class_icon.dart'; diff --git a/firka/lib/ui/shared/counter_digit.dart b/firka/lib/ui/shared/counter_digit.dart index 16cf02f..88857fc 100644 --- a/firka/lib/ui/shared/counter_digit.dart +++ b/firka/lib/ui/shared/counter_digit.dart @@ -1,21 +1 @@ -import 'package:firka/ui/theme/style.dart'; -import 'package:flutter/material.dart'; - -class CounterDigitWidget extends StatelessWidget { - final String c; - final TextStyle? style; - - const CounterDigitWidget(this.c, this.style, {super.key}); - - @override - Widget build(BuildContext context) { - return Card( - shadowColor: Colors.transparent, - color: appStyle.colors.buttonSecondaryFill, - child: Padding( - padding: EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4), - child: Text(c, style: style), - ), - ); - } -} +export 'package:firka_common/ui/shared/counter_digit.dart'; diff --git a/firka/lib/ui/shared/delayed_spinner.dart b/firka/lib/ui/shared/delayed_spinner.dart index d505ed5..d1176ae 100644 --- a/firka/lib/ui/shared/delayed_spinner.dart +++ b/firka/lib/ui/shared/delayed_spinner.dart @@ -1,45 +1 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:firka/core/state/firka_state.dart'; -import 'package:firka/ui/theme/style.dart'; - -class DelayedSpinnerWidget extends StatefulWidget { - const DelayedSpinnerWidget({super.key}); - - @override - State createState() => _DelayedSpinner(); -} - -class _DelayedSpinner extends FirkaState { - Timer? timer; - bool showSpinner = false; - - @override - void initState() { - super.initState(); - - timer = Timer(Duration(milliseconds: 50), () { - setState(() { - showSpinner = true; - }); - }); - } - - @override - Widget build(BuildContext context) { - if (showSpinner) { - return CircularProgressIndicator(color: appStyle.colors.accent); - } else { - return SizedBox(); - } - } - - @override - void dispose() { - super.dispose(); - - timer?.cancel(); - } -} +export 'package:firka_common/ui/shared/delayed_spinner.dart'; diff --git a/firka/lib/ui/shared/firka_icon.dart b/firka/lib/ui/shared/firka_icon.dart index 6bc8118..347aeb2 100644 --- a/firka/lib/ui/shared/firka_icon.dart +++ b/firka/lib/ui/shared/firka_icon.dart @@ -1,42 +1 @@ -import 'dart:typed_data'; - -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:majesticons_flutter/majesticons_flutter.dart'; - -enum FirkaIconType { icons, majesticons, majesticonsLocal } - -class FirkaIconWidget extends StatelessWidget { - final FirkaIconType iconType; - final Object iconData; - final Color color; - final double? size; - - const FirkaIconWidget( - this.iconType, - this.iconData, { - super.key, - this.color = Colors.white, - this.size, - }); - - @override - Widget build(BuildContext context) { - switch (iconType) { - case FirkaIconType.icons: - return SvgPicture.asset( - 'assets/icons/${iconData as String}.svg', - color: color, - height: size, - ); - case FirkaIconType.majesticons: - return Majesticon(iconData as Uint8List, color: color, size: size); - case FirkaIconType.majesticonsLocal: - return SvgPicture.asset( - 'assets/majesticons/${iconData as String}.svg', - color: color, - height: size, - ); - } - } -} +export 'package:firka_common/ui/shared/firka_icon.dart'; diff --git a/firka/lib/ui/shared/grade_small_card.dart b/firka/lib/ui/shared/grade_small_card.dart index 8c6fb24..e01f9cd 100644 --- a/firka/lib/ui/shared/grade_small_card.dart +++ b/firka/lib/ui/shared/grade_small_card.dart @@ -1,60 +1 @@ -import 'package:kreta_api/kreta_api.dart'; -import 'package:firka/ui/components/firka_card.dart'; -import 'package:firka/ui/components/grade_helpers.dart'; -import 'package:firka/ui/shared/class_icon.dart'; -import 'package:flutter/material.dart'; - -import 'package:firka/ui/theme/style.dart'; - -class GradeSmallCard extends FirkaCard { - final List grades; - final Subject subject; - - GradeSmallCard(this.grades, this.subject, {super.key}) - : super( - left: [ - ClassIconWidget( - uid: subject.uid, - className: subject.name, - category: subject.category.name!, - color: appStyle.colors.accent, - ), - SizedBox(width: 4), - SizedBox( - width: 200, - child: Text( - subject.name, - style: appStyle.fonts.B_16SB.apply( - color: appStyle.colors.textPrimary, - ), - ), - ), - ], - right: [ - grades.getAverageBySubject(subject).isNaN - ? SizedBox() - : Card( - shadowColor: Colors.transparent, - color: getGradeColor( - grades.getAverageBySubject(subject), - ).withAlpha(38), - child: Padding( - 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), - ), - ), - ), - ), - ), - ], - ); -} +export 'package:firka_common/ui/shared/grade_small_card.dart'; diff --git a/firka/lib/ui/theme/style.dart b/firka/lib/ui/theme/style.dart index 691bbfd..8ce3436 100644 --- a/firka/lib/ui/theme/style.dart +++ b/firka/lib/ui/theme/style.dart @@ -1,309 +1 @@ -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; - TextStyle H_H2; - TextStyle H_16px; - TextStyle H_14px; - TextStyle H_12px; - - TextStyle H_16px_trimmed; - - TextStyle B_16R; - TextStyle B_16SB; - TextStyle B_15SB; - - TextStyle B_14R; - TextStyle B_14SB; - - TextStyle B_12R; - TextStyle B_12SB; - - TextStyle P_14; - TextStyle P_12; - - FirkaFonts({ - required this.H_H1, - required this.H_18px, - required this.H_H2, - required this.H_16px, - required this.H_14px, - required this.H_12px, - required this.H_16px_trimmed, - required this.B_16R, - required this.B_16SB, - required this.B_15SB, - required this.B_14R, - required this.B_14SB, - required this.B_12R, - required this.B_12SB, - required this.P_14, - required this.P_12, - }); -} - -class FirkaColors { - Color background; - Color backgroundAmoled; - Color background0p; - Color success; - int shadowBlur; - - Color textPrimary; - Color textSecondary; - Color textTertiary; - - Color textPrimaryLight; - Color textSecondaryLight; - Color textTertiaryLight; - - Color card; - Color cardTranslucent; - - Color buttonSecondaryFill; - - Color accent; - Color secondary; - Color shadowColor; - Color a10p; // 10% - Color a15p; // 15% - - Color warningAccent; - Color warningText; - Color warning15p; - Color warningCard; - - Color errorAccent; - Color errorText; - Color error15p; - Color errorCard; - - Color grade5; - Color grade4; - Color grade3; - Color grade2; - Color grade1; - - FirkaColors({ - required this.background, - required this.backgroundAmoled, - required this.background0p, - required this.success, - required this.shadowBlur, - required this.textPrimary, - required this.textSecondary, - required this.textTertiary, - required this.textPrimaryLight, - required this.textSecondaryLight, - required this.textTertiaryLight, - required this.card, - required this.cardTranslucent, - required this.buttonSecondaryFill, - required this.accent, - required this.secondary, - required this.shadowColor, - required this.a10p, - required this.a15p, - required this.warningAccent, - required this.warningText, - required this.warning15p, - required this.warningCard, - required this.errorAccent, - required this.errorText, - required this.error15p, - required this.errorCard, - required this.grade5, - required this.grade4, - required this.grade3, - required this.grade2, - required this.grade1, - }); -} - -class FirkaStyle { - FirkaColors colors; - FirkaFonts fonts; - bool isLight; - - FirkaStyle({ - required this.isLight, - required this.colors, - required this.fonts, - }); -} - -final _defaultFonts = FirkaFonts( - H_H1: TextStyle( - fontSize: 30, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 700)], - ), - H_18px: TextStyle( - fontSize: 18, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 700)], - ), - H_H2: TextStyle( - fontSize: 20, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 700)], - ), - H_16px: TextStyle( - fontSize: 16, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 600)], - ), - H_14px: TextStyle( - fontSize: 14, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 600)], - ), - H_12px: TextStyle( - fontSize: 12, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 600)], - ), - H_16px_trimmed: TextStyle( - fontSize: 16, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 600)], - height: 1.3, - ), - B_16R: TextStyle( - fontSize: 16, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 600)], - height: 1.3, - ), - B_16SB: TextStyle( - fontSize: 16, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 700)], - height: 1.3, - ), - B_14R: TextStyle( - fontSize: 14, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 600)], - height: 1.3, - ), - B_14SB: TextStyle( - fontSize: 14, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 700)], - height: 1.3, - ), - B_15SB: TextStyle( - fontSize: 15, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 700)], - height: 1.3, - ), - B_12R: TextStyle( - fontSize: 12, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 600)], - height: 1.3, - ), - B_12SB: TextStyle( - fontSize: 12, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 700)], - height: 1.3, - ), - P_14: TextStyle( - fontSize: 14, - fontFamily: 'RobotoMono', - fontVariations: [FontVariation("wght", 700)], - ), - P_12: TextStyle( - fontSize: 12, - fontFamily: 'RobotoMono', - fontVariations: [FontVariation("wght", 700)], - ), -); - -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, -); - -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, -); - -FirkaStyle appStyle = lightStyle; -FirkaStyle wearStyle = darkStyle; +export 'package:firka_common/ui/theme/style.dart'; diff --git a/firka/pubspec.yaml b/firka/pubspec.yaml index b60ee83..6cf647b 100644 --- a/firka/pubspec.yaml +++ b/firka/pubspec.yaml @@ -10,6 +10,8 @@ environment: dependencies: flutter: sdk: flutter + firka_common: + path: ../firka_common kreta_api: path: ../kreta_api diff --git a/firka_common/lib/core/debug_helper.dart b/firka_common/lib/core/debug_helper.dart new file mode 100644 index 0000000..027d8e8 --- /dev/null +++ b/firka_common/lib/core/debug_helper.dart @@ -0,0 +1,17 @@ +DateTime? debugFakeTime; +DateTime? debugSetAt; +var debugTimeAdvance = false; + +DateTime timeNow() { + if (debugFakeTime != null) { + if (debugTimeAdvance && debugSetAt != null) { + var diff = DateTime.now().difference(debugSetAt!); + + return debugFakeTime!.add(diff); + } else { + return debugFakeTime!; + } + } else { + return DateTime.now(); + } +} diff --git a/firka_common/lib/core/extensions.dart b/firka_common/lib/core/extensions.dart new file mode 100644 index 0000000..fd5388d --- /dev/null +++ b/firka_common/lib/core/extensions.dart @@ -0,0 +1,19 @@ +extension IterableExtensionMap on Iterable> { + Map toMap() { + var map = {}; + for (var item in this) { + map[item.key] = item.value; + } + + return map; + } +} + +extension IterableExtension on Iterable { + T? firstWhereOrNull(bool Function(T element) test) { + for (var element in this) { + if (test(element)) return element; + } + return null; + } +} diff --git a/firka_common/lib/core/icon_helper.dart b/firka_common/lib/core/icon_helper.dart new file mode 100644 index 0000000..6779666 --- /dev/null +++ b/firka_common/lib/core/icon_helper.dart @@ -0,0 +1,135 @@ +import 'dart:typed_data'; + +import 'package:majesticons_flutter/majesticons_flutter.dart'; + +enum ClassIcon { + mathematics, + grammar, + literature, + history, + geography, + art, + physics, + music, + pe, + chemistry, + biology, + env, + religion, + economics, + it, + code, + networking, + theatre, + film, + electricalEngineering, + mechanicalEngineering, + technika, + dance, + philosophy, + ofo, + diligence, + attitude, + language, + linux, + database, + applications, + project, +} + +Map _descriptors = { + ClassIcon.mathematics: RegExp(r'mate(k|matika)'), + ClassIcon.grammar: RegExp(r'magyar nyelv|nyelvtan'), + ClassIcon.literature: RegExp(r'irodalom'), + ClassIcon.history: RegExp(r'tor(i|tenelem)'), + ClassIcon.geography: RegExp(r'foldrajz'), + ClassIcon.art: RegExp(r'rajz|muvtori|muveszet|vizualis'), + ClassIcon.physics: RegExp(r'fizika'), + ClassIcon.music: RegExp(r'^enek|zene|szolfezs|zongora|korus'), + 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.religion: RegExp(r'(hit|erkolcs)tan|vallas|etika|bibliaismeret'), + ClassIcon.economics: RegExp(r'penzugy|gazdasag'), + ClassIcon.it: RegExp(r'informatika|szoftver|iroda|digitalis'), + ClassIcon.code: RegExp(r'prog|alkalmazas'), + ClassIcon.networking: RegExp(r'halozat'), + ClassIcon.theatre: RegExp(r'szinhaz'), + ClassIcon.film: RegExp(r'film|media'), + ClassIcon.electricalEngineering: RegExp(r'elektro(tech)?nika'), + ClassIcon.mechanicalEngineering: RegExp(r'gepesz|mernok|ipar'), + ClassIcon.technika: RegExp(r'technika'), + ClassIcon.dance: RegExp(r'tanc'), + ClassIcon.philosophy: RegExp(r'filozofia'), + 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.linux: RegExp(r'linux'), + ClassIcon.database: RegExp(r'adatbazis.*'), + ClassIcon.applications: RegExp(r'asztali alkalmazasok'), + ClassIcon.project: RegExp(r'projekt'), +}; + +Map _iconMap = { + ClassIcon.mathematics: Majesticon.calculatorSolid, + ClassIcon.grammar: Majesticon.bookSolid, + ClassIcon.literature: Majesticon.bookOpenSolid, + ClassIcon.history: Majesticon.compass2Solid, + ClassIcon.geography: Majesticon.globeEarth2Solid, + ClassIcon.art: Majesticon.editPen2Solid, + ClassIcon.music: Majesticon.musicNoteSolid, + ClassIcon.chemistry: Majesticon.testTubeFilledSolid, + ClassIcon.biology: Majesticon.covidSolid, + ClassIcon.it: Majesticon.laptopSolid, + ClassIcon.code: Majesticon.curlyBracesSolid, + ClassIcon.networking: Majesticon.cloudSolid, + ClassIcon.technika: Majesticon.ruler2Solid, + ClassIcon.language: Majesticon.tooltipsSolid, + ClassIcon.database: Majesticon.dataSolid, +}; + +ClassIcon? getIconType(String uid, String className, String category) { + ClassIcon? icon; + if (category.toLowerCase() == "matematika") { + icon = ClassIcon.mathematics; + } + + 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(), + )) { + icon = desc.key; + + break; + } + } + } + + return icon; +} + +Uint8List getIconData(ClassIcon? icon) { + if (icon == null) return Majesticon.alertCircleSolid; + + var iconData = _iconMap[icon]; + iconData ??= Majesticon.alertCircleSolid; + + return iconData; +} diff --git a/firka_common/lib/core/json_helper.dart b/firka_common/lib/core/json_helper.dart new file mode 100644 index 0000000..4e07713 --- /dev/null +++ b/firka_common/lib/core/json_helper.dart @@ -0,0 +1,9 @@ +List listToTyped(List dynamicList) { + var newList = List.empty(growable: true); + + for (var item in dynamicList) { + newList.add(item as T); + } + + return newList; +} diff --git a/firka_common/lib/firka_common.dart b/firka_common/lib/firka_common.dart new file mode 100644 index 0000000..b3f95c9 --- /dev/null +++ b/firka_common/lib/firka_common.dart @@ -0,0 +1,16 @@ +library firka_common; + +export 'core/debug_helper.dart'; +export 'core/extensions.dart'; +export 'core/icon_helper.dart'; +export 'core/json_helper.dart'; +export 'ui/components/firka_card.dart'; +export 'ui/components/firka_shadow.dart'; +export 'ui/components/grade.dart'; +export 'ui/components/grade_helpers.dart'; +export 'ui/shared/class_icon.dart'; +export 'ui/shared/counter_digit.dart'; +export 'ui/shared/delayed_spinner.dart'; +export 'ui/shared/firka_icon.dart'; +export 'ui/shared/grade_small_card.dart'; +export 'ui/theme/style.dart'; diff --git a/firka_common/lib/ui/components/firka_card.dart b/firka_common/lib/ui/components/firka_card.dart new file mode 100644 index 0000000..b29a260 --- /dev/null +++ b/firka_common/lib/ui/components/firka_card.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; + +import 'package:firka_common/ui/components/firka_shadow.dart'; +import 'package:firka_common/ui/theme/style.dart'; + +enum Attach { none, bottom, top } + +class FirkaCard extends StatelessWidget { + final List left; + final List? center; + final double? height; + final List? right; + final bool shadow; + final Widget? extra; + final Attach? attached; + final Color? color; + final bool? isLightMode; + + const FirkaCard({ + required this.left, + this.shadow = true, + this.center, + this.right, + this.extra, + this.attached, + this.color, + this.height, + this.isLightMode, + super.key, + }); + + @override + Widget build(BuildContext context) { + var right = this.right ?? []; + + var attached = this.attached != null ? this.attached! : Attach.none; + final defaultRounding = 16.0; + final attachedRounding = 8.0; + final isLight = + isLightMode ?? Theme.of(context).brightness == Brightness.light; + + if (extra != null) { + return SizedBox( + width: MediaQuery.of(context).size.width, + height: height, + child: FirkaShadow( + shadow: shadow, + isLightMode: isLight, + child: Card( + color: color ?? appStyle.colors.card, + shadowColor: isLight && 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 ?? const SizedBox(), + ], + ), + ), + ), + ), + ); + } else { + return SizedBox( + width: MediaQuery.of(context).size.width, + height: height, + child: FirkaShadow( + shadow: shadow, + isLightMode: isLight, + child: Card( + color: color ?? appStyle.colors.card, + shadowColor: isLight && shadow ? null : Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular( + attached == Attach.top ? attachedRounding : defaultRounding, + ), + topRight: Radius.circular( + attached == Attach.top ? attachedRounding : defaultRounding, + ), + bottomLeft: Radius.circular( + attached == Attach.bottom + ? attachedRounding + : defaultRounding, + ), + bottomRight: Radius.circular( + attached == Attach.bottom + ? attachedRounding + : defaultRounding, + ), + ), + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row(children: left), + Row(children: center ?? []), + Row(children: right), + ], + ), + ), + ), + ), + ); + } + } +} diff --git a/firka_common/lib/ui/components/firka_shadow.dart b/firka_common/lib/ui/components/firka_shadow.dart new file mode 100644 index 0000000..5c1a24c --- /dev/null +++ b/firka_common/lib/ui/components/firka_shadow.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +import 'package:firka_common/ui/theme/style.dart'; + +class FirkaShadow extends StatelessWidget { + final Widget child; + final bool shadow; + final double radius; + final bool? isLightMode; + + const FirkaShadow({ + required this.shadow, + required this.child, + this.radius = 16.0, + this.isLightMode, + super.key, + }); + + @override + Widget build(BuildContext context) { + final isLight = + isLightMode ?? Theme.of(context).brightness == Brightness.light; + final borderRadius = BorderRadius.circular(radius); + + final shadowBox = BoxDecoration( + color: Colors.transparent, + shape: BoxShape.rectangle, + boxShadow: [ + BoxShadow( + color: appStyle.colors.shadowColor, + spreadRadius: -4, + blurRadius: 0, + offset: const Offset(0, 2), + ), + ], + borderRadius: BorderRadius.all(Radius.circular(radius)), + ); + + if (!shadow) { + return ClipRRect(borderRadius: borderRadius, child: child); + } + + if (isLight) { + return child; + } else { + return Container( + decoration: shadowBox, + child: ClipRRect(borderRadius: borderRadius, child: child), + ); + } + } +} diff --git a/firka_common/lib/ui/components/grade.dart b/firka_common/lib/ui/components/grade.dart new file mode 100644 index 0000000..0a2a555 --- /dev/null +++ b/firka_common/lib/ui/components/grade.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:kreta_api/kreta_api.dart'; + +import 'package:firka_common/ui/components/grade_helpers.dart'; +import 'package:firka_common/ui/theme/style.dart'; + +class GradeWidget extends StatelessWidget { + const GradeWidget(this.grade, {super.key}) + : gradeValue = null, + _fromValue = false; + + const GradeWidget.gradeValue(int value, {super.key}) + : grade = null, + gradeValue = value, + _fromValue = true; + + final Grade? grade; + final int? gradeValue; + final bool _fromValue; + + @override + Widget build(BuildContext context) { + if (_fromValue && gradeValue != null) { + return _buildNumericCircle( + gradeValue!, + getGradeColor(gradeValue!.toDouble()), + ); + } + + final g = grade!; + Color gradeColor = appStyle.colors.grade1; + final gradeStr = g.numericValue?.toString() ?? '0'; + + if (g.valueType.name == 'Szazalekos') { + if (g.numericValue != null) { + gradeColor = getGradeColor( + percentageToGrade(g.numericValue!).toDouble(), + ); + } + + final str = g.strValue.replaceAll('%', ''); + return Card( + shape: const CircleBorder(), + shadowColor: Colors.transparent, + color: gradeColor.withAlpha(38), + child: Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(str, style: appStyle.fonts.P_14.copyWith(color: gradeColor)), + Text('%', style: appStyle.fonts.P_12.copyWith(color: gradeColor)), + ], + ), + ), + ); + } + + if (g.numericValue != null) { + gradeColor = getGradeColor(g.numericValue!.toDouble()); + } + + if (gradeStr == '0') { + return Card( + shadowColor: Colors.transparent, + color: gradeColor.withAlpha(38), + child: Padding( + padding: const EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2), + child: Text( + g.strValue, + style: appStyle.fonts.H_H1.copyWith( + fontSize: 16, + color: gradeColor, + ), + ), + ), + ); + } + + return _buildNumericCircle(g.numericValue!, gradeColor); + } + + Widget _buildNumericCircle(int value, Color gradeColor) { + return Card( + shape: const CircleBorder(), + shadowColor: Colors.transparent, + color: gradeColor.withAlpha(38), + child: Padding( + padding: const EdgeInsets.only(left: 8, right: 8), + child: Text( + value.toString(), + style: appStyle.fonts.H_H1.copyWith(fontSize: 24, color: gradeColor), + ), + ), + ); + } +} diff --git a/firka_common/lib/ui/components/grade_helpers.dart b/firka_common/lib/ui/components/grade_helpers.dart new file mode 100644 index 0000000..7d28172 --- /dev/null +++ b/firka_common/lib/ui/components/grade_helpers.dart @@ -0,0 +1,100 @@ +import 'dart:ui'; + +import 'package:firka_common/ui/theme/style.dart'; +import 'package:kreta_api/kreta_api.dart'; + +int roundGrade( + double grade, { + double t1 = 1, + double t2 = 0.5, + double t3 = 0.5, + double t4 = 0.5, +}) { + if (grade < 1 + t1) { + return 1; + } + if (grade < 2 + t2) { + return 2; + } + if (grade < 3 + t3) { + return 3; + } + if (grade < 4 + t4) { + return 4; + } + + return 5; +} + +int percentageToGrade(int grade) { + if (grade < 50) { + return 1; + } + if (grade < 60) { + return 2; + } + if (grade < 70) { + return 3; + } + if (grade < 80) { + return 4; + } + + return 5; +} + +Color getGradeColor( + double grade, { + double t1 = 1, + double t2 = 0.5, + double t3 = 0.5, + double t4 = 0.5, +}) { + switch (roundGrade(grade, t1: t1, t2: t2, t3: t3, t4: t4)) { + case 2: + return appStyle.colors.grade2; + case 3: + return appStyle.colors.grade3; + case 4: + return appStyle.colors.grade4; + case 5: + return appStyle.colors.grade5; + default: + return appStyle.colors.grade1; + } +} + +(int total, List countsByGrade) getGradeDistribution(List grades) { + final filtered = grades + .where((g) => g.type.name != "felevi_jegy_ertekeles") + .toList(); + final counts = [0, 0, 0, 0, 0]; + for (final g in filtered) { + if (g.numericValue == null) continue; + final value = g.valueType.name == "Szazalekos" + ? percentageToGrade(g.numericValue!.round()) + : g.numericValue!.round().clamp(1, 5); + counts[value - 1]++; + } + return (filtered.length, counts); +} + +extension GradeListExtension on List { + double getAverageBySubject(Subject subject) { + var weightTotal = 0.00; + var sum = 0.00; + + for (var grade in this) { + if (grade.subject.uid == subject.uid) { + if (grade.numericValue != null) { + var weight = (grade.weightPercentage ?? 100) / 100.0; + weightTotal += weight; + + sum += grade.numericValue! * weight; + } + } + } + + return sum / weightTotal; + } +} diff --git a/firka_common/lib/ui/shared/class_icon.dart b/firka_common/lib/ui/shared/class_icon.dart new file mode 100644 index 0000000..1ee71aa --- /dev/null +++ b/firka_common/lib/ui/shared/class_icon.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +import 'package:firka_common/core/icon_helper.dart'; +import 'package:firka_common/ui/shared/firka_icon.dart'; + +class ClassIconWidget extends StatelessWidget { + final String _uid; + final String _className; + final String _category; + 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; + + @override + Widget build(BuildContext context) { + var iconCategory = getIconType(_uid, _className, _category); + + return FirkaIconWidget( + FirkaIconType.majesticons, + getIconData(iconCategory), + color: color, + size: size, + ); + } +} diff --git a/firka_common/lib/ui/shared/counter_digit.dart b/firka_common/lib/ui/shared/counter_digit.dart new file mode 100644 index 0000000..4ce82e0 --- /dev/null +++ b/firka_common/lib/ui/shared/counter_digit.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +import 'package:firka_common/ui/theme/style.dart'; + +class CounterDigitWidget extends StatelessWidget { + final String c; + final TextStyle? style; + + const CounterDigitWidget(this.c, this.style, {super.key}); + + @override + Widget build(BuildContext context) { + return Card( + shadowColor: Colors.transparent, + color: appStyle.colors.buttonSecondaryFill, + child: Padding( + padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4), + child: Text(c, style: style), + ), + ); + } +} diff --git a/firka_common/lib/ui/shared/delayed_spinner.dart b/firka_common/lib/ui/shared/delayed_spinner.dart new file mode 100644 index 0000000..a86c37d --- /dev/null +++ b/firka_common/lib/ui/shared/delayed_spinner.dart @@ -0,0 +1,48 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'package:firka_common/ui/theme/style.dart'; + +class DelayedSpinnerWidget extends StatefulWidget { + final Color? color; + + const DelayedSpinnerWidget({super.key, this.color}); + + @override + State createState() => _DelayedSpinner(); +} + +class _DelayedSpinner extends State { + Timer? timer; + bool showSpinner = false; + + @override + void initState() { + super.initState(); + + timer = Timer(const Duration(milliseconds: 50), () { + setState(() { + showSpinner = true; + }); + }); + } + + @override + Widget build(BuildContext context) { + if (showSpinner) { + return CircularProgressIndicator( + color: widget.color ?? appStyle.colors.accent, + ); + } else { + return const SizedBox(); + } + } + + @override + void dispose() { + super.dispose(); + + timer?.cancel(); + } +} diff --git a/firka_common/lib/ui/shared/firka_icon.dart b/firka_common/lib/ui/shared/firka_icon.dart new file mode 100644 index 0000000..2fc3236 --- /dev/null +++ b/firka_common/lib/ui/shared/firka_icon.dart @@ -0,0 +1,46 @@ +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:majesticons_flutter/majesticons_flutter.dart'; + +enum FirkaIconType { icons, majesticons, majesticonsLocal } + +class FirkaIconWidget extends StatelessWidget { + final FirkaIconType iconType; + final Object iconData; + final Color color; + final double? size; + final String? package; + + const FirkaIconWidget( + this.iconType, + this.iconData, { + super.key, + this.color = Colors.white, + this.size, + this.package, + }); + + @override + Widget build(BuildContext context) { + switch (iconType) { + case FirkaIconType.icons: + return SvgPicture.asset( + 'assets/icons/${iconData as String}.svg', + color: color, + height: size, + package: package, + ); + case FirkaIconType.majesticons: + return Majesticon(iconData as Uint8List, color: color, size: size); + case FirkaIconType.majesticonsLocal: + return SvgPicture.asset( + 'assets/majesticons/${iconData as String}.svg', + color: color, + height: size, + package: package, + ); + } + } +} diff --git a/firka_common/lib/ui/shared/grade_small_card.dart b/firka_common/lib/ui/shared/grade_small_card.dart new file mode 100644 index 0000000..f18252e --- /dev/null +++ b/firka_common/lib/ui/shared/grade_small_card.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:kreta_api/kreta_api.dart'; + +import 'package:firka_common/ui/components/firka_card.dart'; +import 'package:firka_common/ui/components/grade_helpers.dart'; +import 'package:firka_common/ui/shared/class_icon.dart'; +import 'package:firka_common/ui/theme/style.dart'; + +class GradeSmallCard extends FirkaCard { + final List grades; + final Subject subject; + + GradeSmallCard(this.grades, this.subject, {super.key}) + : super( + left: [ + ClassIconWidget( + uid: subject.uid, + className: subject.name, + category: subject.category.name!, + color: appStyle.colors.accent, + ), + const SizedBox(width: 4), + SizedBox( + width: 200, + child: Text( + subject.name, + style: appStyle.fonts.B_16SB.apply( + color: appStyle.colors.textPrimary, + ), + ), + ), + ], + right: [ + grades.getAverageBySubject(subject).isNaN + ? const SizedBox() + : Card( + shadowColor: Colors.transparent, + color: getGradeColor( + grades.getAverageBySubject(subject), + ).withAlpha(38), + child: Padding( + padding: const 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), + ), + ), + ), + ), + ), + ], + ); +} diff --git a/firka_common/lib/ui/theme/style.dart b/firka_common/lib/ui/theme/style.dart new file mode 100644 index 0000000..691bbfd --- /dev/null +++ b/firka_common/lib/ui/theme/style.dart @@ -0,0 +1,309 @@ +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; + TextStyle H_H2; + TextStyle H_16px; + TextStyle H_14px; + TextStyle H_12px; + + TextStyle H_16px_trimmed; + + TextStyle B_16R; + TextStyle B_16SB; + TextStyle B_15SB; + + TextStyle B_14R; + TextStyle B_14SB; + + TextStyle B_12R; + TextStyle B_12SB; + + TextStyle P_14; + TextStyle P_12; + + FirkaFonts({ + required this.H_H1, + required this.H_18px, + required this.H_H2, + required this.H_16px, + required this.H_14px, + required this.H_12px, + required this.H_16px_trimmed, + required this.B_16R, + required this.B_16SB, + required this.B_15SB, + required this.B_14R, + required this.B_14SB, + required this.B_12R, + required this.B_12SB, + required this.P_14, + required this.P_12, + }); +} + +class FirkaColors { + Color background; + Color backgroundAmoled; + Color background0p; + Color success; + int shadowBlur; + + Color textPrimary; + Color textSecondary; + Color textTertiary; + + Color textPrimaryLight; + Color textSecondaryLight; + Color textTertiaryLight; + + Color card; + Color cardTranslucent; + + Color buttonSecondaryFill; + + Color accent; + Color secondary; + Color shadowColor; + Color a10p; // 10% + Color a15p; // 15% + + Color warningAccent; + Color warningText; + Color warning15p; + Color warningCard; + + Color errorAccent; + Color errorText; + Color error15p; + Color errorCard; + + Color grade5; + Color grade4; + Color grade3; + Color grade2; + Color grade1; + + FirkaColors({ + required this.background, + required this.backgroundAmoled, + required this.background0p, + required this.success, + required this.shadowBlur, + required this.textPrimary, + required this.textSecondary, + required this.textTertiary, + required this.textPrimaryLight, + required this.textSecondaryLight, + required this.textTertiaryLight, + required this.card, + required this.cardTranslucent, + required this.buttonSecondaryFill, + required this.accent, + required this.secondary, + required this.shadowColor, + required this.a10p, + required this.a15p, + required this.warningAccent, + required this.warningText, + required this.warning15p, + required this.warningCard, + required this.errorAccent, + required this.errorText, + required this.error15p, + required this.errorCard, + required this.grade5, + required this.grade4, + required this.grade3, + required this.grade2, + required this.grade1, + }); +} + +class FirkaStyle { + FirkaColors colors; + FirkaFonts fonts; + bool isLight; + + FirkaStyle({ + required this.isLight, + required this.colors, + required this.fonts, + }); +} + +final _defaultFonts = FirkaFonts( + H_H1: TextStyle( + fontSize: 30, + fontFamily: 'Montserrat', + fontVariations: [FontVariation("wght", 700)], + ), + H_18px: TextStyle( + fontSize: 18, + fontFamily: 'Montserrat', + fontVariations: [FontVariation("wght", 700)], + ), + H_H2: TextStyle( + fontSize: 20, + fontFamily: 'Montserrat', + fontVariations: [FontVariation("wght", 700)], + ), + H_16px: TextStyle( + fontSize: 16, + fontFamily: 'Montserrat', + fontVariations: [FontVariation("wght", 600)], + ), + H_14px: TextStyle( + fontSize: 14, + fontFamily: 'Montserrat', + fontVariations: [FontVariation("wght", 600)], + ), + H_12px: TextStyle( + fontSize: 12, + fontFamily: 'Montserrat', + fontVariations: [FontVariation("wght", 600)], + ), + H_16px_trimmed: TextStyle( + fontSize: 16, + fontFamily: 'Montserrat', + fontVariations: [FontVariation("wght", 600)], + height: 1.3, + ), + B_16R: TextStyle( + fontSize: 16, + fontFamily: 'Figtree', + fontVariations: [FontVariation("wght", 600)], + height: 1.3, + ), + B_16SB: TextStyle( + fontSize: 16, + fontFamily: 'Figtree', + fontVariations: [FontVariation("wght", 700)], + height: 1.3, + ), + B_14R: TextStyle( + fontSize: 14, + fontFamily: 'Figtree', + fontVariations: [FontVariation("wght", 600)], + height: 1.3, + ), + B_14SB: TextStyle( + fontSize: 14, + fontFamily: 'Figtree', + fontVariations: [FontVariation("wght", 700)], + height: 1.3, + ), + B_15SB: TextStyle( + fontSize: 15, + fontFamily: 'Figtree', + fontVariations: [FontVariation("wght", 700)], + height: 1.3, + ), + B_12R: TextStyle( + fontSize: 12, + fontFamily: 'Figtree', + fontVariations: [FontVariation("wght", 600)], + height: 1.3, + ), + B_12SB: TextStyle( + fontSize: 12, + fontFamily: 'Figtree', + fontVariations: [FontVariation("wght", 700)], + height: 1.3, + ), + P_14: TextStyle( + fontSize: 14, + fontFamily: 'RobotoMono', + fontVariations: [FontVariation("wght", 700)], + ), + P_12: TextStyle( + fontSize: 12, + fontFamily: 'RobotoMono', + fontVariations: [FontVariation("wght", 700)], + ), +); + +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, +); + +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, +); + +FirkaStyle appStyle = lightStyle; +FirkaStyle wearStyle = darkStyle; diff --git a/firka_common/pubspec.yaml b/firka_common/pubspec.yaml new file mode 100644 index 0000000..61b4c41 --- /dev/null +++ b/firka_common/pubspec.yaml @@ -0,0 +1,21 @@ +name: firka_common +description: Shared widgets, data structures, and helpers for firka and firka_wear. +publish_to: "none" +version: 0.1.0 + +environment: + sdk: ^3.11.0 + +dependencies: + flutter: + sdk: flutter + kreta_api: + path: ../kreta_api + majesticons_flutter: ^0.0.1 + flutter_svg: ^1.1.6 + intl: any + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 diff --git a/firka_wear/lib/core/debug_helper.dart b/firka_wear/lib/core/debug_helper.dart index 027d8e8..d374108 100644 --- a/firka_wear/lib/core/debug_helper.dart +++ b/firka_wear/lib/core/debug_helper.dart @@ -1,17 +1 @@ -DateTime? debugFakeTime; -DateTime? debugSetAt; -var debugTimeAdvance = false; - -DateTime timeNow() { - if (debugFakeTime != null) { - if (debugTimeAdvance && debugSetAt != null) { - var diff = DateTime.now().difference(debugSetAt!); - - return debugFakeTime!.add(diff); - } else { - return debugFakeTime!; - } - } else { - return DateTime.now(); - } -} +export 'package:firka_common/core/debug_helper.dart'; diff --git a/firka_wear/lib/core/extensions.dart b/firka_wear/lib/core/extensions.dart index ec9cd2b..179e003 100644 --- a/firka_wear/lib/core/extensions.dart +++ b/firka_wear/lib/core/extensions.dart @@ -3,27 +3,10 @@ import 'package:intl/intl.dart'; import 'package:firka_wear/core/debug_helper.dart'; import 'package:firka_wear/l10n/app_localizations.dart'; +import 'package:firka_common/core/extensions.dart'; import 'package:kreta_api/kreta_api.dart'; -extension IterableExtensionMap on Iterable> { - Map toMap() { - var map = {}; - for (var item in this) { - map[item.key] = item.value; - } - - return map; - } -} - -extension IterableExtension on Iterable { - T? firstWhereOrNull(bool Function(T element) test) { - for (var element in this) { - if (test(element)) return element; - } - return null; - } -} +export 'package:firka_common/core/extensions.dart'; extension DurationExtension on Duration { String formatDuration() { diff --git a/firka_wear/lib/core/icon_helper.dart b/firka_wear/lib/core/icon_helper.dart index 34d5343..dbf6f91 100644 --- a/firka_wear/lib/core/icon_helper.dart +++ b/firka_wear/lib/core/icon_helper.dart @@ -1,152 +1 @@ -import 'dart:typed_data'; - -import 'package:majesticons_flutter/majesticons_flutter.dart'; - -enum ClassIcon { - mathematics, - grammar, - literature, - history, - geography, - art, - physics, - music, - pe, - chemistry, - biology, - env, - religion, - economics, - it, - code, - networking, - theatre, - film, - electricalEngineering, - mechanicalEngineering, - technika, - dance, - philosophy, - ofo, - diligence, - attitude, - language, - linux, - database, - applications, - project, -} - -Map _descriptors = { - ClassIcon.mathematics: RegExp(r'mate(k|matika)'), - ClassIcon.grammar: RegExp(r'magyar nyelv|nyelvtan'), - ClassIcon.literature: RegExp(r'irodalom'), - ClassIcon.history: RegExp(r'tor(i|tenelem)'), - ClassIcon.geography: RegExp(r'foldrajz'), - ClassIcon.art: RegExp(r'rajz|muvtori|muveszet|vizualis'), - ClassIcon.physics: RegExp(r'fizika'), - ClassIcon.music: RegExp(r'^enek|zene|szolfezs|zongora|korus'), - 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.religion: RegExp(r'(hit|erkolcs)tan|vallas|etika|bibliaismeret'), - ClassIcon.economics: RegExp(r'penzugy|gazdasag'), - ClassIcon.it: RegExp(r'informatika|szoftver|iroda|digitalis'), - ClassIcon.code: RegExp(r'prog|alkalmazas'), - ClassIcon.networking: RegExp(r'halozat'), - ClassIcon.theatre: RegExp(r'szinhaz'), - ClassIcon.film: RegExp(r'film|media'), - ClassIcon.electricalEngineering: RegExp(r'elektro(tech)?nika'), - ClassIcon.mechanicalEngineering: RegExp(r'gepesz|mernok|ipar'), - ClassIcon.technika: RegExp(r'technika'), - ClassIcon.dance: RegExp(r'tanc'), - ClassIcon.philosophy: RegExp(r'filozofia'), - 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.linux: RegExp(r'linux'), - ClassIcon.database: RegExp(r'adatbazis.*'), - ClassIcon.applications: RegExp(r'asztali alkalmazasok'), - ClassIcon.project: RegExp(r'projekt'), -}; - -Map _iconMap = { - ClassIcon.mathematics: Majesticon.calculatorSolid, - ClassIcon.grammar: Majesticon.bookSolid, - ClassIcon.literature: Majesticon.bookOpenSolid, - ClassIcon.history: Majesticon.compass2Solid, - ClassIcon.geography: Majesticon.globeEarth2Solid, - ClassIcon.art: Majesticon.editPen2Solid, - // ClassIcon.physics: , - ClassIcon.music: Majesticon.musicNoteSolid, - // ClassIcon.pe: , - ClassIcon.chemistry: Majesticon.testTubeFilledSolid, - ClassIcon.biology: Majesticon.covidSolid, - // ClassIcon.env: , - // ClassIcon.religion: , - // ClassIcon.economics: , - ClassIcon.it: Majesticon.laptopSolid, - ClassIcon.code: Majesticon.curlyBracesSolid, - ClassIcon.networking: Majesticon.cloudSolid, - // ClassIcon.theatre: , - // ClassIcon.film: , - // ClassIcon.electricalEngineering: , - // ClassIcon.mechanicalEngineering: , - ClassIcon.technika: Majesticon.ruler2Solid, - // ClassIcon.dance: , - // ClassIcon.philosophy: , - // ClassIcon.ofo: , - // ClassIcon.diligence: , - // ClassIcon.attitude: , - ClassIcon.language: Majesticon.tooltipsSolid, - // ClassIcon.linux: , - ClassIcon.database: Majesticon.dataSolid, - // ClassIcon.applications: , - // ClassIcon.project: , -}; - -ClassIcon? getIconType(String uid, String className, String category) { - ClassIcon? icon; - if (category.toLowerCase() == "matematika") { - icon = ClassIcon.mathematics; - } - - 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(), - )) { - icon = desc.key; - - break; - } - } - } - - return icon; -} - -Uint8List getIconData(ClassIcon? icon) { - if (icon == null) return Majesticon.alertCircleSolid; - - var iconData = _iconMap[icon]; - iconData ??= Majesticon.alertCircleSolid; - - return iconData; -} +export 'package:firka_common/core/icon_helper.dart'; diff --git a/firka_wear/lib/core/json_helper.dart b/firka_wear/lib/core/json_helper.dart index 4e07713..cd391aa 100644 --- a/firka_wear/lib/core/json_helper.dart +++ b/firka_wear/lib/core/json_helper.dart @@ -1,9 +1 @@ -List listToTyped(List dynamicList) { - var newList = List.empty(growable: true); - - for (var item in dynamicList) { - newList.add(item as T); - } - - return newList; -} +export 'package:firka_common/core/json_helper.dart'; diff --git a/firka_wear/lib/data/models/homework_cache_model.dart b/firka_wear/lib/data/models/homework_cache_model.dart index a38682d..2621cd3 100644 --- a/firka_wear/lib/data/models/homework_cache_model.dart +++ b/firka_wear/lib/data/models/homework_cache_model.dart @@ -19,7 +19,7 @@ Future resetOldHomeworkCache(Isar isar) async { var date = getDate(week.cacheKey!); if (date.millisecondsSinceEpoch < - now.subtract(Duration(days: 30)).millisecondsSinceEpoch) { + now.subtract(const Duration(days: 30)).millisecondsSinceEpoch) { weeksToRemove.add(week.cacheKey!); } } diff --git a/firka_wear/lib/data/models/timetable_cache_model.dart b/firka_wear/lib/data/models/timetable_cache_model.dart index b6c879a..c5c7757 100644 --- a/firka_wear/lib/data/models/timetable_cache_model.dart +++ b/firka_wear/lib/data/models/timetable_cache_model.dart @@ -19,7 +19,7 @@ Future resetOldTimeTableCache(Isar isar) async { var date = getDate(week.cacheKey!); if (date.millisecondsSinceEpoch < - now.subtract(Duration(days: 30)).millisecondsSinceEpoch) { + now.subtract(const Duration(days: 30)).millisecondsSinceEpoch) { weeksToRemove.add(week.cacheKey!); } } diff --git a/firka_wear/lib/ui/components/firka_card.dart b/firka_wear/lib/ui/components/firka_card.dart index ae11e1e..d732b90 100644 --- a/firka_wear/lib/ui/components/firka_card.dart +++ b/firka_wear/lib/ui/components/firka_card.dart @@ -1,57 +1 @@ -import 'package:flutter/material.dart'; - -import 'package:firka_wear/ui/theme/style.dart'; - -class FirkaCard extends StatelessWidget { - final List left; - final List? right; - final Widget? extra; - - const FirkaCard({required this.left, this.right, this.extra, super.key}); - - @override - Widget build(BuildContext context) { - var right = this.right ?? []; - - if (extra != null) { - return SizedBox( - width: MediaQuery.of(context).size.width, - child: Card( - color: appStyle.colors.card, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row(children: left), - Row(children: right), - ], - ), - extra ?? SizedBox(), - ], - ), - ), - ), - ); - } else { - return SizedBox( - width: MediaQuery.of(context).size.width, - child: Card( - color: appStyle.colors.card, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row(children: left), - Row(children: right), - ], - ), - ), - ), - ); - } - } -} +export 'package:firka_common/ui/components/firka_card.dart'; diff --git a/firka_wear/lib/ui/components/firka_shadow.dart b/firka_wear/lib/ui/components/firka_shadow.dart index 9818a0e..29a96dc 100644 --- a/firka_wear/lib/ui/components/firka_shadow.dart +++ b/firka_wear/lib/ui/components/firka_shadow.dart @@ -1,44 +1 @@ -import 'package:flutter/material.dart'; - -import 'package:firka_wear/ui/theme/style.dart'; - -class FirkaShadow extends StatelessWidget { - final Widget child; - final bool shadow; - final double radius; - - const FirkaShadow({ - required this.shadow, - required this.child, - this.radius = 16.0, - super.key, - }); - - @override - Widget build(BuildContext context) { - final borderRadius = BorderRadius.circular(radius); - - final shadowBox = BoxDecoration( - color: Colors.transparent, - shape: BoxShape.rectangle, - boxShadow: [ - BoxShadow( - color: wearStyle.colors.shadowColor, - spreadRadius: -4, - blurRadius: 0, - offset: Offset(0, 2), - ), - ], - borderRadius: borderRadius, - ); - - if (!shadow) { - return ClipRRect(borderRadius: borderRadius, child: child); - } - - return Container( - decoration: shadowBox, - child: ClipRRect(borderRadius: borderRadius, child: child), - ); - } -} +export 'package:firka_common/ui/components/firka_shadow.dart'; diff --git a/firka_wear/lib/ui/components/grade.dart b/firka_wear/lib/ui/components/grade.dart index 45e8698..fdc5b4a 100644 --- a/firka_wear/lib/ui/components/grade.dart +++ b/firka_wear/lib/ui/components/grade.dart @@ -1,87 +1 @@ -import 'package:flutter/material.dart'; -import 'package:kreta_api/kreta_api.dart'; - -import 'package:firka_wear/ui/theme/style.dart'; -import 'grade_helpers.dart'; - -class GradeWidget extends StatelessWidget { - final Grade grade; - - const GradeWidget(this.grade, {super.key}); - - @override - Widget build(BuildContext context) { - Color gradeColor = appStyle.colors.grade1; - var gradeStr = grade.numericValue?.toString() ?? "0"; - double eccentricity = 0; - - if (grade.valueType.name == "Szazalekos") { - gradeStr = grade.strValue.replaceAll("%", ""); - if (grade.numericValue != null) { - gradeColor = getGradeColor( - percentageToGrade(grade.numericValue!).toDouble(), - ); - } - - if (grade.numericValue != null && grade.numericValue == 100) { - return Card( - shape: CircleBorder(eccentricity: eccentricity), - shadowColor: Colors.transparent, - 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), - ), - ], - ), - ), - ); - } else { - return Card( - shape: CircleBorder(eccentricity: eccentricity), - shadowColor: Colors.transparent, - 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), - ), - ], - ), - ), - ); - } - } else { - if (grade.numericValue != null) { - gradeColor = getGradeColor(grade.numericValue!.toDouble()); - } - - 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, - ), - ), - ), - ); - } - } -} +export 'package:firka_common/ui/components/grade.dart'; diff --git a/firka_wear/lib/ui/components/grade_helpers.dart b/firka_wear/lib/ui/components/grade_helpers.dart index 4e00135..cfa04d2 100644 --- a/firka_wear/lib/ui/components/grade_helpers.dart +++ b/firka_wear/lib/ui/components/grade_helpers.dart @@ -1,75 +1 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:kreta_api/kreta_api.dart'; - -import 'package:firka_wear/ui/theme/style.dart'; - -int roundGrade(double grade) { - if (grade < 2) { - return 1; - } - if (grade < 2.5) { - return 2; - } - if (grade < 3.5) { - return 3; - } - if (grade < 4.5) { - return 4; - } - - return 5; -} - -int percentageToGrade(int grade) { - if (grade < 50) { - return 1; - } - if (grade < 60) { - return 2; - } - if (grade < 70) { - return 3; - } - if (grade < 80) { - return 4; - } - - return 5; -} - -Color getGradeColor(double grade) { - switch (roundGrade(grade)) { - case 2: - return appStyle.colors.grade2; - case 3: - return appStyle.colors.grade3; - case 4: - return appStyle.colors.grade4; - case 5: - return appStyle.colors.grade5; - default: - return appStyle.colors.grade1; - } -} - -extension GradeListExtension on List { - double getAverageBySubject(Subject subject) { - var weightTotal = 0.00; - var sum = 0.00; - - for (var grade in this) { - if (grade.subject.uid == subject.uid) { - if (grade.numericValue != null) { - var weight = (grade.weightPercentage ?? 100) / 100.0; - weightTotal += weight; - - sum += grade.numericValue! * weight; - } - } - } - - return sum / weightTotal; - } -} +export 'package:firka_common/ui/components/grade_helpers.dart'; diff --git a/firka_wear/lib/ui/shared/class_icon.dart b/firka_wear/lib/ui/shared/class_icon.dart index bf7c04e..c650795 100644 --- a/firka_wear/lib/ui/shared/class_icon.dart +++ b/firka_wear/lib/ui/shared/class_icon.dart @@ -1,36 +1 @@ -import 'package:flutter/material.dart'; - -import 'package:firka_wear/core/icon_helper.dart'; - -import 'firka_icon.dart'; - -class ClassIconWidget extends StatelessWidget { - final String _uid; - final String _className; - final String _category; - 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; - - @override - Widget build(BuildContext context) { - var iconCategory = getIconType(_uid, _className, _category); - - return FirkaIconWidget( - FirkaIconType.Majesticons, - getIconData(iconCategory), - color: color, - size: size, - ); - } -} +export 'package:firka_common/ui/shared/class_icon.dart'; diff --git a/firka_wear/lib/ui/shared/counter_digit.dart b/firka_wear/lib/ui/shared/counter_digit.dart index fd1d7e7..88857fc 100644 --- a/firka_wear/lib/ui/shared/counter_digit.dart +++ b/firka_wear/lib/ui/shared/counter_digit.dart @@ -1,22 +1 @@ -import 'package:flutter/material.dart'; - -import 'package:firka_wear/ui/theme/style.dart'; - -class CounterDigitWidget extends StatelessWidget { - final String c; - final TextStyle? style; - - const CounterDigitWidget(this.c, this.style, {super.key}); - - @override - Widget build(BuildContext context) { - return Card( - shadowColor: Colors.transparent, - color: appStyle.colors.buttonSecondaryFill, - child: Padding( - padding: EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4), - child: Text(c, style: style), - ), - ); - } -} +export 'package:firka_common/ui/shared/counter_digit.dart'; diff --git a/firka_wear/lib/ui/shared/delayed_spinner.dart b/firka_wear/lib/ui/shared/delayed_spinner.dart index e359014..d1176ae 100644 --- a/firka_wear/lib/ui/shared/delayed_spinner.dart +++ b/firka_wear/lib/ui/shared/delayed_spinner.dart @@ -1,42 +1 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -class DelayedSpinnerWidget extends StatefulWidget { - const DelayedSpinnerWidget({super.key}); - - @override - State createState() => _DelayedSpinner(); -} - -class _DelayedSpinner extends State { - Timer? timer; - bool showSpinner = false; - - @override - void initState() { - super.initState(); - - timer = Timer(Duration(milliseconds: 50), () { - setState(() { - showSpinner = true; - }); - }); - } - - @override - Widget build(BuildContext context) { - if (showSpinner) { - return CircularProgressIndicator(); - } else { - return SizedBox(); - } - } - - @override - void dispose() { - super.dispose(); - - timer?.cancel(); - } -} +export 'package:firka_common/ui/shared/delayed_spinner.dart'; diff --git a/firka_wear/lib/ui/shared/firka_icon.dart b/firka_wear/lib/ui/shared/firka_icon.dart index 2ba4418..347aeb2 100644 --- a/firka_wear/lib/ui/shared/firka_icon.dart +++ b/firka_wear/lib/ui/shared/firka_icon.dart @@ -1,39 +1 @@ -// Enum values match external asset/API naming. -// ignore_for_file: constant_identifier_names - -import 'dart:typed_data'; - -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:majesticons_flutter/majesticons_flutter.dart'; - -enum FirkaIconType { Majesticons, MajesticonsLocal } - -class FirkaIconWidget extends StatelessWidget { - final FirkaIconType iconType; - final Object iconData; - final Color color; - final double? size; - - const FirkaIconWidget( - this.iconType, - this.iconData, { - super.key, - this.color = Colors.white, - this.size, - }); - - @override - Widget build(BuildContext context) { - switch (iconType) { - case FirkaIconType.Majesticons: - return Majesticon(iconData as Uint8List, color: color, size: size); - case FirkaIconType.MajesticonsLocal: - return SvgPicture.asset( - 'assets/majesticons/${iconData as String}.svg', - color: color, - height: size, - ); - } - } -} +export 'package:firka_common/ui/shared/firka_icon.dart'; diff --git a/firka_wear/lib/ui/shared/grade_small_card.dart b/firka_wear/lib/ui/shared/grade_small_card.dart index 6c7acd4..e01f9cd 100644 --- a/firka_wear/lib/ui/shared/grade_small_card.dart +++ b/firka_wear/lib/ui/shared/grade_small_card.dart @@ -1,60 +1 @@ -import 'package:flutter/material.dart'; -import 'package:kreta_api/kreta_api.dart'; - -import 'package:firka_wear/ui/components/firka_card.dart'; -import 'package:firka_wear/ui/components/grade_helpers.dart'; -import 'package:firka_wear/ui/shared/class_icon.dart'; -import 'package:firka_wear/ui/theme/style.dart'; - -class GradeSmallCard extends FirkaCard { - final List grades; - final Subject subject; - - GradeSmallCard(this.grades, this.subject, {super.key}) - : super( - left: [ - ClassIconWidget( - uid: subject.uid, - className: subject.name, - category: subject.category.name!, - color: appStyle.colors.accent, - ), - SizedBox(width: 4), - SizedBox( - width: 200, - child: Text( - subject.name, - style: appStyle.fonts.B_16SB.apply( - color: appStyle.colors.textPrimary, - ), - ), - ), - ], - right: [ - grades.getAverageBySubject(subject).isNaN - ? SizedBox() - : Card( - shadowColor: Colors.transparent, - color: getGradeColor( - grades.getAverageBySubject(subject), - ).withAlpha(38), - child: Padding( - 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), - ), - ), - ), - ), - ), - ], - ); -} +export 'package:firka_common/ui/shared/grade_small_card.dart'; diff --git a/firka_wear/lib/ui/theme/style.dart b/firka_wear/lib/ui/theme/style.dart index 63e7b3d..8ce3436 100644 --- a/firka_wear/lib/ui/theme/style.dart +++ b/firka_wear/lib/ui/theme/style.dart @@ -1,271 +1 @@ -// Design token names (e.g. H_H1, B_16R) follow the design system. -// ignore_for_file: non_constant_identifier_names - -import 'package:flutter/material.dart'; - -class FirkaFonts { - TextStyle H_H1; - TextStyle H_18px; - TextStyle H_H2; - TextStyle H_16px; - TextStyle H_14px; - TextStyle H_12px; - - TextStyle H_16px_trimmed; // TODO: somehow implement this - // the design has this trimmed to 130% line height - - TextStyle B_16R; - TextStyle B_16SB; - - TextStyle B_14R; - TextStyle B_14SB; - - TextStyle B_12R; - TextStyle B_12SB; - - TextStyle P_14; - TextStyle P_12; - - FirkaFonts({ - required this.H_H1, - required this.H_18px, - required this.H_H2, - required this.H_16px, - required this.H_14px, - required this.H_12px, - required this.H_16px_trimmed, - required this.B_16R, - required this.B_16SB, - required this.B_14R, - required this.B_14SB, - required this.B_12R, - required this.B_12SB, - required this.P_14, - required this.P_12, - }); -} - -class FirkaColors { - Color background; - Color backgroundAmoled; - Color background0p; - Color success; - int shadowBlur; - - Color textPrimary; - Color textSecondary; - Color textTertiary; - - Color card; - Color cardTranslucent; - - Color buttonSecondaryFill; - - Color accent; - Color secondary; - Color shadowColor; - Color a15p; // 15% - - Color warningAccent; - Color warningText; - Color warning15p; - Color warningCard; - - Color errorAccent; - Color errorText; - Color error15p; - Color errorCard; - - Color grade5; - Color grade4; - Color grade3; - Color grade2; - Color grade1; - - FirkaColors({ - required this.background, - required this.backgroundAmoled, - required this.background0p, - required this.success, - required this.shadowBlur, - required this.textPrimary, - required this.textSecondary, - required this.textTertiary, - required this.card, - required this.cardTranslucent, - required this.buttonSecondaryFill, - required this.accent, - required this.secondary, - required this.shadowColor, - required this.a15p, - required this.warningAccent, - required this.warningText, - required this.warning15p, - required this.warningCard, - required this.errorAccent, - required this.errorText, - required this.error15p, - required this.errorCard, - required this.grade5, - required this.grade4, - required this.grade3, - required this.grade2, - required this.grade1, - }); -} - -class FirkaStyle { - FirkaColors colors; - FirkaFonts fonts; - - FirkaStyle({required this.colors, required this.fonts}); -} - -final _defaultFonts = FirkaFonts( - H_H1: TextStyle( - fontSize: 30, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 700)], - ), - H_18px: TextStyle( - fontSize: 18, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 700)], - ), - H_H2: TextStyle( - fontSize: 20, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 700)], - ), - H_16px: TextStyle( - fontSize: 16, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 600)], - ), - H_14px: TextStyle( - fontSize: 14, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 600)], - ), - H_12px: TextStyle( - fontSize: 12, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 600)], - ), - H_16px_trimmed: TextStyle( - fontSize: 16, - fontFamily: 'Montserrat', - fontVariations: [FontVariation("wght", 600)], - ), - B_16R: TextStyle( - fontSize: 16, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 600)], - ), - B_16SB: TextStyle( - fontSize: 16, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 700)], - ), - B_14R: TextStyle( - fontSize: 14, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 600)], - ), - B_14SB: TextStyle( - fontSize: 14, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 700)], - ), - B_12R: TextStyle( - fontSize: 12, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 600)], - ), - B_12SB: TextStyle( - fontSize: 12, - fontFamily: 'Figtree', - fontVariations: [FontVariation("wght", 700)], - ), - P_14: TextStyle( - fontSize: 14, - fontFamily: 'RobotoMono', - fontVariations: [FontVariation("wght", 700)], - ), - P_12: TextStyle( - fontSize: 12, - fontFamily: 'RobotoMono', - fontVariations: [FontVariation("wght", 700)], - ), -); - -final FirkaStyle lightStyle = FirkaStyle( - 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), - card: Color(0xFFF3FBDE), - cardTranslucent: Color(0x80F3FBDE), - buttonSecondaryFill: Color(0xFFFEFFFD), - accent: Color(0xFFA7DC22), - secondary: Color(0xFF6E8F1B), - shadowColor: Color(0x33647e22), - 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( - 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), - card: Color(0xFF141905), - cardTranslucent: Color(0x80141905), - buttonSecondaryFill: Color(0xFF20290B), - accent: Color(0xFFA7DC22), - secondary: Color(0xFFCBEE71), - shadowColor: Color(0x26CBEE71), - 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; +export 'package:firka_common/ui/theme/style.dart'; diff --git a/firka_wear/pubspec.yaml b/firka_wear/pubspec.yaml index 19c3a58..08f6dd6 100644 --- a/firka_wear/pubspec.yaml +++ b/firka_wear/pubspec.yaml @@ -33,6 +33,8 @@ environment: dependencies: flutter: sdk: flutter + firka_common: + path: ../firka_common kreta_api: path: ../kreta_api