From 939b07f262fba845c5f8fe21753fa009fad4386c Mon Sep 17 00:00:00 2001 From: Pratik Canopas <109139581+cp-pratik-k@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:16:26 +0530 Subject: [PATCH] Dropbox sign in (#46) * Implement dropbox sign in * Implement dropbox sign in * Implement dropbox-sign-in * Fix dropbox-sign-in * Fix dropbox-sign-in * Fix dropbox-sign-in * Added feature flags * Added feature flags --- .idea/libraries/Dart_Packages.xml | 206 +++++- .idea/libraries/Flutter_Plugins.xml | 55 +- app/android/app/src/main/AndroidManifest.xml | 15 + app/assets/images/icons/dropbox.svg | 4 + app/assets/locales/app_en.arb | 9 +- app/ios/Podfile.lock | 34 +- app/ios/Runner/Info.plist | 1 + app/ios/Runner/Runner.entitlements | 4 + app/lib/domain/assets/assets_paths.dart | 1 + .../extensions/app_error_extensions.dart | 6 +- .../domain/handlers/deep_links_handler.dart | 56 ++ app/lib/main.dart | 3 + app/lib/ui/flow/accounts/accounts_screen.dart | 177 +++-- .../accounts/accounts_screen_view_model.dart | 32 +- .../flow/accounts/components/account_tab.dart | 45 +- app/lib/ui/flow/home/components/hints.dart | 49 +- .../ui/flow/home/home_screen_view_model.dart | 4 +- .../flutter/generated_plugin_registrant.cc | 4 + app/linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 4 + app/pubspec.lock | 276 +++++--- app/pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + app/windows/flutter/generated_plugins.cmake | 1 + data/.flutter-plugins | 7 + data/.flutter-plugins-dependencies | 2 +- .../apis/dropbox/dropbox_auth_endpoints.dart | 85 +++ .../google_drive/google_drive_endpoint.dart | 6 +- data/lib/apis/network/base_url.dart | 4 - data/lib/apis/network/client.dart | 21 +- .../dropbox_auth_interceptor.dart | 54 ++ ...art => google_drive_auth_interceptor.dart} | 1 + data/lib/apis/network/oauth2.dart | 54 ++ data/lib/apis/network/secrets.dart | 4 + data/lib/apis/network/urls.dart | 16 + data/lib/domain/config.dart | 5 + data/lib/errors/app_error.dart | 24 +- data/lib/errors/l10n_error_codes.dart | 7 +- data/lib/extensions/date_time_extension.dart | 3 + .../dropbox_account/dropbox_account.dart | 40 ++ .../dropbox_account.freezed.dart | 642 ++++++++++++++++++ .../dropbox_account/dropbox_account.g.dart | 58 ++ data/lib/models/token/token.dart | 40 ++ data/lib/models/token/token.freezed.dart | 297 ++++++++ data/lib/models/token/token.g.dart | 30 + data/lib/services/auth_service.dart | 126 +++- data/lib/services/dropbox_services.dart | 35 + data/lib/storage/app_preferences.dart | 30 +- .../provider/preferences_provider.dart | 33 + data/pubspec.yaml | 2 + style/lib/buttons/buttons_list.dart | 4 +- style/lib/theme/colors.dart | 1 + 52 files changed, 2291 insertions(+), 331 deletions(-) create mode 100644 app/assets/images/icons/dropbox.svg create mode 100644 app/lib/domain/handlers/deep_links_handler.dart create mode 100644 data/lib/apis/dropbox/dropbox_auth_endpoints.dart delete mode 100644 data/lib/apis/network/base_url.dart create mode 100644 data/lib/apis/network/interceptors/dropbox_auth_interceptor.dart rename data/lib/apis/network/interceptors/{auth_interceptor.dart => google_drive_auth_interceptor.dart} (96%) create mode 100644 data/lib/apis/network/oauth2.dart create mode 100644 data/lib/apis/network/secrets.dart create mode 100644 data/lib/apis/network/urls.dart create mode 100644 data/lib/domain/config.dart create mode 100644 data/lib/extensions/date_time_extension.dart create mode 100644 data/lib/models/dropbox_account/dropbox_account.dart create mode 100644 data/lib/models/dropbox_account/dropbox_account.freezed.dart create mode 100644 data/lib/models/dropbox_account/dropbox_account.g.dart create mode 100644 data/lib/models/token/token.dart create mode 100644 data/lib/models/token/token.freezed.dart create mode 100644 data/lib/models/token/token.g.dart create mode 100644 data/lib/services/dropbox_services.dart diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index a4b4973..5b84c32 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -5,6 +5,7 @@ + @@ -37,9 +38,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -75,6 +105,7 @@ + @@ -96,6 +127,7 @@ + @@ -110,6 +142,7 @@ + @@ -159,6 +192,7 @@ + @@ -173,6 +207,7 @@ + @@ -187,6 +222,7 @@ + @@ -201,6 +237,7 @@ + @@ -208,7 +245,7 @@ - @@ -250,6 +287,7 @@ + @@ -292,6 +330,7 @@ + @@ -299,6 +338,7 @@ + @@ -306,7 +346,7 @@ - @@ -327,6 +367,7 @@ + @@ -440,6 +481,7 @@ + @@ -454,7 +496,7 @@ - @@ -468,6 +510,7 @@ + @@ -482,6 +525,7 @@ + @@ -489,6 +533,7 @@ + @@ -517,6 +562,7 @@ + @@ -524,10 +570,18 @@ + + + + + + + @@ -538,7 +592,7 @@ - @@ -580,6 +634,7 @@ + @@ -630,6 +685,7 @@ + @@ -665,6 +721,7 @@ + @@ -707,7 +764,7 @@ - @@ -728,6 +785,7 @@ + @@ -749,6 +807,7 @@ + @@ -763,7 +822,7 @@ - @@ -777,14 +836,14 @@ - - @@ -819,6 +878,7 @@ + @@ -847,6 +907,7 @@ + @@ -952,6 +1013,7 @@ + @@ -1064,7 +1126,7 @@ - @@ -1085,7 +1147,7 @@ - @@ -1099,14 +1161,43 @@ + + + + + + + + + + + + + + + + + + + - + + + + + + @@ -1148,7 +1239,7 @@ - @@ -1176,28 +1267,28 @@ - - - - @@ -1239,6 +1330,7 @@ + @@ -1253,7 +1345,7 @@ - @@ -1267,13 +1359,14 @@ - + @@ -1281,6 +1374,7 @@ + @@ -1302,20 +1396,29 @@ + + + + + + + + + @@ -1323,30 +1426,38 @@ + + + + - + + + + - + + @@ -1360,26 +1471,34 @@ + - + + + + + + + - + + @@ -1388,37 +1507,43 @@ + + - + + + - + - - + + + + @@ -1434,6 +1559,7 @@ + @@ -1449,37 +1575,45 @@ - + - + + - + + + + + - + - - - - + + + + + - + - + + + diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index a10e5f6..70cf51d 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -2,44 +2,61 @@ - - + + + + - - - - - - + + + + + + + + + + + + - - + - - + + + - - - + + - - - + + + - + + + + + + + + + + + diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml index 9cea4f0..9c7b7ef 100644 --- a/app/android/app/src/main/AndroidManifest.xml +++ b/app/android/app/src/main/AndroidManifest.xml @@ -29,10 +29,25 @@ + + + + + + + + + + + + + + diff --git a/app/assets/images/icons/dropbox.svg b/app/assets/images/icons/dropbox.svg new file mode 100644 index 0000000..5559d5f --- /dev/null +++ b/app/assets/images/icons/dropbox.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/locales/app_en.arb b/app/assets/locales/app_en.arb index 3697275..9adaf0c 100644 --- a/app/assets/locales/app_en.arb +++ b/app/assets/locales/app_en.arb @@ -8,6 +8,7 @@ "common_accounts": "Accounts", "common_local": "Local", "common_google_drive": "Google Drive", + "common_dropbox": "Dropbox", "common_term_and_condition": "Terms and Conditions", "common_privacy_policy": "Privacy Policy", "common_today": "Today", @@ -28,6 +29,7 @@ "something_went_wrong_error": "Something went wrong! Please try again later.", "user_google_sign_in_account_not_found_error": "You haven't signed in with Google account yet. Please sign in with Google account and try again.", "back_up_folder_not_found_error": "Back up folder not found!", + "auth_session_expired_error": "Your session has expired. Please log in again to continue using the app.", "unable_to_load_media_error": "Unable to load media!", @@ -53,9 +55,7 @@ "greetings_hey_there_text": "Hey There!", "greetings_hey_text": "Hey", - "hint_google_sign_in_message": "Sign in with Google and effortlessly link your Google Drive to your Cloud Gallery. Enjoy quick access to all your awesome content in one spot", - "hint_google_auto_backup_message": "Enable Auto Back Up to Google Drive and never lose your precious memories. Your photos and videos will be automatically backed up to your Google Drive", - "hint_action_auto_backup": "Enable Auto Back Up", + "hint_sign_in_message": "Sign in with Google Drive or Dropbox and enjoy quick access to all your awesome content in one spot", "delete_media_from_device_confirmation_message": "Are you sure you want to delete this media? It will be permanently deleted from your device.", "delete_media_from_google_drive_confirmation_message": "Are you sure you want to delete this media? It will be permanently deleted from your Google Drive.", @@ -79,6 +79,9 @@ "download_from_google_drive_text": "Download from Google Drive", "download_from_google_drive_alert_message": " Are you sure you want to download this media? It will be saved to your gallery.", + "sign_in_with_google_drive_text": "Sign in with Google Drive", + "sign_in_with_dropbox_text": "Sign in with Dropbox", + "name_text": "Name", "size_text": "Size", "created_at_text": "Created at", diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index f6cf19a..957989c 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - app_links (0.0.2): + - Flutter - AppAuth (1.7.5): - AppAuth/Core (= 1.7.5) - AppAuth/ExternalUserAgent (= 1.7.5) @@ -7,14 +9,14 @@ PODS: - AppAuth/Core - Firebase/CoreOnly (11.4.0): - FirebaseCore (= 11.4.0) - - firebase_core (3.7.0): + - firebase_core (3.8.0): - Firebase/CoreOnly (= 11.4.0) - Flutter - FirebaseCore (11.4.0): - FirebaseCoreInternal (~> 11.0) - GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Logger (~> 8.0) - - FirebaseCoreInternal (11.4.2): + - FirebaseCoreInternal (11.5.0): - "GoogleUtilities/NSData+zlib (~> 8.0)" - Flutter (1.0.0) - flutter_local_notifications (0.0.1): @@ -23,11 +25,9 @@ PODS: - Flutter - Toast - google_sign_in_ios (0.0.1): - - AppAuth (>= 1.7.4) - Flutter - FlutterMacOS - - GoogleSignIn (~> 7.1) - - GTMSessionFetcher (>= 3.4.0) + - GoogleSignIn (~> 7.0) - GoogleSignIn (7.1.0): - AppAuth (< 2.0, >= 1.7.3) - GTMAppAuth (< 5.0, >= 4.1.1) @@ -43,11 +43,7 @@ PODS: - GTMAppAuth (4.1.1): - AppAuth/Core (~> 1.7) - GTMSessionFetcher/Core (< 4.0, >= 3.3) - - GTMSessionFetcher (3.5.0): - - GTMSessionFetcher/Full (= 3.5.0) - GTMSessionFetcher/Core (3.5.0) - - GTMSessionFetcher/Full (3.5.0): - - GTMSessionFetcher/Core - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -67,6 +63,8 @@ PODS: - Flutter - FlutterMacOS - Toast (4.1.1) + - url_launcher_ios (0.0.1): + - Flutter - video_player_avfoundation (0.0.1): - Flutter - FlutterMacOS @@ -75,6 +73,7 @@ PODS: - FlutterMacOS DEPENDENCIES: + - app_links (from `.symlinks/plugins/app_links/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - Flutter (from `Flutter`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) @@ -87,6 +86,7 @@ DEPENDENCIES: - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) @@ -103,6 +103,8 @@ SPEC REPOS: - Toast EXTERNAL SOURCES: + app_links: + :path: ".symlinks/plugins/app_links/ios" firebase_core: :path: ".symlinks/plugins/firebase_core/ios" Flutter: @@ -127,34 +129,38 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" sqflite_darwin: :path: ".symlinks/plugins/sqflite_darwin/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" video_player_avfoundation: :path: ".symlinks/plugins/video_player_avfoundation/darwin" webview_flutter_wkwebview: :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: + app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0 AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99 - firebase_core: f8c5b220a8f9c436fdbd075ae321cef3d96ef181 + firebase_core: 9efc3ecf689cdbc90f13f4dc58108c83ea46b266 FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771 - FirebaseCoreInternal: 35731192cab10797b88411be84940d2beb33a238 + FirebaseCoreInternal: f47dd28ae7782e6a4738aad3106071a8fe0af604 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_local_notifications: df98d66e515e1ca797af436137b4459b160ad8c9 fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c - google_sign_in_ios: 07375bfbf2620bc93a602c0e27160d6afc6ead38 + google_sign_in_ios: 989eea5abe94af62050782714daf920be883d4a2 GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e - video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 + url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586 + video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579 webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4 PODFILE CHECKSUM: 4c438addb11b6da45ed7ae408823d68256222460 diff --git a/app/ios/Runner/Info.plist b/app/ios/Runner/Info.plist index e68707c..a805d32 100644 --- a/app/ios/Runner/Info.plist +++ b/app/ios/Runner/Info.plist @@ -26,6 +26,7 @@ CFBundleURLSchemes com.googleusercontent.apps.64221198282-h8h8gjo3991iijeg2agdkmfh6tr014nq + cloudgallery diff --git a/app/ios/Runner/Runner.entitlements b/app/ios/Runner/Runner.entitlements index 903def2..4d2025c 100644 --- a/app/ios/Runner/Runner.entitlements +++ b/app/ios/Runner/Runner.entitlements @@ -4,5 +4,9 @@ aps-environment development + com.apple.developer.associated-domains + + applinks:cloudgallery.com + diff --git a/app/lib/domain/assets/assets_paths.dart b/app/lib/domain/assets/assets_paths.dart index 332d3d7..a639cbf 100644 --- a/app/lib/domain/assets/assets_paths.dart +++ b/app/lib/domain/assets/assets_paths.dart @@ -10,4 +10,5 @@ class PathImages { class PathIcons { String get googleDrive => 'assets/images/icons/google-drive.svg'; + String get dropbox => 'assets/images/icons/dropbox.svg'; } diff --git a/app/lib/domain/extensions/app_error_extensions.dart b/app/lib/domain/extensions/app_error_extensions.dart index 0a7e3ee..0e8fcc7 100644 --- a/app/lib/domain/extensions/app_error_extensions.dart +++ b/app/lib/domain/extensions/app_error_extensions.dart @@ -7,14 +7,16 @@ extension AppErrorExtensions on Object { String l10nMessage(BuildContext context) { if (this is AppError) { switch ((this as AppError).l10nCode) { - case AppErrorL10nCodes.noInternetConnection: + case AppErrorL10nCodes.noInternetConnectionError: return context.l10n.no_internet_connection_error; case AppErrorL10nCodes.somethingWentWrongError: return context.l10n.something_went_wrong_error; case AppErrorL10nCodes.googleSignInUserNotFoundError: return context.l10n.user_google_sign_in_account_not_found_error; - case AppErrorL10nCodes.backUpFolderNotFound: + case AppErrorL10nCodes.backUpFolderNotFoundError: return context.l10n.back_up_folder_not_found_error; + case AppErrorL10nCodes.authSessionExpiredError: + return context.l10n.auth_session_expired_error; default: return (this as AppError).message ?? context.l10n.something_went_wrong_error; diff --git a/app/lib/domain/handlers/deep_links_handler.dart b/app/lib/domain/handlers/deep_links_handler.dart new file mode 100644 index 0000000..8e93b88 --- /dev/null +++ b/app/lib/domain/handlers/deep_links_handler.dart @@ -0,0 +1,56 @@ +import 'dart:developer'; + +import 'package:app_links/app_links.dart'; +import 'package:data/apis/network/urls.dart'; +import 'package:data/services/auth_service.dart'; +import 'package:data/services/dropbox_services.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final appLinksProvider = Provider((ref) { + return AppLinks(); +}); + +class DeepLinkHandler { + static Future observeDeepLinks({ + required ProviderContainer container, + }) async { + final appLinks = container.read(appLinksProvider); + + Future handleDeepLink(Uri link) async { + if (link.toString().contains(RedirectURL.auth) && + link.queryParameters['code'] != null) { + // Set the dropbox token from the code + final authService = container.read(authServiceProvider); + await authService.setDropboxTokenFromCode( + code: link.queryParameters['code']!, + ); + + final dropboxService = container.read(dropboxServiceProvider); + await dropboxService.setCurrentUserAccount(); + } + } + + try { + final initialLink = await appLinks.getInitialLink(); + if (initialLink != null && !kDebugMode) handleDeepLink(initialLink); + } catch (error) { + log( + "Failed to handle initial deep link", + error: error, + name: "DeepLinkHandler", + ); + } + + appLinks.uriLinkStream.listen( + (link) => handleDeepLink(link), + onError: (error) { + log( + "Failed to listen to deep links", + error: error, + name: "DeepLinkHandler", + ); + }, + ); + } +} diff --git a/app/lib/main.dart b/app/lib/main.dart index 833d962..75efe01 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'domain/handlers/deep_links_handler.dart'; import 'firebase_options.dart'; import 'package:data/storage/provider/preferences_provider.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -23,6 +24,8 @@ Future main() async { final container = await _configureContainerWithAsyncDependency(); + DeepLinkHandler.observeDeepLinks(container: container); + runApp( UncontrolledProviderScope( container: container, diff --git a/app/lib/ui/flow/accounts/accounts_screen.dart b/app/lib/ui/flow/accounts/accounts_screen.dart index 2ff16e0..f1d3fe2 100644 --- a/app/lib/ui/flow/accounts/accounts_screen.dart +++ b/app/lib/ui/flow/accounts/accounts_screen.dart @@ -1,4 +1,5 @@ import '../../../components/app_page.dart'; +import '../../../domain/assets/assets_paths.dart'; import '../../../domain/extensions/context_extensions.dart'; import '../../../domain/extensions/widget_extensions.dart'; import 'accounts_screen_view_model.dart'; @@ -6,8 +7,9 @@ import 'components/settings_action_list.dart'; import 'package:data/storage/app_preferences.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:style/animations/on_tap_scale.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:style/extensions/context_extensions.dart'; import 'package:style/text/app_text_style.dart'; import 'package:style/theme/colors.dart'; @@ -15,6 +17,7 @@ import 'package:style/buttons/buttons_list.dart'; import 'package:style/buttons/switch.dart'; import '../../../components/snack_bar.dart'; import 'components/account_tab.dart'; +import 'package:data/domain/config.dart'; class AccountsScreen extends ConsumerStatefulWidget { const AccountsScreen({super.key}); @@ -45,66 +48,138 @@ class _AccountsScreenState extends ConsumerState { @override Widget build(BuildContext context) { _errorObserver(); - final googleAccount = ref.watch( - accountsStateNotifierProvider.select((value) => value.googleAccount), - ); return AppPage( title: context.l10n.common_accounts, bodyBuilder: (context) { return ListView( - padding: context.systemPadding + const EdgeInsets.all(16), + padding: context.systemPadding + + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), children: [ - if (googleAccount != null) - AccountsTab( - name: googleAccount.displayName ?? googleAccount.email, - serviceDescription: context.l10n.common_google_drive, - profileImage: googleAccount.photoUrl, - actionList: ActionList( - buttons: [ - ActionListButton( - title: context.l10n.common_auto_back_up, - trailing: Consumer( - builder: (context, ref, child) { - final googleDriveAutoBackUp = ref.watch( - AppPreferences.canTakeAutoBackUpInGoogleDrive, - ); - return AppSwitch( - value: googleDriveAutoBackUp, - onChanged: (bool value) { - ref - .read( - AppPreferences - .canTakeAutoBackUpInGoogleDrive - .notifier, - ) - .state = value; - }, - ); + _googleAccount(context: context), + if (FeatureFlags.dropboxEnabled) _dropboxAccount(context: context), + const SettingsActionList(), + const SizedBox(height: 16), + _buildVersion(context: context), + ], + ); + }, + ); + } + + Widget _googleAccount({required BuildContext context}) { + return Consumer( + builder: (context, ref, child) { + final googleAccount = ref.watch( + accountsStateNotifierProvider.select((value) => value.googleAccount), + ); + + if (googleAccount != null) { + return AccountsTab( + name: googleAccount.displayName ?? googleAccount.email, + serviceDescription: + "${context.l10n.common_google_drive} - ${googleAccount.email}", + profileImage: googleAccount.photoUrl, + actionList: ActionList( + buttons: [ + ActionListButton( + title: context.l10n.common_auto_back_up, + trailing: Consumer( + builder: (context, ref, child) { + final googleDriveAutoBackUp = + ref.watch(AppPreferences.googleDriveAutoBackUp); + return AppSwitch( + value: googleDriveAutoBackUp, + onChanged: (bool value) { + ref + .read( + AppPreferences.googleDriveAutoBackUp.notifier, + ) + .state = value; }, - ), - ), - ActionListButton( - title: context.l10n.common_sign_out, - onPressed: notifier.signOutWithGoogle, - ), - ], + ); + }, + ), ), - backgroundColor: AppColors.googleDriveColor.withAlpha(50), + ActionListButton( + title: context.l10n.common_sign_out, + onPressed: notifier.signOutWithGoogle, + ), + ], + ), + backgroundColor: AppColors.googleDriveColor.withAlpha(50), + ); + } + return ActionList( + buttons: [ + ActionListButton( + leading: SvgPicture.asset( + Assets.images.icons.googleDrive, + height: 24, + width: 24, ), - if (googleAccount == null) - OnTapScale( - onTap: () { - notifier.signInWithGoogle(); - }, - child: AccountsTab( - name: context.l10n.add_account_title, - backgroundColor: context.colorScheme.containerNormal, + title: context.l10n.sign_in_with_google_drive_text, + onPressed: () { + notifier.signInWithGoogle(); + }, + ), + ], + ); + }, + ); + } + + Widget _dropboxAccount({required BuildContext context}) { + return Consumer( + builder: (context, ref, child) { + final dropboxAccount = + ref.watch(AppPreferences.dropboxCurrentUserAccount); + if (dropboxAccount != null) { + return AccountsTab( + name: dropboxAccount.name.display_name, + serviceDescription: + "${context.l10n.common_dropbox} - ${dropboxAccount.email}", + profileImage: dropboxAccount.profile_photo_url, + actionList: ActionList( + buttons: [ + ActionListButton( + title: context.l10n.common_auto_back_up, + trailing: Consumer( + builder: (context, ref, child) { + final dropboxAutoBackUp = + ref.watch(AppPreferences.dropboxAutoBackUp); + return AppSwitch( + value: dropboxAutoBackUp, + onChanged: (bool value) { + ref + .read(AppPreferences.dropboxAutoBackUp.notifier) + .state = value; + }, + ); + }, + ), + ), + ActionListButton( + title: context.l10n.common_sign_out, + onPressed: notifier.signOutWithDropbox, ), + ], + ), + backgroundColor: AppColors.dropBoxColor.withAlpha(50), + ); + } + return ActionList( + buttons: [ + ActionListButton( + leading: SvgPicture.asset( + Assets.images.icons.dropbox, + height: 24, + width: 24, ), - const SizedBox(height: 16), - const SettingsActionList(), - const SizedBox(height: 16), - _buildVersion(context: context), + title: context.l10n.sign_in_with_dropbox_text, + onPressed: () { + notifier.signInWithDropbox(); + }, + ), ], ); }, diff --git a/app/lib/ui/flow/accounts/accounts_screen_view_model.dart b/app/lib/ui/flow/accounts/accounts_screen_view_model.dart index 5d78832..fa22f31 100644 --- a/app/lib/ui/flow/accounts/accounts_screen_view_model.dart +++ b/app/lib/ui/flow/accounts/accounts_screen_view_model.dart @@ -1,8 +1,6 @@ import 'dart:async'; import 'package:data/services/auth_service.dart'; import 'package:data/services/device_service.dart'; -import 'package:data/storage/app_preferences.dart'; -import 'package:data/storage/provider/preferences_provider.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:google_sign_in/google_sign_in.dart'; @@ -14,7 +12,6 @@ final accountsStateNotifierProvider = (ref) => AccountsStateNotifier( ref.read(deviceServiceProvider), ref.read(authServiceProvider), - ref.read(AppPreferences.canTakeAutoBackUpInGoogleDrive.notifier), ), ); @@ -22,13 +19,9 @@ class AccountsStateNotifier extends StateNotifier { final DeviceService _deviceService; final AuthService _authService; StreamSubscription? _googleAccountSubscription; - PreferenceNotifier canTakeAutoBackUpInGoogleDrive; - AccountsStateNotifier( - this._deviceService, - this._authService, - this.canTakeAutoBackUpInGoogleDrive, - ) : super(AccountsState(googleAccount: _authService.googleAccount)); + AccountsStateNotifier(this._deviceService, this._authService) + : super(AccountsState(googleAccount: _authService.googleAccount)); Future init() async { _getAppVersion(); @@ -50,6 +43,7 @@ class AccountsStateNotifier extends StateNotifier { Future signInWithGoogle() async { try { + state = state.copyWith(error: null); await _authService.signInWithGoogle(); } catch (e) { state = state.copyWith(error: e); @@ -58,8 +52,26 @@ class AccountsStateNotifier extends StateNotifier { Future signOutWithGoogle() async { try { + state = state.copyWith(error: null); await _authService.signOutWithGoogle(); - canTakeAutoBackUpInGoogleDrive.state = false; + } catch (e) { + state = state.copyWith(error: e); + } + } + + Future signInWithDropbox() async { + try { + state = state.copyWith(error: null); + await _authService.signInWithDropBox(); + } catch (e) { + state = state.copyWith(error: e); + } + } + + Future signOutWithDropbox() async { + try { + state = state.copyWith(error: null); + await _authService.signOutWithDropBox(); } catch (e) { state = state.copyWith(error: e); } diff --git a/app/lib/ui/flow/accounts/components/account_tab.dart b/app/lib/ui/flow/accounts/components/account_tab.dart index a855142..85c8439 100644 --- a/app/lib/ui/flow/accounts/components/account_tab.dart +++ b/app/lib/ui/flow/accounts/components/account_tab.dart @@ -1,3 +1,4 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/cupertino.dart'; import 'package:style/extensions/context_extensions.dart'; import 'package:style/text/app_text_style.dart'; @@ -21,6 +22,7 @@ class AccountsTab extends StatelessWidget { @override Widget build(BuildContext context) { return Container( + margin: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(12), @@ -28,38 +30,43 @@ class AccountsTab extends StatelessWidget { child: Column( children: [ Padding( - padding: const EdgeInsets.all(16), + padding: + const EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8), child: Row( children: [ _buildProfileAvtar(context: context), const SizedBox(width: 16), - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - name, - style: AppTextStyles.subtitle2.copyWith( - color: context.colorScheme.textPrimary, - ), - ), - if (serviceDescription != null) ...[ - const SizedBox(height: 4), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Text( - serviceDescription ?? '', - style: AppTextStyles.body2.copyWith( - color: context.colorScheme.textSecondary, + name, + style: AppTextStyles.subtitle2.copyWith( + color: context.colorScheme.textPrimary, + overflow: TextOverflow.ellipsis, ), ), + if (serviceDescription != null) ...[ + const SizedBox(height: 4), + Text( + serviceDescription ?? '', + style: AppTextStyles.body2.copyWith( + color: context.colorScheme.textSecondary, + overflow: TextOverflow.visible, + ), + ), + ], ], - ], + ), ), ], ), ), if (actionList != null) Padding( - padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), + padding: const EdgeInsets.only(left: 8, right: 8), child: actionList, ), ], @@ -75,7 +82,7 @@ class AccountsTab extends StatelessWidget { border: Border.all(color: context.colorScheme.outline, width: 0.8), image: profileImage != null ? DecorationImage( - image: NetworkImage(profileImage!), + image: CachedNetworkImageProvider(profileImage!), ) : null, shape: BoxShape.circle, diff --git a/app/lib/ui/flow/home/components/hints.dart b/app/lib/ui/flow/home/components/hints.dart index 0ad518f..02c2b90 100644 --- a/app/lib/ui/flow/home/components/hints.dart +++ b/app/lib/ui/flow/home/components/hints.dart @@ -1,5 +1,6 @@ import '../../../../domain/extensions/context_extensions.dart'; -import '../home_screen_view_model.dart'; +import '../../../navigation/app_route.dart'; +import 'package:data/services/auth_service.dart'; import 'package:data/storage/app_preferences.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -10,49 +11,21 @@ class HomeScreenHints extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final googleAccount = - ref.watch(homeViewStateNotifier.select((value) => value.googleAccount)); - final canTakeAutoBackUpInGoogleDrive = - ref.watch(AppPreferences.canTakeAutoBackUpInGoogleDrive); - final googleDriveSignInHintShown = - ref.watch(AppPreferences.googleDriveSignInHintShown); - final googleDriveAutoBackUpHintShown = - ref.watch(AppPreferences.googleDriveAutoBackUpHintShown); - if (!googleDriveSignInHintShown && googleAccount == null) { + final googleAccount = ref.watch(googleUserAccountProvider); + final dropboxAccount = ref.watch(AppPreferences.dropboxCurrentUserAccount); + final signInHintShown = ref.watch(AppPreferences.signInHintShown); + + if (!signInHintShown && googleAccount == null && dropboxAccount == null) { return HintView( title: context.l10n.greetings_hey_there_text, - hint: context.l10n.hint_google_sign_in_message, + hint: context.l10n.hint_sign_in_message, onClose: () { - ref.read(AppPreferences.googleDriveSignInHintShown.notifier).state = - true; + ref.read(AppPreferences.signInHintShown.notifier).state = true; }, actionTitle: context.l10n.add_account_title, onActionTap: () { - ref.read(homeViewStateNotifier.notifier).signInWithGoogle(); - ref.read(AppPreferences.googleDriveSignInHintShown.notifier).state = - true; - }, - ); - } else if (googleAccount != null && - !googleDriveAutoBackUpHintShown && - !canTakeAutoBackUpInGoogleDrive) { - return HintView( - title: - "${context.l10n.greetings_hey_text} ${googleAccount.displayName?.split(' ').first ?? "There"}!", - hint: context.l10n.hint_google_auto_backup_message, - onClose: () { - ref - .read(AppPreferences.googleDriveAutoBackUpHintShown.notifier) - .state = true; - }, - actionTitle: context.l10n.hint_action_auto_backup, - onActionTap: () { - ref - .read(AppPreferences.canTakeAutoBackUpInGoogleDrive.notifier) - .state = true; - ref - .read(AppPreferences.googleDriveAutoBackUpHintShown.notifier) - .state = true; + AccountRoute().push(context); + ref.read(AppPreferences.signInHintShown.notifier).state = true; }, ); } else { diff --git a/app/lib/ui/flow/home/home_screen_view_model.dart b/app/lib/ui/flow/home/home_screen_view_model.dart index 20c3fc1..9339a1a 100644 --- a/app/lib/ui/flow/home/home_screen_view_model.dart +++ b/app/lib/ui/flow/home/home_screen_view_model.dart @@ -26,10 +26,10 @@ final homeViewStateNotifier = ref.read(googleDriveServiceProvider), ref.read(authServiceProvider), ref.read(googleDriveProcessRepoProvider), - ref.read(AppPreferences.canTakeAutoBackUpInGoogleDrive), + ref.read(AppPreferences.googleDriveAutoBackUp), ); - ref.listen(AppPreferences.canTakeAutoBackUpInGoogleDrive, (previous, next) { + ref.listen(AppPreferences.googleDriveAutoBackUp, (previous, next) { homeView.updateAutoBackUpStatus(next); }); return homeView; diff --git a/app/linux/flutter/generated_plugin_registrant.cc b/app/linux/flutter/generated_plugin_registrant.cc index f6f23bf..3792af4 100644 --- a/app/linux/flutter/generated_plugin_registrant.cc +++ b/app/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) gtk_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); + gtk_plugin_register_with_registrar(gtk_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/app/linux/flutter/generated_plugins.cmake b/app/linux/flutter/generated_plugins.cmake index f16b4c3..5d07423 100644 --- a/app/linux/flutter/generated_plugins.cmake +++ b/app/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + gtk url_launcher_linux ) diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift index 7d5f5c0..01d269d 100644 --- a/app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import app_links import firebase_core import flutter_local_notifications import google_sign_in_ios @@ -14,10 +15,12 @@ import photo_manager import share_plus import shared_preferences_foundation import sqflite_darwin +import url_launcher_macos import video_player_avfoundation import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) @@ -27,6 +30,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) } diff --git a/app/pubspec.lock b/app/pubspec.lock index 7faa8bf..ad28987 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _discoveryapis_commons - sha256: "113c4100b90a5b70a983541782431b82168b3cae166ab130649c36eb3559d498" + sha256: f8bb1fdbd77f3d5c1d62b5b0eca75fbf1e41bf4f6c62628f880582e2182ae45d url: "https://pub.dev" source: hosted - version: "1.0.7" + version: "1.0.6" _fe_analyzer_shared: dependency: transitive description: @@ -38,14 +38,46 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.3" + app_links: + dependency: "direct main" + description: + name: app_links + sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" args: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.4.2" async: dependency: transitive description: @@ -82,10 +114,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.1" build_resolvers: dependency: transitive description: @@ -106,10 +138,10 @@ packages: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "7.3.0" built_collection: dependency: transitive description: @@ -122,10 +154,10 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.9.1" cached_network_image: dependency: "direct main" description: @@ -178,10 +210,10 @@ packages: dependency: transitive description: name: cli_util - sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.4.1" clock: dependency: transitive description: @@ -194,10 +226,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.10.1" + version: "4.10.0" collection: dependency: "direct main" description: @@ -210,10 +242,10 @@ packages: dependency: transitive description: name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.1" cross_file: dependency: transitive description: @@ -226,18 +258,18 @@ packages: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.3" csslib: dependency: transitive description: name: csslib - sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.0" cupertino_icons: dependency: "direct main" description: @@ -282,10 +314,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "2.3.6" data: dependency: "direct main" description: @@ -337,26 +369,26 @@ packages: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.2" file: dependency: transitive description: name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.0" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: e59141ff83e70a9ba571a1f8733c5598cf57e6e68037ab185581d7fc0a436738 + sha256: "2438a75ad803e818ad3bd5df49137ee619c46b6fc7101f4dbc23da07305ce553" url: "https://pub.dev" source: hosted - version: "3.7.0" + version: "3.8.0" firebase_core_platform_interface: dependency: transitive description: @@ -377,10 +409,10 @@ packages: dependency: transitive description: name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -493,10 +525,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "3.2.0" glob: dependency: transitive description: @@ -509,10 +541,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: ce89c5a993ca5eea74535f798478502c30a625ecb10a1de4d7fef5cd1bcac2a4 + sha256: "8ae664a70174163b9f65ea68dd8673e29db8f9095de7b5cd00e167c621f4fef5" url: "https://pub.dev" source: hosted - version: "14.4.1" + version: "14.6.0" go_router_builder: dependency: "direct dev" description: @@ -525,10 +557,10 @@ packages: dependency: transitive description: name: google_identity_services_web - sha256: "304e2a4c25d84c287df6d7ebf5b93d8bbd4ceb817d96c2686d44f6a346f227b6" + sha256: bf215f340648e78697fdac486d75fca67f85944502e579bdcecd029babc6f4d8 url: "https://pub.dev" source: hosted - version: "0.3.1+5" + version: "0.3.2" google_sign_in: dependency: "direct main" description: @@ -541,18 +573,18 @@ packages: dependency: transitive description: name: google_sign_in_android - sha256: "0928059d2f0840f63c7b07a30cf73b593ae872cdd0dbd46d1b9ba878d2599c01" + sha256: bfd42c81c30c6faba16e0f62968d5505a87504aaa672b3155ee931461abb0a49 url: "https://pub.dev" source: hosted - version: "6.1.33" + version: "6.1.21" google_sign_in_ios: dependency: transitive description: name: google_sign_in_ios - sha256: "83f015169102df1ab2905cf8abd8934e28f87db9ace7a5fa676998842fed228a" + sha256: a7d653803468d30b82ceb47ea00fe86d23c56e63eb2e5c2248bb68e9df203217 url: "https://pub.dev" source: hosted - version: "5.7.8" + version: "5.7.4" google_sign_in_platform_interface: dependency: transitive description: @@ -581,18 +613,26 @@ packages: dependency: transitive description: name: googleapis_auth - sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938 + sha256: cafc46446574fd42826aa4cd4d623c94482598fda0a5a5649bf2781bcbc09258 url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "1.5.0" graphs: dependency: transitive description: name: graphs - sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.1" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.dev" + source: hosted + version: "2.1.0" hotreloader: dependency: transitive description: @@ -605,10 +645,10 @@ packages: dependency: transitive description: name: html - sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" url: "https://pub.dev" source: hosted - version: "0.15.5" + version: "0.15.4" http: dependency: transitive description: @@ -653,10 +693,10 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.6.7" json_annotation: dependency: transitive description: @@ -709,10 +749,10 @@ packages: dependency: transitive description: name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.0" macros: dependency: transitive description: @@ -749,10 +789,10 @@ packages: dependency: transitive description: name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "1.0.5" octo_image: dependency: transitive description: @@ -797,10 +837,10 @@ packages: dependency: transitive description: name: path_parsing - sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.0.1" path_provider: dependency: "direct main" description: @@ -821,10 +861,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -845,10 +885,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.2.1" permission_handler: dependency: "direct main" description: @@ -861,10 +901,10 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" + sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474" url: "https://pub.dev" source: hosted - version: "12.0.13" + version: "12.0.5" permission_handler_apple: dependency: transitive description: @@ -877,18 +917,18 @@ packages: dependency: transitive description: name: permission_handler_html - sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851 + sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d" url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.1" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9 + sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78" url: "https://pub.dev" source: hosted - version: "4.2.3" + version: "4.2.0" permission_handler_windows: dependency: transitive description: @@ -925,10 +965,10 @@ packages: dependency: transitive description: name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.6" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: @@ -957,10 +997,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.3" riverpod: dependency: transitive description: @@ -1077,10 +1117,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "1.0.4" sky_engine: dependency: transitive description: flutter @@ -1209,10 +1249,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" url: "https://pub.dev" source: hosted - version: "3.3.0+3" + version: "3.1.0+1" term_glyph: dependency: transitive description: @@ -1233,10 +1273,10 @@ packages: dependency: transitive description: name: timezone - sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0" url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.2" timing: dependency: transitive description: @@ -1249,18 +1289,50 @@ packages: dependency: transitive description: name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" + url: "https://pub.dev" + source: hosted + version: "6.2.6" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "6.2.5" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.1.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" url_launcher_platform_interface: dependency: transitive description: @@ -1305,10 +1377,10 @@ packages: dependency: transitive description: name: vector_graphics_codec - sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.12" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: @@ -1337,34 +1409,34 @@ packages: dependency: transitive description: name: video_player_android - sha256: "391e092ba4abe2f93b3e625bd6b6a6ec7d7414279462c1c0ee42b5ab8d0a0898" + sha256: "4dd9b8b86d70d65eecf3dcabfcdfbb9c9115d244d022654aba49a00336d540c2" url: "https://pub.dev" source: hosted - version: "2.7.16" + version: "2.4.12" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: cd5ab8a8bc0eab65ab0cea40304097edc46da574c8c1ecdee96f28cd8ef3792f + sha256: "309e3962795e761be010869bae65c0b0e45b5230c5cee1bec72197ca7db040ed" url: "https://pub.dev" source: hosted - version: "2.6.2" + version: "2.5.6" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - sha256: "229d7642ccd9f3dc4aba169609dd6b5f3f443bb4cc15b82f7785fcada5af9bbb" + sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" url: "https://pub.dev" source: hosted - version: "6.2.3" + version: "6.2.2" video_player_web: dependency: transitive description: name: video_player_web - sha256: "881b375a934d8ebf868c7fb1423b2bfaa393a0a265fa3f733079a86536064a10" + sha256: "8e9cb7fe94e49490e67bbc15149691792b58a0ade31b32e3f3688d104a0e057b" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.2.0" visibility_detector: dependency: "direct main" description: @@ -1397,22 +1469,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - web_socket: - dependency: transitive - description: - name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" - url: "https://pub.dev" - source: hosted - version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "2.4.0" webview_flutter: dependency: "direct main" description: @@ -1425,10 +1489,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: dec83a8da0a2dcd8a25418534cc59348dbc2855fa1dd0cc929c62b6029fde392 + sha256: "86c2d01c37c4578ee46560109cf2e18fb271f0d080a796f09188d0952352e057" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" webview_flutter_platform_interface: dependency: transitive description: @@ -1441,26 +1505,26 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: f14ee08021772fed913da8daebcfdeb46be457081e521e93e9918fe6cd1ce9e8 + sha256: "3be297aa4ca78205abdd284cf55f168c35246c75b3079990ad8ba9d257681a30" url: "https://pub.dev" source: hosted - version: "3.16.1" + version: "3.16.2" win32: dependency: transitive description: name: win32 - sha256: "84ba388638ed7a8cb3445a320c8273136ab2631cd5f2c57888335504ddab1bc2" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "5.8.0" + version: "5.5.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.0.4" xml: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 4eb3d1f..d8a3583 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -52,6 +52,7 @@ dependencies: flutter_local_notifications: ^18.0.1 share_plus: ^10.1.2 webview_flutter: ^4.10.0 + app_links: ^6.3.2 photo_manager: ^3.6.2 photo_manager_image_provider: ^2.2.0 diff --git a/app/windows/flutter/generated_plugin_registrant.cc b/app/windows/flutter/generated_plugin_registrant.cc index df715fe..d21bfa4 100644 --- a/app/windows/flutter/generated_plugin_registrant.cc +++ b/app/windows/flutter/generated_plugin_registrant.cc @@ -6,12 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); PermissionHandlerWindowsPluginRegisterWithRegistrar( diff --git a/app/windows/flutter/generated_plugins.cmake b/app/windows/flutter/generated_plugins.cmake index 2c858c6..5b2c499 100644 --- a/app/windows/flutter/generated_plugins.cmake +++ b/app/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + app_links firebase_core permission_handler_windows share_plus diff --git a/data/.flutter-plugins b/data/.flutter-plugins index 7b899f9..2d28e59 100644 --- a/data/.flutter-plugins +++ b/data/.flutter-plugins @@ -16,3 +16,10 @@ shared_preferences_foundation=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/sha shared_preferences_linux=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/ shared_preferences_web=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.2/ shared_preferences_windows=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/ +url_launcher=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher-6.3.1/ +url_launcher_android=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.14/ +url_launcher_ios=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.1/ +url_launcher_linux=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/ +url_launcher_macos=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.1/ +url_launcher_web=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_web-2.3.3/ +url_launcher_windows=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.3/ diff --git a/data/.flutter-plugins-dependencies b/data/.flutter-plugins-dependencies index 347d170..5232ece 100644 --- a/data/.flutter-plugins-dependencies +++ b/data/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.8/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.0/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.6.2/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"google_sign_in_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_android-6.1.33/","native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_android-2.2.12/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.6.2/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_android-2.3.3/","native_build":true,"dependencies":[]}],"macos":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.8/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.0/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.6.2/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","native_build":false,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"google_sign_in_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_web-0.12.4+3/","dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.2/","dependencies":[]}]},"dependencyGraph":[{"name":"google_sign_in","dependencies":["google_sign_in_android","google_sign_in_ios","google_sign_in_web"]},{"name":"google_sign_in_android","dependencies":[]},{"name":"google_sign_in_ios","dependencies":[]},{"name":"google_sign_in_web","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"photo_manager","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2024-11-12 12:27:29.401092","version":"3.24.4","swift_package_manager_enabled":false} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.8/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.0/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.6.2/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"url_launcher_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.1/","native_build":true,"dependencies":[]}],"android":[{"name":"google_sign_in_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_android-6.1.33/","native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_android-2.2.12/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.6.2/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_android-2.3.3/","native_build":true,"dependencies":[]},{"name":"url_launcher_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_android-6.3.14/","native_build":true,"dependencies":[]}],"macos":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.8/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.0/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.6.2/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"url_launcher_macos","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_macos-3.2.1/","native_build":true,"dependencies":[]}],"linux":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"]},{"name":"url_launcher_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.1/","native_build":true,"dependencies":[]}],"windows":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","native_build":false,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"]},{"name":"url_launcher_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.3/","native_build":true,"dependencies":[]}],"web":[{"name":"google_sign_in_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_web-0.12.4+3/","dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-8.1.1/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.2/","dependencies":[]},{"name":"url_launcher_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/url_launcher_web-2.3.3/","dependencies":[]}]},"dependencyGraph":[{"name":"google_sign_in","dependencies":["google_sign_in_android","google_sign_in_ios","google_sign_in_web"]},{"name":"google_sign_in_android","dependencies":[]},{"name":"google_sign_in_ios","dependencies":[]},{"name":"google_sign_in_web","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"photo_manager","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2024-11-15 10:45:14.730523","version":"3.24.4","swift_package_manager_enabled":false} \ No newline at end of file diff --git a/data/lib/apis/dropbox/dropbox_auth_endpoints.dart b/data/lib/apis/dropbox/dropbox_auth_endpoints.dart new file mode 100644 index 0000000..ea813ed --- /dev/null +++ b/data/lib/apis/dropbox/dropbox_auth_endpoints.dart @@ -0,0 +1,85 @@ +import '../network/urls.dart'; +import '../network/endpoint.dart'; + +class DropboxTokenEndpoint extends Endpoint { + final String code; + final String codeVerifier; + final String clientId; + final String redirectUrl; + final String clientSecret; + + const DropboxTokenEndpoint({ + required this.redirectUrl, + required this.code, + required this.codeVerifier, + required this.clientId, + required this.clientSecret, + }); + + @override + String get baseUrl => BaseURL.dropboxOAuth2Api; + + @override + String get path => '/token'; + + @override + HttpMethod get method => HttpMethod.post; + + @override + String? get contentType => 'application/x-www-form-urlencoded'; + + @override + Map get data => { + 'code': code, + 'grant_type': 'authorization_code', + 'redirect_uri': redirectUrl, + 'code_verifier': codeVerifier, + 'client_id': clientId, + 'client_secret': clientSecret, + }; +} + +class DropboxRefreshTokenEndpoint extends Endpoint { + final String refreshToken; + final String clientId; + final String clientSecret; + + const DropboxRefreshTokenEndpoint({ + required this.refreshToken, + required this.clientId, + required this.clientSecret, + }); + + @override + String get baseUrl => BaseURL.dropboxOAuth2Api; + + @override + String get path => '/token'; + + @override + HttpMethod get method => HttpMethod.post; + + @override + String? get contentType => 'application/x-www-form-urlencoded'; + + @override + Map get data => { + 'refresh_token': refreshToken, + 'grant_type': 'refresh_token', + 'client_id': clientId, + 'client_secret': clientSecret, + }; +} + +class DropboxGetUserAccountEndpoint extends Endpoint { + const DropboxGetUserAccountEndpoint(); + + @override + String get baseUrl => BaseURL.dropboxV2; + + @override + HttpMethod get method => HttpMethod.post; + + @override + String get path => '/users/get_current_account'; +} diff --git a/data/lib/apis/google_drive/google_drive_endpoint.dart b/data/lib/apis/google_drive/google_drive_endpoint.dart index 0e11348..561e3e1 100644 --- a/data/lib/apis/google_drive/google_drive_endpoint.dart +++ b/data/lib/apis/google_drive/google_drive_endpoint.dart @@ -1,10 +1,10 @@ import 'dart:convert'; -import '../network/base_url.dart'; import '../network/endpoint.dart'; import '../../models/media_content/media_content.dart'; import 'package:dio/dio.dart'; import 'package:googleapis/drive/v3.dart' as drive; import 'package:http_parser/http_parser.dart'; +import '../network/urls.dart'; class UploadGoogleDriveFile extends Endpoint { final drive.File request; @@ -20,7 +20,7 @@ class UploadGoogleDriveFile extends Endpoint { }); @override - String get baseUrl => BaseURL.googleDriveUpload; + String get baseUrl => BaseURL.googleDriveUploadV3; @override CancelToken? get cancelToken => cancellationToken; @@ -77,7 +77,7 @@ class DownloadGoogleDriveFileContent extends DownloadEndpoint { }); @override - String get baseUrl => BaseURL.googleDrive; + String get baseUrl => BaseURL.googleDriveV3; @override String get path => '/files/$id'; diff --git a/data/lib/apis/network/base_url.dart b/data/lib/apis/network/base_url.dart deleted file mode 100644 index 0096322..0000000 --- a/data/lib/apis/network/base_url.dart +++ /dev/null @@ -1,4 +0,0 @@ -class BaseURL { - static const googleDriveUpload = 'https://www.googleapis.com/upload/drive/v3'; - static const googleDrive = 'https://www.googleapis.com/drive/v3'; -} diff --git a/data/lib/apis/network/client.dart b/data/lib/apis/network/client.dart index 3791b8e..b7f2419 100644 --- a/data/lib/apis/network/client.dart +++ b/data/lib/apis/network/client.dart @@ -1,9 +1,11 @@ -import 'interceptors/auth_interceptor.dart'; import '../../errors/app_error.dart'; import '../../services/auth_service.dart'; import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../storage/app_preferences.dart'; import 'endpoint.dart'; +import 'interceptors/dropbox_auth_interceptor.dart'; +import 'interceptors/google_drive_auth_interceptor.dart'; final googleAuthenticatedDioProvider = Provider((ref) { return Dio() @@ -17,6 +19,19 @@ final googleAuthenticatedDioProvider = Provider((ref) { ); }); +final dropboxAuthenticatedDioProvider = Provider((ref) { + return Dio() + ..options.connectTimeout = const Duration(seconds: 60) + ..options.sendTimeout = const Duration(seconds: 60) + ..options.receiveTimeout = const Duration(seconds: 60) + ..interceptors.add( + DropboxAuthInterceptor( + authService: ref.read(authServiceProvider), + dropboxTokenController: ref.read(AppPreferences.dropboxToken.notifier), + ), + ); +}); + final rawDioProvider = Provider((ref) { return Dio() ..options.connectTimeout = const Duration(seconds: 60) @@ -43,8 +58,8 @@ extension DioExtensions on Dio { onReceiveProgress: endpoint.onReceiveProgress, onSendProgress: endpoint.onSendProgress, ); - } catch (error) { - throw AppError.fromError(error); + } catch (e) { + throw AppError.fromError(e); } } diff --git a/data/lib/apis/network/interceptors/dropbox_auth_interceptor.dart b/data/lib/apis/network/interceptors/dropbox_auth_interceptor.dart new file mode 100644 index 0000000..34d637c --- /dev/null +++ b/data/lib/apis/network/interceptors/dropbox_auth_interceptor.dart @@ -0,0 +1,54 @@ +import 'dart:async'; +import '../../../services/auth_service.dart'; +import 'package:dio/dio.dart'; +import '../../../models/token/token.dart'; +import '../../../storage/provider/preferences_provider.dart'; + +class DropboxAuthInterceptor extends Interceptor { + final AuthService authService; + final PreferenceNotifier dropboxTokenController; + + Completer? _refreshTokenCompleter; + + DropboxAuthInterceptor({ + required this.dropboxTokenController, + required this.authService, + }); + + @override + Future onRequest( + RequestOptions options, + RequestInterceptorHandler handler, + ) async { + final dropboxToken = dropboxTokenController.state; + if (dropboxToken != null) { + await _refreshTokenIfNeeded(dropboxToken); + options.headers.addAll({ + 'Authorization': 'Bearer ${dropboxToken.access_token}', + }); + } + handler.next(options); + } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + if (err.response?.statusCode == 401 && + dropboxTokenController.state != null) { + await _refreshTokenIfNeeded(dropboxTokenController.state!); + } + handler.next(err); + } + + Future _refreshTokenIfNeeded(DropboxToken dropboxToken) async { + if (dropboxToken.expires_in.isBefore(DateTime.now())) { + if (_refreshTokenCompleter == null) { + _refreshTokenCompleter = Completer(); + await authService.refreshDropboxToken(); + _refreshTokenCompleter?.complete(); + _refreshTokenCompleter = null; + } else { + await _refreshTokenCompleter!.future; + } + } + } +} diff --git a/data/lib/apis/network/interceptors/auth_interceptor.dart b/data/lib/apis/network/interceptors/google_drive_auth_interceptor.dart similarity index 96% rename from data/lib/apis/network/interceptors/auth_interceptor.dart rename to data/lib/apis/network/interceptors/google_drive_auth_interceptor.dart index b4501c6..7bcc654 100644 --- a/data/lib/apis/network/interceptors/auth_interceptor.dart +++ b/data/lib/apis/network/interceptors/google_drive_auth_interceptor.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:dio/dio.dart'; import 'package:google_sign_in/google_sign_in.dart'; diff --git a/data/lib/apis/network/oauth2.dart b/data/lib/apis/network/oauth2.dart new file mode 100644 index 0000000..544489b --- /dev/null +++ b/data/lib/apis/network/oauth2.dart @@ -0,0 +1,54 @@ +import 'dart:convert'; +import 'dart:math' show Random; +import 'package:crypto/crypto.dart' show sha256; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final oauth2Provider = Provider((ref) => Oauth2()); + +class Oauth2 { + String _generateCodeVerifier({ + String charset = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~', + }) => + List.generate( + 128, + (i) => charset[Random.secure().nextInt(charset.length)], + ).join(); + + String get generateCodeVerifier => _generateCodeVerifier(); + + Uri getAuthorizationUrl({ + required String clientId, + required Uri authorizationEndpoint, + required String redirectUri, + required String codeVerifier, + String responseType = 'code', + List scopes = const [], + String delimiter = ' ', + Map additionalParameters = const {}, + String? state, + }) { + final codeChallenge = base64Url + .encode(sha256.convert(ascii.encode(codeVerifier)).bytes) + .replaceAll('=', ''); + + final parameters = { + 'client_id': clientId, + 'redirect_uri': redirectUri, + 'response_type': responseType, + 'code_challenge': codeChallenge, + 'code_challenge_method': 'S256', + }; + + if (additionalParameters.isNotEmpty) { + parameters.addAll(additionalParameters); + } + if (state != null) parameters['state'] = state; + if (scopes.isNotEmpty) parameters['scope'] = scopes.join(delimiter); + + return authorizationEndpoint.replace( + queryParameters: Map.from(authorizationEndpoint.queryParameters) + ..addAll(parameters), + ); + } +} diff --git a/data/lib/apis/network/secrets.dart b/data/lib/apis/network/secrets.dart new file mode 100644 index 0000000..470ebe4 --- /dev/null +++ b/data/lib/apis/network/secrets.dart @@ -0,0 +1,4 @@ +class AppSecretes { + static const dropBoxAppKey = '873x7j2iwh8mrea'; + static const dropBoxAppSecret = 'mq2azqdd6y1upzr'; +} diff --git a/data/lib/apis/network/urls.dart b/data/lib/apis/network/urls.dart new file mode 100644 index 0000000..b9a2894 --- /dev/null +++ b/data/lib/apis/network/urls.dart @@ -0,0 +1,16 @@ +class BaseURL { + // Google Drive API Base URL + static const googleDriveUploadV3 = + 'https://www.googleapis.com/upload/drive/v3'; + static const googleDriveV3 = 'https://www.googleapis.com/drive/v3'; + + // Dropbox API Base URL + static const dropboxContentV2 = 'https://content.dropboxapi.com/2'; + static const dropboxOAuth2Web = 'https://www.dropbox.com/oauth2'; + static const dropboxOAuth2Api = 'https://api.dropboxapi.com/oauth2'; + static const dropboxV2 = 'https://api.dropboxapi.com/2'; +} + +class RedirectURL { + static const String auth = 'cloudgallery://cloudgallery.com/auth'; +} diff --git a/data/lib/domain/config.dart b/data/lib/domain/config.dart new file mode 100644 index 0000000..d2725f3 --- /dev/null +++ b/data/lib/domain/config.dart @@ -0,0 +1,5 @@ +import 'package:flutter/foundation.dart'; + +class FeatureFlags { + static const dropboxEnabled = kDebugMode; +} diff --git a/data/lib/errors/app_error.dart b/data/lib/errors/app_error.dart index 654d07b..1d9b6ef 100644 --- a/data/lib/errors/app_error.dart +++ b/data/lib/errors/app_error.dart @@ -36,7 +36,7 @@ class AppError implements Exception { class NoConnectionError extends AppError { const NoConnectionError() : super( - l10nCode: AppErrorL10nCodes.noInternetConnection, + l10nCode: AppErrorL10nCodes.noInternetConnectionError, message: "No internet connection. Please check your network and try again.", ); @@ -61,17 +61,33 @@ class RequestCancelledByUser extends AppError { class BackUpFolderNotFound extends AppError { const BackUpFolderNotFound() : super( - l10nCode: AppErrorL10nCodes.backUpFolderNotFound, + l10nCode: AppErrorL10nCodes.backUpFolderNotFoundError, message: "Back up folder not found", + statusCode: 404, ); } class UnableToSaveFileInGallery extends AppError { const UnableToSaveFileInGallery() - : super(message: "Unable to save file in gallery"); + : super( + l10nCode: AppErrorL10nCodes.unableToSaveFileInGalleryError, + message: "Unable to save file in gallery", + ); } class SomethingWentWrongError extends AppError { const SomethingWentWrongError({super.message, super.statusCode}) - : super(l10nCode: AppErrorL10nCodes.somethingWentWrongError); + : super( + l10nCode: AppErrorL10nCodes.somethingWentWrongError, + ); +} + +class AuthSessionExpiredError extends AppError { + const AuthSessionExpiredError() + : super( + l10nCode: AppErrorL10nCodes.authSessionExpiredError, + message: + "User authentication session expired. Unable to get access token.", + statusCode: 401, + ); } diff --git a/data/lib/errors/l10n_error_codes.dart b/data/lib/errors/l10n_error_codes.dart index fcad6a8..434426d 100644 --- a/data/lib/errors/l10n_error_codes.dart +++ b/data/lib/errors/l10n_error_codes.dart @@ -1,6 +1,9 @@ class AppErrorL10nCodes { - static const noInternetConnection = 'no-internet-connection'; + static const noInternetConnectionError = 'no-internet-connection'; static const somethingWentWrongError = 'something-went-wrong'; + static const authSessionExpiredError = 'auth-session-expired'; static const googleSignInUserNotFoundError = 'google-sing-in-user-not-found'; - static const backUpFolderNotFound = 'back-up-folder-not-found'; + static const backUpFolderNotFoundError = 'back-up-folder-not-found'; + static const unableToSaveFileInGalleryError = + 'unable-to-save-file-in-gallery'; } diff --git a/data/lib/extensions/date_time_extension.dart b/data/lib/extensions/date_time_extension.dart new file mode 100644 index 0000000..2fea8fb --- /dev/null +++ b/data/lib/extensions/date_time_extension.dart @@ -0,0 +1,3 @@ +extension DateTimeExtension on DateTime { + int get secondsSinceEpoch => millisecondsSinceEpoch ~/ 1000; +} diff --git a/data/lib/models/dropbox_account/dropbox_account.dart b/data/lib/models/dropbox_account/dropbox_account.dart new file mode 100644 index 0000000..a21afbe --- /dev/null +++ b/data/lib/models/dropbox_account/dropbox_account.dart @@ -0,0 +1,40 @@ +// ignore_for_file: non_constant_identifier_names + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'dropbox_account.freezed.dart'; +part 'dropbox_account.g.dart'; + +@freezed +class DropboxAccount with _$DropboxAccount { + const factory DropboxAccount({ + required String account_id, + required DropboxAccountName name, + required String email, + required bool email_verified, + required bool disabled, + required String country, + required String locale, + required String referral_link, + required bool is_paired, + String? profile_photo_url, + String? team_member_id, + }) = _DropboxAccount; + + factory DropboxAccount.fromJson(Map json) => + _$DropboxAccountFromJson(json); +} + +@freezed +class DropboxAccountName with _$DropboxAccountName { + const factory DropboxAccountName({ + required String abbreviated_name, + required String display_name, + required String familiar_name, + required String given_name, + required String surname, + }) = _DropboxAccountName; + + factory DropboxAccountName.fromJson(Map json) => + _$DropboxAccountNameFromJson(json); +} diff --git a/data/lib/models/dropbox_account/dropbox_account.freezed.dart b/data/lib/models/dropbox_account/dropbox_account.freezed.dart new file mode 100644 index 0000000..2a4da4f --- /dev/null +++ b/data/lib/models/dropbox_account/dropbox_account.freezed.dart @@ -0,0 +1,642 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'dropbox_account.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +DropboxAccount _$DropboxAccountFromJson(Map json) { + return _DropboxAccount.fromJson(json); +} + +/// @nodoc +mixin _$DropboxAccount { + String get account_id => throw _privateConstructorUsedError; + DropboxAccountName get name => throw _privateConstructorUsedError; + String get email => throw _privateConstructorUsedError; + bool get email_verified => throw _privateConstructorUsedError; + bool get disabled => throw _privateConstructorUsedError; + String get country => throw _privateConstructorUsedError; + String get locale => throw _privateConstructorUsedError; + String get referral_link => throw _privateConstructorUsedError; + bool get is_paired => throw _privateConstructorUsedError; + String? get profile_photo_url => throw _privateConstructorUsedError; + String? get team_member_id => throw _privateConstructorUsedError; + + /// Serializes this DropboxAccount to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of DropboxAccount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $DropboxAccountCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DropboxAccountCopyWith<$Res> { + factory $DropboxAccountCopyWith( + DropboxAccount value, $Res Function(DropboxAccount) then) = + _$DropboxAccountCopyWithImpl<$Res, DropboxAccount>; + @useResult + $Res call( + {String account_id, + DropboxAccountName name, + String email, + bool email_verified, + bool disabled, + String country, + String locale, + String referral_link, + bool is_paired, + String? profile_photo_url, + String? team_member_id}); + + $DropboxAccountNameCopyWith<$Res> get name; +} + +/// @nodoc +class _$DropboxAccountCopyWithImpl<$Res, $Val extends DropboxAccount> + implements $DropboxAccountCopyWith<$Res> { + _$DropboxAccountCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of DropboxAccount + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? account_id = null, + Object? name = null, + Object? email = null, + Object? email_verified = null, + Object? disabled = null, + Object? country = null, + Object? locale = null, + Object? referral_link = null, + Object? is_paired = null, + Object? profile_photo_url = freezed, + Object? team_member_id = freezed, + }) { + return _then(_value.copyWith( + account_id: null == account_id + ? _value.account_id + : account_id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as DropboxAccountName, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + email_verified: null == email_verified + ? _value.email_verified + : email_verified // ignore: cast_nullable_to_non_nullable + as bool, + disabled: null == disabled + ? _value.disabled + : disabled // ignore: cast_nullable_to_non_nullable + as bool, + country: null == country + ? _value.country + : country // ignore: cast_nullable_to_non_nullable + as String, + locale: null == locale + ? _value.locale + : locale // ignore: cast_nullable_to_non_nullable + as String, + referral_link: null == referral_link + ? _value.referral_link + : referral_link // ignore: cast_nullable_to_non_nullable + as String, + is_paired: null == is_paired + ? _value.is_paired + : is_paired // ignore: cast_nullable_to_non_nullable + as bool, + profile_photo_url: freezed == profile_photo_url + ? _value.profile_photo_url + : profile_photo_url // ignore: cast_nullable_to_non_nullable + as String?, + team_member_id: freezed == team_member_id + ? _value.team_member_id + : team_member_id // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } + + /// Create a copy of DropboxAccount + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $DropboxAccountNameCopyWith<$Res> get name { + return $DropboxAccountNameCopyWith<$Res>(_value.name, (value) { + return _then(_value.copyWith(name: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$DropboxAccountImplCopyWith<$Res> + implements $DropboxAccountCopyWith<$Res> { + factory _$$DropboxAccountImplCopyWith(_$DropboxAccountImpl value, + $Res Function(_$DropboxAccountImpl) then) = + __$$DropboxAccountImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String account_id, + DropboxAccountName name, + String email, + bool email_verified, + bool disabled, + String country, + String locale, + String referral_link, + bool is_paired, + String? profile_photo_url, + String? team_member_id}); + + @override + $DropboxAccountNameCopyWith<$Res> get name; +} + +/// @nodoc +class __$$DropboxAccountImplCopyWithImpl<$Res> + extends _$DropboxAccountCopyWithImpl<$Res, _$DropboxAccountImpl> + implements _$$DropboxAccountImplCopyWith<$Res> { + __$$DropboxAccountImplCopyWithImpl( + _$DropboxAccountImpl _value, $Res Function(_$DropboxAccountImpl) _then) + : super(_value, _then); + + /// Create a copy of DropboxAccount + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? account_id = null, + Object? name = null, + Object? email = null, + Object? email_verified = null, + Object? disabled = null, + Object? country = null, + Object? locale = null, + Object? referral_link = null, + Object? is_paired = null, + Object? profile_photo_url = freezed, + Object? team_member_id = freezed, + }) { + return _then(_$DropboxAccountImpl( + account_id: null == account_id + ? _value.account_id + : account_id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as DropboxAccountName, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + email_verified: null == email_verified + ? _value.email_verified + : email_verified // ignore: cast_nullable_to_non_nullable + as bool, + disabled: null == disabled + ? _value.disabled + : disabled // ignore: cast_nullable_to_non_nullable + as bool, + country: null == country + ? _value.country + : country // ignore: cast_nullable_to_non_nullable + as String, + locale: null == locale + ? _value.locale + : locale // ignore: cast_nullable_to_non_nullable + as String, + referral_link: null == referral_link + ? _value.referral_link + : referral_link // ignore: cast_nullable_to_non_nullable + as String, + is_paired: null == is_paired + ? _value.is_paired + : is_paired // ignore: cast_nullable_to_non_nullable + as bool, + profile_photo_url: freezed == profile_photo_url + ? _value.profile_photo_url + : profile_photo_url // ignore: cast_nullable_to_non_nullable + as String?, + team_member_id: freezed == team_member_id + ? _value.team_member_id + : team_member_id // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$DropboxAccountImpl implements _DropboxAccount { + const _$DropboxAccountImpl( + {required this.account_id, + required this.name, + required this.email, + required this.email_verified, + required this.disabled, + required this.country, + required this.locale, + required this.referral_link, + required this.is_paired, + this.profile_photo_url, + this.team_member_id}); + + factory _$DropboxAccountImpl.fromJson(Map json) => + _$$DropboxAccountImplFromJson(json); + + @override + final String account_id; + @override + final DropboxAccountName name; + @override + final String email; + @override + final bool email_verified; + @override + final bool disabled; + @override + final String country; + @override + final String locale; + @override + final String referral_link; + @override + final bool is_paired; + @override + final String? profile_photo_url; + @override + final String? team_member_id; + + @override + String toString() { + return 'DropboxAccount(account_id: $account_id, name: $name, email: $email, email_verified: $email_verified, disabled: $disabled, country: $country, locale: $locale, referral_link: $referral_link, is_paired: $is_paired, profile_photo_url: $profile_photo_url, team_member_id: $team_member_id)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DropboxAccountImpl && + (identical(other.account_id, account_id) || + other.account_id == account_id) && + (identical(other.name, name) || other.name == name) && + (identical(other.email, email) || other.email == email) && + (identical(other.email_verified, email_verified) || + other.email_verified == email_verified) && + (identical(other.disabled, disabled) || + other.disabled == disabled) && + (identical(other.country, country) || other.country == country) && + (identical(other.locale, locale) || other.locale == locale) && + (identical(other.referral_link, referral_link) || + other.referral_link == referral_link) && + (identical(other.is_paired, is_paired) || + other.is_paired == is_paired) && + (identical(other.profile_photo_url, profile_photo_url) || + other.profile_photo_url == profile_photo_url) && + (identical(other.team_member_id, team_member_id) || + other.team_member_id == team_member_id)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + account_id, + name, + email, + email_verified, + disabled, + country, + locale, + referral_link, + is_paired, + profile_photo_url, + team_member_id); + + /// Create a copy of DropboxAccount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DropboxAccountImplCopyWith<_$DropboxAccountImpl> get copyWith => + __$$DropboxAccountImplCopyWithImpl<_$DropboxAccountImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$DropboxAccountImplToJson( + this, + ); + } +} + +abstract class _DropboxAccount implements DropboxAccount { + const factory _DropboxAccount( + {required final String account_id, + required final DropboxAccountName name, + required final String email, + required final bool email_verified, + required final bool disabled, + required final String country, + required final String locale, + required final String referral_link, + required final bool is_paired, + final String? profile_photo_url, + final String? team_member_id}) = _$DropboxAccountImpl; + + factory _DropboxAccount.fromJson(Map json) = + _$DropboxAccountImpl.fromJson; + + @override + String get account_id; + @override + DropboxAccountName get name; + @override + String get email; + @override + bool get email_verified; + @override + bool get disabled; + @override + String get country; + @override + String get locale; + @override + String get referral_link; + @override + bool get is_paired; + @override + String? get profile_photo_url; + @override + String? get team_member_id; + + /// Create a copy of DropboxAccount + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DropboxAccountImplCopyWith<_$DropboxAccountImpl> get copyWith => + throw _privateConstructorUsedError; +} + +DropboxAccountName _$DropboxAccountNameFromJson(Map json) { + return _DropboxAccountName.fromJson(json); +} + +/// @nodoc +mixin _$DropboxAccountName { + String get abbreviated_name => throw _privateConstructorUsedError; + String get display_name => throw _privateConstructorUsedError; + String get familiar_name => throw _privateConstructorUsedError; + String get given_name => throw _privateConstructorUsedError; + String get surname => throw _privateConstructorUsedError; + + /// Serializes this DropboxAccountName to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of DropboxAccountName + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $DropboxAccountNameCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DropboxAccountNameCopyWith<$Res> { + factory $DropboxAccountNameCopyWith( + DropboxAccountName value, $Res Function(DropboxAccountName) then) = + _$DropboxAccountNameCopyWithImpl<$Res, DropboxAccountName>; + @useResult + $Res call( + {String abbreviated_name, + String display_name, + String familiar_name, + String given_name, + String surname}); +} + +/// @nodoc +class _$DropboxAccountNameCopyWithImpl<$Res, $Val extends DropboxAccountName> + implements $DropboxAccountNameCopyWith<$Res> { + _$DropboxAccountNameCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of DropboxAccountName + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? abbreviated_name = null, + Object? display_name = null, + Object? familiar_name = null, + Object? given_name = null, + Object? surname = null, + }) { + return _then(_value.copyWith( + abbreviated_name: null == abbreviated_name + ? _value.abbreviated_name + : abbreviated_name // ignore: cast_nullable_to_non_nullable + as String, + display_name: null == display_name + ? _value.display_name + : display_name // ignore: cast_nullable_to_non_nullable + as String, + familiar_name: null == familiar_name + ? _value.familiar_name + : familiar_name // ignore: cast_nullable_to_non_nullable + as String, + given_name: null == given_name + ? _value.given_name + : given_name // ignore: cast_nullable_to_non_nullable + as String, + surname: null == surname + ? _value.surname + : surname // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$DropboxAccountNameImplCopyWith<$Res> + implements $DropboxAccountNameCopyWith<$Res> { + factory _$$DropboxAccountNameImplCopyWith(_$DropboxAccountNameImpl value, + $Res Function(_$DropboxAccountNameImpl) then) = + __$$DropboxAccountNameImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String abbreviated_name, + String display_name, + String familiar_name, + String given_name, + String surname}); +} + +/// @nodoc +class __$$DropboxAccountNameImplCopyWithImpl<$Res> + extends _$DropboxAccountNameCopyWithImpl<$Res, _$DropboxAccountNameImpl> + implements _$$DropboxAccountNameImplCopyWith<$Res> { + __$$DropboxAccountNameImplCopyWithImpl(_$DropboxAccountNameImpl _value, + $Res Function(_$DropboxAccountNameImpl) _then) + : super(_value, _then); + + /// Create a copy of DropboxAccountName + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? abbreviated_name = null, + Object? display_name = null, + Object? familiar_name = null, + Object? given_name = null, + Object? surname = null, + }) { + return _then(_$DropboxAccountNameImpl( + abbreviated_name: null == abbreviated_name + ? _value.abbreviated_name + : abbreviated_name // ignore: cast_nullable_to_non_nullable + as String, + display_name: null == display_name + ? _value.display_name + : display_name // ignore: cast_nullable_to_non_nullable + as String, + familiar_name: null == familiar_name + ? _value.familiar_name + : familiar_name // ignore: cast_nullable_to_non_nullable + as String, + given_name: null == given_name + ? _value.given_name + : given_name // ignore: cast_nullable_to_non_nullable + as String, + surname: null == surname + ? _value.surname + : surname // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$DropboxAccountNameImpl implements _DropboxAccountName { + const _$DropboxAccountNameImpl( + {required this.abbreviated_name, + required this.display_name, + required this.familiar_name, + required this.given_name, + required this.surname}); + + factory _$DropboxAccountNameImpl.fromJson(Map json) => + _$$DropboxAccountNameImplFromJson(json); + + @override + final String abbreviated_name; + @override + final String display_name; + @override + final String familiar_name; + @override + final String given_name; + @override + final String surname; + + @override + String toString() { + return 'DropboxAccountName(abbreviated_name: $abbreviated_name, display_name: $display_name, familiar_name: $familiar_name, given_name: $given_name, surname: $surname)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DropboxAccountNameImpl && + (identical(other.abbreviated_name, abbreviated_name) || + other.abbreviated_name == abbreviated_name) && + (identical(other.display_name, display_name) || + other.display_name == display_name) && + (identical(other.familiar_name, familiar_name) || + other.familiar_name == familiar_name) && + (identical(other.given_name, given_name) || + other.given_name == given_name) && + (identical(other.surname, surname) || other.surname == surname)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, abbreviated_name, display_name, + familiar_name, given_name, surname); + + /// Create a copy of DropboxAccountName + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DropboxAccountNameImplCopyWith<_$DropboxAccountNameImpl> get copyWith => + __$$DropboxAccountNameImplCopyWithImpl<_$DropboxAccountNameImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$DropboxAccountNameImplToJson( + this, + ); + } +} + +abstract class _DropboxAccountName implements DropboxAccountName { + const factory _DropboxAccountName( + {required final String abbreviated_name, + required final String display_name, + required final String familiar_name, + required final String given_name, + required final String surname}) = _$DropboxAccountNameImpl; + + factory _DropboxAccountName.fromJson(Map json) = + _$DropboxAccountNameImpl.fromJson; + + @override + String get abbreviated_name; + @override + String get display_name; + @override + String get familiar_name; + @override + String get given_name; + @override + String get surname; + + /// Create a copy of DropboxAccountName + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DropboxAccountNameImplCopyWith<_$DropboxAccountNameImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/data/lib/models/dropbox_account/dropbox_account.g.dart b/data/lib/models/dropbox_account/dropbox_account.g.dart new file mode 100644 index 0000000..695f536 --- /dev/null +++ b/data/lib/models/dropbox_account/dropbox_account.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'dropbox_account.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$DropboxAccountImpl _$$DropboxAccountImplFromJson(Map json) => + _$DropboxAccountImpl( + account_id: json['account_id'] as String, + name: DropboxAccountName.fromJson(json['name'] as Map), + email: json['email'] as String, + email_verified: json['email_verified'] as bool, + disabled: json['disabled'] as bool, + country: json['country'] as String, + locale: json['locale'] as String, + referral_link: json['referral_link'] as String, + is_paired: json['is_paired'] as bool, + profile_photo_url: json['profile_photo_url'] as String?, + team_member_id: json['team_member_id'] as String?, + ); + +Map _$$DropboxAccountImplToJson( + _$DropboxAccountImpl instance) => + { + 'account_id': instance.account_id, + 'name': instance.name, + 'email': instance.email, + 'email_verified': instance.email_verified, + 'disabled': instance.disabled, + 'country': instance.country, + 'locale': instance.locale, + 'referral_link': instance.referral_link, + 'is_paired': instance.is_paired, + 'profile_photo_url': instance.profile_photo_url, + 'team_member_id': instance.team_member_id, + }; + +_$DropboxAccountNameImpl _$$DropboxAccountNameImplFromJson( + Map json) => + _$DropboxAccountNameImpl( + abbreviated_name: json['abbreviated_name'] as String, + display_name: json['display_name'] as String, + familiar_name: json['familiar_name'] as String, + given_name: json['given_name'] as String, + surname: json['surname'] as String, + ); + +Map _$$DropboxAccountNameImplToJson( + _$DropboxAccountNameImpl instance) => + { + 'abbreviated_name': instance.abbreviated_name, + 'display_name': instance.display_name, + 'familiar_name': instance.familiar_name, + 'given_name': instance.given_name, + 'surname': instance.surname, + }; diff --git a/data/lib/models/token/token.dart b/data/lib/models/token/token.dart new file mode 100644 index 0000000..3a48909 --- /dev/null +++ b/data/lib/models/token/token.dart @@ -0,0 +1,40 @@ +// ignore_for_file: non_constant_identifier_names + +import '../../extensions/date_time_extension.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'token.freezed.dart'; +part 'token.g.dart'; + +class ExpiresInJsonConverter implements JsonConverter { + const ExpiresInJsonConverter(); + + @override + DateTime fromJson(int json) { + final date = DateTime.fromMillisecondsSinceEpoch( + DateTime.now().millisecondsSinceEpoch + (json * 1000), + ); + return date; + } + + @override + int toJson(DateTime dateTime) { + return dateTime.secondsSinceEpoch; + } +} + +@freezed +abstract class DropboxToken with _$DropboxToken { + const factory DropboxToken({ + required String access_token, + required String token_type, + @ExpiresInJsonConverter() required DateTime expires_in, + required String refresh_token, + required String account_id, + required String scope, + required String uid, + }) = _DropboxToken; + + factory DropboxToken.fromJson(Map json) => + _$DropboxTokenFromJson(json); +} diff --git a/data/lib/models/token/token.freezed.dart b/data/lib/models/token/token.freezed.dart new file mode 100644 index 0000000..39879bc --- /dev/null +++ b/data/lib/models/token/token.freezed.dart @@ -0,0 +1,297 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'token.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +DropboxToken _$DropboxTokenFromJson(Map json) { + return _DropboxToken.fromJson(json); +} + +/// @nodoc +mixin _$DropboxToken { + String get access_token => throw _privateConstructorUsedError; + String get token_type => throw _privateConstructorUsedError; + @ExpiresInJsonConverter() + DateTime get expires_in => throw _privateConstructorUsedError; + String get refresh_token => throw _privateConstructorUsedError; + String get account_id => throw _privateConstructorUsedError; + String get scope => throw _privateConstructorUsedError; + String get uid => throw _privateConstructorUsedError; + + /// Serializes this DropboxToken to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of DropboxToken + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $DropboxTokenCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DropboxTokenCopyWith<$Res> { + factory $DropboxTokenCopyWith( + DropboxToken value, $Res Function(DropboxToken) then) = + _$DropboxTokenCopyWithImpl<$Res, DropboxToken>; + @useResult + $Res call( + {String access_token, + String token_type, + @ExpiresInJsonConverter() DateTime expires_in, + String refresh_token, + String account_id, + String scope, + String uid}); +} + +/// @nodoc +class _$DropboxTokenCopyWithImpl<$Res, $Val extends DropboxToken> + implements $DropboxTokenCopyWith<$Res> { + _$DropboxTokenCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of DropboxToken + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? access_token = null, + Object? token_type = null, + Object? expires_in = null, + Object? refresh_token = null, + Object? account_id = null, + Object? scope = null, + Object? uid = null, + }) { + return _then(_value.copyWith( + access_token: null == access_token + ? _value.access_token + : access_token // ignore: cast_nullable_to_non_nullable + as String, + token_type: null == token_type + ? _value.token_type + : token_type // ignore: cast_nullable_to_non_nullable + as String, + expires_in: null == expires_in + ? _value.expires_in + : expires_in // ignore: cast_nullable_to_non_nullable + as DateTime, + refresh_token: null == refresh_token + ? _value.refresh_token + : refresh_token // ignore: cast_nullable_to_non_nullable + as String, + account_id: null == account_id + ? _value.account_id + : account_id // ignore: cast_nullable_to_non_nullable + as String, + scope: null == scope + ? _value.scope + : scope // ignore: cast_nullable_to_non_nullable + as String, + uid: null == uid + ? _value.uid + : uid // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$DropboxTokenImplCopyWith<$Res> + implements $DropboxTokenCopyWith<$Res> { + factory _$$DropboxTokenImplCopyWith( + _$DropboxTokenImpl value, $Res Function(_$DropboxTokenImpl) then) = + __$$DropboxTokenImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String access_token, + String token_type, + @ExpiresInJsonConverter() DateTime expires_in, + String refresh_token, + String account_id, + String scope, + String uid}); +} + +/// @nodoc +class __$$DropboxTokenImplCopyWithImpl<$Res> + extends _$DropboxTokenCopyWithImpl<$Res, _$DropboxTokenImpl> + implements _$$DropboxTokenImplCopyWith<$Res> { + __$$DropboxTokenImplCopyWithImpl( + _$DropboxTokenImpl _value, $Res Function(_$DropboxTokenImpl) _then) + : super(_value, _then); + + /// Create a copy of DropboxToken + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? access_token = null, + Object? token_type = null, + Object? expires_in = null, + Object? refresh_token = null, + Object? account_id = null, + Object? scope = null, + Object? uid = null, + }) { + return _then(_$DropboxTokenImpl( + access_token: null == access_token + ? _value.access_token + : access_token // ignore: cast_nullable_to_non_nullable + as String, + token_type: null == token_type + ? _value.token_type + : token_type // ignore: cast_nullable_to_non_nullable + as String, + expires_in: null == expires_in + ? _value.expires_in + : expires_in // ignore: cast_nullable_to_non_nullable + as DateTime, + refresh_token: null == refresh_token + ? _value.refresh_token + : refresh_token // ignore: cast_nullable_to_non_nullable + as String, + account_id: null == account_id + ? _value.account_id + : account_id // ignore: cast_nullable_to_non_nullable + as String, + scope: null == scope + ? _value.scope + : scope // ignore: cast_nullable_to_non_nullable + as String, + uid: null == uid + ? _value.uid + : uid // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$DropboxTokenImpl implements _DropboxToken { + const _$DropboxTokenImpl( + {required this.access_token, + required this.token_type, + @ExpiresInJsonConverter() required this.expires_in, + required this.refresh_token, + required this.account_id, + required this.scope, + required this.uid}); + + factory _$DropboxTokenImpl.fromJson(Map json) => + _$$DropboxTokenImplFromJson(json); + + @override + final String access_token; + @override + final String token_type; + @override + @ExpiresInJsonConverter() + final DateTime expires_in; + @override + final String refresh_token; + @override + final String account_id; + @override + final String scope; + @override + final String uid; + + @override + String toString() { + return 'DropboxToken(access_token: $access_token, token_type: $token_type, expires_in: $expires_in, refresh_token: $refresh_token, account_id: $account_id, scope: $scope, uid: $uid)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DropboxTokenImpl && + (identical(other.access_token, access_token) || + other.access_token == access_token) && + (identical(other.token_type, token_type) || + other.token_type == token_type) && + (identical(other.expires_in, expires_in) || + other.expires_in == expires_in) && + (identical(other.refresh_token, refresh_token) || + other.refresh_token == refresh_token) && + (identical(other.account_id, account_id) || + other.account_id == account_id) && + (identical(other.scope, scope) || other.scope == scope) && + (identical(other.uid, uid) || other.uid == uid)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, access_token, token_type, + expires_in, refresh_token, account_id, scope, uid); + + /// Create a copy of DropboxToken + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DropboxTokenImplCopyWith<_$DropboxTokenImpl> get copyWith => + __$$DropboxTokenImplCopyWithImpl<_$DropboxTokenImpl>(this, _$identity); + + @override + Map toJson() { + return _$$DropboxTokenImplToJson( + this, + ); + } +} + +abstract class _DropboxToken implements DropboxToken { + const factory _DropboxToken( + {required final String access_token, + required final String token_type, + @ExpiresInJsonConverter() required final DateTime expires_in, + required final String refresh_token, + required final String account_id, + required final String scope, + required final String uid}) = _$DropboxTokenImpl; + + factory _DropboxToken.fromJson(Map json) = + _$DropboxTokenImpl.fromJson; + + @override + String get access_token; + @override + String get token_type; + @override + @ExpiresInJsonConverter() + DateTime get expires_in; + @override + String get refresh_token; + @override + String get account_id; + @override + String get scope; + @override + String get uid; + + /// Create a copy of DropboxToken + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DropboxTokenImplCopyWith<_$DropboxTokenImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/data/lib/models/token/token.g.dart b/data/lib/models/token/token.g.dart new file mode 100644 index 0000000..c8fa152 --- /dev/null +++ b/data/lib/models/token/token.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'token.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$DropboxTokenImpl _$$DropboxTokenImplFromJson(Map json) => + _$DropboxTokenImpl( + access_token: json['access_token'] as String, + token_type: json['token_type'] as String, + expires_in: const ExpiresInJsonConverter() + .fromJson((json['expires_in'] as num).toInt()), + refresh_token: json['refresh_token'] as String, + account_id: json['account_id'] as String, + scope: json['scope'] as String, + uid: json['uid'] as String, + ); + +Map _$$DropboxTokenImplToJson(_$DropboxTokenImpl instance) => + { + 'access_token': instance.access_token, + 'token_type': instance.token_type, + 'expires_in': const ExpiresInJsonConverter().toJson(instance.expires_in), + 'refresh_token': instance.refresh_token, + 'account_id': instance.account_id, + 'scope': instance.scope, + 'uid': instance.uid, + }; diff --git a/data/lib/services/auth_service.dart b/data/lib/services/auth_service.dart index df9e983..85e5f05 100644 --- a/data/lib/services/auth_service.dart +++ b/data/lib/services/auth_service.dart @@ -1,6 +1,19 @@ +import 'dart:async'; +import '../apis/network/client.dart'; +import '../apis/network/oauth2.dart'; +import '../errors/app_error.dart'; +import '../models/dropbox_account/dropbox_account.dart'; +import '../storage/app_preferences.dart'; +import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:googleapis/drive/v3.dart' as google_drive; +import 'package:url_launcher/url_launcher.dart'; +import '../apis/dropbox/dropbox_auth_endpoints.dart'; +import '../apis/network/secrets.dart'; +import '../apis/network/urls.dart'; +import '../models/token/token.dart'; +import '../storage/provider/preferences_provider.dart'; final googleUserAccountProvider = StateProvider((ref) { final googleSignIn = ref.read(googleSignInProvider); @@ -23,21 +36,44 @@ final googleSignInProvider = Provider( final authServiceProvider = Provider( (ref) => AuthService( ref.read(googleSignInProvider), + ref.read(rawDioProvider), + ref.read(oauth2Provider), + ref.read(AppPreferences.dropboxToken.notifier), + ref.read(AppPreferences.dropboxPKCECodeVerifier.notifier), + ref.read(AppPreferences.dropboxCurrentUserAccount.notifier), + ref.read(AppPreferences.googleDriveAutoBackUp.notifier), + ref.read(AppPreferences.dropboxAutoBackUp.notifier), ), ); class AuthService { final GoogleSignIn _googleSignIn; + final Oauth2 _oauth2; + final Dio _dio; + final PreferenceNotifier _dropboxTokenController; + final PreferenceNotifier _dropboxAccountController; + final PreferenceNotifier _dropboxCodeVerifierPrefProvider; + final PreferenceNotifier _googleDriveAutoBackUpController; + final PreferenceNotifier _dropboxAutoBackUpController; - AuthService(this._googleSignIn) { + AuthService( + this._googleSignIn, + this._dio, + this._oauth2, + this._dropboxTokenController, + this._dropboxCodeVerifierPrefProvider, + this._dropboxAccountController, + this._googleDriveAutoBackUpController, + this._dropboxAutoBackUpController, + ) { signInSilently(); } Future signInSilently() async { try { await _googleSignIn.signInSilently(suppressErrors: true); - } catch (_) { - rethrow; + } catch (e) { + throw AppError.fromError(e); } } @@ -47,16 +83,92 @@ class AuthService { if (googleSignInAccount != null) { await googleSignInAccount.authentication; } - } catch (_) { - rethrow; + } catch (e) { + throw AppError.fromError(e); } } Future signOutWithGoogle() async { try { await _googleSignIn.signOut(); - } catch (_) { - rethrow; + _googleDriveAutoBackUpController.state = false; + } catch (e) { + throw AppError.fromError(e); + } + } + + /// Launches the URL in the browser for OAuth 2 authentication with Dropbox. + /// Retrieves the access token using the Proof of Key Code Exchange (PKCE) flow. + Future signInWithDropBox() async { + try { + final codeVerifier = _oauth2.generateCodeVerifier; + _dropboxCodeVerifierPrefProvider.state = codeVerifier; + final authorizationUrl = _oauth2.getAuthorizationUrl( + clientId: AppSecretes.dropBoxAppKey, + authorizationEndpoint: + Uri.parse('${BaseURL.dropboxOAuth2Web}/authorize'), + additionalParameters: {'token_access_type': 'offline'}, + redirectUri: RedirectURL.auth, + codeVerifier: codeVerifier, + ); + await launchUrl(authorizationUrl); + } catch (e) { + throw AppError.fromError(e); + } + } + + Future setDropboxTokenFromCode({required String code}) async { + try { + if (_dropboxCodeVerifierPrefProvider.state == null) { + throw const SomethingWentWrongError( + message: "Dropbox code verifier is missing", + ); + } + final res = await _dio.req( + DropboxTokenEndpoint( + code: code, + codeVerifier: _dropboxCodeVerifierPrefProvider.state!, + clientId: AppSecretes.dropBoxAppKey, + redirectUrl: RedirectURL.auth, + clientSecret: AppSecretes.dropBoxAppSecret, + ), + ); + if (res.data != null) { + _dropboxTokenController.state = DropboxToken.fromJson(res.data); + _dropboxCodeVerifierPrefProvider.state = null; + } + } catch (e) { + throw AppError.fromError(e); + } + } + + Future signOutWithDropBox() async { + _dropboxTokenController.state = null; + _dropboxAccountController.state = null; + _dropboxAutoBackUpController.state = false; + } + + Future refreshDropboxToken() async { + try { + if (_dropboxTokenController.state != null) { + final res = await _dio.req( + DropboxRefreshTokenEndpoint( + refreshToken: _dropboxTokenController.state!.refresh_token, + clientId: AppSecretes.dropBoxAppKey, + clientSecret: AppSecretes.dropBoxAppSecret, + ), + ); + final newToken = DropboxToken.fromJson(res.data); + _dropboxTokenController.state = _dropboxTokenController.state!.copyWith( + access_token: newToken.access_token, + expires_in: newToken.expires_in, + token_type: newToken.token_type, + ); + } else { + throw const AuthSessionExpiredError(); + } + } catch (e) { + throw AppError.fromError(e); } } diff --git a/data/lib/services/dropbox_services.dart b/data/lib/services/dropbox_services.dart new file mode 100644 index 0000000..2861ce0 --- /dev/null +++ b/data/lib/services/dropbox_services.dart @@ -0,0 +1,35 @@ +import '../apis/network/client.dart'; +import '../errors/app_error.dart'; +import '../models/dropbox_account/dropbox_account.dart'; +import '../storage/app_preferences.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../apis/dropbox/dropbox_auth_endpoints.dart'; +import '../storage/provider/preferences_provider.dart'; + +final dropboxServiceProvider = Provider((ref) { + return DropboxService( + ref.read(dropboxAuthenticatedDioProvider), + ref.read(AppPreferences.dropboxCurrentUserAccount.notifier), + ); +}); + +class DropboxService { + final Dio _dropboxAuthenticatedDio; + final PreferenceNotifier _dropboxAccountController; + + const DropboxService( + this._dropboxAuthenticatedDio, + this._dropboxAccountController, + ); + + Future setCurrentUserAccount() async { + try { + final res = await _dropboxAuthenticatedDio + .req(const DropboxGetUserAccountEndpoint()); + _dropboxAccountController.state = DropboxAccount.fromJson(res.data); + } catch (e) { + AppError.fromError(e); + } + } +} diff --git a/data/lib/storage/app_preferences.dart b/data/lib/storage/app_preferences.dart index 85db6ce..c468e7b 100644 --- a/data/lib/storage/app_preferences.dart +++ b/data/lib/storage/app_preferences.dart @@ -1,3 +1,5 @@ +import '../models/dropbox_account/dropbox_account.dart'; +import '../models/token/token.dart'; import 'provider/preferences_provider.dart'; class AppPreferences { @@ -16,18 +18,36 @@ class AppPreferences { defaultValue: true, ); - static final canTakeAutoBackUpInGoogleDrive = createPrefProvider( + static final googleDriveAutoBackUp = createPrefProvider( prefKey: "google_drive_auto_backup", defaultValue: false, ); - static final googleDriveSignInHintShown = createPrefProvider( - prefKey: "google_drive_sign_in_hint_shown", + static final dropboxAutoBackUp = createPrefProvider( + prefKey: "dropbox_auto_backup", defaultValue: false, ); - static final googleDriveAutoBackUpHintShown = createPrefProvider( - prefKey: "google_drive_sign_in_hint_shown", + static final signInHintShown = createPrefProvider( + prefKey: "sign_in_hint_shown", defaultValue: false, ); + + static final dropboxToken = createEncodedPrefProvider( + prefKey: "dropbox_token", + toJson: (value) => value.toJson(), + fromJson: (json) => DropboxToken.fromJson(json), + ); + + static final dropboxCurrentUserAccount = + createEncodedPrefProvider( + prefKey: "dropbox_current_user_account", + toJson: (value) => value.toJson(), + fromJson: (json) => DropboxAccount.fromJson(json), + ); + + static final dropboxPKCECodeVerifier = createPrefProvider( + prefKey: "dropbox_code_verifier", + defaultValue: null, + ); } diff --git a/data/lib/storage/provider/preferences_provider.dart b/data/lib/storage/provider/preferences_provider.dart index 2f32645..647729f 100644 --- a/data/lib/storage/provider/preferences_provider.dart +++ b/data/lib/storage/provider/preferences_provider.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -31,6 +33,37 @@ StateNotifierProvider, T> createPrefProvider({ ); } +StateNotifierProvider, T?> createEncodedPrefProvider({ + required String prefKey, + T? defaultValue, + required Map Function(T value) toJson, + required T Function(Map json) fromJson, +}) { + T? jsonPodToObject(String? json, T? defaultValue) { + if (json == null) { + return defaultValue; + } + return fromJson(jsonDecode(json)); + } + + return StateNotifierProvider, T?>( + (ref) => PreferenceNotifier( + jsonPodToObject( + ref.watch(sharedPreferencesProvider).getString(prefKey), + defaultValue, + ), + (curr) { + final prefs = ref.watch(sharedPreferencesProvider); + if (curr == null) { + prefs.remove(prefKey); + } else { + prefs.setString(prefKey, jsonEncode(toJson(curr))); + } + }, + ), + ); +} + class PreferenceNotifier extends StateNotifier { Function(T curr)? onUpdate; diff --git a/data/pubspec.yaml b/data/pubspec.yaml index 7c4dc98..817ae6d 100644 --- a/data/pubspec.yaml +++ b/data/pubspec.yaml @@ -17,6 +17,8 @@ dependencies: photo_manager: ^3.6.2 package_info_plus: ^8.1.1 path_provider: ^2.1.5 + crypto: ^3.0.3 + url_launcher: ^6.2.6 # authentication google_sign_in: ^6.2.2 diff --git a/style/lib/buttons/buttons_list.dart b/style/lib/buttons/buttons_list.dart index ea266ec..4f975d6 100644 --- a/style/lib/buttons/buttons_list.dart +++ b/style/lib/buttons/buttons_list.dart @@ -20,14 +20,14 @@ class ActionListButton { class ActionList extends StatelessWidget { final List buttons; - final EdgeInsetsGeometry? margin; + final EdgeInsetsGeometry margin; final BorderRadius borderRadius; final Color? background; const ActionList({ super.key, required this.buttons, - this.margin, + this.margin = const EdgeInsets.symmetric(vertical: 8), this.background, this.borderRadius = const BorderRadius.all(Radius.circular(12)), }); diff --git a/style/lib/theme/colors.dart b/style/lib/theme/colors.dart index 867f7f4..c41b7bc 100644 --- a/style/lib/theme/colors.dart +++ b/style/lib/theme/colors.dart @@ -39,4 +39,5 @@ class AppColors { static const awarenessWarningColor = Color(0xFFFFA500); static const googleDriveColor = Color(0xFF4285F4); + static const dropBoxColor = Color(0xFF0061FE); }