diff --git a/refilc/lib/theme/colors/utils.dart b/refilc/lib/theme/colors/utils.dart new file mode 100644 index 0000000..d67d7ef --- /dev/null +++ b/refilc/lib/theme/colors/utils.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class ColorsUtils { + Color darken(Color color, {double amount = .1}) { + assert(amount >= 0 && amount <= 1); + + final hsl = HSLColor.fromColor(color); + final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)); + + return hslDark.toColor(); + } + + Color lighten(Color color, {double amount = .1}) { + assert(amount >= 0 && amount <= 1); + + final hsl = HSLColor.fromColor(color); + final hslLight = + hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0)); + + return hslLight.toColor(); + } +} diff --git a/refilc/lib/theme/theme.dart b/refilc/lib/theme/theme.dart index 1c85088..a6aac8a 100644 --- a/refilc/lib/theme/theme.dart +++ b/refilc/lib/theme/theme.dart @@ -1,6 +1,7 @@ import 'package:refilc/models/settings.dart'; import 'package:refilc/theme/colors/accent.dart'; import 'package:refilc/theme/colors/colors.dart'; +import 'package:refilc/theme/colors/utils.dart'; import 'package:refilc/theme/observer.dart'; import 'package:flutter/material.dart'; import 'package:material_color_utilities/material_color_utilities.dart'; @@ -61,18 +62,22 @@ class AppTheme { : _paletteHighlightLight(palette)) ?? lightColors.highlight; + Color newPrimary = ColorsUtils().darken(accent, amount: 0.4); + // Color newScaffoldBg = ColorsUtils().lighten(accent, amount: 0.4); + Color newScaffoldBg = backgroundColor; + return ThemeData( brightness: Brightness.light, useMaterial3: true, fontFamily: _defaultFontFamily, textTheme: googleFontsMap[settings.fontFamily], - scaffoldBackgroundColor: backgroundColor, + scaffoldBackgroundColor: newScaffoldBg, primaryColor: lightColors.filc, dividerColor: const Color(0x00000000), colorScheme: ColorScheme( - primary: accent, + primary: newPrimary, onPrimary: - (accent.computeLuminance() > 0.5 ? Colors.black : Colors.white) + (newPrimary.computeLuminance() > 0.5 ? Colors.black : Colors.white) .withOpacity(.9), secondary: accent, onSecondary: diff --git a/refilc_mobile_ui/lib/pages/notes/notes_page.dart b/refilc_mobile_ui/lib/pages/notes/notes_page.dart new file mode 100644 index 0000000..117e1d6 --- /dev/null +++ b/refilc_mobile_ui/lib/pages/notes/notes_page.dart @@ -0,0 +1,308 @@ +// ignore_for_file: no_leading_underscores_for_local_identifiers, use_build_context_synchronously + +import 'dart:math'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:refilc/api/providers/database_provider.dart'; +import 'package:refilc/api/providers/self_note_provider.dart'; +import 'package:refilc/api/providers/update_provider.dart'; +import 'package:refilc/utils/format.dart'; +import 'package:refilc_kreta_api/models/absence.dart'; +import 'package:refilc_kreta_api/models/homework.dart'; +import 'package:refilc_kreta_api/models/subject.dart'; +import 'package:refilc/api/providers/user_provider.dart'; +import 'package:refilc/theme/colors/colors.dart'; +import 'package:refilc_kreta_api/providers/homework_provider.dart'; +import 'package:refilc_mobile_ui/common/empty.dart'; +import 'package:refilc_mobile_ui/common/panel/panel.dart'; +import 'package:refilc_mobile_ui/common/profile_image/profile_button.dart'; +import 'package:refilc_mobile_ui/common/profile_image/profile_image.dart'; +import 'package:refilc_mobile_ui/common/soon_alert/soon_alert.dart'; +import 'package:refilc_mobile_ui/common/widgets/tick_tile.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:refilc_mobile_ui/screens/notes/add_note_screen.dart'; +import 'package:refilc_mobile_ui/screens/notes/note_view_screen.dart'; +import 'package:refilc_mobile_ui/screens/notes/self_note_tile.dart'; +import 'package:refilc_plus/models/premium_scopes.dart'; +import 'package:refilc_plus/providers/premium_provider.dart'; +import 'package:refilc_plus/ui/mobile/premium/premium_inline.dart'; +import 'package:refilc_plus/ui/mobile/premium/upsell.dart'; +import 'notes_page.i18n.dart'; + +enum AbsenceFilter { absences, delays, misses } + +class SubjectAbsence { + GradeSubject subject; + List absences; + double percentage; + + SubjectAbsence( + {required this.subject, this.absences = const [], this.percentage = 0.0}); +} + +class NotesPage extends StatefulWidget { + const NotesPage({super.key}); + + @override + NotesPageState createState() => NotesPageState(); +} + +class NotesPageState extends State with TickerProviderStateMixin { + late UserProvider user; + late UpdateProvider updateProvider; + late DatabaseProvider databaseProvider; + late SelfNoteProvider selfNoteProvider; + + late String firstName; + + Map doneItems = {}; + List noteTiles = []; + + void generateTiles() async { + doneItems = await databaseProvider.userQuery.toDoItems(userId: user.id!); + + List tiles = []; + + List hw = Provider.of(context, listen: false) + .homework + .where((e) => e.deadline.isAfter(DateTime.now())) + // e.deadline.isBefore(DateTime(DateTime.now().year, + // DateTime.now().month, DateTime.now().day + 3))) + .toList(); + + // todo tiles + List toDoTiles = []; + + if (hw.isNotEmpty && + !Provider.of(context, listen: false) + .hasScope(PremiumScopes.unlimitedSelfNotes)) { + toDoTiles.addAll(hw.map((e) => TickTile( + padding: EdgeInsets.zero, + title: 'homework'.i18n, + description: + '${(e.subject.isRenamed ? e.subject.renamedTo : e.subject.name) ?? ''}, ${e.content.escapeHtml()}', + isTicked: doneItems[e.id] ?? false, + onTap: (p0) async { + if (!doneItems.containsKey(e.id)) { + doneItems.addAll({e.id: p0}); + } else { + doneItems[e.id] = p0; + } + await databaseProvider.userStore + .storeToDoItem(doneItems, userId: user.id!); + }, + ))); + } + + if (toDoTiles.isNotEmpty) { + tiles.add(const SizedBox( + height: 10.0, + )); + + tiles.add(Panel( + title: Text('todo'.i18n), + child: Column( + children: toDoTiles, + ), + )); + } + + // self notes + List selfNoteTiles = []; + + if (selfNoteProvider.notes.isNotEmpty) { + selfNoteTiles.addAll(selfNoteProvider.notes.reversed.map( + (e) => SelfNoteTile( + title: e.title ?? e.content.split(' ')[0], + content: e.content, + onTap: () => Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + builder: (context) => NoteViewScreen(note: e))), + ), + )); + } + + if (selfNoteTiles.isNotEmpty) { + // padding + tiles.add(const SizedBox( + height: 28.0, + )); + + // actual thing + tiles.add(Panel( + title: Text('your_notes'.i18n), + padding: EdgeInsets.zero, + isTransparent: true, + child: Wrap( + spacing: 18.0, + runSpacing: 18.0, + children: selfNoteTiles, + ), + )); + } + + // insert empty tile + if (tiles.isEmpty) { + tiles.insert( + 0, + Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Empty(subtitle: "empty".i18n), + ), + ); + } + + tiles.add(Provider.of(context, listen: false).hasPremium + ? const SizedBox() + : const Padding( + padding: EdgeInsets.only(top: 24.0), + child: PremiumInline(features: [ + PremiumInlineFeature.stats, + ]), + )); + + // padding + tiles.add(const SizedBox(height: 32.0)); + + noteTiles = List.castFrom(tiles); + + setState(() {}); + } + + @override + Widget build(BuildContext context) { + user = Provider.of(context); + databaseProvider = Provider.of(context); + updateProvider = Provider.of(context); + selfNoteProvider = Provider.of(context); + + List nameParts = user.displayName?.split(" ") ?? ["?"]; + firstName = nameParts.length > 1 ? nameParts[1] : nameParts[0]; + + generateTiles(); + + return Scaffold( + body: Padding( + padding: const EdgeInsets.only(top: 12.0), + child: NestedScrollView( + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics()), + headerSliverBuilder: (context, _) => [ + SliverAppBar( + pinned: true, + floating: false, + snap: false, + centerTitle: false, + surfaceTintColor: Theme.of(context).scaffoldBackgroundColor, + actions: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 5.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + GestureDetector( + onTap: () async { + SoonAlert.show(context: context); + }, + child: Icon( + FeatherIcons.search, + color: AppColors.of(context).text, + size: 22.0, + ), + ), + const SizedBox( + width: 12.0, + ), + GestureDetector( + onTap: () async { + // handle tap + if (!Provider.of(context, + listen: false) + .hasScope(PremiumScopes.unlimitedSelfNotes) && + noteTiles.length > 10) { + return PremiumLockedFeatureUpsell.show( + context: context, + feature: PremiumFeature.selfNotes); + } + + Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + builder: (context) => const AddNoteScreen())); + }, + child: Icon( + FeatherIcons.plus, + color: AppColors.of(context).text, + ), + ), + ], + ), + ), + + // Profile Icon + Padding( + padding: const EdgeInsets.only(right: 24.0), + child: ProfileButton( + child: ProfileImage( + heroTag: "profile", + name: firstName, + backgroundColor: Theme.of(context) + .colorScheme + .primary, //ColorUtils.stringToColor(user.displayName ?? "?"), + badge: updateProvider.available, + role: user.role, + profilePictureString: user.picture, + ), + ), + ), + ], + automaticallyImplyLeading: false, + shadowColor: Theme.of(context).shadowColor, + title: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Text( + "notes".i18n, + style: TextStyle( + color: AppColors.of(context).text, + fontSize: 32.0, + fontWeight: FontWeight.bold), + ), + ), + ), + ], + body: RefreshIndicator( + onRefresh: () { + var state = Provider.of(context, listen: false) + .fetch( + from: DateTime.now().subtract(const Duration(days: 30))); + Provider.of(context, listen: false).restore(); + + generateTiles(); + + return state; + }, + color: Theme.of(context).colorScheme.secondary, + child: ListView.builder( + padding: EdgeInsets.zero, + physics: const BouncingScrollPhysics(), + itemCount: max(noteTiles.length, 1), + itemBuilder: (context, index) { + if (noteTiles.isNotEmpty) { + const EdgeInsetsGeometry panelPadding = + EdgeInsets.symmetric(horizontal: 24.0); + + return Padding( + padding: panelPadding, child: noteTiles[index]); + } else { + return Container(); + } + }, + ), + ), + ), + ), + ); + } +} diff --git a/refilc_mobile_ui/lib/pages/notes/notes_page.i18n.dart b/refilc_mobile_ui/lib/pages/notes/notes_page.i18n.dart new file mode 100644 index 0000000..7924e35 --- /dev/null +++ b/refilc_mobile_ui/lib/pages/notes/notes_page.i18n.dart @@ -0,0 +1,45 @@ +import 'package:i18n_extension/i18n_extension.dart'; + +extension ScreensLocalization on String { + static final _t = Translations.byLocale("hu_hu") + + { + "en_en": { + "notes": "Notes", + "empty": "You don't have any notes", + "todo": "Tasks", + "homework": "Homework", + "new_note": "New Note", + "edit_note": "Edit Note", + "hint": "Note content...", + "hint_t": "Note title...", + "your_notes": "Your Notes", + }, + "hu_hu": { + "notes": "Füzet", + "empty": "Nincsenek jegyzeteid", + "todo": "Feladatok", + "homework": "Házi feladat", + "new_note": "Új jegyzet", + "edit_note": "Jegyzet szerkesztése", + "hint": "Jegyzet tartalma...", + "hint_t": "Jegyzet címe...", + "your_notes": "Jegyzeteid", + }, + "de_de": { + "notes": "Broschüre", + "empty": "Sie haben keine Notizen", + "todo": "Aufgaben", + "homework": "Hausaufgaben", + "new_note": "Neue Notiz", + "edit_note": "Notiz bearbeiten", + "hint": "Inhalt beachten...", + "hint_t": "Titel notieren...", + "your_notes": "Deine Noten", + }, + }; + + String get i18n => localize(this, _t); + String fill(List params) => localizeFill(this, params); + String plural(int value) => localizePlural(value, this, _t); + String version(Object modifier) => localizeVersion(modifier, this, _t); +} diff --git a/refilc_mobile_ui/lib/screens/navigation/navbar_item.dart b/refilc_mobile_ui/lib/screens/navigation/navbar_item.dart index e8adab5..fa370c7 100644 --- a/refilc_mobile_ui/lib/screens/navigation/navbar_item.dart +++ b/refilc_mobile_ui/lib/screens/navigation/navbar_item.dart @@ -38,24 +38,25 @@ class NavbarItem extends StatelessWidget { : null, borderRadius: BorderRadius.circular(14.0), ), - child: Stack( - children: [ - IconTheme( - data: IconThemeData( - color: Theme.of(context).colorScheme.secondary, - ), - child: icon, - ), - IconTheme( - data: IconThemeData( - color: Theme.of(context).brightness == Brightness.light - ? Colors.black.withOpacity(.5) - : Colors.white.withOpacity(.3), - ), - child: icon, - ), - ], - ), + child: icon, + // child: Stack( + // children: [ + // IconTheme( + // data: IconThemeData( + // color: Theme.of(context).colorScheme.secondary, + // ), + // child: icon, + // ), + // IconTheme( + // data: IconThemeData( + // color: Theme.of(context).brightness == Brightness.light + // ? Colors.black.withOpacity(.5) + // : Colors.white.withOpacity(.3), + // ), + // child: icon, + // ), + // ], + // ), ), ), ), diff --git a/refilc_mobile_ui/lib/screens/navigation/navigation_route.dart b/refilc_mobile_ui/lib/screens/navigation/navigation_route.dart index 44037f2..e5a3c0a 100644 --- a/refilc_mobile_ui/lib/screens/navigation/navigation_route.dart +++ b/refilc_mobile_ui/lib/screens/navigation/navigation_route.dart @@ -7,7 +7,7 @@ class NavigationRoute { "grades", "timetable", "notes", - "inbox", + "absences", // "messages", // "absences", ]; diff --git a/refilc_mobile_ui/lib/screens/navigation/navigation_route_handler.dart b/refilc_mobile_ui/lib/screens/navigation/navigation_route_handler.dart index de9579e..7c06e1f 100644 --- a/refilc_mobile_ui/lib/screens/navigation/navigation_route_handler.dart +++ b/refilc_mobile_ui/lib/screens/navigation/navigation_route_handler.dart @@ -1,7 +1,9 @@ +// import 'package:refilc_mobile_ui/pages/absences/absences_page.dart'; import 'package:refilc_mobile_ui/pages/absences/absences_page.dart'; import 'package:refilc_mobile_ui/pages/grades/grades_page.dart'; import 'package:refilc_mobile_ui/pages/home/home_page.dart'; -import 'package:refilc_mobile_ui/pages/messages/messages_page.dart'; +import 'package:refilc_mobile_ui/pages/notes/notes_page.dart'; +// import 'package:refilc_mobile_ui/pages/messages/messages_page.dart'; import 'package:refilc_mobile_ui/pages/timetable/timetable_page.dart'; import 'package:flutter/material.dart'; import 'package:animations/animations.dart'; @@ -15,8 +17,8 @@ Route navigationRouteHandler(RouteSettings settings) { case "timetable": return navigationPageRoute((context) => const TimetablePage()); case "notes": - return navigationPageRoute((context) => const MessagesPage()); - case "inbox": + return navigationPageRoute((context) => const NotesPage()); + case "absences": return navigationPageRoute((context) => const AbsencesPage()); // case "messages": // return navigationPageRoute((context) => const MessagesPage()); diff --git a/refilc_mobile_ui/lib/screens/navigation/navigation_screen.dart b/refilc_mobile_ui/lib/screens/navigation/navigation_screen.dart index 0f5ace2..820b87f 100644 --- a/refilc_mobile_ui/lib/screens/navigation/navigation_screen.dart +++ b/refilc_mobile_ui/lib/screens/navigation/navigation_screen.dart @@ -1,6 +1,8 @@ +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_svg/svg.dart'; import 'package:refilc/api/providers/update_provider.dart'; import 'package:refilc/helpers/quick_actions.dart'; +import 'package:refilc/icons/filc_icons.dart'; import 'package:refilc/models/settings.dart'; import 'package:refilc/theme/observer.dart'; import 'package:refilc_kreta_api/client/client.dart'; @@ -324,7 +326,7 @@ class NavigationScreenState extends State 'assets/svg/menu_icons/today.svg', color: Theme.of(context).colorScheme.secondary, - height: 26, + height: 24, ), Transform.translate( offset: const Offset(0, 1.6), @@ -347,7 +349,7 @@ class NavigationScreenState extends State 'assets/svg/menu_icons/today_selected.svg', color: Theme.of(context).colorScheme.secondary, - height: 26, + height: 24, ), Transform.translate( offset: const Offset(0, 1.8), @@ -369,12 +371,12 @@ class NavigationScreenState extends State icon: SvgPicture.asset( 'assets/svg/menu_icons/grades.svg', color: Theme.of(context).colorScheme.secondary, - height: 24, + height: 22, ), activeIcon: SvgPicture.asset( 'assets/svg/menu_icons/grades_selected.svg', color: Theme.of(context).colorScheme.secondary, - height: 24, + height: 22, ), ), NavItem( @@ -382,12 +384,12 @@ class NavigationScreenState extends State icon: SvgPicture.asset( 'assets/svg/menu_icons/timetable.svg', color: Theme.of(context).colorScheme.secondary, - height: 24, + height: 22, ), activeIcon: SvgPicture.asset( 'assets/svg/menu_icons/timetable_selected.svg', color: Theme.of(context).colorScheme.secondary, - height: 24, + height: 22, ), ), NavItem( @@ -395,25 +397,25 @@ class NavigationScreenState extends State icon: SvgPicture.asset( 'assets/svg/menu_icons/notes.svg', color: Theme.of(context).colorScheme.secondary, - height: 24, + height: 22, ), activeIcon: SvgPicture.asset( 'assets/svg/menu_icons/notes_selected.svg', color: Theme.of(context).colorScheme.secondary, - height: 24, + height: 22, ), ), NavItem( - title: "inbox".i18n, - icon: SvgPicture.asset( - 'assets/svg/menu_icons/inbox.svg', + title: "absences".i18n, + icon: Icon( + FeatherIcons.clock, color: Theme.of(context).colorScheme.secondary, - height: 24, + size: 24.0, ), - activeIcon: SvgPicture.asset( - 'assets/svg/menu_icons/inbox_selected.svg', + activeIcon: Icon( + FilcIcons.absencesfill, color: Theme.of(context).colorScheme.secondary, - height: 24, + size: 24.0, ), ), // NavItem(