diff --git a/refilc/assets/images/banner_texture.png b/refilc/assets/images/banner_texture.png new file mode 100644 index 0000000..23f6aa7 Binary files /dev/null and b/refilc/assets/images/banner_texture.png differ diff --git a/refilc/assets/images/ext_logo/apple.png b/refilc/assets/images/ext_logo/apple.png new file mode 100644 index 0000000..7c5fd14 Binary files /dev/null and b/refilc/assets/images/ext_logo/apple.png differ diff --git a/refilc/assets/images/ext_logo/google.png b/refilc/assets/images/ext_logo/google.png new file mode 100644 index 0000000..a4a9918 Binary files /dev/null and b/refilc/assets/images/ext_logo/google.png differ diff --git a/refilc/lib/database/init.dart b/refilc/lib/database/init.dart index 7637892..98a71de 100644 --- a/refilc/lib/database/init.dart +++ b/refilc/lib/database/init.dart @@ -72,6 +72,7 @@ const userDataDB = DatabaseStruct("user_data", { // v5 shit "roundings": String, "grade_rarities": String, + "linked_accounts": String, }); Future createTable(Database db, DatabaseStruct struct) => @@ -134,6 +135,7 @@ Future initDB(DatabaseProvider database) async { // v5 shit "roundings": "{}", "grade_rarities": "{}", + "linked_accounts": "{}", }); } catch (error) { print("ERROR: migrateDB: $error"); diff --git a/refilc/lib/database/query.dart b/refilc/lib/database/query.dart index d2d82cc..4758ba2 100644 --- a/refilc/lib/database/query.dart +++ b/refilc/lib/database/query.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:refilc/api/providers/database_provider.dart'; +import 'package:refilc/models/linked_account.dart'; import 'package:refilc/models/self_note.dart'; import 'package:refilc/models/subject_lesson_count.dart'; import 'package:refilc/models/user.dart'; @@ -133,7 +134,14 @@ class UserDatabaseQuery { .map((e) => SendRecipient.fromJson( e, SendRecipientType.fromJson(e != null - ? e['tipus'] + ? (e['tipus'] ?? + { + 'azonosito': '', + 'kod': '', + 'leiras': '', + 'nev': '', + 'rovidNev': '' + }) : { 'azonosito': '', 'kod': '', @@ -326,4 +334,17 @@ class UserDatabaseQuery { return (jsonDecode(raritiesJson) as Map) .map((key, value) => MapEntry(key.toString(), value.toString())); } + + Future> getLinkedAccounts( + {required String userId}) async { + List userData = + await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.isEmpty) return []; + String? accountsJson = userData.elementAt(0)["linked_accounts"] as String?; + if (accountsJson == null) return []; + List accounts = (jsonDecode(accountsJson) as List) + .map((e) => LinkedAccount.fromJson(e)) + .toList(); + return accounts; + } } diff --git a/refilc/lib/models/linked_account.dart b/refilc/lib/models/linked_account.dart new file mode 100644 index 0000000..636812c --- /dev/null +++ b/refilc/lib/models/linked_account.dart @@ -0,0 +1,35 @@ +enum AccountType { + apple, + google, + meta, + qwid, +} + +class LinkedAccount { + AccountType type; + String username; + String displayName; + String id; + + LinkedAccount({ + required this.type, + required this.username, + required this.displayName, + required this.id, + }); + + factory LinkedAccount.fromJson(Map json) { + return LinkedAccount( + type: json['type'] == 'apple' + ? AccountType.apple + : json['type'] == 'google' + ? AccountType.google + : json['type'] == 'meta' + ? AccountType.meta + : AccountType.qwid, + username: json['username'], + displayName: json['display_name'], + id: json['id'], + ); + } +} diff --git a/refilc/lib/providers/third_party_provider.dart b/refilc/lib/providers/third_party_provider.dart index 2b6af23..99dccf0 100644 --- a/refilc/lib/providers/third_party_provider.dart +++ b/refilc/lib/providers/third_party_provider.dart @@ -1,4 +1,8 @@ import 'package:extension_google_sign_in_as_googleapis_auth/extension_google_sign_in_as_googleapis_auth.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/linked_account.dart'; import 'package:refilc_kreta_api/controllers/timetable_controller.dart'; import 'package:refilc_kreta_api/models/lesson.dart'; import 'package:flutter/foundation.dart'; @@ -8,7 +12,9 @@ import 'package:google_sign_in/google_sign_in.dart'; class ThirdPartyProvider with ChangeNotifier { late List? _googleEvents; - // late BuildContext _context; + late List _linkedAccounts; + + late BuildContext _context; static final _googleSignIn = GoogleSignIn(scopes: [ CalendarApi.calendarScope, @@ -16,13 +22,31 @@ class ThirdPartyProvider with ChangeNotifier { ]); List get googleEvents => _googleEvents ?? []; + List get linkedAccounts => _linkedAccounts; ThirdPartyProvider({ required BuildContext context, + List? initialLinkedAccounts, }) { - // _context = context; + _context = context; + _linkedAccounts = initialLinkedAccounts ?? []; } + Future restore() async { + String? userId = Provider.of(_context, listen: false).id; + + // Load absences from the database + if (userId != null) { + var dbLinkedAccounts = + await Provider.of(_context, listen: false) + .userQuery + .getLinkedAccounts(userId: userId); + _linkedAccounts = dbLinkedAccounts; + } + } + + void fetch() async {} + Future googleSignIn() async { if (!await _googleSignIn.isSignedIn()) { return _googleSignIn.signIn(); diff --git a/refilc/pubspec.yaml b/refilc/pubspec.yaml index e89da14..918146e 100644 --- a/refilc/pubspec.yaml +++ b/refilc/pubspec.yaml @@ -96,6 +96,7 @@ flutter: - assets/images/ - assets/images/subject_covers/ - assets/launch_icons/ + - assets/images/ext_logo/ fonts: - family: FilcIcons diff --git a/refilc_kreta_api/lib/models/message.dart b/refilc_kreta_api/lib/models/message.dart index 9d2982c..41807f5 100644 --- a/refilc_kreta_api/lib/models/message.dart +++ b/refilc_kreta_api/lib/models/message.dart @@ -198,7 +198,7 @@ class SendRecipientType { factory SendRecipientType.fromJson(Map json) { return SendRecipientType( - id: json['azonosito'], + id: json['azonosito'] != '' ? int.parse(json['azonosito']) : 0, code: json['kod'], description: json['leiras'], name: json['nev'], diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart index e05f7ed..9907cc4 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart @@ -99,6 +99,7 @@ extension SettingsLocalization on String { "show_breaks": "Show Breaks", "fonts": "Fonts", "font_family": "Font Family", + "calendar_sync": "Calendar Sync", }, "hu_hu": { "personal_details": "Személyes információk", @@ -196,6 +197,7 @@ extension SettingsLocalization on String { "show_breaks": "Szünetek megjelenítése", "fonts": "Betűk", "font_family": "Betűtípus", + "calendar_sync": "Naptár szinkronizálás", }, "de_de": { "personal_details": "Persönliche Angaben", @@ -293,6 +295,7 @@ extension SettingsLocalization on String { "show_breaks": "Pausen anzeigen", "fonts": "Schriftarten", "font_family": "Schriftfamilie", + "calendar_sync": "heil hitler", }, }; diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/calendar_sync.dart b/refilc_mobile_ui/lib/screens/settings/submenu/calendar_sync.dart new file mode 100644 index 0000000..d2737e7 --- /dev/null +++ b/refilc_mobile_ui/lib/screens/settings/submenu/calendar_sync.dart @@ -0,0 +1,262 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:collection/collection.dart'; +import 'package:refilc/api/providers/user_provider.dart'; +import 'package:refilc/models/linked_account.dart'; +import 'package:refilc/models/settings.dart'; +import 'package:refilc/models/shared_theme.dart'; +import 'package:refilc/providers/third_party_provider.dart'; +import 'package:refilc/theme/colors/accent.dart'; +import 'package:refilc/theme/colors/colors.dart'; +import 'package:refilc/theme/observer.dart'; +import 'package:refilc_kreta_api/providers/share_provider.dart'; +import 'package:refilc_mobile_ui/common/custom_snack_bar.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/screens/settings/settings_screen.i18n.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:flutter_any_logo/flutter_logo.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 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); + + 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), + ), + 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(0.2), + size: 20.0, + ), + const SizedBox(width: 10), + Image.asset( + 'assets/icons/ic_rounded.png', + width: 64, + height: 64, + ) + ], + ), + ), + ), + ), + + const SizedBox( + height: 18.0, + ), + // choose account if not logged in + if (Provider.of(context) + .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(); + }, + 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( + 'Hamarosan'.i18n, + style: const TextStyle( + fontStyle: FontStyle.italic, + fontSize: 14.0), + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12), + bottom: Radius.circular(12), + ), + ), + ], + ), + ], + ), + + const SizedBox( + height: 18.0, + ), + // own paints + SplittedPanel( + title: Text('public_paint'.i18n), + padding: EdgeInsets.zero, + cardPadding: const EdgeInsets.all(4.0), + children: [], + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/extras_screen.dart b/refilc_mobile_ui/lib/screens/settings/submenu/extras_screen.dart index f75edef..613c387 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/extras_screen.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/extras_screen.dart @@ -9,6 +9,7 @@ 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/screens/settings/submenu/calendar_sync.dart'; import 'package:refilc_plus/models/premium_scopes.dart'; import 'package:refilc_plus/providers/premium_provider.dart'; import 'package:refilc_plus/ui/mobile/premium/upsell.dart'; @@ -147,6 +148,16 @@ class ExtrasSettingsScreenState extends State { WelcomeMessagePanelButton(settingsProvider, user), ], ), + SplittedPanel( + padding: const EdgeInsets.only(top: 9.0), + cardPadding: const EdgeInsets.all(4.0), + isSeparated: true, + children: [ + MenuCalendarSync( + borderRadius: BorderRadius.circular(12.0), + ), + ], + ), ], ), ), diff --git a/refilc_mobile_ui/pubspec.yaml b/refilc_mobile_ui/pubspec.yaml index 32762f7..1ff876d 100644 --- a/refilc_mobile_ui/pubspec.yaml +++ b/refilc_mobile_ui/pubspec.yaml @@ -65,6 +65,7 @@ dependencies: maps_launcher: ^2.2.0 google_fonts: ^6.1.0 flutter_stripe: ^10.0.0 + flutter_any_logo: ^1.1.1 dev_dependencies: flutter_lints: ^3.0.1