From b317685d134eae35df566cbb737e4d362ba9c53c Mon Sep 17 00:00:00 2001 From: Csani Date: Wed, 29 Apr 2026 18:15:34 +0200 Subject: [PATCH 1/6] Made the app use crop_your_image instead of image_crop_plus --- .../notes/submenu/create_image_note.dart | 139 ++------ .../lib/screens/settings/profile_screen.dart | 322 +++++++----------- .../screens/settings/user/profile_pic.dart | 106 ++---- folio_mobile_ui/pubspec.yaml | 2 +- 4 files changed, 191 insertions(+), 378 deletions(-) diff --git a/folio_mobile_ui/lib/pages/notes/submenu/create_image_note.dart b/folio_mobile_ui/lib/pages/notes/submenu/create_image_note.dart index f8502980..f41b427d 100644 --- a/folio_mobile_ui/lib/pages/notes/submenu/create_image_note.dart +++ b/folio_mobile_ui/lib/pages/notes/submenu/create_image_note.dart @@ -6,7 +6,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:image_crop_plus/image_crop_plus.dart'; +import 'package:crop_your_image/crop_your_image.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; import 'package:folio/api/providers/database_provider.dart'; @@ -28,30 +28,22 @@ class ImageNoteEditor extends StatefulWidget { class _ImageNoteEditorState extends State { final _title = TextEditingController(); + final CropController _controller = CropController(); - final cropKey = GlobalKey(); File? _file; - File? _sample; - File? _lastCropped; + Uint8List? _imageData; - File? image; - Future pickImage() async { + Future pickImage() async { try { - final image = await ImagePicker().pickImage(source: ImageSource.gallery); - if (image == null) return; - File imageFile = File(image.path); + final picked = await ImagePicker().pickImage(source: ImageSource.gallery); + if (picked == null) return; - final sample = await ImageCrop.sampleImage( - file: imageFile, - preferredSize: context.size!.longestSide.ceil(), - ); - - _sample?.delete(); - _file?.delete(); + final file = File(picked.path); + final bytes = await file.readAsBytes(); setState(() { - _sample = sample; - _file = imageFile; + _file = file; + _imageData = bytes; }); } on PlatformException catch (e) { log('Failed to pick image: $e'); @@ -61,10 +53,11 @@ class _ImageNoteEditorState extends State { Widget cropImageWidget() { return SizedBox( height: 300, - child: Crop.file( - _sample!, - key: cropKey, - // aspectRatio: 1.0, + child: Crop( + image: _imageData!, + controller: _controller, + // no aspectRatio → free crop like your original + onCropped: _onCropped, ), ); } @@ -74,7 +67,7 @@ class _ImageNoteEditorState extends State { customBorder: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14.0), ), - onTap: () => pickImage(), + onTap: pickImage, child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey), @@ -91,43 +84,19 @@ class _ImageNoteEditorState extends State { fontWeight: FontWeight.w600, ), ), - Text( - "select_image".i18n, - style: const TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.w500, - ), - ) + Text("select_image".i18n), ], ), ), ); } - Future _cropImage() async { - final scale = cropKey.currentState!.scale; - final area = cropKey.currentState!.area; - if (area == null || _file == null) { - return; - } - - final sample = await ImageCrop.sampleImage( - file: _file!, - preferredSize: (2000 / scale).round(), - ); - - final file = await ImageCrop.cropImage( - file: sample, - area: area, - ); - - sample.delete(); - - _lastCropped?.delete(); - _lastCropped = file; + void _cropImage() { + _controller.crop(); // triggers _onCropped + } - List imageBytes = await _lastCropped!.readAsBytes(); - String base64Image = base64Encode(imageBytes); + Future _onCropped(Uint8List croppedData) async { + final base64Image = base64Encode(croppedData); List selfNotes = await Provider.of(context, listen: false) @@ -148,22 +117,22 @@ class _ImageNoteEditorState extends State { Provider.of(context, listen: false).restore(); Provider.of(context, listen: false).restoreTodo(); - debugPrint('$file'); + Navigator.of(context).pop(true); } @override void dispose() { + _title.dispose(); + _file = null; // no temp files anymore super.dispose(); - _file?.delete(); - _sample?.delete(); - _lastCropped?.delete(); } @override Widget build(BuildContext context) { return AlertDialog( shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(14.0))), + borderRadius: BorderRadius.all(Radius.circular(14.0)), + ), contentPadding: const EdgeInsets.only(top: 10.0), title: Text("new_image".i18n), content: Column( @@ -172,76 +141,36 @@ class _ImageNoteEditorState extends State { Padding( padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0), - child: _sample == null ? openImageWidget() : cropImageWidget(), + child: _imageData == null ? openImageWidget() : cropImageWidget(), ), Padding( padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0), child: TextField( controller: _title, - onEditingComplete: () async {}, decoration: InputDecoration( border: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.grey, width: 1.5), - borderRadius: BorderRadius.circular(12.0), - ), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: Colors.grey, width: 1.5), borderRadius: BorderRadius.circular(12.0), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), hintText: 'title'.i18n, suffixIcon: IconButton( - icon: const Icon( - Icons.close_rounded, - color: Colors.grey, - ), - onPressed: () { - setState(() { - _title.text = ''; - }); - }, + icon: const Icon(Icons.close_rounded), + onPressed: () => setState(() => _title.clear()), ), ), ), ), - // if (widget.u.picture != "") - // TextButton( - // child: Text( - // "remove_profile_picture".i18n, - // style: const TextStyle( - // fontWeight: FontWeight.w500, color: Colors.red), - // ), - // onPressed: () { - // widget.u.picture = ""; - // Provider.of(context, listen: false) - // .store - // .storeUser(widget.u); - // Provider.of(context, listen: false).refresh(); - // Navigator.of(context).pop(true); - // }, - // ), ], ), actions: [ TextButton( - child: Text( - "cancel".i18n, - style: const TextStyle(fontWeight: FontWeight.w500), - ), - onPressed: () { - Navigator.of(context).maybePop(); - }, + child: Text("cancel".i18n), + onPressed: () => Navigator.of(context).maybePop(), ), TextButton( - child: Text( - "next".i18n, - style: const TextStyle(fontWeight: FontWeight.w500), - ), - onPressed: () async { - await _cropImage(); - Navigator.of(context).pop(true); - }, + child: Text("next".i18n), + onPressed: _cropImage, ), ], ); diff --git a/folio_mobile_ui/lib/screens/settings/profile_screen.dart b/folio_mobile_ui/lib/screens/settings/profile_screen.dart index c5c4fa81..8e1f7bec 100644 --- a/folio_mobile_ui/lib/screens/settings/profile_screen.dart +++ b/folio_mobile_ui/lib/screens/settings/profile_screen.dart @@ -7,7 +7,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as tabs; -import 'package:image_crop_plus/image_crop_plus.dart'; +import 'package:crop_your_image/crop_your_image.dart'; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; @@ -76,9 +76,7 @@ class ProfileScreenState extends State } void _onTabChanged() { - if (_tabController.index == 1 && - _contactData == null && - !_contactLoading) { + if (_tabController.index == 1 && _contactData == null && !_contactLoading) { _loadContactAndCerts(); } } @@ -151,7 +149,8 @@ class ProfileScreenState extends State Provider.of(context, listen: false).restore(), Provider.of(context, listen: false).restore(), Provider.of(context, listen: false).restore(), - Provider.of(context, listen: false).restoreRecipients(), + Provider.of(context, listen: false) + .restoreRecipients(), Provider.of(context, listen: false).restore(), Provider.of(context, listen: false).restore(), Provider.of(context, listen: false).restore(), @@ -178,9 +177,8 @@ class ProfileScreenState extends State showRoundedModalBottomSheet( context, child: _ContactBottomSheet( - initialEmail: _contactData?["Email"] as String? ?? - user.student?.email ?? - "", + initialEmail: + _contactData?["Email"] as String? ?? user.student?.email ?? "", initialPhone: _contactData?["Telefonszam"] as String? ?? user.student?.phone ?? "", @@ -222,8 +220,7 @@ class ProfileScreenState extends State ); // Refresh student data if (user.user != null && iss.isNotEmpty) { - final studentJson = - await kretaClient.getAPI(KretaAPI.student(iss)); + final studentJson = await kretaClient.getAPI(KretaAPI.student(iss)); if (studentJson != null) { user.user!.student = Student.fromJson(Map.from(studentJson as Map)); @@ -289,8 +286,8 @@ class ProfileScreenState extends State showDialog( context: context, builder: (_) => AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0)), + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), title: Text('oopsie'.i18n), content: Text('session_expired'.i18n), actions: [ @@ -304,8 +301,7 @@ class ProfileScreenState extends State .removeUser(userId); if (user.getUsers().isNotEmpty) { user.setUser(user.getUsers().first.id); - restore() - .then((_) => user.setUser(user.getUsers().first.id)); + restore().then((_) => user.setUser(user.getUsers().first.id)); Navigator.of(context).pop(); Navigator.of(context).pushNamed("login_back"); } else { @@ -339,9 +335,8 @@ class ProfileScreenState extends State final String firstName = settings.presentationMode ? "János" : (nameParts.length > 1 ? nameParts[1] : nameParts[0]); - final String displayName = settings.presentationMode - ? "Teszt János" - : (user.displayName ?? "?"); + final String displayName = + settings.presentationMode ? "Teszt János" : (user.displayName ?? "?"); final String username = settings.presentationMode ? "01234567890" : (user.name ?? ""); @@ -353,8 +348,8 @@ class ProfileScreenState extends State Container( decoration: BoxDecoration( color: colorScheme.primaryContainer, - borderRadius: const BorderRadius.vertical( - bottom: Radius.circular(28.0)), + borderRadius: + const BorderRadius.vertical(bottom: Radius.circular(28.0)), ), child: SafeArea( bottom: false, @@ -362,8 +357,7 @@ class ProfileScreenState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: - const EdgeInsets.fromLTRB(20.0, 12.0, 20.0, 0.0), + padding: const EdgeInsets.fromLTRB(20.0, 12.0, 20.0, 0.0), child: Row( children: [ GestureDetector( @@ -412,15 +406,13 @@ class ProfileScreenState extends State indicatorPadding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0), indicator: BoxDecoration( - color: - colorScheme.secondary.withValues(alpha: 0.15), + color: colorScheme.secondary.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(14.0), ), overlayColor: WidgetStateProperty.all( colorScheme.secondary.withValues(alpha: 0.08), ), - padding: const EdgeInsets.fromLTRB( - 12.0, 8.0, 12.0, 14.0), + padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 14.0), tabs: [ Tab(text: "profile".i18n), Tab(text: "your_data".i18n), @@ -544,8 +536,8 @@ class ProfileScreenState extends State // Edit panel: profile pic + nickname + DKT SplittedPanel( - padding: const EdgeInsets.only( - bottom: 0.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 0.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), children: [ PanelButton( @@ -563,8 +555,7 @@ class ProfileScreenState extends State color: AppColors.of(context).text.withValues(alpha: 0.3), ), borderRadius: const BorderRadius.vertical( - top: Radius.circular(12.0), - bottom: Radius.circular(4.0)), + top: Radius.circular(12.0), bottom: Radius.circular(4.0)), ), PanelButton( padding: const EdgeInsets.only(left: 14.0, right: 6.0), @@ -581,8 +572,7 @@ class ProfileScreenState extends State color: AppColors.of(context).text.withValues(alpha: 0.3), ), borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), - bottom: Radius.circular(4.0)), + top: Radius.circular(4.0), bottom: Radius.circular(4.0)), ), PanelButton( padding: const EdgeInsets.only(left: 14.0, right: 6.0), @@ -599,8 +589,7 @@ class ProfileScreenState extends State color: AppColors.of(context).text.withValues(alpha: 0.3), ), borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), - bottom: Radius.circular(12.0)), + top: Radius.circular(4.0), bottom: Radius.circular(12.0)), ), ], ), @@ -693,13 +682,13 @@ class ProfileScreenState extends State // ── Personal info ── _sectionHeader(context, "personal_info".i18n), SplittedPanel( - padding: const EdgeInsets.only( - bottom: 0.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 0.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), children: [ for (int i = 0; i < personalItems.length; i++) - _buildInfoButton(context, personalItems[i], i, - personalItems.length), + _buildInfoButton( + context, personalItems[i], i, personalItems.length), ], ), @@ -721,9 +710,8 @@ class ProfileScreenState extends State context, "bank_account".i18n, actionLabel: "edit".i18n, - onAction: student.bankAccountReadOnly == true - ? null - : _editBankAccount, + onAction: + student.bankAccountReadOnly == true ? null : _editBankAccount, ), _buildBankAccountSection(context, colorScheme, student), @@ -739,8 +727,7 @@ class ProfileScreenState extends State ); } - Widget _buildContactSection( - BuildContext context, ColorScheme colorScheme) { + Widget _buildContactSection(BuildContext context, ColorScheme colorScheme) { if (_contactLoading) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 8.0), @@ -751,14 +738,12 @@ class ProfileScreenState extends State ); } - final String email = _contactData?["Email"] as String? ?? - user.student?.email ?? - ""; + final String email = + _contactData?["Email"] as String? ?? user.student?.email ?? ""; final bool emailVerified = _contactData?["IsEmailMegerositve"] as bool? ?? false; - final String phone = _contactData?["Telefonszam"] as String? ?? - user.student?.phone ?? - ""; + final String phone = + _contactData?["Telefonszam"] as String? ?? user.student?.phone ?? ""; final List<_InfoItem> items = [ if (email.isNotEmpty) @@ -787,8 +772,7 @@ class ProfileScreenState extends State } return SplittedPanel( - padding: - const EdgeInsets.only(bottom: 0.0, left: 24.0, right: 24.0), + padding: const EdgeInsets.only(bottom: 0.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), children: [ for (int i = 0; i < items.length; i++) @@ -805,8 +789,7 @@ class ProfileScreenState extends State if (accountNum == null || accountNum.isEmpty) { return Padding( - padding: - const EdgeInsets.symmetric(horizontal: 24.0, vertical: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 8.0), child: Text("no_bank_account".i18n, style: TextStyle( color: colorScheme.onSurface.withValues(alpha: 0.35), @@ -830,8 +813,7 @@ class ProfileScreenState extends State ]; return SplittedPanel( - padding: - const EdgeInsets.only(bottom: 0.0, left: 24.0, right: 24.0), + padding: const EdgeInsets.only(bottom: 0.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), children: [ for (int i = 0; i < items.length; i++) @@ -874,8 +856,7 @@ class ProfileScreenState extends State } return SplittedPanel( - padding: - const EdgeInsets.only(bottom: 0.0, left: 24.0, right: 24.0), + padding: const EdgeInsets.only(bottom: 0.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), children: [ for (int i = 0; i < certs.length; i++) @@ -884,10 +865,9 @@ class ProfileScreenState extends State ); } - Widget _buildCertButton(BuildContext context, DigitalCertification cert, - int index, int total) { - final String title = - cert.name ?? cert.schoolYear ?? "cert_issued".i18n; + Widget _buildCertButton( + BuildContext context, DigitalCertification cert, int index, int total) { + final String title = cert.name ?? cert.schoolYear ?? "cert_issued".i18n; final String subtitle = [ if (cert.typeName != null) cert.typeName!, if (cert.schoolYear != null && cert.name != null) cert.schoolYear!, @@ -962,19 +942,17 @@ class ProfileScreenState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 8.0), - _sectionHeader(context, "account".i18n), SplittedPanel( - padding: const EdgeInsets.only( - bottom: 0.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 0.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), children: [ ClipRRect( borderRadius: BorderRadius.circular(12.0), child: AccountTile( name: Text(displayName, - style: - const TextStyle(fontWeight: FontWeight.w600)), + style: const TextStyle(fontWeight: FontWeight.w600)), username: Text(username), profileImage: ProfileImage( name: firstName, @@ -987,51 +965,42 @@ class ProfileScreenState extends State ), ], ), - if (otherAccounts.isNotEmpty) ...[ const SizedBox(height: 4.0), _sectionHeader(context, "switch_account".i18n), SplittedPanel( - padding: const EdgeInsets.only( - bottom: 0.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 0.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), children: [ for (int i = 0; i < otherAccounts.length; i++) - _buildOtherAccountItem(context, colorScheme, - otherAccounts[i], i, otherAccounts.length), + _buildOtherAccountItem(context, colorScheme, otherAccounts[i], + i, otherAccounts.length), ], ), ], - const SizedBox(height: 4.0), - SplittedPanel( - padding: const EdgeInsets.only( - bottom: 0.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 0.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), children: [ PanelButton( padding: const EdgeInsets.only(left: 14.0, right: 6.0), - onPressed: () => - Navigator.of(context).pushNamed("login_back"), + onPressed: () => Navigator.of(context).pushNamed("login_back"), leading: Icon( Icons.person_add_rounded, size: 22.0, - color: AppColors.of(context) - .text - .withValues(alpha: 0.85), + color: AppColors.of(context).text.withValues(alpha: 0.85), ), title: Text("add_user".i18n), trailing: Icon( Icons.chevron_right_rounded, size: 20.0, - color: AppColors.of(context) - .text - .withValues(alpha: 0.3), + color: AppColors.of(context).text.withValues(alpha: 0.3), ), borderRadius: const BorderRadius.vertical( - top: Radius.circular(12.0), - bottom: Radius.circular(4.0)), + top: Radius.circular(12.0), bottom: Radius.circular(4.0)), ), PanelButton( padding: const EdgeInsets.only(left: 14.0, right: 6.0), @@ -1041,8 +1010,8 @@ class ProfileScreenState extends State final isDemo = user.isDemo; final hasOtherUsers = user.getUsers().length > 1; if (!hasOtherUsers) { - Navigator.of(context).pushNamedAndRemoveUntil( - "login", (_) => false); + Navigator.of(context) + .pushNamedAndRemoveUntil("login", (_) => false); user.removeUser(userId); if (!isDemo) { await Provider.of(context, @@ -1053,14 +1022,13 @@ class ProfileScreenState extends State return; } user.removeUser(userId); - await Provider.of(context, - listen: false) + await Provider.of(context, listen: false) .store .removeUser(userId); if (user.getUsers().isNotEmpty) { user.setUser(user.getUsers().first.id); - restore().then( - (_) => user.setUser(user.getUsers().first.id)); + restore() + .then((_) => user.setUser(user.getUsers().first.id)); } }, leading: Icon( @@ -1073,24 +1041,20 @@ class ProfileScreenState extends State style: TextStyle(color: colorScheme.error), ), borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), - bottom: Radius.circular(12.0)), + top: Radius.circular(4.0), bottom: Radius.circular(12.0)), ), ], ), - SizedBox(height: MediaQuery.of(context).padding.bottom + 24.0), ], ), ); } - Widget _buildOtherAccountItem(BuildContext context, - ColorScheme colorScheme, User account, int index, int total) { + Widget _buildOtherAccountItem(BuildContext context, ColorScheme colorScheme, + User account, int index, int total) { final List nameParts = - (account.nickname.isNotEmpty - ? account.nickname - : account.displayName) + (account.nickname.isNotEmpty ? account.nickname : account.displayName) .split(" "); final String firstName = settings.presentationMode ? "János" @@ -1102,9 +1066,7 @@ class ProfileScreenState extends State name: Text( settings.presentationMode ? "János" - : (account.nickname.isNotEmpty - ? account.nickname - : account.name), + : (account.nickname.isNotEmpty ? account.nickname : account.name), style: const TextStyle(fontWeight: FontWeight.w500), ), username: Text( @@ -1126,8 +1088,7 @@ class ProfileScreenState extends State Widget _sectionHeader(BuildContext context, String label) { return Padding( - padding: - const EdgeInsets.only(top: 10.0, bottom: 6.0, left: 28.0), + padding: const EdgeInsets.only(top: 10.0, bottom: 6.0, left: 28.0), child: Row( children: [ Container( @@ -1145,10 +1106,8 @@ class ProfileScreenState extends State fontSize: 13.0, fontWeight: FontWeight.w800, letterSpacing: 1.2, - color: Theme.of(context) - .colorScheme - .primary - .withValues(alpha: 0.8), + color: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.8), ), ), ], @@ -1163,8 +1122,8 @@ class ProfileScreenState extends State VoidCallback? onAction, }) { return Padding( - padding: - const EdgeInsets.only(top: 10.0, bottom: 6.0, left: 28.0, right: 28.0), + padding: const EdgeInsets.only( + top: 10.0, bottom: 6.0, left: 28.0, right: 28.0), child: Row( children: [ Container( @@ -1243,7 +1202,8 @@ class ProfileScreenState extends State style: TextStyle( fontSize: 11.0, fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.error.withValues(alpha: 0.8), + color: + Theme.of(context).colorScheme.error.withValues(alpha: 0.8), ), ), ], @@ -1322,8 +1282,8 @@ class _NicknameBottomSheetState extends State<_NicknameBottomSheet> { padding: const EdgeInsets.only(bottom: 16.0), child: Text( "edit_nickname".i18n, - style: const TextStyle( - fontSize: 17.0, fontWeight: FontWeight.w700), + style: + const TextStyle(fontSize: 17.0, fontWeight: FontWeight.w700), ), ), TextField( @@ -1335,9 +1295,7 @@ class _NicknameBottomSheetState extends State<_NicknameBottomSheet> { borderSide: BorderSide.none, ), filled: true, - fillColor: Theme.of(context) - .colorScheme - .surfaceContainerHigh, + fillColor: Theme.of(context).colorScheme.surfaceContainerHigh, hintText: widget.u.name, suffixIcon: IconButton( icon: const Icon(Icons.close_rounded, size: 18.0), @@ -1351,8 +1309,7 @@ class _NicknameBottomSheetState extends State<_NicknameBottomSheet> { children: [ TextButton( child: Text("cancel".i18n, - style: - const TextStyle(fontWeight: FontWeight.w500)), + style: const TextStyle(fontWeight: FontWeight.w500)), onPressed: () => Navigator.of(context).maybePop(), ), const SizedBox(width: 8.0), @@ -1363,8 +1320,7 @@ class _NicknameBottomSheetState extends State<_NicknameBottomSheet> { Provider.of(context, listen: false) .store .storeUser(widget.u); - Provider.of(context, listen: false) - .refresh(); + Provider.of(context, listen: false).refresh(); Navigator.of(context).pop(true); }, ), @@ -1428,8 +1384,8 @@ class _ContactBottomSheetState extends State<_ContactBottomSheet> { padding: const EdgeInsets.only(bottom: 16.0), child: Text( "edit_contact".i18n, - style: const TextStyle( - fontSize: 17.0, fontWeight: FontWeight.w700), + style: + const TextStyle(fontSize: 17.0, fontWeight: FontWeight.w700), ), ), TextField( @@ -1441,8 +1397,7 @@ class _ContactBottomSheetState extends State<_ContactBottomSheet> { borderSide: BorderSide.none, ), filled: true, - fillColor: - Theme.of(context).colorScheme.surfaceContainerHigh, + fillColor: Theme.of(context).colorScheme.surfaceContainerHigh, labelText: "email".i18n, ), ), @@ -1456,8 +1411,7 @@ class _ContactBottomSheetState extends State<_ContactBottomSheet> { borderSide: BorderSide.none, ), filled: true, - fillColor: - Theme.of(context).colorScheme.surfaceContainerHigh, + fillColor: Theme.of(context).colorScheme.surfaceContainerHigh, labelText: "phone".i18n, ), ), @@ -1467,8 +1421,7 @@ class _ContactBottomSheetState extends State<_ContactBottomSheet> { children: [ TextButton( child: Text("cancel".i18n, - style: - const TextStyle(fontWeight: FontWeight.w500)), + style: const TextStyle(fontWeight: FontWeight.w500)), onPressed: () => Navigator.of(context).maybePop(), ), const SizedBox(width: 8.0), @@ -1524,8 +1477,7 @@ class _BankAccountBottomSheet extends StatefulWidget { _BankAccountBottomSheetState(); } -class _BankAccountBottomSheetState - extends State<_BankAccountBottomSheet> { +class _BankAccountBottomSheetState extends State<_BankAccountBottomSheet> { late final TextEditingController _numberCtrl; late final TextEditingController _ownerCtrl; bool _saving = false; @@ -1564,8 +1516,8 @@ class _BankAccountBottomSheetState padding: const EdgeInsets.only(bottom: 16.0), child: Text( "edit_bank_account".i18n, - style: const TextStyle( - fontSize: 17.0, fontWeight: FontWeight.w700), + style: + const TextStyle(fontSize: 17.0, fontWeight: FontWeight.w700), ), ), TextField( @@ -1623,8 +1575,7 @@ class _BankAccountBottomSheetState const Spacer(), TextButton( child: Text("cancel".i18n, - style: - const TextStyle(fontWeight: FontWeight.w500)), + style: const TextStyle(fontWeight: FontWeight.w500)), onPressed: () => Navigator.of(context).maybePop(), ), const SizedBox(width: 8.0), @@ -1665,66 +1616,45 @@ class _ProfilePicBottomSheet extends StatefulWidget { final User u; @override - State<_ProfilePicBottomSheet> createState() => - _ProfilePicBottomSheetState(); + State<_ProfilePicBottomSheet> createState() => _ProfilePicBottomSheetState(); } class _ProfilePicBottomSheetState extends State<_ProfilePicBottomSheet> { - final cropKey = GlobalKey(); + final CropController _controller = CropController(); + File? _file; - File? _sample; - File? _lastCropped; + Uint8List? _imageData; Future _pickImage() async { try { - final picked = - await ImagePicker().pickImage(source: ImageSource.gallery); + final picked = await ImagePicker().pickImage(source: ImageSource.gallery); if (picked == null) return; - final imageFile = File(picked.path); - final sample = await ImageCrop.sampleImage( - file: imageFile, - preferredSize: MediaQuery.of(context).size.longestSide.ceil(), - ); - _sample?.delete(); - _file?.delete(); + + final file = File(picked.path); + final bytes = await file.readAsBytes(); + setState(() { - _sample = sample; - _file = imageFile; + _file = file; + _imageData = bytes; }); } on PlatformException catch (e) { log('Failed to pick image: $e'); } } - Future _cropAndSave() async { - final scale = cropKey.currentState!.scale; - final area = cropKey.currentState!.area; - if (area == null || _file == null) return; - - final sample = await ImageCrop.sampleImage( - file: _file!, - preferredSize: (2000 / scale).round(), - ); - - final cropped = await ImageCrop.cropImage( - file: sample, - area: area, - ); - - _lastCropped?.delete(); - _sample?.delete(); + void _cropAndSave() { + _controller.crop(); + } - setState(() { - _sample = sample; - _lastCropped = cropped; - }); + void _onCropped(Uint8List croppedData) async { + widget.u.picture = base64Encode(croppedData); - final bytes = await cropped.readAsBytes(); - widget.u.picture = base64Encode(bytes); Provider.of(context, listen: false) .store .storeUser(widget.u); + Provider.of(context, listen: false).refresh(); + Navigator.of(context).pop(true); } @@ -1745,17 +1675,18 @@ class _ProfilePicBottomSheetState extends State<_ProfilePicBottomSheet> { padding: const EdgeInsets.only(bottom: 16.0), child: Text( "select_profile_picture".i18n, - style: const TextStyle( - fontSize: 17.0, fontWeight: FontWeight.w700), + style: + const TextStyle(fontSize: 17.0, fontWeight: FontWeight.w700), ), ), - if (_sample != null) ...[ + if (_imageData != null) ...[ SizedBox( - height: 280.0, + height: 280, child: Crop( - key: cropKey, - image: FileImage(_sample!), + image: _imageData!, + controller: _controller, aspectRatio: 1.0, + onCropped: _onCropped, ), ), ] else ...[ @@ -1765,9 +1696,7 @@ class _ProfilePicBottomSheetState extends State<_ProfilePicBottomSheet> { height: 240.0, width: double.infinity, decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .surfaceContainerHigh, + color: Theme.of(context).colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(16.0), ), child: Column( @@ -1776,22 +1705,10 @@ class _ProfilePicBottomSheetState extends State<_ProfilePicBottomSheet> { Icon( Icons.add_photo_alternate_rounded, size: 48.0, - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.3), ), const SizedBox(height: 12.0), Text( "select_profile_picture".i18n, - style: TextStyle( - fontSize: 15.0, - fontWeight: FontWeight.w500, - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.45), - ), ), ], ), @@ -1804,29 +1721,28 @@ class _ProfilePicBottomSheetState extends State<_ProfilePicBottomSheet> { children: [ if (widget.u.picture.isNotEmpty) TextButton( - child: Text("remove_profile_picture".i18n, - style: TextStyle( - color: Theme.of(context).colorScheme.error, - fontWeight: FontWeight.w500)), + child: Text( + "remove_profile_picture".i18n, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + ), + ), onPressed: () { widget.u.picture = ""; Provider.of(context, listen: false) .store .storeUser(widget.u); - Provider.of(context, listen: false) - .refresh(); + Provider.of(context, listen: false).refresh(); Navigator.of(context).pop(true); }, ), const Spacer(), TextButton( - child: Text("cancel".i18n, - style: - const TextStyle(fontWeight: FontWeight.w500)), + child: Text("cancel".i18n), onPressed: () => Navigator.of(context).maybePop(), ), const SizedBox(width: 8.0), - if (_sample == null) + if (_imageData == null) FilledButton( onPressed: _pickImage, child: Text("select_profile_picture".i18n), diff --git a/folio_mobile_ui/lib/screens/settings/user/profile_pic.dart b/folio_mobile_ui/lib/screens/settings/user/profile_pic.dart index 9aee9b7f..d8f1cec5 100644 --- a/folio_mobile_ui/lib/screens/settings/user/profile_pic.dart +++ b/folio_mobile_ui/lib/screens/settings/user/profile_pic.dart @@ -13,7 +13,7 @@ import 'package:flutter/services.dart'; import 'package:folio_mobile_ui/screens/settings/settings_screen.i18n.dart'; import 'package:provider/provider.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:image_crop_plus/image_crop_plus.dart'; +import 'package:crop_your_image/crop_your_image.dart'; // ignore: must_be_immutable class UserMenuProfilePic extends StatelessWidget { @@ -52,29 +52,22 @@ class UserProfilePicEditor extends StatefulWidget { class _UserProfilePicEditorState extends State { late final UserProvider user; - final cropKey = GlobalKey(); + final CropController _controller = CropController(); + File? _file; - File? _sample; - File? _lastCropped; + Uint8List? _imageData; - File? image; - Future pickImage() async { + Future pickImage() async { try { - final image = await ImagePicker().pickImage(source: ImageSource.gallery); - if (image == null) return; - File imageFile = File(image.path); - - final sample = await ImageCrop.sampleImage( - file: imageFile, - preferredSize: context.size!.longestSide.ceil(), - ); + final picked = await ImagePicker().pickImage(source: ImageSource.gallery); + if (picked == null) return; - _sample?.delete(); - _file?.delete(); + final file = File(picked.path); + final bytes = await file.readAsBytes(); setState(() { - _sample = sample; - _file = imageFile; + _file = file; + _imageData = bytes; }); } on PlatformException catch (e) { log('Failed to pick image: $e'); @@ -84,10 +77,11 @@ class _UserProfilePicEditorState extends State { Widget cropImageWidget() { return SizedBox( height: 300, - child: Crop.file( - _sample!, - key: cropKey, + child: Crop( + image: _imageData!, + controller: _controller, aspectRatio: 1.0, + onCropped: _onCropped, ), ); } @@ -97,7 +91,7 @@ class _UserProfilePicEditorState extends State { customBorder: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14.0), ), - onTap: () => pickImage(), + onTap: pickImage, child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey), @@ -116,48 +110,29 @@ class _UserProfilePicEditorState extends State { ), Text( "select_profile_picture".i18n, - style: const TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.w500, - ), - ) + ), ], ), ), ); } - Future _cropImage() async { - final scale = cropKey.currentState!.scale; - final area = cropKey.currentState!.area; - if (area == null || _file == null) { - return; - } - - final sample = await ImageCrop.sampleImage( - file: _file!, - preferredSize: (2000 / scale).round(), - ); - - final file = await ImageCrop.cropImage( - file: sample, - area: area, - ); - - sample.delete(); + void _cropImage() { + _controller.crop(); // triggers onCropped + } - _lastCropped?.delete(); - _lastCropped = file; + void _onCropped(Uint8List croppedData) async { + final base64Image = base64Encode(croppedData); - List imageBytes = await _lastCropped!.readAsBytes(); - String base64Image = base64Encode(imageBytes); widget.u.picture = base64Image; + Provider.of(context, listen: false) .store .storeUser(widget.u); + Provider.of(context, listen: false).refresh(); - debugPrint('$file'); + debugPrint('Image cropped and saved'); } @override @@ -168,17 +143,16 @@ class _UserProfilePicEditorState extends State { @override void dispose() { + _file = null; // no temp files to delete anymore super.dispose(); - _file?.delete(); - _sample?.delete(); - _lastCropped?.delete(); } @override Widget build(BuildContext context) { return AlertDialog( shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(14.0))), + borderRadius: BorderRadius.all(Radius.circular(14.0)), + ), contentPadding: const EdgeInsets.only(top: 10.0), title: Text("edit_profile_picture".i18n), content: Column( @@ -187,14 +161,16 @@ class _UserProfilePicEditorState extends State { Padding( padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0), - child: _sample == null ? openImageWidget() : cropImageWidget(), + child: _imageData == null ? openImageWidget() : cropImageWidget(), ), if (widget.u.picture != "") TextButton( child: Text( "remove_profile_picture".i18n, style: const TextStyle( - fontWeight: FontWeight.w500, color: Colors.red), + fontWeight: FontWeight.w500, + color: Colors.red, + ), ), onPressed: () { widget.u.picture = ""; @@ -209,21 +185,13 @@ class _UserProfilePicEditorState extends State { ), actions: [ TextButton( - child: Text( - "cancel".i18n, - style: const TextStyle(fontWeight: FontWeight.w500), - ), - onPressed: () { - Navigator.of(context).maybePop(); - }, + child: Text("cancel".i18n), + onPressed: () => Navigator.of(context).maybePop(), ), TextButton( - child: Text( - "done".i18n, - style: const TextStyle(fontWeight: FontWeight.w500), - ), - onPressed: () async { - await _cropImage(); + child: Text("done".i18n), + onPressed: () { + _cropImage(); Navigator.of(context).pop(true); }, ), diff --git a/folio_mobile_ui/pubspec.yaml b/folio_mobile_ui/pubspec.yaml index 8e5f1e29..e4529e1f 100644 --- a/folio_mobile_ui/pubspec.yaml +++ b/folio_mobile_ui/pubspec.yaml @@ -43,7 +43,7 @@ dependencies: share_plus: ^12.0.2 image_picker: ^1.1.2 path_provider: ^2.1.5 - image_crop_plus: ^1.0.0 + crop_your_image: ^1.0.0 uuid: ^4.5.1 maps_launcher: ^3.0.0+1 google_fonts: ^8.0.2 From 64e294f9ca310d28141ae6e94526307026e17a33 Mon Sep 17 00:00:00 2001 From: Csani Date: Wed, 29 Apr 2026 19:24:44 +0200 Subject: [PATCH 2/6] Updated Grade class Updated Grade class to include a rarity value, Updated GradeValue with getRarity function, Updated demo_data to use new Grade class Updated grade_provider to use new Grade class --- folio_kreta_api/lib/demo/demo_data.dart | 314 ++++++++++++++---- folio_kreta_api/lib/models/grade.dart | 56 +++- .../lib/providers/grade_provider.dart | 16 +- 3 files changed, 303 insertions(+), 83 deletions(-) diff --git a/folio_kreta_api/lib/demo/demo_data.dart b/folio_kreta_api/lib/demo/demo_data.dart index 07b93b69..5c51b9e3 100644 --- a/folio_kreta_api/lib/demo/demo_data.dart +++ b/folio_kreta_api/lib/demo/demo_data.dart @@ -20,12 +20,15 @@ class DemoData { ); static final _subjectHun = GradeSubject( id: 'demo-subject-hun', - category: Category(id: 'magyar_nyelv_es_irodalom', name: 'magyar_nyelv_es_irodalom'), + category: Category( + id: 'magyar_nyelv_es_irodalom', name: 'magyar_nyelv_es_irodalom'), name: 'Magyar nyelv és irodalom', ); static final _subjectHist = GradeSubject( id: 'demo-subject-hist', - category: Category(id: 'tortenelem_es_allampolgari_ismeretek', name: 'tortenelem_es_allampolgari_ismeretek'), + category: Category( + id: 'tortenelem_es_allampolgari_ismeretek', + name: 'tortenelem_es_allampolgari_ismeretek'), name: 'Történelem', ); static final _subjectEng = GradeSubject( @@ -40,7 +43,8 @@ class DemoData { ); static final _subjectBio = GradeSubject( id: 'demo-subject-bio', - category: Category(id: 'biologia_egeszsegtan', name: 'biologia_egeszsegtan'), + category: + Category(id: 'biologia_egeszsegtan', name: 'biologia_egeszsegtan'), name: 'Biológia', ); static final _subjectChem = GradeSubject( @@ -50,7 +54,8 @@ class DemoData { ); static final _subjectPE = GradeSubject( id: 'demo-subject-pe', - category: Category(id: 'testnevelés_és_sport', name: 'testnevelés_és_sport'), + category: + Category(id: 'testnevelés_és_sport', name: 'testnevelés_és_sport'), name: 'Testnevelés', ); static final _subjectIT = GradeSubject( @@ -66,28 +71,200 @@ class DemoData { static final _teacherVarga = Teacher.fromString('Varga Erzsébet'); static final _teacherFekete = Teacher.fromString('Fekete Gábor'); - static final _modeOral = Category(id: '1,SzobaliVizsga', name: 'Szóbeli vizsga', description: 'Szóbeli vizsga'); - static final _modeWritten = Category(id: '2,IrasbaliVizsga', name: 'Írásbeli vizsga', description: 'Írásbeli vizsga'); - static final _modePractical = Category(id: '3,Gyakorlati', name: 'Gyakorlati', description: 'Gyakorlati'); + static final _modeOral = Category( + id: '1,SzobaliVizsga', + name: 'Szóbeli vizsga', + description: 'Szóbeli vizsga'); + static final _modeWritten = Category( + id: '2,IrasbaliVizsga', + name: 'Írásbeli vizsga', + description: 'Írásbeli vizsga'); + static final _modePractical = Category( + id: '3,Gyakorlati', name: 'Gyakorlati', description: 'Gyakorlati'); static List get grades { final now = DateTime.now(); return [ - _makeGrade('demo-g-1', 5, 'Jeles', 'Ötös', 100, _subjectMath, _teacherNagy, 'Függvények', now.subtract(const Duration(days: 3)), _modeWritten, GradeType.midYear), - _makeGrade('demo-g-2', 4, 'Jó', 'Négyes', 100, _subjectMath, _teacherNagy, 'Egyenletek', now.subtract(const Duration(days: 14)), _modeWritten, GradeType.midYear), - _makeGrade('demo-g-3', 5, 'Jeles', 'Ötös', 100, _subjectHun, _teacherKovacs, 'Arany János költészete', now.subtract(const Duration(days: 5)), _modeOral, GradeType.midYear), - _makeGrade('demo-g-4', 4, 'Jó', 'Négyes', 100, _subjectHun, _teacherKovacs, 'Fogalmazás', now.subtract(const Duration(days: 20)), _modeWritten, GradeType.midYear), - _makeGrade('demo-g-5', 5, 'Jeles', 'Ötös', 100, _subjectEng, _teacherSzabo, 'Grammar test', now.subtract(const Duration(days: 7)), _modeWritten, GradeType.midYear), - _makeGrade('demo-g-6', 5, 'Jeles', 'Ötös', 100, _subjectEng, _teacherSzabo, 'Speaking', now.subtract(const Duration(days: 18)), _modeOral, GradeType.midYear), - _makeGrade('demo-g-7', 4, 'Jó', 'Négyes', 100, _subjectHist, _teacherToth, 'Az első világháború', now.subtract(const Duration(days: 10)), _modeOral, GradeType.midYear), - _makeGrade('demo-g-8', 3, 'Közepes', 'Hármas', 100, _subjectHist, _teacherToth, 'Témazáró', now.subtract(const Duration(days: 25)), _modeWritten, GradeType.midYear), - _makeGrade('demo-g-9', 5, 'Jeles', 'Ötös', 100, _subjectPhy, _teacherVarga, 'Mechanika', now.subtract(const Duration(days: 8)), _modeWritten, GradeType.midYear), - _makeGrade('demo-g-10', 4, 'Jó', 'Négyes', 100, _subjectBio, _teacherFekete, 'Sejtbiológia', now.subtract(const Duration(days: 12)), _modeOral, GradeType.midYear), - _makeGrade('demo-g-11', 5, 'Jeles', 'Ötös', 100, _subjectChem, _teacherVarga, 'Kémiai kötések', now.subtract(const Duration(days: 6)), _modeWritten, GradeType.midYear), - _makeGrade('demo-g-12', 5, 'Jeles', 'Ötös', 100, _subjectIT, _teacherSzabo, 'Programozás alapjai', now.subtract(const Duration(days: 4)), _modePractical, GradeType.midYear), - _makeGrade('demo-g-13', 4, 'Jó', 'Négyes', 100, _subjectMath, _teacherNagy, 'Statisztika', now.subtract(const Duration(days: 30)), _modeWritten, GradeType.halfYear), - _makeGrade('demo-g-14', 5, 'Jeles', 'Ötös', 100, _subjectHun, _teacherKovacs, 'Félévzáró', now.subtract(const Duration(days: 60)), _modeWritten, GradeType.halfYear), - _makeGrade('demo-g-15', 4, 'Jó', 'Négyes', 100, _subjectEng, _teacherSzabo, 'Mid-year exam', now.subtract(const Duration(days: 60)), _modeWritten, GradeType.halfYear), + _makeGrade( + 'demo-g-1', + 5, + 'Jeles', + 'Ötös', + 100, + _subjectMath, + _teacherNagy, + 'Függvények', + now.subtract(const Duration(days: 3)), + _modeWritten, + GradeType.midYear), + _makeGrade( + 'demo-g-2', + 4, + 'Jó', + 'Négyes', + 100, + _subjectMath, + _teacherNagy, + 'Egyenletek', + now.subtract(const Duration(days: 14)), + _modeWritten, + GradeType.midYear), + _makeGrade( + 'demo-g-3', + 5, + 'Jeles', + 'Ötös', + 100, + _subjectHun, + _teacherKovacs, + 'Arany János költészete', + now.subtract(const Duration(days: 5)), + _modeOral, + GradeType.midYear), + _makeGrade( + 'demo-g-4', + 4, + 'Jó', + 'Négyes', + 100, + _subjectHun, + _teacherKovacs, + 'Fogalmazás', + now.subtract(const Duration(days: 20)), + _modeWritten, + GradeType.midYear), + _makeGrade( + 'demo-g-5', + 5, + 'Jeles', + 'Ötös', + 100, + _subjectEng, + _teacherSzabo, + 'Grammar test', + now.subtract(const Duration(days: 7)), + _modeWritten, + GradeType.midYear), + _makeGrade( + 'demo-g-6', + 5, + 'Jeles', + 'Ötös', + 100, + _subjectEng, + _teacherSzabo, + 'Speaking', + now.subtract(const Duration(days: 18)), + _modeOral, + GradeType.midYear), + _makeGrade( + 'demo-g-7', + 4, + 'Jó', + 'Négyes', + 100, + _subjectHist, + _teacherToth, + 'Az első világháború', + now.subtract(const Duration(days: 10)), + _modeOral, + GradeType.midYear), + _makeGrade( + 'demo-g-8', + 3, + 'Közepes', + 'Hármas', + 100, + _subjectHist, + _teacherToth, + 'Témazáró', + now.subtract(const Duration(days: 25)), + _modeWritten, + GradeType.midYear), + _makeGrade( + 'demo-g-9', + 5, + 'Jeles', + 'Ötös', + 100, + _subjectPhy, + _teacherVarga, + 'Mechanika', + now.subtract(const Duration(days: 8)), + _modeWritten, + GradeType.midYear), + _makeGrade( + 'demo-g-10', + 4, + 'Jó', + 'Négyes', + 100, + _subjectBio, + _teacherFekete, + 'Sejtbiológia', + now.subtract(const Duration(days: 12)), + _modeOral, + GradeType.midYear), + _makeGrade( + 'demo-g-11', + 5, + 'Jeles', + 'Ötös', + 100, + _subjectChem, + _teacherVarga, + 'Kémiai kötések', + now.subtract(const Duration(days: 6)), + _modeWritten, + GradeType.midYear), + _makeGrade( + 'demo-g-12', + 5, + 'Jeles', + 'Ötös', + 100, + _subjectIT, + _teacherSzabo, + 'Programozás alapjai', + now.subtract(const Duration(days: 4)), + _modePractical, + GradeType.midYear), + _makeGrade( + 'demo-g-13', + 4, + 'Jó', + 'Négyes', + 100, + _subjectMath, + _teacherNagy, + 'Statisztika', + now.subtract(const Duration(days: 30)), + _modeWritten, + GradeType.halfYear), + _makeGrade( + 'demo-g-14', + 5, + 'Jeles', + 'Ötös', + 100, + _subjectHun, + _teacherKovacs, + 'Félévzáró', + now.subtract(const Duration(days: 60)), + _modeWritten, + GradeType.halfYear), + _makeGrade( + 'demo-g-15', + 4, + 'Jó', + 'Négyes', + 100, + _subjectEng, + _teacherSzabo, + 'Mid-year exam', + now.subtract(const Duration(days: 60)), + _modeWritten, + GradeType.halfYear), ]; } @@ -104,27 +281,29 @@ class DemoData { Category mode, GradeType type, ) { + var gradeValue = GradeValue(value, valueName, shortName, weight); + return Grade( - id: id, - date: date, - value: GradeValue(value, valueName, shortName, weight), - teacher: teacher, - description: description, - type: type, - groupId: 'demo-group', - subject: subject, - mode: mode, - writeDate: date, - seenDate: date, - form: '', - json: { - 'SzamErtek': value, - 'SzovegesErtek': valueName, - 'SzovegesErtekelesRovidNev': shortName, - 'SulySzazalekErteke': weight, - 'ErtekFajta': null, - }, - ); + id: id, + date: date, + value: gradeValue, + teacher: teacher, + description: description, + type: type, + groupId: 'demo-group', + subject: subject, + mode: mode, + writeDate: date, + seenDate: date, + form: '', + json: { + 'SzamErtek': value, + 'SzovegesErtek': valueName, + 'SzovegesErtekelesRovidNev': shortName, + 'SulySzazalekErteke': weight, + 'ErtekFajta': null, + }, + rarity: gradeValue.getRarity()); } static Map> get timetable { @@ -139,7 +318,8 @@ class DemoData { // Monday lessons.addAll(_dayLessons(monday, [ _LessonDef('Matematika', _subjectMath, _teacherNagy, '101', '9A'), - _LessonDef('Magyar nyelv és irodalom', _subjectHun, _teacherKovacs, '203', '9A'), + _LessonDef( + 'Magyar nyelv és irodalom', _subjectHun, _teacherKovacs, '203', '9A'), _LessonDef('Történelem', _subjectHist, _teacherToth, '105', '9A'), _LessonDef('Angol nyelv', _subjectEng, _teacherSzabo, '102', '9A'), _LessonDef('Testnevelés', _subjectPE, _teacherFekete, 'Tornaterem', '9A'), @@ -150,12 +330,14 @@ class DemoData { _LessonDef('Fizika', _subjectPhy, _teacherVarga, '204', '9A'), _LessonDef('Biológia', _subjectBio, _teacherFekete, '205', '9A'), _LessonDef('Matematika', _subjectMath, _teacherNagy, '101', '9A'), - _LessonDef('Informatika', _subjectIT, _teacherSzabo, 'Számítóterem', '9A'), + _LessonDef( + 'Informatika', _subjectIT, _teacherSzabo, 'Számítóterem', '9A'), ])); // Wednesday lessons.addAll(_dayLessons(monday.add(const Duration(days: 2)), [ - _LessonDef('Magyar nyelv és irodalom', _subjectHun, _teacherKovacs, '203', '9A'), + _LessonDef( + 'Magyar nyelv és irodalom', _subjectHun, _teacherKovacs, '203', '9A'), _LessonDef('Kémia', _subjectChem, _teacherVarga, '206', '9A'), _LessonDef('Angol nyelv', _subjectEng, _teacherSzabo, '102', '9A'), _LessonDef('Történelem', _subjectHist, _teacherToth, '105', '9A'), @@ -167,12 +349,14 @@ class DemoData { _LessonDef('Matematika', _subjectMath, _teacherNagy, '101', '9A'), _LessonDef('Biológia', _subjectBio, _teacherFekete, '205', '9A'), _LessonDef('Fizika', _subjectPhy, _teacherVarga, '204', '9A'), - _LessonDef('Informatika', _subjectIT, _teacherSzabo, 'Számítóterem', '9A'), + _LessonDef( + 'Informatika', _subjectIT, _teacherSzabo, 'Számítóterem', '9A'), ])); // Friday lessons.addAll(_dayLessons(monday.add(const Duration(days: 4)), [ - _LessonDef('Magyar nyelv és irodalom', _subjectHun, _teacherKovacs, '203', '9A'), + _LessonDef( + 'Magyar nyelv és irodalom', _subjectHun, _teacherKovacs, '203', '9A'), _LessonDef('Kémia', _subjectChem, _teacherVarga, '206', '9A'), _LessonDef('Matematika', _subjectMath, _teacherNagy, '101', '9A'), _LessonDef('Angol nyelv', _subjectEng, _teacherSzabo, '102', '9A'), @@ -211,12 +395,18 @@ class DemoData { static List get absences { final now = DateTime.now(); return [ - _makeAbsence('demo-abs-1', _subjectMath, _teacherNagy, now.subtract(const Duration(days: 15)), Justification.excused), - _makeAbsence('demo-abs-2', _subjectMath, _teacherNagy, now.subtract(const Duration(days: 15)), Justification.excused), - _makeAbsence('demo-abs-3', _subjectHun, _teacherKovacs, now.subtract(const Duration(days: 15)), Justification.excused), - _makeAbsence('demo-abs-4', _subjectEng, _teacherSzabo, now.subtract(const Duration(days: 8)), Justification.pending), - _makeAbsence('demo-abs-5', _subjectHist, _teacherToth, now.subtract(const Duration(days: 8)), Justification.pending), - _makeAbsence('demo-abs-6', _subjectPhy, _teacherVarga, now.subtract(const Duration(days: 2)), Justification.unexcused), + _makeAbsence('demo-abs-1', _subjectMath, _teacherNagy, + now.subtract(const Duration(days: 15)), Justification.excused), + _makeAbsence('demo-abs-2', _subjectMath, _teacherNagy, + now.subtract(const Duration(days: 15)), Justification.excused), + _makeAbsence('demo-abs-3', _subjectHun, _teacherKovacs, + now.subtract(const Duration(days: 15)), Justification.excused), + _makeAbsence('demo-abs-4', _subjectEng, _teacherSzabo, + now.subtract(const Duration(days: 8)), Justification.pending), + _makeAbsence('demo-abs-5', _subjectHist, _teacherToth, + now.subtract(const Duration(days: 8)), Justification.pending), + _makeAbsence('demo-abs-6', _subjectPhy, _teacherVarga, + now.subtract(const Duration(days: 2)), Justification.unexcused), ]; } @@ -287,7 +477,8 @@ class DemoData { byTeacher: true, homeworkEnabled: true, teacher: _teacherNagy, - content: 'Oldjátok meg a tankönyv 84-85. oldalán lévő feladatokat (1-15).', + content: + 'Oldjátok meg a tankönyv 84-85. oldalán lévő feladatokat (1-15).', subject: _subjectMath, group: 'demo-group', attachments: [], @@ -300,7 +491,8 @@ class DemoData { byTeacher: true, homeworkEnabled: true, teacher: _teacherKovacs, - content: 'Írjatok egy 1-2 oldalas elemzést Petőfi Sándor "Szeptember végén" c. verséről.', + content: + 'Írjatok egy 1-2 oldalas elemzést Petőfi Sándor "Szeptember végén" c. verséről.', subject: _subjectHun, group: 'demo-group', attachments: [], @@ -331,7 +523,8 @@ class DemoData { deleted: false, date: now.subtract(const Duration(days: 1)), author: 'Nagy Katalin', - content: 'Kedves Szülők!\n\nTájékoztatjuk Önöket, hogy jövő héten pótdolgozatot írunk matematikából. Kérem, segítsenek gyermeküknek a felkészülésben.\n\nÜdvözlettel,\nNagy Katalin', + content: + 'Kedves Szülők!\n\nTájékoztatjuk Önöket, hogy jövő héten pótdolgozatot írunk matematikából. Kérem, segítsenek gyermeküknek a felkészülésben.\n\nÜdvözlettel,\nNagy Katalin', subject: 'Pótdolgozat - Matematika', type: MessageType.inbox, recipients: [Recipient(id: 1, name: 'Demo Diák', kretaId: 1)], @@ -345,7 +538,8 @@ class DemoData { deleted: false, date: now.subtract(const Duration(days: 3)), author: 'Kovács Mária', - content: 'Kedves Diákok!\n\nEmlékeztetem Önöket, hogy a fogalmazást péntekig be kell adni. Kérem, ne felejtsék el!\n\nÜdvözlettel,\nKovács Mária', + content: + 'Kedves Diákok!\n\nEmlékeztetem Önöket, hogy a fogalmazást péntekig be kell adni. Kérem, ne felejtsék el!\n\nÜdvözlettel,\nKovács Mária', subject: 'Fogalmazás határideje', type: MessageType.inbox, recipients: [Recipient(id: 1, name: 'Demo Diák', kretaId: 1)], @@ -359,7 +553,8 @@ class DemoData { deleted: false, date: now.subtract(const Duration(days: 7)), author: 'Tóth László', - content: 'Kedves Szülők!\n\nAz osztálykirándulás időpontja: március 20. Kérem, hogy a beleegyező nyilatkozatot hozzák vissza aláírva.\n\nÜdvözlettel,\nTóth László\nosztályfőnök', + content: + 'Kedves Szülők!\n\nAz osztálykirándulás időpontja: március 20. Kérem, hogy a beleegyező nyilatkozatot hozzák vissza aláírva.\n\nÜdvözlettel,\nTóth László\nosztályfőnök', subject: 'Osztálykirándulás', type: MessageType.inbox, recipients: [Recipient(id: 1, name: 'Demo Diák', kretaId: 1)], @@ -390,5 +585,6 @@ class _LessonDef { final Teacher teacher; final String room; final String group; - const _LessonDef(this.name, this.subject, this.teacher, this.room, this.group); + const _LessonDef( + this.name, this.subject, this.teacher, this.room, this.group); } diff --git a/folio_kreta_api/lib/models/grade.dart b/folio_kreta_api/lib/models/grade.dart index 0f333cd9..cc5ea357 100644 --- a/folio_kreta_api/lib/models/grade.dart +++ b/folio_kreta_api/lib/models/grade.dart @@ -21,6 +21,7 @@ class Grade { DateTime writeDate; DateTime seenDate; String form; + String rarity; Grade({ required this.id, @@ -36,24 +37,27 @@ class Grade { required this.writeDate, required this.seenDate, required this.form, + required this.rarity, this.json, }); factory Grade.fromJson(Map json) { + var gradeValue = GradeValue( + json["SzamErtek"] ?? 0, + json["SzovegesErtek"] ?? "", + json["SzovegesErtekelesRovidNev"] ?? "", + json["SulySzazalekErteke"] ?? 0, + percentage: json["ErtekFajta"] != null + ? json["ErtekFajta"]["Uid"] == "3,Szazalekos" + : false, + ); + return Grade( id: json["Uid"] ?? "", date: json["KeszitesDatuma"] != null ? DateTime.parse(json["KeszitesDatuma"]).toLocal() : DateTime(0), - value: GradeValue( - json["SzamErtek"] ?? 0, - json["SzovegesErtek"] ?? "", - json["SzovegesErtekelesRovidNev"] ?? "", - json["SulySzazalekErteke"] ?? 0, - percentage: json["ErtekFajta"] != null - ? json["ErtekFajta"]["Uid"] == "3,Szazalekos" - : false, - ), + value: gradeValue, teacher: Teacher.fromString((json["ErtekeloTanarNeve"] ?? "").trim()), description: json["Tema"] ?? "", type: json["Tipus"] != null @@ -71,22 +75,24 @@ class Grade { seenDate: json["LattamozasDatuma"] != null ? DateTime.parse(json["LattamozasDatuma"]).toLocal() : DateTime(0), + rarity: gradeValue.getRarity(), form: (json["Jelleg"] ?? "Na") != "Na" ? json["Jelleg"] : "", json: json, ); } factory Grade.fromExportJson(Map json) { + var gradeValue = GradeValue( + json["value"] ?? 0, + json["value_name"] ?? "", + json["value_name"] ?? "", + json["weight"] ?? 0, + percentage: false, + ); return Grade( id: const Uuid().v4(), date: json["date"] != null ? DateTime.parse(json["date"]) : DateTime(0), - value: GradeValue( - json["value"] ?? 0, - json["value_name"] ?? "", - json["value_name"] ?? "", - json["weight"] ?? 0, - percentage: false, - ), + value: gradeValue, teacher: Teacher.fromString((json["teacher"] ?? "").trim()), description: json["description"] ?? "", type: json["type"] != null @@ -105,6 +111,7 @@ class Grade { json["date"] != null ? DateTime.parse(json["date"]) : DateTime(0), seenDate: json["date"] != null ? DateTime.parse(json["date"]) : DateTime(0), + rarity: gradeValue.getRarity(), form: "", json: json, ); @@ -177,6 +184,23 @@ class GradeValue { _valueName = valueName, _weight = weight, _percentage = percentage; + + String getRarity() { + switch (value) { + case 5: + return "legendary"; + case 4: + return "epic"; + case 3: + return "rare"; + case 2: + return "uncommon"; + case 1: + return "common"; + default: + return "legendary"; + } + } } enum GradeType { diff --git a/folio_kreta_api/lib/providers/grade_provider.dart b/folio_kreta_api/lib/providers/grade_provider.dart index 0340929b..6d8e5fcf 100644 --- a/folio_kreta_api/lib/providers/grade_provider.dart +++ b/folio_kreta_api/lib/providers/grade_provider.dart @@ -60,15 +60,15 @@ class GradeProvider with ChangeNotifier { } } -Future unseenAll() async { - String? userId = _user.id; - if (userId != null) { - final userStore = _database.userStore; - await userStore.storeLastSeen(DateTime(1969), - userId: userId, category: LastSeenCategory.surprisegrade); - _lastSeen = DateTime(1969); + Future unseenAll() async { + String? userId = _user.id; + if (userId != null) { + final userStore = _database.userStore; + await userStore.storeLastSeen(DateTime(1969), + userId: userId, category: LastSeenCategory.surprisegrade); + _lastSeen = DateTime(1969); + } } -} Future restore() async { String? userId = _user.id; From a10ceff6da89b27d120534f5494aafb5f8ca6523 Mon Sep 17 00:00:00 2001 From: Csani Date: Wed, 29 Apr 2026 19:25:49 +0200 Subject: [PATCH 3/6] Fixed SurpriseGrade to use new Grade class Fixed SurpriseGrade not displaying custom grade rarity text --- .../common/widgets/grade/surprise_grade.dart | 537 +++++++++--------- 1 file changed, 269 insertions(+), 268 deletions(-) diff --git a/folio_mobile_ui/lib/common/widgets/grade/surprise_grade.dart b/folio_mobile_ui/lib/common/widgets/grade/surprise_grade.dart index 804e388d..fa1aabb6 100644 --- a/folio_mobile_ui/lib/common/widgets/grade/surprise_grade.dart +++ b/folio_mobile_ui/lib/common/widgets/grade/surprise_grade.dart @@ -118,320 +118,321 @@ class _SurpriseGradeState extends State } } -@override -Widget build(BuildContext context) { - settingsProvider = Provider.of(context); + @override + Widget build(BuildContext context) { + settingsProvider = Provider.of(context); - return AnimatedBuilder( - animation: _revealAnimFade, - builder: (context, child) { - return FadeTransition( - opacity: _revealAnimFade, - child: Material( - color: Colors.black.withOpacity(0.75), - child: Container( - color: Theme.of(context) - .colorScheme - .secondary - .withOpacity(0.05), + return AnimatedBuilder( + animation: _revealAnimFade, + builder: (context, child) { + return FadeTransition( + opacity: _revealAnimFade, + child: Material( + color: Colors.black.withOpacity(0.75), child: Container( - decoration: const BoxDecoration( - gradient: RadialGradient( - colors: [Colors.transparent, Colors.black], - radius: 1.5, - stops: [0.2, 1.0], - ), - ), - child: bg.AnimatedBackground( - vsync: this, - behaviour: bg.RandomParticleBehaviour( - options: bg.ParticleOptions( - baseColor: Theme.of(context).colorScheme.secondary, - spawnMinSpeed: 5.0, - spawnMaxSpeed: 10.0, - minOpacity: 0.05, - maxOpacity: 0.08, - spawnMinRadius: 30.0, - spawnMaxRadius: 50.0, + color: Theme.of(context).colorScheme.secondary.withOpacity(0.05), + child: Container( + decoration: const BoxDecoration( + gradient: RadialGradient( + colors: [Colors.transparent, Colors.black], + radius: 1.5, + stops: [0.2, 1.0], ), ), - child: ScaleTransition( - scale: _revealAnimScale, - child: child, + child: bg.AnimatedBackground( + vsync: this, + behaviour: bg.RandomParticleBehaviour( + options: bg.ParticleOptions( + baseColor: Theme.of(context).colorScheme.secondary, + spawnMinSpeed: 5.0, + spawnMaxSpeed: 10.0, + minOpacity: 0.05, + maxOpacity: 0.08, + spawnMinRadius: 30.0, + spawnMaxRadius: 50.0, + ), + ), + child: ScaleTransition( + scale: _revealAnimScale, + child: child, + ), ), ), ), ), - ), - ); - }, - child: AnimatedBuilder( - animation: _revealAnimGrade, - builder: (context, child) { - return Stack( - alignment: Alignment.center, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SlideTransition( - position: _revealAnimGrade.drive( - Tween(begin: Offset.zero, end: const Offset(0, 0.7))), - child: AnimatedScale( - scale: hold ? 1.1 : 1.0, - curve: Curves.easeOutBack, - duration: const Duration(milliseconds: 200), - child: GestureDetector( - onLongPressDown: (_) => setState(() => hold = true), - onLongPressEnd: (_) => reveal(), - onLongPressCancel: reveal, - child: ScaleTransition( - scale: CurvedAnimation( - curve: Curves.easeInOut, - parent: _revealAnimGrade - .drive(Tween(begin: 1.0, end: 0.8))), - child: Stack( - alignment: Alignment.center, - children: [ - SizedBox( - width: 300, - height: 300, - child: Icon( - Icons.backpack, - size: 200.0, - color: Theme.of(context).colorScheme.secondary, + ); + }, + child: AnimatedBuilder( + animation: _revealAnimGrade, + builder: (context, child) { + return Stack( + alignment: Alignment.center, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SlideTransition( + position: _revealAnimGrade.drive( + Tween(begin: Offset.zero, end: const Offset(0, 0.7))), + child: AnimatedScale( + scale: hold ? 1.1 : 1.0, + curve: Curves.easeOutBack, + duration: const Duration(milliseconds: 200), + child: GestureDetector( + onLongPressDown: (_) => setState(() => hold = true), + onLongPressEnd: (_) => reveal(), + onLongPressCancel: reveal, + child: ScaleTransition( + scale: CurvedAnimation( + curve: Curves.easeInOut, + parent: _revealAnimGrade + .drive(Tween(begin: 1.0, end: 0.8))), + child: Stack( + alignment: Alignment.center, + children: [ + SizedBox( + width: 300, + height: 300, + child: Icon( + Icons.backpack, + size: 200.0, + color: + Theme.of(context).colorScheme.secondary, + ), ), - ), - SlideTransition( - position: _revealAnimParticle.drive(Tween( - begin: const Offset(0, 0.3), - end: const Offset(0, 0.8))), - child: FadeTransition( - opacity: _revealAnimParticle, - child: ClipRRect( - borderRadius: BorderRadius.circular(24.0), - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: 32.0, sigmaY: 32.0), - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric( - horizontal: 32.0, vertical: 20.0), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.3), - borderRadius: - BorderRadius.circular(24.0), - border: Border.all( - color: Colors.black.withOpacity(0.3), - width: 1.0), - ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.grade.description != - "") - Text( - widget.grade.description, - style: const TextStyle( - color: Colors.white, - fontWeight: - FontWeight.bold, - fontSize: 26.0, + SlideTransition( + position: _revealAnimParticle.drive(Tween( + begin: const Offset(0, 0.3), + end: const Offset(0, 0.8))), + child: FadeTransition( + opacity: _revealAnimParticle, + child: ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 32.0, sigmaY: 32.0), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: 32.0, vertical: 20.0), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.3), + borderRadius: + BorderRadius.circular(24.0), + border: Border.all( + color: + Colors.black.withOpacity(0.3), + width: 1.0), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget + .grade.description != + "") + Text( + widget.grade.description, + style: const TextStyle( + color: Colors.white, + fontWeight: + FontWeight.bold, + fontSize: 26.0, + ), + maxLines: 2, + overflow: + TextOverflow.ellipsis, ), + Text( + widget.grade.subject + .renamedTo ?? + widget + .grade.subject.name + .capital(), + style: TextStyle( + color: Colors.white + .withOpacity(0.8), + fontWeight: + FontWeight.bold, + fontSize: 24.0, + fontStyle: widget + .grade + .subject + .isRenamed && + settingsProvider + .renamedSubjectsItalics + ? FontStyle.italic + : null), maxLines: 2, overflow: TextOverflow.ellipsis, ), - Text( - widget.grade.subject - .renamedTo ?? - widget.grade.subject.name - .capital(), - style: TextStyle( + const SizedBox(height: 2), + Text( + "${widget.grade.value.weight}%", + style: TextStyle( color: Colors.white - .withOpacity(0.8), + .withOpacity(0.7), fontWeight: - FontWeight.bold, - fontSize: 24.0, - fontStyle: widget - .grade - .subject - .isRenamed && - settingsProvider - .renamedSubjectsItalics - ? FontStyle.italic - : null), - maxLines: 2, - overflow: - TextOverflow.ellipsis, - ), - const SizedBox(height: 2), - Text( - "${widget.grade.value.weight}%", - style: TextStyle( - color: Colors.white - .withOpacity(0.7), - fontWeight: FontWeight.w600, - fontSize: 20.0, + FontWeight.w600, + fontSize: 20.0, + ), + maxLines: 2, + overflow: + TextOverflow.ellipsis, ), - maxLines: 2, - overflow: - TextOverflow.ellipsis, - ), - ], + ], + ), + ), + const SizedBox(width: 20.0), + Icon( + SubjectIcon.resolveVariant( + subject: widget.grade.subject, + context: context), + color: Colors.white, + size: 82.0, ), - ), - const SizedBox(width: 20.0), - Icon( - SubjectIcon.resolveVariant( - subject: widget.grade.subject, - context: context), - color: Colors.white, - size: 82.0, - ), - ], + ], + ), ), ), ), ), ), - ), - ], + ], + ), ), ), ), ), - ), - const SizedBox(height: 42.0), - AnimatedOpacity( - opacity: subtitle ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: Text( - "open_subtitle".i18n, - style: TextStyle( - color: Colors.white.withOpacity(0.8), - fontWeight: FontWeight.w600, - fontSize: 24.0, + const SizedBox(height: 42.0), + AnimatedOpacity( + opacity: subtitle ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: Text( + "open_subtitle".i18n, + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontWeight: FontWeight.w600, + fontSize: 24.0, + ), ), ), - ), - ], - ), - if (_revealAnimGrade.value > 0) - AnimatedBuilder( - animation: _revealAnimParticle, - builder: (context, child) { - bool shouldPaint = false; - if (_revealAnimParticle.status == - AnimationStatus.forward || - _revealAnimParticle.status == - AnimationStatus.reverse) { - shouldPaint = true; - } + ], + ), + if (_revealAnimGrade.value > 0) + AnimatedBuilder( + animation: _revealAnimParticle, + builder: (context, child) { + bool shouldPaint = false; + if (_revealAnimParticle.status == AnimationStatus.forward || + _revealAnimParticle.status == AnimationStatus.reverse) { + shouldPaint = true; + } - String? rr = rarities[widget.grade.value.value.toString()]; - rr ??= ''; + String? rr = rarities[widget.grade.value.getRarity()]; - if (rr.replaceAll(' ', '') == '') { - rr = defaultRarities[widget.grade.value.value - 1].i18n; - } + rr ??= ''; - return ScaleTransition( - scale: _revealAnimGrade, - child: FadeTransition( - opacity: _revealAnimGrade, - child: SlideTransition( - position: _revealAnimGrade.drive(Tween( - begin: Offset.zero, end: const Offset(0, -0.6))), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SlideTransition( - position: _revealAnimGrade.drive(Tween( - begin: Offset.zero, - end: const Offset(0, -0.9))), - child: Text( - rr, - style: TextStyle( - fontSize: 46.0, - fontWeight: FontWeight.bold, - color: gradeColor( - context: context, - value: widget.grade.value.value), - shadows: [ - Shadow( - color: gradeColor( - context: context, - value: widget.grade.value.value) - .withOpacity(0.5), - blurRadius: 24.0, - ), - Shadow( - color: gradeColor( - context: context, - value: widget.grade.value.value) - .withOpacity(0.3), - offset: const Offset(-3, -3), - ), - ], + if (rr.replaceAll(' ', '') == '') { + rr = defaultRarities[widget.grade.value.value - 1].i18n; + } + + return ScaleTransition( + scale: _revealAnimGrade, + child: FadeTransition( + opacity: _revealAnimGrade, + child: SlideTransition( + position: _revealAnimGrade.drive(Tween( + begin: Offset.zero, end: const Offset(0, -0.6))), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SlideTransition( + position: _revealAnimGrade.drive(Tween( + begin: Offset.zero, + end: const Offset(0, -0.9))), + child: Text( + rr, + style: TextStyle( + fontSize: 46.0, + fontWeight: FontWeight.bold, + color: gradeColor( + context: context, + value: widget.grade.value.value), + shadows: [ + Shadow( + color: gradeColor( + context: context, + value: widget.grade.value.value) + .withOpacity(0.5), + blurRadius: 24.0, + ), + Shadow( + color: gradeColor( + context: context, + value: widget.grade.value.value) + .withOpacity(0.3), + offset: const Offset(-3, -3), + ), + ], + ), ), ), - ), - const SizedBox(height: 32.0), - ScaleTransition( - scale: CurvedAnimation( - curve: Curves.easeInOutBack, - parent: _revealAnimParticle - .drive(Tween(begin: 0.6, end: 1.0))), - child: CustomPaint( - painter: PimpPainter( - particle: Sprinkles(), - controller: _revealAnimParticle, - seed: seed + 1, - shouldPaint: shouldPaint, - ), + const SizedBox(height: 32.0), + ScaleTransition( + scale: CurvedAnimation( + curve: Curves.easeInOutBack, + parent: _revealAnimParticle + .drive(Tween(begin: 0.6, end: 1.0))), child: CustomPaint( painter: PimpPainter( particle: Sprinkles(), controller: _revealAnimParticle, - seed: seed, + seed: seed + 1, shouldPaint: shouldPaint, ), - child: RotationTransition( - turns: CurvedAnimation( - curve: Curves.easeInBack, - parent: _revealAnimGrade) - .drive(Tween(begin: 0.95, end: 1.0)), - child: GradeValueWidget( - widget.grade.value, - fill: true, - contrast: true, - shadow: true, - outline: true, - size: 100.0, + child: CustomPaint( + painter: PimpPainter( + particle: Sprinkles(), + controller: _revealAnimParticle, + seed: seed, + shouldPaint: shouldPaint, + ), + child: RotationTransition( + turns: CurvedAnimation( + curve: Curves.easeInBack, + parent: _revealAnimGrade) + .drive(Tween(begin: 0.95, end: 1.0)), + child: GradeValueWidget( + widget.grade.value, + fill: true, + contrast: true, + shadow: true, + outline: true, + size: 100.0, + ), ), ), ), ), - ), - ], + ], + ), ), ), - ), - ); - }, - ), - ], - ); - }, - ), - ); -} + ); + }, + ), + ], + ); + }, + ), + ); + } } class PimpPainter extends CustomPainter { From 5051789fc9d1db607766fa503882d6d02093c3c5 Mon Sep 17 00:00:00 2001 From: Csani Date: Wed, 29 Apr 2026 19:27:19 +0200 Subject: [PATCH 4/6] Fixed GradeCalculator to use new Grade class --- .../grades/calculator/grade_calculator.dart | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/folio_mobile_ui/lib/pages/grades/calculator/grade_calculator.dart b/folio_mobile_ui/lib/pages/grades/calculator/grade_calculator.dart index db00bc2e..f22c6b77 100644 --- a/folio_mobile_ui/lib/pages/grades/calculator/grade_calculator.dart +++ b/folio_mobile_ui/lib/pages/grades/calculator/grade_calculator.dart @@ -181,25 +181,27 @@ class GradeCalculatorState extends State { date = g.isNotEmpty ? g.first.date : DateTime.now(); } + var gradeValue = GradeValue(newValue, "", "", newWeight); + calculatorProvider.addGhost(Grade( - id: _randomId(), - date: date, - writeDate: date, - description: "Ghost Grade".i18n, - value: GradeValue(newValue, "", "", newWeight), - teacher: Teacher.fromString("Ghost"), - type: GradeType.ghost, - form: "", - subject: widget.subject ?? - GradeSubject( - id: _randomId(), - category: Category(id: _randomId()), - name: 'All', - ), - mode: Category.fromJson({}), - seenDate: DateTime(0), - groupId: "", - )); + id: _randomId(), + date: date, + writeDate: date, + description: "Ghost Grade".i18n, + value: gradeValue, + teacher: Teacher.fromString("Ghost"), + type: GradeType.ghost, + form: "", + subject: widget.subject ?? + GradeSubject( + id: _randomId(), + category: Category(id: _randomId()), + name: 'All', + ), + mode: Category.fromJson({}), + seenDate: DateTime(0), + groupId: "", + rarity: gradeValue.getRarity())); }, child: Text( "Add Grade".i18n, From 27bc93d4b05e655a84e2c89d1c288d9891f3a661 Mon Sep 17 00:00:00 2001 From: Csani Date: Wed, 29 Apr 2026 19:27:31 +0200 Subject: [PATCH 5/6] Fixed formatting --- .../lib/common/widgets/grade/new_grades.dart | 29 +- .../lib/screens/settings/settings_screen.dart | 272 +++++++++--------- 2 files changed, 155 insertions(+), 146 deletions(-) diff --git a/folio_mobile_ui/lib/common/widgets/grade/new_grades.dart b/folio_mobile_ui/lib/common/widgets/grade/new_grades.dart index 5822ed14..f8f0e7d2 100644 --- a/folio_mobile_ui/lib/common/widgets/grade/new_grades.dart +++ b/folio_mobile_ui/lib/common/widgets/grade/new_grades.dart @@ -39,21 +39,20 @@ class NewGradesSurprise extends StatelessWidget { height: 44, child: Center( child: Container( - decoration: BoxDecoration(boxShadow: [ - BoxShadow( - color: Theme.of(context) - .colorScheme - .secondary - .withValues(alpha: .5), - blurRadius: 18.0, - ) - ]), - child: Icon( - Icons.backpack, - size: 36.0, - color: Theme.of(context).colorScheme.secondary, - ) - ), + decoration: BoxDecoration(boxShadow: [ + BoxShadow( + color: Theme.of(context) + .colorScheme + .secondary + .withValues(alpha: .5), + blurRadius: 18.0, + ) + ]), + child: Icon( + Icons.backpack, + size: 36.0, + color: Theme.of(context).colorScheme.secondary, + )), ), ), title: censored diff --git a/folio_mobile_ui/lib/screens/settings/settings_screen.dart b/folio_mobile_ui/lib/screens/settings/settings_screen.dart index 1d546285..a0af3c92 100644 --- a/folio_mobile_ui/lib/screens/settings/settings_screen.dart +++ b/folio_mobile_ui/lib/screens/settings/settings_screen.dart @@ -524,8 +524,7 @@ class SettingsScreenState extends State return Container( margin: const EdgeInsets.only(left: 10.0), decoration: BoxDecoration( - color: - Theme.of(context).colorScheme.secondary.withValues(alpha: 0.06), + color: Theme.of(context).colorScheme.secondary.withValues(alpha: 0.06), borderRadius: BorderRadius.circular(4.0), ), child: child, @@ -1131,63 +1130,64 @@ class SettingsScreenState extends State padding: const EdgeInsets.only(left: 14.0, right: 6.0), onPressed: () { SettingsHelper.bellDelay(context); - setState(() {}); + setState(() {}); + }, + title: Text("bell_delay".i18n, + style: TextStyle( + color: AppColors.of(context).text.withValues( + alpha: settings.bellDelayEnabled ? .95 : .25))), + leading: Icon( + settings.bellDelayEnabled + ? Icons.notifications_outlined + : Icons.notifications_off_rounded, + size: 22.0, + color: AppColors.of(context) + .text + .withValues(alpha: settings.bellDelayEnabled ? .95 : .25), + ), + trailingDivider: true, + trailing: Switch( + onChanged: (v) { + _haptic(); + settings.update(bellDelayEnabled: v); }, - title: Text("bell_delay".i18n, - style: TextStyle( - color: AppColors.of(context).text.withValues( - alpha: settings.bellDelayEnabled ? .95 : .25))), - leading: Icon( - settings.bellDelayEnabled - ? Icons.notifications_outlined - : Icons.notifications_off_rounded, + value: settings.bellDelayEnabled, + activeColor: Theme.of(context).colorScheme.secondary, + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), bottom: Radius.circular(4.0)), + ), + PanelButton( + padding: const EdgeInsets.only(left: 14.0, right: 6.0), + onPressed: () { + settings.update(showBreaks: !settings.showBreaks); + setState(() {}); + }, + title: Text("show_breaks".i18n, + style: TextStyle( + color: AppColors.of(context) + .text + .withValues(alpha: settings.showBreaks ? .95 : .25))), + leading: Icon( + settings.showBreaks + ? Icons.visibility_rounded + : Icons.visibility_off_rounded, size: 22.0, color: AppColors.of(context) .text - .withValues(alpha: settings.bellDelayEnabled ? .95 : .25), - ), - trailingDivider: true, - trailing: Switch( - onChanged: (v) { - _haptic(); - settings.update(bellDelayEnabled: v); - }, - value: settings.bellDelayEnabled, - activeColor: Theme.of(context).colorScheme.secondary, - ), - borderRadius: const BorderRadius.vertical( - top: Radius.circular(12.0), bottom: Radius.circular(4.0)), - ), - PanelButton( - padding: const EdgeInsets.only(left: 14.0, right: 6.0), - onPressed: () { - settings.update(showBreaks: !settings.showBreaks); - setState(() {}); + .withValues(alpha: settings.showBreaks ? .95 : .25)), + trailing: Switch( + onChanged: (v) { + _haptic(); + settings.update(showBreaks: v); }, - title: Text("show_breaks".i18n, - style: TextStyle( - color: AppColors.of(context).text.withValues( - alpha: settings.showBreaks ? .95 : .25))), - leading: Icon( - settings.showBreaks - ? Icons.visibility_rounded - : Icons.visibility_off_rounded, - size: 22.0, - color: AppColors.of(context) - .text - .withValues(alpha: settings.showBreaks ? .95 : .25)), - trailing: Switch( - onChanged: (v) { - _haptic(); - settings.update(showBreaks: v); - }, - value: settings.showBreaks, - activeColor: Theme.of(context).colorScheme.secondary, - ), - borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), bottom: Radius.circular(12.0)), + value: settings.showBreaks, + activeColor: Theme.of(context).colorScheme.secondary, ), - ], + borderRadius: const BorderRadius.vertical( + top: Radius.circular(4.0), bottom: Radius.circular(12.0)), + ), + ], ), ), @@ -1199,7 +1199,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: true, children: [ @@ -1275,7 +1276,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -1293,8 +1295,7 @@ class SettingsScreenState extends State 'push_notifications'.i18n, style: TextStyle( color: AppColors.of(context).text.withValues( - alpha: - settings.notificationsEnabled ? .95 : .25)), + alpha: settings.notificationsEnabled ? .95 : .25)), ), leading: Icon( settings.notificationsEnabled @@ -1342,8 +1343,7 @@ class SettingsScreenState extends State Icons.bookmark_rounded, size: 22.0, color: AppColors.of(context).text.withValues( - alpha: - settings.notificationsGradesEnabled ? .95 : .25), + alpha: settings.notificationsGradesEnabled ? .95 : .25), ), trailing: Switch( onChanged: (v) { @@ -1355,8 +1355,7 @@ class SettingsScreenState extends State activeColor: Theme.of(context).colorScheme.secondary, ), borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), - bottom: Radius.circular(4.0)), + top: Radius.circular(4.0), bottom: Radius.circular(4.0)), )), // Absences _subSetting(PanelButton( @@ -1393,8 +1392,7 @@ class SettingsScreenState extends State activeColor: Theme.of(context).colorScheme.secondary, ), borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), - bottom: Radius.circular(4.0)), + top: Radius.circular(4.0), bottom: Radius.circular(4.0)), )), // Messages _subSetting(PanelButton( @@ -1431,8 +1429,7 @@ class SettingsScreenState extends State activeColor: Theme.of(context).colorScheme.secondary, ), borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), - bottom: Radius.circular(4.0)), + top: Radius.circular(4.0), bottom: Radius.circular(4.0)), )), // Lessons _subSetting(PanelButton( @@ -1469,8 +1466,7 @@ class SettingsScreenState extends State activeColor: Theme.of(context).colorScheme.secondary, ), borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), - bottom: Radius.circular(12.0)), + top: Radius.circular(4.0), bottom: Radius.circular(12.0)), )), ], ], @@ -1495,7 +1491,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -1602,7 +1599,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -1779,7 +1777,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -1832,7 +1831,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -1888,7 +1888,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -1958,7 +1959,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -2048,7 +2050,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -2100,7 +2103,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -2127,7 +2131,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -2196,7 +2201,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: true, children: [ @@ -2247,7 +2253,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -2314,7 +2321,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: false, children: [ @@ -2442,7 +2450,7 @@ class SettingsScreenState extends State ), borderRadius: const BorderRadius.vertical( top: Radius.circular(4.0), bottom: Radius.circular(12.0)), - ), + ) ], ), ), @@ -2480,7 +2488,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), isSeparated: true, children: [ @@ -2529,7 +2538,8 @@ class SettingsScreenState extends State widget: Padding( padding: EdgeInsets.zero, child: SplittedPanel( - padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), + padding: + const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), children: [ Tooltip( @@ -2610,48 +2620,48 @@ class SettingsScreenState extends State padding: const EdgeInsets.only(bottom: 14.0, left: 24.0, right: 24.0), cardPadding: const EdgeInsets.all(4.0), children: [ - PanelButton( - leading: Icon(Icons.lock_outline_rounded, - size: 22.0, - color: AppColors.of(context).text.withValues(alpha: 0.95)), - title: Text("privacy".i18n), - onPressed: () => _openPrivacy(context), - borderRadius: const BorderRadius.vertical( - top: Radius.circular(12.0), bottom: Radius.circular(4.0)), - ), - PanelButton( - leading: Icon(Icons.alternate_email_rounded, - size: 22.0, - color: AppColors.of(context).text.withValues(alpha: 0.95)), - title: const Text("Discord"), - onPressed: () => launchUrl( - Uri.parse("https://discord.gg/6DvjyPAw2T"), - mode: LaunchMode.externalApplication), - borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), bottom: Radius.circular(4.0)), - ), - PanelButton( - leading: Icon(Icons.code_rounded, - size: 22.0, - color: AppColors.of(context).text.withValues(alpha: 0.95)), - title: const Text("GitHub"), - onPressed: () => launchUrl( - Uri.parse("https://github.com/zan1456/folio"), - mode: LaunchMode.externalApplication), - borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), bottom: Radius.circular(4.0)), - ), - PanelButton( - leading: Icon(Icons.emoji_events_rounded, - size: 22.0, - color: AppColors.of(context).text.withValues(alpha: 0.95)), - title: Text("licenses".i18n), - onPressed: () => showLicensePage(context: context), - borderRadius: const BorderRadius.vertical( - top: Radius.circular(4.0), bottom: Radius.circular(12.0)), - ), - ], - ), + PanelButton( + leading: Icon(Icons.lock_outline_rounded, + size: 22.0, + color: AppColors.of(context).text.withValues(alpha: 0.95)), + title: Text("privacy".i18n), + onPressed: () => _openPrivacy(context), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), bottom: Radius.circular(4.0)), + ), + PanelButton( + leading: Icon(Icons.alternate_email_rounded, + size: 22.0, + color: AppColors.of(context).text.withValues(alpha: 0.95)), + title: const Text("Discord"), + onPressed: () => launchUrl( + Uri.parse("https://discord.gg/6DvjyPAw2T"), + mode: LaunchMode.externalApplication), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(4.0), bottom: Radius.circular(4.0)), + ), + PanelButton( + leading: Icon(Icons.code_rounded, + size: 22.0, + color: AppColors.of(context).text.withValues(alpha: 0.95)), + title: const Text("GitHub"), + onPressed: () => launchUrl( + Uri.parse("https://github.com/zan1456/folio"), + mode: LaunchMode.externalApplication), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(4.0), bottom: Radius.circular(4.0)), + ), + PanelButton( + leading: Icon(Icons.emoji_events_rounded, + size: 22.0, + color: AppColors.of(context).text.withValues(alpha: 0.95)), + title: Text("licenses".i18n), + onPressed: () => showLicensePage(context: context), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(4.0), bottom: Radius.circular(12.0)), + ), + ], + ), ), ]; } @@ -2702,7 +2712,8 @@ class SettingsScreenState extends State if (pendingCode != null) PanelButton( padding: const EdgeInsets.only(left: 14.0, right: 14.0), - onPressed: () => _showPairingDialog(context, wear, pendingCode), + onPressed: () => + _showPairingDialog(context, wear, pendingCode), title: const Text( 'WearOS app találva — Csatlakoztatás', style: TextStyle(fontWeight: FontWeight.w700), @@ -3059,8 +3070,7 @@ class _PairingDialogState extends State<_PairingDialog> { @override Widget build(BuildContext context) { return AlertDialog( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0)), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0)), icon: const Icon(Icons.watch_rounded, size: 36.0, color: Colors.green), title: const Text( 'WearOS app találva', @@ -3091,8 +3101,8 @@ class _PairingDialogState extends State<_PairingDialog> { hintText: '------', counterText: '', errorText: _isError ? 'Helytelen kód' : null, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12.0)), + border: + OutlineInputBorder(borderRadius: BorderRadius.circular(12.0)), ), ), ], @@ -3107,8 +3117,8 @@ class _PairingDialogState extends State<_PairingDialog> { ), FilledButton( onPressed: () async { - final ok = await widget.wear - .confirmPairing(_codeController.text.trim()); + final ok = + await widget.wear.confirmPairing(_codeController.text.trim()); if (ok) { if (mounted) Navigator.pop(context); } else { From 7efaf64f4a2439634845447c1ffa2e285af7b77f Mon Sep 17 00:00:00 2001 From: Csani Date: Wed, 29 Apr 2026 19:27:43 +0200 Subject: [PATCH 6/6] Fixed missing localizations --- .../lib/screens/settings/settings_helper.dart | 43 ++++++++----- .../settings/settings_screen.i18n.dart | 60 ++++++++++++++----- 2 files changed, 74 insertions(+), 29 deletions(-) diff --git a/folio_mobile_ui/lib/screens/settings/settings_helper.dart b/folio_mobile_ui/lib/screens/settings/settings_helper.dart index 00f62e3f..4c9d4c5b 100644 --- a/folio_mobile_ui/lib/screens/settings/settings_helper.dart +++ b/folio_mobile_ui/lib/screens/settings/settings_helper.dart @@ -1036,7 +1036,13 @@ class GradeRarityTextSetting extends StatefulWidget { class _GradeRarityTextSettingState extends State { late List _controllers; - final List _keys = ['common', 'uncommon', 'rare', 'epic', 'legendary']; + final List _keys = [ + 'common', + 'uncommon', + 'rare', + 'epic', + 'legendary' + ]; @override void initState() { @@ -1081,16 +1087,18 @@ class _GradeRarityTextSettingState extends State { style: const TextStyle(fontSize: 18.0, fontWeight: FontWeight.w700), ), const SizedBox(height: 12.0), - ...List.generate(_controllers.length, (i) => Padding( - padding: const EdgeInsets.only(bottom: 10.0), - child: TextField( - controller: _controllers[i], - decoration: InputDecoration( - labelText: _keys[i], - border: const OutlineInputBorder(), - ), - ), - )), + ...List.generate( + _controllers.length, + (i) => Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: TextField( + controller: _controllers[i], + decoration: InputDecoration( + labelText: SettingsLocalization(_keys[i]).i18n, + border: const OutlineInputBorder(), + ), + ), + )), const SizedBox(height: 8.0), Row( mainAxisAlignment: MainAxisAlignment.end, @@ -1102,13 +1110,18 @@ class _GradeRarityTextSettingState extends State { const SizedBox(width: 8.0), FilledButton( onPressed: () async { - final db = Provider.of(context, listen: false); - final user = Provider.of(context, listen: false); + final db = + Provider.of(context, listen: false); + final user = + Provider.of(context, listen: false); final Map rarities = {}; - for (int i = 0; i < _keys.length && i < _controllers.length; i++) { + for (int i = 0; + i < _keys.length && i < _controllers.length; + i++) { rarities[_keys[i]] = _controllers[i].text; } - await db.userStore.storeGradeRarities(rarities, userId: user.id!); + await db.userStore + .storeGradeRarities(rarities, userId: user.id!); Navigator.of(context).maybePop(); }, child: Text(widget.done), diff --git a/folio_mobile_ui/lib/screens/settings/settings_screen.i18n.dart b/folio_mobile_ui/lib/screens/settings/settings_screen.i18n.dart index 47e3cb86..655a1a0b 100644 --- a/folio_mobile_ui/lib/screens/settings/settings_screen.i18n.dart +++ b/folio_mobile_ui/lib/screens/settings/settings_screen.i18n.dart @@ -1,8 +1,7 @@ import 'package:i18n_extension/i18n_extension.dart'; extension SettingsLocalization on String { - static final _t = - Translations.byLocale("hu-HU") + + static final _t = Translations.byLocale("hu-HU") + { "en-US": { "heads_up": "Heads up!", @@ -227,10 +226,14 @@ extension SettingsLocalization on String { "notification_messages": "Messages", "notification_lessons": "Lessons", "firebase_reg_title": "Firebase Registration", - "firebase_reg_registered": "Device is registered and the FCM token is up to date.", - "firebase_reg_not_registered": "Device is not registered for push notifications.", - "firebase_reg_token_mismatch": "Registered, but FCM token has changed. Will update on next launch.", - "firebase_reg_no_token": "Could not retrieve FCM token from Firebase.", + "firebase_reg_registered": + "Device is registered and the FCM token is up to date.", + "firebase_reg_not_registered": + "Device is not registered for push notifications.", + "firebase_reg_token_mismatch": + "Registered, but FCM token has changed. Will update on next launch.", + "firebase_reg_no_token": + "Could not retrieve FCM token from Firebase.", "firebase_checking": "Checking registration status...", "countdown_enabled": "Countdown", "countdown_before_lesson": "Countdown before lessons", @@ -247,6 +250,13 @@ extension SettingsLocalization on String { "navbar_slots": "%s / %s navbar slots used", "navbar_more_fixed": "Always shown as last item", "more_empty": "All pages are in the navbar", + "rarity_title": "Rarity Text", + // default rarities + "common": "Common", + "uncommon": "Uncommon", + "rare": "Rare", + "epic": "Epic", + "legendary": "Legendary", }, "hu-HU": { "heads_up": "Figyelem!", @@ -470,10 +480,14 @@ extension SettingsLocalization on String { "notification_messages": "Üzenetek", "notification_lessons": "Órák", "firebase_reg_title": "Firebase regisztráció", - "firebase_reg_registered": "Az eszköz sikeresen regisztrálva van, és az FCM token naprakész.", - "firebase_reg_not_registered": "Az eszköz nincs regisztrálva push értesítésekre.", - "firebase_reg_token_mismatch": "Regisztrálva van, de az FCM token megváltozott. Az indításkor frissül.", - "firebase_reg_no_token": "Nem sikerült lekérni az FCM tokent a Firebase-től.", + "firebase_reg_registered": + "Az eszköz sikeresen regisztrálva van, és az FCM token naprakész.", + "firebase_reg_not_registered": + "Az eszköz nincs regisztrálva push értesítésekre.", + "firebase_reg_token_mismatch": + "Regisztrálva van, de az FCM token megváltozott. Az indításkor frissül.", + "firebase_reg_no_token": + "Nem sikerült lekérni az FCM tokent a Firebase-től.", "firebase_checking": "Regisztráció ellenőrzése...", "countdown_enabled": "Visszaszámlálás", "countdown_before_lesson": "Visszaszámlálás tanórák előtt", @@ -493,6 +507,13 @@ extension SettingsLocalization on String { "navbar_slots": "%s / %s navbar hely foglalt", "navbar_more_fixed": "Mindig az utolsó helyen jelenik meg", "more_empty": "Minden oldal a navbaron van", + "rarity_title": "Ritkaság szövege", + // default rarities + "common": "Gyakori", + "uncommon": "Nem gyakori", + "rare": "Ritka", + "epic": "Epikus", + "legendary": "Legendás", }, "de-DE": { "heads_up": "Achtung!", @@ -736,11 +757,22 @@ extension SettingsLocalization on String { "notification_messages": "Nachrichten", "notification_lessons": "Unterricht", "firebase_reg_title": "Firebase-Registrierung", - "firebase_reg_registered": "Gerät ist registriert und der FCM-Token ist aktuell.", - "firebase_reg_not_registered": "Gerät ist nicht für Push-Benachrichtigungen registriert.", - "firebase_reg_token_mismatch": "Registriert, aber FCM-Token hat sich geändert. Wird beim nächsten Start aktualisiert.", - "firebase_reg_no_token": "FCM-Token konnte nicht von Firebase abgerufen werden.", + "firebase_reg_registered": + "Gerät ist registriert und der FCM-Token ist aktuell.", + "firebase_reg_not_registered": + "Gerät ist nicht für Push-Benachrichtigungen registriert.", + "firebase_reg_token_mismatch": + "Registriert, aber FCM-Token hat sich geändert. Wird beim nächsten Start aktualisiert.", + "firebase_reg_no_token": + "FCM-Token konnte nicht von Firebase abgerufen werden.", "firebase_checking": "Registrierung wird überprüft...", + "rarity_title": "Text zur Seltenheit", + // default rarities + "common": "Gemeinsam", + "uncommon": "Gelegentlich", + "rare": "Selten", + "epic": "Episch", + "legendary": "Legendär", }, };