diff --git a/lib/ui/mobile/settings/settings_helper.dart b/lib/ui/mobile/settings/settings_helper.dart new file mode 100644 index 0000000..51cb01d --- /dev/null +++ b/lib/ui/mobile/settings/settings_helper.dart @@ -0,0 +1,144 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:provider/provider.dart'; +import 'package:refilc/api/providers/database_provider.dart'; +import 'package:refilc/api/providers/user_provider.dart'; +import 'package:refilc/models/settings.dart'; +import 'package:refilc/ui/widgets/grade/grade_tile.dart'; +import 'package:refilc_kreta_api/models/grade.dart'; + +class GradeRarityTextSetting extends StatefulWidget { + const GradeRarityTextSetting({ + super.key, + required this.title, + required this.cancel, + required this.done, + required this.defaultRarities, + }); + + final String title; + final String cancel; + final String done; + final List defaultRarities; + + @override + _GradeRarityTextSettingState createState() => _GradeRarityTextSettingState(); +} + +class _GradeRarityTextSettingState extends State { + late SettingsProvider settings; + late DatabaseProvider db; + late UserProvider user; + + final _rarityText = TextEditingController(); + + @override + void initState() { + super.initState(); + settings = Provider.of(context, listen: false); + db = Provider.of(context, listen: false); + user = Provider.of(context, listen: false); + } + + @override + Widget build(BuildContext context) { + return Column(children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(5, (index) { + return ClipOval( + child: Material( + type: MaterialType.transparency, + child: InkWell( + onTap: () async { + showRenameDialog( + title: widget.title, + cancel: widget.cancel, + done: widget.done, + rarities: + await db.userQuery.getGradeRarities(userId: user.id!), + gradeIndex: (index + 1).toString(), + defaultRarities: widget.defaultRarities, + ); + }, + child: GradeValueWidget(GradeValue(index + 1, "", "", 0), + fill: true, size: 36.0), + ), + ), + ); + }), + ), + ), + ]); + } + + void showRenameDialog( + {required String title, + required String cancel, + required String done, + required Map rarities, + required String gradeIndex, + required List defaultRarities, + required}) { + showDialog( + context: context, + builder: (context) => StatefulBuilder(builder: (context, setS) { + String? rr = rarities[gradeIndex]; + rr ??= ''; + + _rarityText.text = rr; + + return AlertDialog( + title: Text(title), + content: TextField( + controller: _rarityText, + autofocus: true, + decoration: InputDecoration( + border: const OutlineInputBorder(), + label: Text(defaultRarities[int.parse(gradeIndex) - 1]), + suffixIcon: IconButton( + icon: const Icon(FeatherIcons.x), + onPressed: () { + setState(() { + _rarityText.clear(); + }); + }, + ), + ), + ), + actions: [ + TextButton( + child: Text( + cancel, + style: const TextStyle(fontWeight: FontWeight.w500), + ), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + TextButton( + child: Text( + done, + style: const TextStyle(fontWeight: FontWeight.w500), + ), + onPressed: () { + rarities[gradeIndex] = _rarityText.text; + + Provider.of(context, listen: false) + .userStore + .storeGradeRarities(rarities, userId: user.id!); + + Navigator.of(context).pop(true); + }, + ), + ], + ); + }), + ).then((val) { + _rarityText.clear(); + }); + } +} diff --git a/lib/ui/mobile/settings/submenu/calendar_sync.dart b/lib/ui/mobile/settings/submenu/calendar_sync.dart new file mode 100644 index 0000000..4bd057e --- /dev/null +++ b/lib/ui/mobile/settings/submenu/calendar_sync.dart @@ -0,0 +1,602 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:refilc/api/providers/user_provider.dart'; +import 'package:refilc/models/linked_account.dart'; +import 'package:refilc/models/settings.dart'; +import 'package:refilc/providers/third_party_provider.dart'; +import 'package:refilc/theme/colors/colors.dart'; +import 'package:refilc_kreta_api/providers/share_provider.dart'; +import 'package:refilc_mobile_ui/common/dot.dart'; +import 'package:refilc_mobile_ui/common/panel/panel_button.dart'; +import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:provider/provider.dart'; +import 'package:refilc_mobile_ui/common/widgets/custom_segmented_control.dart'; +import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart'; + +class MenuCalendarSync extends StatelessWidget { + const MenuCalendarSync({ + super.key, + this.borderRadius = const BorderRadius.vertical( + top: Radius.circular(4.0), bottom: Radius.circular(4.0)), + }); + + final BorderRadius borderRadius; + + @override + Widget build(BuildContext context) { + return PanelButton( + onPressed: () async { + Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute( + builder: (context) => const CalendarSyncScreen())); + }, + title: Text( + "calendar_sync".i18n, + style: TextStyle( + color: AppColors.of(context).text.withOpacity(.95), + ), + ), + leading: Icon( + FeatherIcons.calendar, + size: 22.0, + color: AppColors.of(context).text.withOpacity(.95), + ), + trailing: Icon( + FeatherIcons.chevronRight, + size: 22.0, + color: AppColors.of(context).text.withOpacity(0.95), + ), + borderRadius: borderRadius, + ); + } +} + +class CalendarSyncScreen extends StatefulWidget { + const CalendarSyncScreen({super.key}); + + @override + CalendarSyncScreenState createState() => CalendarSyncScreenState(); +} + +class CalendarSyncScreenState extends State + with SingleTickerProviderStateMixin { + late SettingsProvider settingsProvider; + late UserProvider user; + late ShareProvider shareProvider; + late ThirdPartyProvider thirdPartyProvider; + + late AnimationController _hideContainersController; + + @override + void initState() { + super.initState(); + + shareProvider = Provider.of(context, listen: false); + + _hideContainersController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 200)); + } + + @override + Widget build(BuildContext context) { + settingsProvider = Provider.of(context); + user = Provider.of(context); + thirdPartyProvider = Provider.of(context); + + return AnimatedBuilder( + animation: _hideContainersController, + builder: (context, child) => Opacity( + opacity: 1 - _hideContainersController.value, + child: Scaffold( + appBar: AppBar( + surfaceTintColor: Theme.of(context).scaffoldBackgroundColor, + leading: BackButton(color: AppColors.of(context).text), + title: Text( + "calendar_sync".i18n, + style: TextStyle(color: AppColors.of(context).text), + ), + ), + body: SingleChildScrollView( + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), + child: Column( + children: [ + // banner + Padding( + padding: const EdgeInsets.only(top: 10), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.0), + image: const DecorationImage( + image: AssetImage( + 'assets/images/banner_texture.png', + ), + fit: BoxFit.cover, + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 40, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16.0), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 4.0, + spreadRadius: 0.01, + ), + ], + ), + height: 64, + width: 64, + child: const Icon( + Icons.calendar_month, + size: 38.0, + ), + ), + const SizedBox(width: 10), + Icon( + Icons.sync_alt_outlined, + color: AppColors.of(context).text.withOpacity( + thirdPartyProvider.linkedAccounts.isEmpty + ? 0.2 + : 0.5), + size: 20.0, + ), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(16.0), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 4.0, + spreadRadius: 0.01, + ), + ], + ), + child: Image.asset( + 'assets/icons/ic_rounded.png', + width: 64, + height: 64, + ), + ), + ], + ), + ), + ), + ), + + const SizedBox( + height: 18.0, + ), + // choose account if not logged in + if (thirdPartyProvider.linkedAccounts.isEmpty) + Column( + children: [ + SplittedPanel( + title: Text('choose_account'.i18n), + padding: EdgeInsets.zero, + cardPadding: const EdgeInsets.all(4.0), + isSeparated: true, + children: [ + PanelButton( + onPressed: () async { + await Provider.of(context, + listen: false) + .googleSignIn(); + + setState(() {}); + }, + title: Text( + 'Google', + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity(.95), + ), + ), + leading: Image.asset( + 'assets/images/ext_logo/google.png', + width: 24.0, + height: 24.0, + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12), + bottom: Radius.circular(12), + ), + ), + ], + ), + const SizedBox( + height: 9.0, + ), + SplittedPanel( + padding: EdgeInsets.zero, + cardPadding: const EdgeInsets.all(4.0), + isSeparated: true, + children: [ + PanelButton( + onPressed: null, + title: Text( + 'Apple', + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity(.55), + decoration: TextDecoration.lineThrough, + ), + ), + leading: Image.asset( + 'assets/images/ext_logo/apple.png', + width: 24.0, + height: 24.0, + ), + trailing: Text( + 'soon'.i18n, + style: const TextStyle( + fontStyle: FontStyle.italic, + fontSize: 14.0), + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12), + bottom: Radius.circular(12), + ), + ), + ], + ), + ], + ), + + // show options if logged in + if (thirdPartyProvider.linkedAccounts.isNotEmpty) + Column( + children: [ + SplittedPanel( + title: Text('your_account'.i18n), + padding: EdgeInsets.zero, + cardPadding: const EdgeInsets.all(4.0), + children: [ + PanelButton( + onPressed: null, + title: Text( + thirdPartyProvider + .linkedAccounts.first.username, + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity(.95), + ), + ), + leading: Image.asset( + 'assets/images/ext_logo/${thirdPartyProvider.linkedAccounts.first.type == AccountType.google ? "google" : "apple"}.png', + width: 24.0, + height: 24.0, + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12), + bottom: Radius.circular(12), + ), + ), + PanelButton( + onPressed: () async { + await thirdPartyProvider.signOutAll(); + setState(() {}); + }, + title: Text( + 'change_account'.i18n, + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity(.95), + ), + ), + trailing: Icon( + FeatherIcons.chevronRight, + size: 22.0, + color: AppColors.of(context) + .text + .withOpacity(0.95), + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12), + bottom: Radius.circular(12), + ), + ), + ], + ), + const SizedBox( + height: 18.0, + ), + SplittedPanel( + title: Text('choose_calendar'.i18n), + padding: EdgeInsets.zero, + cardPadding: EdgeInsets.zero, + isTransparent: true, + children: getCalendarList(), + ), + const SizedBox( + height: 18.0, + ), + SplittedPanel( + title: Text('room_num_location'.i18n), + padding: EdgeInsets.zero, + cardPadding: EdgeInsets.zero, + isTransparent: true, + children: [ + CustomSegmentedControl( + onChanged: (v) { + settingsProvider.update( + calSyncRoomLocation: + v == 0 ? 'location' : 'description'); + }, + value: settingsProvider.calSyncRoomLocation == + 'location' + ? 0 + : 1, + height: 45, + children: [ + Text( + 'location'.i18n, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16.0, + ), + ), + Text( + 'description'.i18n, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16.0, + ), + ), + ], + ), + ], + ), + const SizedBox( + height: 18.0, + ), + SplittedPanel( + title: Text('options'.i18n), + padding: EdgeInsets.zero, + cardPadding: EdgeInsets.zero, + isTransparent: true, + isSeparated: true, + children: [ + SplittedPanel( + padding: EdgeInsets.zero, + cardPadding: const EdgeInsets.all(4.0), + children: [ + PanelButton( + padding: const EdgeInsets.only( + left: 14.0, right: 6.0), + onPressed: () async { + settingsProvider.update( + calSyncShowExams: + !settingsProvider.calSyncShowExams); + + setState(() {}); + }, + title: Text( + "show_exams".i18n, + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity( + settingsProvider.calSyncShowExams + ? .95 + : .25), + ), + ), + trailing: Switch( + onChanged: (v) async { + settingsProvider.update( + calSyncShowExams: v); + + setState(() {}); + }, + value: settingsProvider.calSyncShowExams, + activeColor: + Theme.of(context).colorScheme.secondary, + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), + bottom: Radius.circular(12.0), + ), + ), + ], + ), + SplittedPanel( + padding: EdgeInsets.zero, + cardPadding: const EdgeInsets.all(4.0), + children: [ + PanelButton( + padding: const EdgeInsets.only( + left: 14.0, right: 6.0), + onPressed: () async { + settingsProvider.update( + calSyncShowTeacher: !settingsProvider + .calSyncShowTeacher); + + setState(() {}); + }, + title: Text( + "show_teacher".i18n, + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity(settingsProvider + .calSyncShowTeacher + ? .95 + : .25), + ), + ), + trailing: Switch( + onChanged: (v) async { + settingsProvider.update( + calSyncShowTeacher: v); + + setState(() {}); + }, + value: settingsProvider.calSyncShowTeacher, + activeColor: + Theme.of(context).colorScheme.secondary, + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), + bottom: Radius.circular(12.0), + ), + ), + ], + ), + SplittedPanel( + padding: EdgeInsets.zero, + cardPadding: const EdgeInsets.all(4.0), + children: [ + PanelButton( + padding: const EdgeInsets.only( + left: 14.0, right: 6.0), + onPressed: () async { + settingsProvider.update( + calSyncRenamed: + !settingsProvider.calSyncRenamed); + + setState(() {}); + }, + title: Text( + "show_renamed".i18n, + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity( + settingsProvider.calSyncRenamed + ? .95 + : .25), + ), + ), + trailing: Switch( + onChanged: (v) async { + settingsProvider.update( + calSyncRenamed: v); + + setState(() {}); + }, + value: settingsProvider.calSyncRenamed, + activeColor: + Theme.of(context).colorScheme.secondary, + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), + bottom: Radius.circular(12.0), + ), + ), + ], + ), + ], + ), + ], + ), + ], + ), + ), + ), + ), + ), + ); + } + + List getCalendarList() { + // List widgets = thirdPartyProvider.googleCalendars + // .map( + // (e) => Container( + // margin: const EdgeInsets.only(bottom: 3.0), + // decoration: BoxDecoration( + // border: Border.all( + // color: Theme.of(context).colorScheme.primary.withOpacity(.25), + // width: 1.0, + // ), + // borderRadius: BorderRadius.circular(12.0), + // ), + // child: PanelButton( + // onPressed: () async { + // print((e.backgroundColor ?? '#000000').replaceAll('#', '0x')); + // setState(() {}); + // }, + // title: Text( + // e.summary ?? 'no_title'.i18n, + // style: TextStyle( + // color: AppColors.of(context).text.withOpacity(.95), + // ), + // ), + // leading: Dot( + // color: colorFromHex( + // e.backgroundColor ?? '#000', + // ) ?? + // Colors.black, + // ), + // borderRadius: const BorderRadius.vertical( + // top: Radius.circular(12), + // bottom: Radius.circular(12), + // ), + // ), + // ), + // ) + // .toList(); + + List widgets = []; + + widgets.add( + Container( + margin: const EdgeInsets.only(bottom: 3.0), + decoration: BoxDecoration( + // border: Border.all( + // color: Theme.of(context).colorScheme.primary.withOpacity(.25), + // width: 1.0, + // ), + color: AppColors.of(context).highlight, + borderRadius: BorderRadius.circular(16.0), + ), + child: PanelButton( + onPressed: null, + // onPressed: () async { + // // thirdPartyProvider.pushTimetable(context, timetable); + // setState(() {}); + // }, + title: Text( + 'reFilc - Órarend', + style: TextStyle( + color: AppColors.of(context).text.withOpacity(.95), + ), + ), + // leading: Icon( + // FeatherIcons.plus, + // size: 20.0, + // color: AppColors.of(context).text.withOpacity(0.75), + // ), + leading: Dot( + color: Theme.of(context).colorScheme.primary, + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12), + bottom: Radius.circular(12), + ), + ), + ), + ); + + return widgets; + } +} diff --git a/lib/ui/mobile/settings/welcome_message.dart b/lib/ui/mobile/settings/welcome_message.dart index 88dd012..f126646 100644 --- a/lib/ui/mobile/settings/welcome_message.dart +++ b/lib/ui/mobile/settings/welcome_message.dart @@ -16,8 +16,7 @@ class WelcomeMessagePanelButton extends StatelessWidget { late SettingsProvider settingsProvider; late UserProvider user; - WelcomeMessagePanelButton(this.settingsProvider, this.user, {Key? key}) - : super(key: key); + WelcomeMessagePanelButton(this.settingsProvider, this.user, {super.key}); @override Widget build(BuildContext context) { @@ -36,6 +35,7 @@ class WelcomeMessagePanelButton extends StatelessWidget { context: context, feature: PremiumFeature.welcomeMessage); return; } + showDialog( context: context, builder: (context) => WelcomeMessageEditor(settingsProvider)); @@ -74,7 +74,7 @@ class WelcomeMessagePanelButton extends StatelessWidget { class WelcomeMessageEditor extends StatefulWidget { late SettingsProvider settingsProvider; - WelcomeMessageEditor(this.settingsProvider, {Key? key}) : super(key: key); + WelcomeMessageEditor(this.settingsProvider, {super.key}); @override State createState() => _WelcomeMessageEditorState(); diff --git a/pubspec.yaml b/pubspec.yaml index a67a145..b69c610 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: flutter_svg: ^2.0.10+1 flutter_dynamic_icon: ^2.1.0 android_dynamic_icon: ^1.0.1 + i18n_extension: ^11.0.12 dev_dependencies: flutter_lints: ^3.0.1