diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index 7b549e7..a7e9847 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -12,6 +12,7 @@ + @@ -33,6 +34,7 @@ + @@ -215,6 +217,7 @@ + @@ -285,6 +288,7 @@ + @@ -722,6 +726,7 @@ + @@ -729,6 +734,7 @@ + @@ -765,6 +771,7 @@ + @@ -1165,6 +1172,7 @@ + @@ -1193,6 +1201,7 @@ + @@ -1214,6 +1223,7 @@ + @@ -1264,7 +1274,6 @@ @@ -1279,7 +1288,6 @@ @@ -1384,6 +1392,7 @@ + @@ -1478,7 +1487,9 @@ + + @@ -1506,6 +1517,7 @@ + @@ -1515,6 +1527,7 @@ + @@ -1577,13 +1590,16 @@ + + + @@ -1647,23 +1663,24 @@ + + + - - @@ -1680,6 +1697,7 @@ + diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index ee6b26b..393d7ec 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -1,62 +1,60 @@ - - - - - - + - + - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - diff --git a/app/.gitignore b/app/.gitignore index 76763da..f9608b2 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/app/assets/images/ic_download.svg b/app/assets/images/ic_download.svg new file mode 100644 index 0000000..d452f49 --- /dev/null +++ b/app/assets/images/ic_download.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/icons/ic_dropbox.svg b/app/assets/images/ic_dropbox.svg similarity index 100% rename from app/assets/images/icons/ic_dropbox.svg rename to app/assets/images/ic_dropbox.svg diff --git a/app/assets/images/ic_error.svg b/app/assets/images/ic_error.svg new file mode 100644 index 0000000..6d37d3c --- /dev/null +++ b/app/assets/images/ic_error.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/icons/ic_google_drive.svg b/app/assets/images/ic_google_drive.svg similarity index 100% rename from app/assets/images/icons/ic_google_drive.svg rename to app/assets/images/ic_google_drive.svg diff --git a/app/assets/images/ic_no_internet.svg b/app/assets/images/ic_no_internet.svg new file mode 100644 index 0000000..fe22715 --- /dev/null +++ b/app/assets/images/ic_no_internet.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/ic_upload.svg b/app/assets/images/ic_upload.svg new file mode 100644 index 0000000..e4e2da5 --- /dev/null +++ b/app/assets/images/ic_upload.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/locales/app_en.arb b/app/assets/locales/app_en.arb index 1967eba..a2f400f 100644 --- a/app/assets/locales/app_en.arb +++ b/app/assets/locales/app_en.arb @@ -18,16 +18,20 @@ "common_delete": "Delete", "common_share": "Share", "common_cancel": "Cancel", + "common_retry": "Retry", "common_done": "Done", "common_not_available": "N/A", "common_open_settings": "Open Settings", + "common_sign_in": "Sign In", + "common_skip_for_now": "Skip for Now", "@_ERROR":{}, "no_internet_connection_error": "No internet connection! Please check your network and try again.", - "something_went_wrong_error": "Something went wrong! Please try again later.", + "something_went_wrong_error": "Oops, something went wrong. Please try again, we’re on it and will fix it soon!", "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.", + "save_media_in_gallery_error": "There was an issue saving your media to the gallery. Please try again, we’ll have it sorted out shortly", "@_MEDIA_ACTIONS":{}, "upload_to_google_drive_title": "Upload to Google Drive", @@ -51,7 +55,15 @@ "@_HOME":{}, "unable_to_load_media_error": "Unable to load media!", "unable_to_load_media_message": "Oops! It looks like we're having trouble loading the media right now. Please try again later.", - "hint_sign_in_message": "Sign in with Google Drive or Dropbox and enjoy quick access to all your awesome content in one spot", + "sign_in_hint_message": "Store your media in the cloud! Sign in with Google Drive or Dropbox and never lose track of your awesome memories.", + + "@_NO_INTERNET_CONNECTION_SCREEN":{}, + "no_internet_connection_title": "No Internet Connection", + "no_internet_connection_message": "Looks like the internet took a coffee break. We couldn’t sync to the cloud—please check your connection?", + + "@_ERROR_SCREEN":{}, + "error_screen_title": "Something Went Wrong", + "error_screen_message": "Uh-oh! Something didn’t go as planned. Please try again—rest assured, we're working to get things back on track as quickly as possible!", "@_NO_MEDIA_ACCESS": {}, "no_media_access_screen_title": "Media Access Required", @@ -87,10 +99,10 @@ "@_TRANSFER":{}, "transfer_screen_title": "Transfer", - "empty_upload_title":"No Files Being Uploaded", - "empty_upload_message": "No uploads are happening right now. If you have files to upload, go ahead and start uploading.", - "empty_download_title":"No Files Being Downloaded", - "empty_download_message": "No downloads are happening right now. If you have files to download, go ahead and start downloading.", + "empty_upload_title": "Your Cloud Awaits!", + "empty_upload_message": "Let’s store your precious memories safely in the cloud so you’ll never lose them. Start uploading now and keep them secure forever!", + "empty_download_title": "Memories on Demand!", + "empty_download_message": "Bring your favorite photos and videos with you to enjoy anytime, even without the internet. Start downloading now and make them yours!", "@_PREVIEW":{}, "download_require_text": "Download required", diff --git a/app/ios/Runner.xcodeproj/project.pbxproj b/app/ios/Runner.xcodeproj/project.pbxproj index ee31399..2209f4b 100644 --- a/app/ios/Runner.xcodeproj/project.pbxproj +++ b/app/ios/Runner.xcodeproj/project.pbxproj @@ -317,6 +317,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); diff --git a/app/lib/components/error_screen.dart b/app/lib/components/error_screen.dart new file mode 100644 index 0000000..e9ca778 --- /dev/null +++ b/app/lib/components/error_screen.dart @@ -0,0 +1,51 @@ +import '../domain/extensions/context_extensions.dart'; +import 'place_holder_screen.dart'; +import 'package:data/errors/app_error.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_svg/svg.dart'; +import '../gen/assets.gen.dart'; + +class ErrorScreen extends StatelessWidget { + final Object error; + final VoidCallback onRetryTap; + + const ErrorScreen({super.key, required this.error, required this.onRetryTap}); + + @override + Widget build(BuildContext context) { + final error = AppError.fromError(this.error); + if (error is NoConnectionError) { + return _noInternetConnectionScreen(context); + } else { + return _errorScreen(context); + } + } + + Widget _noInternetConnectionScreen(BuildContext context) => PlaceHolderScreen( + icon: SvgPicture.asset( + Assets.images.icNoInternet, + height: 120, + width: 120, + ), + title: context.l10n.no_internet_connection_title, + message: context.l10n.no_internet_connection_message, + action: PlaceHolderScreenAction( + title: context.l10n.common_retry, + onPressed: onRetryTap, + ), + ); + + Widget _errorScreen(BuildContext context) => PlaceHolderScreen( + icon: SvgPicture.asset( + Assets.images.icError, + height: 120, + width: 120, + ), + title: context.l10n.error_screen_title, + message: context.l10n.error_screen_message, + action: PlaceHolderScreenAction( + title: context.l10n.common_retry, + onPressed: onRetryTap, + ), + ); +} diff --git a/app/lib/components/error_view.dart b/app/lib/components/place_holder_screen.dart similarity index 89% rename from app/lib/components/error_view.dart rename to app/lib/components/place_holder_screen.dart index 1a8b2fb..fa0f4d0 100644 --- a/app/lib/components/error_view.dart +++ b/app/lib/components/place_holder_screen.dart @@ -3,21 +3,21 @@ import 'package:style/buttons/primary_button.dart'; import 'package:style/extensions/context_extensions.dart'; import 'package:style/text/app_text_style.dart'; -class ErrorViewAction { +class PlaceHolderScreenAction { final String title; final VoidCallback onPressed; - const ErrorViewAction({required this.title, required this.onPressed}); + const PlaceHolderScreenAction({required this.title, required this.onPressed}); } -class ErrorView extends StatelessWidget { +class PlaceHolderScreen extends StatelessWidget { final Widget? icon; final String title; final String message; final Color? foregroundColor; - final ErrorViewAction? action; + final PlaceHolderScreenAction? action; - const ErrorView({ + const PlaceHolderScreen({ super.key, this.icon, required this.title, diff --git a/app/lib/domain/extensions/app_error_extensions.dart b/app/lib/domain/extensions/app_error_extensions.dart index 0e8fcc7..3a64644 100644 --- a/app/lib/domain/extensions/app_error_extensions.dart +++ b/app/lib/domain/extensions/app_error_extensions.dart @@ -5,26 +5,24 @@ import 'package:flutter/cupertino.dart'; extension AppErrorExtensions on Object { String l10nMessage(BuildContext context) { - if (this is AppError) { - switch ((this as AppError).l10nCode) { - 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.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; - } - } else if (this is String) { + if (this is String) { return this as String; - } else { - return context.l10n.something_went_wrong_error; + } + switch (AppError.fromError(this).l10nCode) { + 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.backUpFolderNotFoundError: + return context.l10n.back_up_folder_not_found_error; + case AppErrorL10nCodes.authSessionExpiredError: + return context.l10n.auth_session_expired_error; + case AppErrorL10nCodes.unableToSaveFileInGalleryError: + return context.l10n.save_media_in_gallery_error; + default: + return context.l10n.something_went_wrong_error; } } } diff --git a/app/lib/domain/extensions/map_extensions.dart b/app/lib/domain/extensions/map_extensions.dart deleted file mode 100644 index 017dff4..0000000 --- a/app/lib/domain/extensions/map_extensions.dart +++ /dev/null @@ -1,4 +0,0 @@ -extension MapExtension on Map> { - List valuesWhere(bool Function(E element) test) => - values.expand((element) => element.where(test)).toList(); -} diff --git a/app/lib/domain/handlers/deep_links_handler.dart b/app/lib/domain/handlers/deep_links_handler.dart index 6ba6de7..64e043f 100644 --- a/app/lib/domain/handlers/deep_links_handler.dart +++ b/app/lib/domain/handlers/deep_links_handler.dart @@ -1,7 +1,6 @@ -import 'dart:developer'; - import 'package:app_links/app_links.dart'; import 'package:data/apis/network/urls.dart'; +import 'package:data/log/logger.dart'; import 'package:data/services/auth_service.dart'; import 'package:data/services/dropbox_services.dart'; import 'package:flutter/foundation.dart'; @@ -16,6 +15,7 @@ class DeepLinkHandler { required ProviderContainer container, }) async { final appLinks = container.read(appLinksProvider); + final logger = container.read(loggerProvider); Future handleDeepLink(Uri link) async { if (link.toString().contains(RedirectURL.auth) && @@ -27,29 +27,31 @@ class DeepLinkHandler { ); final dropboxService = container.read(dropboxServiceProvider); - await dropboxService.setCurrentUserAccount(); - await dropboxService.setFileIdAppPropertyTemplate(); + await Future.wait([ + dropboxService.setCurrentUserAccount(), + dropboxService.setFileIdAppPropertyTemplate(), + ]); } } 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", + } catch (e, s) { + logger.e( + "DEEP LINK ERROR: Failed to handle deep link", + error: e, + stackTrace: s, ); } appLinks.uriLinkStream.listen( (link) => handleDeepLink(link), - onError: (error) { - log( - "Failed to listen to deep links", - error: error, - name: "DeepLinkHandler", + onError: (e, s) { + logger.e( + "DEEP LINK ERROR: Failed to handle deep link", + error: e, + stackTrace: s, ); }, ); diff --git a/app/lib/gen/assets.gen.dart b/app/lib/gen/assets.gen.dart index 7048b3e..59df32f 100644 --- a/app/lib/gen/assets.gen.dart +++ b/app/lib/gen/assets.gen.dart @@ -16,24 +16,34 @@ class $AssetsImagesGen { AssetGenImage get appLogo => const AssetGenImage('assets/images/app_logo.png'); - /// Directory path: assets/images/icons - $AssetsImagesIconsGen get icons => const $AssetsImagesIconsGen(); + /// File path: assets/images/ic_download.svg + String get icDownload => 'assets/images/ic_download.svg'; - /// List of all assets - List get values => [appLogo]; -} + /// File path: assets/images/ic_dropbox.svg + String get icDropbox => 'assets/images/ic_dropbox.svg'; + + /// File path: assets/images/ic_error.svg + String get icError => 'assets/images/ic_error.svg'; -class $AssetsImagesIconsGen { - const $AssetsImagesIconsGen(); + /// File path: assets/images/ic_google_drive.svg + String get icGoogleDrive => 'assets/images/ic_google_drive.svg'; - /// File path: assets/images/icons/ic_dropbox.svg - String get icDropbox => 'assets/images/icons/ic_dropbox.svg'; + /// File path: assets/images/ic_no_internet.svg + String get icNoInternet => 'assets/images/ic_no_internet.svg'; - /// File path: assets/images/icons/ic_google_drive.svg - String get icGoogleDrive => 'assets/images/icons/ic_google_drive.svg'; + /// File path: assets/images/ic_upload.svg + String get icUpload => 'assets/images/ic_upload.svg'; /// List of all assets - List get values => [icDropbox, icGoogleDrive]; + List get values => [ + appLogo, + icDownload, + icDropbox, + icError, + icGoogleDrive, + icNoInternet, + icUpload + ]; } class Assets { diff --git a/app/lib/main.dart b/app/lib/main.dart index 8b4a3d6..5d482ad 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -1,8 +1,7 @@ import 'dart:async'; import 'dart:io'; -import 'dart:ui'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; - +import 'package:flutter/foundation.dart'; import 'domain/handlers/deep_links_handler.dart'; import 'firebase_options.dart'; import 'package:data/storage/provider/preferences_provider.dart'; @@ -20,14 +19,16 @@ Future main() async { options: DefaultFirebaseOptions.currentPlatform, ); - // Pass all uncaught "fatal" errors from the framework to Crashlytics - FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; + if (!kDebugMode) { + // Pass all uncaught "fatal" errors from the framework to Crashlytics + FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; - // Pass all uncaught asynchronous errors that aren't handled by the Flutter framework to Crashlytics - PlatformDispatcher.instance.onError = (error, stack) { - FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); - return true; - }; + // Pass all uncaught asynchronous errors that aren't handled by the Flutter framework to Crashlytics + PlatformDispatcher.instance.onError = (error, stack) { + FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); + return true; + }; + } if (Platform.isAndroid) { // Workaround for https://github.com/flutter/flutter/issues/35162 diff --git a/app/lib/ui/flow/accounts/accounts_screen.dart b/app/lib/ui/flow/accounts/accounts_screen.dart index 08e5330..216b6b9 100644 --- a/app/lib/ui/flow/accounts/accounts_screen.dart +++ b/app/lib/ui/flow/accounts/accounts_screen.dart @@ -105,7 +105,7 @@ class _AccountsScreenState extends ConsumerState { buttons: [ ActionListButton( leading: SvgPicture.asset( - Assets.images.icons.icGoogleDrive, + Assets.images.icGoogleDrive, height: 24, width: 24, ), @@ -159,7 +159,7 @@ class _AccountsScreenState extends ConsumerState { buttons: [ ActionListButton( leading: SvgPicture.asset( - Assets.images.icons.icDropbox, + Assets.images.icDropbox, height: 24, width: 24, ), diff --git a/app/lib/ui/flow/home/components/app_media_item.dart b/app/lib/ui/flow/home/components/app_media_item.dart index 822173b..c47c341 100644 --- a/app/lib/ui/flow/home/components/app_media_item.dart +++ b/app/lib/ui/flow/home/components/app_media_item.dart @@ -86,7 +86,7 @@ class AppMediaItem extends StatelessWidget { begin: Alignment.topRight, end: Alignment.bottomRight, colors: [ - Colors.black.withOpacity(0.4), + Colors.black.withValues(alpha: 0.4), Colors.transparent, ], ), @@ -134,7 +134,7 @@ class AppMediaItem extends StatelessWidget { begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ - Colors.black.withOpacity(0.4), + Colors.black.withValues(alpha: 0.4), Colors.transparent, ], ), @@ -149,7 +149,7 @@ class AppMediaItem extends StatelessWidget { color: Colors.white, ), child: SvgPicture.asset( - Assets.images.icons.icGoogleDrive, + Assets.images.icGoogleDrive, height: 10, width: 10, ), @@ -165,7 +165,7 @@ class AppMediaItem extends StatelessWidget { color: Colors.white, ), child: SvgPicture.asset( - Assets.images.icons.icDropbox, + Assets.images.icDropbox, height: 10, width: 10, ), @@ -221,7 +221,7 @@ class AppMediaItem extends StatelessWidget { ), const SizedBox(width: 4), Text( - "${uploadMediaProcess?.progressPercentage.toInt()}%", + "${progressPercentage.toInt()}%", style: AppTextStyles.caption.copyWith( color: Colors.white, shadows: [ diff --git a/app/lib/ui/flow/home/components/hint_view.dart b/app/lib/ui/flow/home/components/hint_view.dart deleted file mode 100644 index f2fccf0..0000000 --- a/app/lib/ui/flow/home/components/hint_view.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:style/buttons/action_button.dart'; -import 'package:style/extensions/context_extensions.dart'; -import 'package:style/text/app_text_style.dart'; - -class HintView extends StatelessWidget { - final String title; - final String hint; - final String actionTitle; - final VoidCallback? onActionTap; - final VoidCallback onClose; - - const HintView({ - super.key, - required this.hint, - required this.onClose, - this.actionTitle = '', - this.onActionTap, - required this.title, - }); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: context.colorScheme.containerNormalOnSurface, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.only(top: 4, left: 16), - child: Text( - title, - style: AppTextStyles.subtitle2.copyWith( - color: context.colorScheme.textPrimary, - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0).copyWith(bottom: 4), - child: ActionButton( - backgroundColor: context.colorScheme.containerNormal, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - size: 28, - onPressed: onClose, - icon: Icon( - CupertinoIcons.xmark, - color: context.colorScheme.textSecondary, - size: 18, - ), - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.only(bottom: 16, left: 16, right: 16), - child: Text( - hint, - style: AppTextStyles.body2.copyWith( - color: context.colorScheme.textSecondary, - ), - ), - ), - if (onActionTap != null) - FilledButton( - onPressed: onActionTap, - style: FilledButton.styleFrom( - backgroundColor: context.colorScheme.containerNormal, - foregroundColor: context.colorScheme.textPrimary, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - minimumSize: const Size(double.maxFinite, 40), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Text( - actionTitle, - style: AppTextStyles.button, - ), - ), - ], - ), - ); - } -} diff --git a/app/lib/ui/flow/home/components/hints.dart b/app/lib/ui/flow/home/components/hints.dart deleted file mode 100644 index af1554f..0000000 --- a/app/lib/ui/flow/home/components/hints.dart +++ /dev/null @@ -1,35 +0,0 @@ -import '../../../../domain/extensions/context_extensions.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'; -import 'hint_view.dart'; - -class HomeScreenHints extends ConsumerWidget { - const HomeScreenHints({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - 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.common_hey_there, - hint: context.l10n.hint_sign_in_message, - onClose: () { - ref.read(AppPreferences.signInHintShown.notifier).state = true; - }, - actionTitle: context.l10n.add_account_title, - onActionTap: () { - AccountRoute().push(context); - ref.read(AppPreferences.signInHintShown.notifier).state = true; - }, - ); - } else { - return const SizedBox(); - } - } -} diff --git a/app/lib/ui/flow/home/components/multi_selection_done_button.dart b/app/lib/ui/flow/home/components/multi_selection_done_button.dart index 54da5c0..da582ab 100644 --- a/app/lib/ui/flow/home/components/multi_selection_done_button.dart +++ b/app/lib/ui/flow/home/components/multi_selection_done_button.dart @@ -105,7 +105,7 @@ class MultiSelectionDoneButton extends ConsumerWidget { ), ), SvgPicture.asset( - Assets.images.icons.icGoogleDrive, + Assets.images.icGoogleDrive, width: 14, height: 14, ), @@ -152,7 +152,7 @@ class MultiSelectionDoneButton extends ConsumerWidget { ), ), SvgPicture.asset( - Assets.images.icons.icGoogleDrive, + Assets.images.icGoogleDrive, width: 14, height: 14, ), @@ -204,7 +204,7 @@ class MultiSelectionDoneButton extends ConsumerWidget { ), ), SvgPicture.asset( - Assets.images.icons.icGoogleDrive, + Assets.images.icGoogleDrive, width: 14, height: 14, ), @@ -254,7 +254,7 @@ class MultiSelectionDoneButton extends ConsumerWidget { ), ), SvgPicture.asset( - Assets.images.icons.icDropbox, + Assets.images.icDropbox, width: 14, height: 14, ), @@ -301,7 +301,7 @@ class MultiSelectionDoneButton extends ConsumerWidget { ), ), SvgPicture.asset( - Assets.images.icons.icDropbox, + Assets.images.icDropbox, width: 14, height: 14, ), @@ -348,7 +348,7 @@ class MultiSelectionDoneButton extends ConsumerWidget { ), ), SvgPicture.asset( - Assets.images.icons.icDropbox, + Assets.images.icDropbox, width: 14, height: 14, ), diff --git a/app/lib/ui/flow/home/components/no_internet_connection_hint.dart b/app/lib/ui/flow/home/components/no_internet_connection_hint.dart new file mode 100644 index 0000000..ea93656 --- /dev/null +++ b/app/lib/ui/flow/home/components/no_internet_connection_hint.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:style/animations/fade_in_switcher.dart'; +import 'package:style/extensions/context_extensions.dart'; +import 'package:style/text/app_text_style.dart'; +import '../../../../domain/extensions/context_extensions.dart'; +import '../../../../gen/assets.gen.dart'; +import '../home_screen_view_model.dart'; + +class NoInternetConnectionHint extends ConsumerWidget { + const NoInternetConnectionHint({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final showHint = ref.watch( + homeViewStateNotifier.select( + (value) => + !value.hasInternet && + (value.dropboxAccount != null || value.googleAccount != null), + ), + ); + return FadeInSwitcher( + child: !showHint + ? const SizedBox() + : Container( + margin: EdgeInsets.all(16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: context.colorScheme.containerLowOnSurface, + border: Border.all( + color: context.colorScheme.outline, + ), + ), + child: Column( + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.images.icNoInternet, + height: 50, + width: 50, + ), + const SizedBox(width: 16), + Expanded( + child: Text( + context.l10n.no_internet_connection_message, + style: AppTextStyles.body2.copyWith( + letterSpacing: 0.05, + color: context.colorScheme.textPrimary, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + FilledButton( + onPressed: () { + ref + .read(homeViewStateNotifier.notifier) + .loadMedias(reload: true); + }, + style: FilledButton.styleFrom( + backgroundColor: context.colorScheme.containerLow, + foregroundColor: context.colorScheme.textPrimary, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + minimumSize: const Size(double.maxFinite, 40), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + context.l10n.common_retry, + style: AppTextStyles.button, + ), + ), + ], + ), + ), + ); + } +} diff --git a/app/lib/ui/flow/home/components/no_local_medias_access_screen.dart b/app/lib/ui/flow/home/components/no_local_medias_access_screen.dart index 06a0512..9d1cd74 100644 --- a/app/lib/ui/flow/home/components/no_local_medias_access_screen.dart +++ b/app/lib/ui/flow/home/components/no_local_medias_access_screen.dart @@ -1,4 +1,4 @@ -import '../../../../components/error_view.dart'; +import '../../../../components/place_holder_screen.dart'; import '../../../../domain/extensions/context_extensions.dart'; import '../home_screen_view_model.dart'; import 'package:flutter/cupertino.dart'; @@ -12,7 +12,7 @@ class NoLocalMediasAccessScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final notifier = ref.read(homeViewStateNotifier.notifier); - return ErrorView( + return PlaceHolderScreen( title: context.l10n.no_media_access_screen_title, icon: Icon( CupertinoIcons.photo_on_rectangle, @@ -20,7 +20,7 @@ class NoLocalMediasAccessScreen extends ConsumerWidget { size: 120, ), message: context.l10n.no_media_access_screen_message, - action: ErrorViewAction( + action: PlaceHolderScreenAction( onPressed: () async { await openAppSettings(); notifier.loadMedias(reload: true); diff --git a/app/lib/ui/flow/home/components/sign_in_hint.dart b/app/lib/ui/flow/home/components/sign_in_hint.dart new file mode 100644 index 0000000..358e9f2 --- /dev/null +++ b/app/lib/ui/flow/home/components/sign_in_hint.dart @@ -0,0 +1,105 @@ +import 'package:style/animations/fade_in_switcher.dart'; +import 'package:style/extensions/context_extensions.dart'; +import 'package:style/text/app_text_style.dart'; +import '../../../../domain/extensions/context_extensions.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'; + +class HomeScreenHints extends ConsumerWidget { + const HomeScreenHints({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final googleAccount = ref.watch(googleUserAccountProvider); + final dropboxAccount = ref.watch(AppPreferences.dropboxCurrentUserAccount); + final signInHintShown = ref.watch(AppPreferences.signInHintShown); + + return FadeInSwitcher( + child: (googleAccount != null || + dropboxAccount != null || + signInHintShown) + ? const SizedBox() + : Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: context.colorScheme.containerLowOnSurface, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: context.colorScheme.outline), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.common_hey_there, + style: AppTextStyles.subtitle2.copyWith( + color: context.colorScheme.textPrimary, + ), + ), + const SizedBox(height: 8), + Text( + context.l10n.sign_in_hint_message, + style: AppTextStyles.body2.copyWith( + color: context.colorScheme.textSecondary, + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: FilledButton( + onPressed: () { + ref + .read(AppPreferences.signInHintShown.notifier) + .state = true; + }, + style: FilledButton.styleFrom( + backgroundColor: context.colorScheme.containerLow, + foregroundColor: context.colorScheme.textPrimary, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + minimumSize: const Size(double.maxFinite, 40), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + context.l10n.common_skip_for_now, + style: AppTextStyles.button, + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: FilledButton( + onPressed: () { + AccountRoute().push(context); + ref + .read(AppPreferences.signInHintShown.notifier) + .state = true; + }, + style: FilledButton.styleFrom( + backgroundColor: context.colorScheme.containerLow, + foregroundColor: context.colorScheme.textPrimary, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + minimumSize: const Size(double.maxFinite, 40), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + context.l10n.common_sign_in, + style: AppTextStyles.button, + ), + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/app/lib/ui/flow/home/home_screen.dart b/app/lib/ui/flow/home/home_screen.dart index e325338..3dcf564 100644 --- a/app/lib/ui/flow/home/home_screen.dart +++ b/app/lib/ui/flow/home/home_screen.dart @@ -1,12 +1,14 @@ import 'dart:io'; import 'package:flutter/services.dart'; import '../../../components/app_page.dart'; -import '../../../components/error_view.dart'; +import '../../../components/error_screen.dart'; import '../../../domain/extensions/widget_extensions.dart'; import '../../../domain/formatter/date_formatter.dart'; import '../../../domain/extensions/context_extensions.dart'; import '../../../gen/assets.gen.dart'; +import 'components/no_internet_connection_hint.dart'; import 'components/no_local_medias_access_screen.dart'; +import 'components/sign_in_hint.dart'; import 'home_screen_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -17,7 +19,6 @@ import 'package:style/text/app_text_style.dart'; import '../../../components/snack_bar.dart'; import '../../navigation/app_route.dart'; import 'components/app_media_item.dart'; -import 'components/hints.dart'; import 'components/multi_selection_done_button.dart'; import 'package:style/buttons/action_button.dart'; import 'package:style/animations/fade_in_switcher.dart'; @@ -87,10 +88,10 @@ class _HomeScreenState extends ConsumerState { return const Center(child: AppCircularProgressIndicator()); } else if (!state.hasMedia && !state.hasLocalMediaAccess) { return const NoLocalMediasAccessScreen(); - } else if (state.error != null) { - return ErrorView( - title: context.l10n.unable_to_load_media_error, - message: context.l10n.unable_to_load_media_message, + } else if (state.error != null && !state.hasMedia) { + return ErrorScreen( + error: state.error!, + onRetryTap: () => _notifier.loadMedias(reload: true), ); } @@ -129,7 +130,12 @@ class _HomeScreenState extends ConsumerState { itemCount: state.medias.length + 2, itemBuilder: (context, index) { if (index == 0) { - return const HomeScreenHints(); + return Column( + children: [ + const HomeScreenHints(), + const NoInternetConnectionHint(), + ], + ); } else if (index == state.medias.length + 1) { return FadeInSwitcher( child: state.loading 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 25aced4..ec2718f 100644 --- a/app/lib/ui/flow/home/home_screen_view_model.dart +++ b/app/lib/ui/flow/home/home_screen_view_model.dart @@ -13,6 +13,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:logger/logger.dart'; +import 'package:data/handlers/connectivity_handler.dart'; import 'home_view_model_helper_mixin.dart'; import 'package:data/repositories/media_process_repository.dart'; import 'package:data/domain/config.dart'; @@ -29,6 +30,7 @@ final homeViewStateNotifier = ref.read(authServiceProvider), ref.read(mediaProcessRepoProvider), ref.read(loggerProvider), + ref.read(connectivityHandlerProvider), ref.read(AppPreferences.dropboxCurrentUserAccount), ); ref.listen(AppPreferences.dropboxCurrentUserAccount, (previous, next) { @@ -45,6 +47,7 @@ class HomeViewStateNotifier extends StateNotifier final DropboxService _dropboxService; final LocalMediaService _localMediaService; final MediaProcessRepo _mediaProcessRepo; + final ConnectivityHandler _connectivityHandler; StreamSubscription? _googleAccountSubscription; @@ -70,6 +73,7 @@ class HomeViewStateNotifier extends StateNotifier this._authService, this._mediaProcessRepo, this._logger, + this._connectivityHandler, DropboxAccount? _dropboxAccount, ) : super(HomeViewState(dropboxAccount: _dropboxAccount)) { _mediaProcessRepo.addListener(_mediaProcessObserve); @@ -86,7 +90,15 @@ class HomeViewStateNotifier extends StateNotifier _authService.onGoogleAccountChange.listen((event) async { if (event != null) { state = state.copyWith(googleAccount: event); - _backUpFolderId = await _googleDriveService.getBackUpFolderId(); + try { + _backUpFolderId = await _googleDriveService.getBackUpFolderId(); + } catch (e, s) { + _logger.e( + "HomeViewStateNotifier: unable to get google drive back up folder id", + error: e, + stackTrace: s, + ); + } loadMedias(reload: true); } else { _backUpFolderId = null; @@ -220,12 +232,22 @@ class HomeViewStateNotifier extends StateNotifier _dropboxMediasWithLocalRef.clear(); } - // Request local media permission if not granted - final hasAccess = await _localMediaService.requestPermission(); - state = state.copyWith(hasLocalMediaAccess: hasAccess); + // Request local media permission and internet connection + final res = await Future.wait([ + _localMediaService.requestPermission(), + _connectivityHandler.hasInternetAccess(), + ]); + + final hasLocalMediaAccess = res[0]; + final hasInternet = res[1]; + + state = state.copyWith( + hasLocalMediaAccess: hasLocalMediaAccess, + hasInternet: hasInternet, + ); // Load local media if access is granted and not max loaded - final localMedia = !hasAccess || _localMaxLoaded + final localMedia = !hasLocalMediaAccess || _localMaxLoaded ? [] : await _localMediaService.getLocalMedia( start: _localMediaCount, @@ -256,7 +278,9 @@ class HomeViewStateNotifier extends StateNotifier final List cloudBasedMedias = []; // Load medias from google drive and separate the local ref medias and only cloud based medias. - if (!_googleDriveMaxLoaded && state.googleAccount != null) { + if (!_googleDriveMaxLoaded && + state.googleAccount != null && + hasInternet) { _backUpFolderId ??= await _googleDriveService.getBackUpFolderId(); final res = await _googleDriveService.getPaginatedMedias( @@ -273,7 +297,7 @@ class HomeViewStateNotifier extends StateNotifier } // Load medias from dropbox and separate the local ref medias and only cloud based medias. - if (!_dropboxMaxLoaded && state.dropboxAccount != null) { + if (!_dropboxMaxLoaded && state.dropboxAccount != null && hasInternet) { final res = await _dropboxService.getPaginatedMedias( folder: ProviderConstants.backupFolderPath, nextPageToken: _dropboxPageToken, @@ -297,7 +321,8 @@ class HomeViewStateNotifier extends StateNotifier // Refill the google drive local ref medias if it is empty and not max loaded if (_googleDriveMediasWithLocalRef.isEmpty && !_googleDriveMaxLoaded && - state.googleAccount != null) { + state.googleAccount != null && + hasInternet) { final res = await _googleDriveService.getPaginatedMedias( folder: _backUpFolderId!, nextPageToken: _googleDrivePageToken, @@ -316,7 +341,8 @@ class HomeViewStateNotifier extends StateNotifier // Refill the dropbox local ref medias if it is empty and not max loaded if (_dropboxMediasWithLocalRef.isEmpty && !_dropboxMaxLoaded && - state.dropboxAccount != null) { + state.dropboxAccount != null && + hasInternet) { final res = await _dropboxService.getPaginatedMedias( folder: ProviderConstants.backupFolderPath, nextPageToken: _dropboxPageToken, @@ -358,7 +384,12 @@ class HomeViewStateNotifier extends StateNotifier cloudLoading: false, ); } catch (e, s) { - state = state.copyWith(error: e, loading: false, cloudLoading: false); + state = state.copyWith( + error: state.medias.isEmpty ? e : null, + actionError: state.medias.isNotEmpty ? e : null, + loading: false, + cloudLoading: false, + ); _logger.e( "HomeViewStateNotifier: unable to load medias", error: e, @@ -398,79 +429,129 @@ class HomeViewStateNotifier extends StateNotifier } Future uploadToGoogleDrive() async { - if (state.googleAccount == null) return; - final selectedMedias = state.selectedMedias.entries - .where( - (element) => element.value.sources.contains(AppMediaSource.local), - ) - .map((e) => e.value) - .toList(); + try { + if (state.googleAccount == null) return; + final selectedMedias = state.selectedMedias.entries + .where( + (element) => element.value.sources.contains(AppMediaSource.local), + ) + .map((e) => e.value) + .toList(); - state = state.copyWith( - selectedMedias: {}, - actionError: null, - ); - _mediaProcessRepo.uploadMedia( - medias: selectedMedias, - provider: MediaProvider.googleDrive, - folderId: _backUpFolderId!, - ); + state = state.copyWith( + selectedMedias: {}, + actionError: null, + ); + if (_backUpFolderId == null) { + _backUpFolderId = await _googleDriveService.getBackUpFolderId(); + } else { + await _connectivityHandler.checkInternetAccess(); + } + _mediaProcessRepo.uploadMedia( + medias: selectedMedias, + provider: MediaProvider.googleDrive, + folderId: _backUpFolderId!, + ); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "HomeViewStateNotifier: unable to upload to google drive", + error: e, + stackTrace: s, + ); + } } Future uploadToDropbox() async { if (state.dropboxAccount == null) return; - final selectedMedias = state.selectedMedias.entries - .where( - (element) => element.value.sources.contains(AppMediaSource.local), - ) - .map((e) => e.value) - .toList(); + try { + final selectedMedias = state.selectedMedias.entries + .where( + (element) => element.value.sources.contains(AppMediaSource.local), + ) + .map((e) => e.value) + .toList(); - state = state.copyWith( - selectedMedias: {}, - actionError: null, - ); - _mediaProcessRepo.uploadMedia( - medias: selectedMedias, - provider: MediaProvider.dropbox, - folderId: ProviderConstants.backupFolderPath, - ); + state = state.copyWith( + selectedMedias: {}, + actionError: null, + ); + await _connectivityHandler.checkInternetAccess(); + _mediaProcessRepo.uploadMedia( + medias: selectedMedias, + provider: MediaProvider.dropbox, + folderId: ProviderConstants.backupFolderPath, + ); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "HomeViewStateNotifier: unable to upload to dropbox", + error: e, + stackTrace: s, + ); + } } Future downloadFromGoogleDrive() async { - if (state.googleAccount == null) return; - final selectedMedias = state.selectedMedias.entries - .where( - (element) => element.value.isGoogleDriveStored, - ) - .map((e) => e.value) - .toList(); - - state = state.copyWith(selectedMedias: {}, actionError: null); - - _mediaProcessRepo.downloadMedia( - folderId: _backUpFolderId!, - medias: selectedMedias, - provider: MediaProvider.googleDrive, - ); + try { + if (state.googleAccount == null) return; + final selectedMedias = state.selectedMedias.entries + .where( + (element) => element.value.isGoogleDriveStored, + ) + .map((e) => e.value) + .toList(); + + state = state.copyWith(selectedMedias: {}, actionError: null); + + if (_backUpFolderId == null) { + _backUpFolderId = await _googleDriveService.getBackUpFolderId(); + } else { + await _connectivityHandler.checkInternetAccess(); + } + + _mediaProcessRepo.downloadMedia( + folderId: _backUpFolderId!, + medias: selectedMedias, + provider: MediaProvider.googleDrive, + ); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "HomeViewStateNotifier: unable to download from google drive", + error: e, + stackTrace: s, + ); + } } Future downloadFromDropbox() async { - if (state.dropboxAccount == null) return; - final selectedMedias = state.selectedMedias.entries - .where( - (element) => element.value.isDropboxStored, - ) - .map((e) => e.value) - .toList(); - - state = state.copyWith(selectedMedias: {}, actionError: null); - - _mediaProcessRepo.downloadMedia( - folderId: ProviderConstants.backupFolderPath, - medias: selectedMedias, - provider: MediaProvider.dropbox, - ); + try { + if (state.dropboxAccount == null) return; + final selectedMedias = state.selectedMedias.entries + .where( + (element) => element.value.isDropboxStored, + ) + .map((e) => e.value) + .toList(); + + state = state.copyWith(selectedMedias: {}, actionError: null); + + await _connectivityHandler.checkInternetAccess(); + + _mediaProcessRepo.downloadMedia( + folderId: ProviderConstants.backupFolderPath, + medias: selectedMedias, + provider: MediaProvider.dropbox, + ); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "HomeViewStateNotifier: unable to download from dropbox", + error: e, + stackTrace: s, + ); + } } Future deleteLocalMedias() async { @@ -611,6 +692,7 @@ class HomeViewState with _$HomeViewState { Object? error, Object? actionError, @Default(false) bool hasLocalMediaAccess, + @Default(false) bool hasInternet, @Default(false) bool loading, @Default(false) bool cloudLoading, GoogleSignInAccount? googleAccount, diff --git a/app/lib/ui/flow/home/home_screen_view_model.freezed.dart b/app/lib/ui/flow/home/home_screen_view_model.freezed.dart index d0c08b6..b07c7e8 100644 --- a/app/lib/ui/flow/home/home_screen_view_model.freezed.dart +++ b/app/lib/ui/flow/home/home_screen_view_model.freezed.dart @@ -19,6 +19,7 @@ mixin _$HomeViewState { Object? get error => throw _privateConstructorUsedError; Object? get actionError => throw _privateConstructorUsedError; bool get hasLocalMediaAccess => throw _privateConstructorUsedError; + bool get hasInternet => throw _privateConstructorUsedError; bool get loading => throw _privateConstructorUsedError; bool get cloudLoading => throw _privateConstructorUsedError; GoogleSignInAccount? get googleAccount => throw _privateConstructorUsedError; @@ -50,6 +51,7 @@ abstract class $HomeViewStateCopyWith<$Res> { {Object? error, Object? actionError, bool hasLocalMediaAccess, + bool hasInternet, bool loading, bool cloudLoading, GoogleSignInAccount? googleAccount, @@ -81,6 +83,7 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState> Object? error = freezed, Object? actionError = freezed, Object? hasLocalMediaAccess = null, + Object? hasInternet = null, Object? loading = null, Object? cloudLoading = null, Object? googleAccount = freezed, @@ -98,6 +101,10 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState> ? _value.hasLocalMediaAccess : hasLocalMediaAccess // ignore: cast_nullable_to_non_nullable as bool, + hasInternet: null == hasInternet + ? _value.hasInternet + : hasInternet // ignore: cast_nullable_to_non_nullable + as bool, loading: null == loading ? _value.loading : loading // ignore: cast_nullable_to_non_nullable @@ -164,6 +171,7 @@ abstract class _$$HomeViewStateImplCopyWith<$Res> {Object? error, Object? actionError, bool hasLocalMediaAccess, + bool hasInternet, bool loading, bool cloudLoading, GoogleSignInAccount? googleAccount, @@ -194,6 +202,7 @@ class __$$HomeViewStateImplCopyWithImpl<$Res> Object? error = freezed, Object? actionError = freezed, Object? hasLocalMediaAccess = null, + Object? hasInternet = null, Object? loading = null, Object? cloudLoading = null, Object? googleAccount = freezed, @@ -211,6 +220,10 @@ class __$$HomeViewStateImplCopyWithImpl<$Res> ? _value.hasLocalMediaAccess : hasLocalMediaAccess // ignore: cast_nullable_to_non_nullable as bool, + hasInternet: null == hasInternet + ? _value.hasInternet + : hasInternet // ignore: cast_nullable_to_non_nullable + as bool, loading: null == loading ? _value.loading : loading // ignore: cast_nullable_to_non_nullable @@ -258,6 +271,7 @@ class _$HomeViewStateImpl implements _HomeViewState { {this.error, this.actionError, this.hasLocalMediaAccess = false, + this.hasInternet = false, this.loading = false, this.cloudLoading = false, this.googleAccount, @@ -281,6 +295,9 @@ class _$HomeViewStateImpl implements _HomeViewState { final bool hasLocalMediaAccess; @override @JsonKey() + final bool hasInternet; + @override + @JsonKey() final bool loading; @override @JsonKey() @@ -332,7 +349,7 @@ class _$HomeViewStateImpl implements _HomeViewState { @override String toString() { - return 'HomeViewState(error: $error, actionError: $actionError, hasLocalMediaAccess: $hasLocalMediaAccess, loading: $loading, cloudLoading: $cloudLoading, googleAccount: $googleAccount, dropboxAccount: $dropboxAccount, medias: $medias, selectedMedias: $selectedMedias, uploadMediaProcesses: $uploadMediaProcesses, downloadMediaProcesses: $downloadMediaProcesses, lastLocalMediaId: $lastLocalMediaId)'; + return 'HomeViewState(error: $error, actionError: $actionError, hasLocalMediaAccess: $hasLocalMediaAccess, hasInternet: $hasInternet, loading: $loading, cloudLoading: $cloudLoading, googleAccount: $googleAccount, dropboxAccount: $dropboxAccount, medias: $medias, selectedMedias: $selectedMedias, uploadMediaProcesses: $uploadMediaProcesses, downloadMediaProcesses: $downloadMediaProcesses, lastLocalMediaId: $lastLocalMediaId)'; } @override @@ -345,6 +362,8 @@ class _$HomeViewStateImpl implements _HomeViewState { .equals(other.actionError, actionError) && (identical(other.hasLocalMediaAccess, hasLocalMediaAccess) || other.hasLocalMediaAccess == hasLocalMediaAccess) && + (identical(other.hasInternet, hasInternet) || + other.hasInternet == hasInternet) && (identical(other.loading, loading) || other.loading == loading) && (identical(other.cloudLoading, cloudLoading) || other.cloudLoading == cloudLoading) && @@ -369,6 +388,7 @@ class _$HomeViewStateImpl implements _HomeViewState { const DeepCollectionEquality().hash(error), const DeepCollectionEquality().hash(actionError), hasLocalMediaAccess, + hasInternet, loading, cloudLoading, googleAccount, @@ -393,6 +413,7 @@ abstract class _HomeViewState implements HomeViewState { {final Object? error, final Object? actionError, final bool hasLocalMediaAccess, + final bool hasInternet, final bool loading, final bool cloudLoading, final GoogleSignInAccount? googleAccount, @@ -410,6 +431,8 @@ abstract class _HomeViewState implements HomeViewState { @override bool get hasLocalMediaAccess; @override + bool get hasInternet; + @override bool get loading; @override bool get cloudLoading; diff --git a/app/lib/ui/flow/media_metadata_details/media_metadata_details.dart b/app/lib/ui/flow/media_metadata_details/media_metadata_details.dart index 1266753..1fbc04f 100644 --- a/app/lib/ui/flow/media_metadata_details/media_metadata_details.dart +++ b/app/lib/ui/flow/media_metadata_details/media_metadata_details.dart @@ -117,12 +117,12 @@ class MediaMetadataDetailsScreen extends StatelessWidget { ), if (media.sources.contains(AppMediaSource.googleDrive)) SvgPicture.asset( - Assets.images.icons.icGoogleDrive, + Assets.images.icGoogleDrive, width: 20, ), if (media.sources.contains(AppMediaSource.dropbox)) SvgPicture.asset( - Assets.images.icons.icDropbox, + Assets.images.icDropbox, width: 20, ), ], diff --git a/app/lib/ui/flow/media_preview/components/download_require_view.dart b/app/lib/ui/flow/media_preview/components/download_require_view.dart index e8c4534..ec1044f 100644 --- a/app/lib/ui/flow/media_preview/components/download_require_view.dart +++ b/app/lib/ui/flow/media_preview/components/download_require_view.dart @@ -1,12 +1,12 @@ import 'package:data/domain/formatters/byte_formatter.dart'; import 'package:data/models/media_process/media_process.dart'; +import '../../../../components/place_holder_screen.dart'; import '../../../../domain/extensions/context_extensions.dart'; import 'package:data/models/media/media.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:style/extensions/context_extensions.dart'; import 'package:style/indicators/circular_progress_indicator.dart'; -import '../../../../components/error_view.dart'; import '../../../../domain/image_providers/app_media_image_provider.dart'; class DownloadRequireView extends StatelessWidget { @@ -52,7 +52,7 @@ class DownloadRequireView extends StatelessWidget { ), if (downloadProcess?.progress != null && downloadProcess!.status.isRunning) ...[ - ErrorView( + PlaceHolderScreen( foregroundColor: context.colorScheme.onPrimary, icon: Stack( alignment: Alignment.center, @@ -77,7 +77,7 @@ class DownloadRequireView extends StatelessWidget { ), ], if (downloadProcess?.progress == null) - ErrorView( + PlaceHolderScreen( foregroundColor: context.colorScheme.onPrimary, icon: Icon( CupertinoIcons.cloud_download, @@ -86,7 +86,7 @@ class DownloadRequireView extends StatelessWidget { ), title: context.l10n.download_require_text, message: context.l10n.download_require_message, - action: ErrorViewAction( + action: PlaceHolderScreenAction( title: context.l10n.common_download, onPressed: onDownload, ), diff --git a/app/lib/ui/flow/media_preview/components/image_preview_screen.dart b/app/lib/ui/flow/media_preview/components/image_preview_screen.dart index 4397abc..2c34b51 100644 --- a/app/lib/ui/flow/media_preview/components/image_preview_screen.dart +++ b/app/lib/ui/flow/media_preview/components/image_preview_screen.dart @@ -1,6 +1,6 @@ import 'dart:io'; import '../../../../components/app_page.dart'; -import '../../../../components/error_view.dart'; +import '../../../../components/place_holder_screen.dart'; import '../../../../domain/extensions/context_extensions.dart'; import 'package:data/models/media/media.dart'; import 'package:data/models/media/media_extension.dart'; @@ -69,7 +69,7 @@ class _ImagePreviewScreenState extends ConsumerState { fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) { return AppPage( - body: ErrorView( + body: PlaceHolderScreen( title: context.l10n.unable_to_load_media_error, message: context.l10n.unable_to_load_media_message, ), diff --git a/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview.dart b/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview.dart index 870310a..60d1fb1 100644 --- a/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview.dart +++ b/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview.dart @@ -1,11 +1,11 @@ import 'dart:io'; +import '../../../../../components/place_holder_screen.dart'; import '../../../../../domain/extensions/context_extensions.dart'; import 'package:data/models/media/media.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:style/indicators/circular_progress_indicator.dart'; import '../../../../../components/app_page.dart'; -import '../../../../../components/error_view.dart'; import 'network_image_preview_view_model.dart'; class NetworkImagePreview extends ConsumerWidget { @@ -27,7 +27,7 @@ class NetworkImagePreview extends ConsumerWidget { fit: BoxFit.fitWidth, errorBuilder: (context, error, stackTrace) { return AppPage( - body: ErrorView( + body: PlaceHolderScreen( title: context.l10n.unable_to_load_media_error, message: context.l10n.unable_to_load_media_message, ), @@ -36,7 +36,7 @@ class NetworkImagePreview extends ConsumerWidget { ), ); } - return ErrorView( + return PlaceHolderScreen( title: context.l10n.unable_to_load_media_error, message: context.l10n.unable_to_load_media_message, ); diff --git a/app/lib/ui/flow/media_preview/components/top_bar.dart b/app/lib/ui/flow/media_preview/components/top_bar.dart index 9b238f9..51f1acf 100644 --- a/app/lib/ui/flow/media_preview/components/top_bar.dart +++ b/app/lib/ui/flow/media_preview/components/top_bar.dart @@ -167,7 +167,7 @@ class _PreviewTopBarState extends ConsumerState { ), ), SvgPicture.asset( - Assets.images.icons.icGoogleDrive, + Assets.images.icGoogleDrive, width: 14, height: 14, ), @@ -227,7 +227,7 @@ class _PreviewTopBarState extends ConsumerState { ), ), SvgPicture.asset( - Assets.images.icons.icGoogleDrive, + Assets.images.icGoogleDrive, width: 14, height: 14, ), @@ -287,7 +287,7 @@ class _PreviewTopBarState extends ConsumerState { ), ), SvgPicture.asset( - Assets.images.icons.icGoogleDrive, + Assets.images.icGoogleDrive, width: 14, height: 14, ), @@ -348,7 +348,7 @@ class _PreviewTopBarState extends ConsumerState { ), ), SvgPicture.asset( - Assets.images.icons.icDropbox, + Assets.images.icDropbox, width: 14, height: 14, ), @@ -408,7 +408,7 @@ class _PreviewTopBarState extends ConsumerState { ), ), SvgPicture.asset( - Assets.images.icons.icDropbox, + Assets.images.icDropbox, width: 14, height: 14, ), @@ -468,7 +468,7 @@ class _PreviewTopBarState extends ConsumerState { ), ), SvgPicture.asset( - Assets.images.icons.icDropbox, + Assets.images.icDropbox, width: 14, height: 14, ), diff --git a/app/lib/ui/flow/media_preview/media_preview_screen.dart b/app/lib/ui/flow/media_preview/media_preview_screen.dart index 24a0236..d2c4581 100644 --- a/app/lib/ui/flow/media_preview/media_preview_screen.dart +++ b/app/lib/ui/flow/media_preview/media_preview_screen.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:data/storage/app_preferences.dart'; import '../../../components/app_page.dart'; -import '../../../components/error_view.dart'; +import '../../../components/place_holder_screen.dart'; import '../../../components/snack_bar.dart'; import '../../../domain/extensions/context_extensions.dart'; import '../../../domain/extensions/widget_extensions.dart'; @@ -229,7 +229,7 @@ class _MediaPreviewState extends ConsumerState { } else if (media.type.isImage) { return ImagePreview(media: media); } else { - return ErrorView( + return PlaceHolderScreen( title: context.l10n.unable_to_load_media_error, message: context.l10n.unable_to_load_media_message, ); diff --git a/app/lib/ui/flow/media_preview/media_preview_view_model.dart b/app/lib/ui/flow/media_preview/media_preview_view_model.dart index 939f7f5..cffac8f 100644 --- a/app/lib/ui/flow/media_preview/media_preview_view_model.dart +++ b/app/lib/ui/flow/media_preview/media_preview_view_model.dart @@ -1,5 +1,7 @@ import 'dart:async'; import 'package:data/domain/config.dart'; +import 'package:data/handlers/connectivity_handler.dart'; +import 'package:data/log/logger.dart'; import 'package:data/models/dropbox/account/dropbox_account.dart'; import 'package:data/models/media/media.dart'; import 'package:data/models/media/media_extension.dart'; @@ -13,6 +15,7 @@ import 'package:data/storage/app_preferences.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:google_sign_in/google_sign_in.dart'; +import 'package:logger/logger.dart'; part 'media_preview_view_model.freezed.dart'; @@ -30,6 +33,8 @@ final mediaPreviewStateNotifierProvider = ref.read(dropboxServiceProvider), ref.read(mediaProcessRepoProvider), ref.read(authServiceProvider), + ref.read(connectivityHandlerProvider), + ref.read(loggerProvider), state.medias, state.startIndex, ref.read(AppPreferences.dropboxCurrentUserAccount), @@ -45,7 +50,9 @@ class MediaPreviewStateNotifier extends StateNotifier { final GoogleDriveService _googleDriveService; final DropboxService _dropboxService; final MediaProcessRepo _mediaProcessRepo; + final ConnectivityHandler _connectivityHandler; final AuthService _authService; + final Logger _logger; StreamSubscription? _googleAccountSubscription; String? _backUpFolderId; @@ -56,6 +63,8 @@ class MediaPreviewStateNotifier extends StateNotifier { this._dropboxService, this._mediaProcessRepo, this._authService, + this._connectivityHandler, + this._logger, List medias, int startIndex, DropboxAccount? dropboxAccount, @@ -92,8 +101,18 @@ class MediaPreviewStateNotifier extends StateNotifier { } Future _setBackUpFolderId() async { - if (state.googleAccount == null) return; - _backUpFolderId = await _googleDriveService.getBackUpFolderId(); + try { + if (state.googleAccount == null) return; + state = state.copyWith(actionError: null); + _backUpFolderId = await _googleDriveService.getBackUpFolderId(); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "MediaPreviewStateNotifier: unable to get backup folder id", + error: e, + stackTrace: s, + ); + } } // Media Process Observer ---------------------------------------------------- @@ -172,8 +191,13 @@ class MediaPreviewStateNotifier extends StateNotifier { } } state = state.copyWith(medias: medias); - } catch (error) { - state = state.copyWith(actionError: error); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "MediaPreviewStateNotifier: unable to delete media from local", + error: e, + stackTrace: s, + ); } } @@ -190,8 +214,13 @@ class MediaPreviewStateNotifier extends StateNotifier { medias.add(media.removeGoogleDriveRef()); } } - } catch (error) { - state = state.copyWith(actionError: error); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "MediaPreviewStateNotifier: unable to delete media from Google Drive", + error: e, + stackTrace: s, + ); } } @@ -208,50 +237,119 @@ class MediaPreviewStateNotifier extends StateNotifier { medias.add(media.removeDropboxRef()); } } - } catch (error) { - state = state.copyWith(actionError: error); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "MediaPreviewStateNotifier: unable to delete media from Dropbox", + error: e, + stackTrace: s, + ); } } Future uploadMediaInGoogleDrive({required AppMedia media}) async { - if (state.googleAccount == null) return; - _mediaProcessRepo.uploadMedia( - folderId: _backUpFolderId!, - medias: [media], - provider: MediaProvider.googleDrive, - ); + try { + if (state.googleAccount == null) return; + + state = state.copyWith(actionError: null); + + if (_backUpFolderId == null) { + _backUpFolderId = await _googleDriveService.getBackUpFolderId(); + } else { + await _connectivityHandler.checkInternetAccess(); + } + + _mediaProcessRepo.uploadMedia( + folderId: _backUpFolderId!, + medias: [media], + provider: MediaProvider.googleDrive, + ); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "MediaPreviewStateNotifier: unable to upload media to Google Drive", + error: e, + stackTrace: s, + ); + } } Future uploadMediaInDropbox({required AppMedia media}) async { - if (_authService.dropboxAccount == null) return; - _mediaProcessRepo.uploadMedia( - folderId: ProviderConstants.backupFolderPath, - medias: [media], - provider: MediaProvider.dropbox, - ); + try { + if (_authService.dropboxAccount == null) return; + + state = state.copyWith(actionError: null); + + await _connectivityHandler.checkInternetAccess(); + + _mediaProcessRepo.uploadMedia( + folderId: ProviderConstants.backupFolderPath, + medias: [media], + provider: MediaProvider.dropbox, + ); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "MediaPreviewStateNotifier: unable to upload media to Dropbox", + error: e, + stackTrace: s, + ); + } } Future downloadFromGoogleDrive({required AppMedia media}) async { - if (state.googleAccount == null) return; - _mediaProcessRepo.downloadMedia( - folderId: _backUpFolderId!, - medias: [media], - provider: MediaProvider.googleDrive, - ); + try { + if (state.googleAccount == null) return; + state = state.copyWith(actionError: null); + + if (_backUpFolderId == null) { + _backUpFolderId = await _googleDriveService.getBackUpFolderId(); + } else { + await _connectivityHandler.checkInternetAccess(); + } + + _mediaProcessRepo.downloadMedia( + folderId: _backUpFolderId!, + medias: [media], + provider: MediaProvider.googleDrive, + ); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "MediaPreviewStateNotifier: unable to download media from Google Drive", + error: e, + stackTrace: s, + ); + } } Future downloadFromDropbox({required AppMedia media}) async { - if (_authService.dropboxAccount == null) return; - _mediaProcessRepo.downloadMedia( - folderId: ProviderConstants.backupFolderPath, - medias: [media], - provider: MediaProvider.dropbox, - ); + try { + if (_authService.dropboxAccount == null) return; + + state = state.copyWith(actionError: null); + + await _connectivityHandler.checkInternetAccess(); + + _mediaProcessRepo.downloadMedia( + folderId: ProviderConstants.backupFolderPath, + medias: [media], + provider: MediaProvider.dropbox, + ); + } catch (e, s) { + state = state.copyWith(actionError: e); + _logger.e( + "MediaPreviewStateNotifier: unable to download media from Dropbox", + error: e, + stackTrace: s, + ); + } } // Preview Actions ----------------------------------------------------------- void changeVisibleMediaIndex(int index) { + ///TODO: return user back if there is no media to preview state = state.copyWith(currentIndex: index); } diff --git a/app/lib/ui/flow/media_transfer/media_transfer_screen.dart b/app/lib/ui/flow/media_transfer/media_transfer_screen.dart index 6a95216..c6e2f79 100644 --- a/app/lib/ui/flow/media_transfer/media_transfer_screen.dart +++ b/app/lib/ui/flow/media_transfer/media_transfer_screen.dart @@ -1,5 +1,7 @@ -import '../../../components/error_view.dart'; +import 'package:flutter_svg/svg.dart'; +import '../../../components/place_holder_screen.dart'; import '../../../domain/extensions/context_extensions.dart'; +import '../../../gen/assets.gen.dart'; import 'components/transfer_item.dart'; import 'media_transfer_view_model.dart'; import 'package:flutter/material.dart'; @@ -100,13 +102,13 @@ class _MediaTransferScreenState extends ConsumerState { ); if (uploadProcesses.isEmpty) { - return ErrorView( + return PlaceHolderScreen( title: context.l10n.empty_upload_title, message: context.l10n.empty_upload_message, - icon: Icon( - Icons.cloud_upload_outlined, - size: 100, - color: context.colorScheme.containerNormal, + icon: SvgPicture.asset( + Assets.images.icUpload, + height: 200, + width: 200, ), ); } @@ -141,13 +143,13 @@ class _MediaTransferScreenState extends ConsumerState { ); if (downloadProcesses.isEmpty) { - return ErrorView( + return PlaceHolderScreen( title: context.l10n.empty_download_title, message: context.l10n.empty_download_message, - icon: Icon( - Icons.cloud_download_outlined, - size: 100, - color: context.colorScheme.containerNormal, + icon: SvgPicture.asset( + Assets.images.icDownload, + height: 200, + width: 200, ), ); } diff --git a/app/lib/ui/flow/onboard/onboard_screen.dart b/app/lib/ui/flow/onboard/onboard_screen.dart index 78611e8..498c629 100644 --- a/app/lib/ui/flow/onboard/onboard_screen.dart +++ b/app/lib/ui/flow/onboard/onboard_screen.dart @@ -24,7 +24,7 @@ class OnBoardScreen extends ConsumerWidget { end: Alignment.bottomCenter, colors: [ Colors.transparent, - context.colorScheme.primary.withOpacity(0.2), + context.colorScheme.primary.withValues(alpha: 0.2), Colors.transparent, ], ), diff --git a/app/pubspec.lock b/app/pubspec.lock index a2b00b1..80b44df 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _flutterfire_internals: dependency: transitive description: @@ -29,15 +29,15 @@ packages: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" analyzer_plugin: dependency: transitive description: @@ -242,10 +242,10 @@ packages: dependency: "direct main" description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" color: dependency: transitive description: @@ -322,10 +322,10 @@ packages: dependency: transitive description: name: custom_lint_visitor - sha256: "8aeb3b6ae2bb765e7716b93d1d10e8356d04e0ff6d7592de6ee04e0dd7d6587d" + sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9 url: "https://pub.dev" source: hosted - version: "1.0.0+6.7.0" + version: "1.0.0+6.11.0" dart_style: dependency: transitive description: @@ -813,18 +813,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -861,10 +861,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -1221,7 +1221,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: @@ -1298,10 +1298,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" state_notifier: dependency: transitive description: @@ -1330,10 +1330,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" style: dependency: "direct main" description: @@ -1361,10 +1361,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" time: dependency: transitive description: @@ -1553,10 +1553,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" watcher: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index ca0ca96..76732ae 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -89,5 +89,4 @@ flutter: generate: true assets: - - assets/images/ - - assets/images/icons/ \ No newline at end of file + - assets/images/ \ No newline at end of file diff --git a/data/.flutter-plugins-dependencies b/data/.flutter-plugins-dependencies index 65c35ff..5c9f04e 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":"flutter_local_notifications","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/flutter_local_notifications-18.0.1/","native_build":true,"dependencies":[]},{"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":"sqflite_darwin","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1/","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.2/","native_build":true,"dependencies":[]}],"android":[{"name":"flutter_local_notifications","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/flutter_local_notifications-18.0.1/","native_build":true,"dependencies":[]},{"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":"sqflite_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/sqflite_android-2.4.0/","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":"flutter_local_notifications","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/flutter_local_notifications-18.0.1/","native_build":true,"dependencies":[]},{"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":"sqflite_darwin","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1/","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.2/","native_build":true,"dependencies":[]}],"linux":[{"name":"flutter_local_notifications_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/flutter_local_notifications_linux-5.0.0/","native_build":false,"dependencies":[]},{"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":"flutter_local_notifications","dependencies":["flutter_local_notifications_linux"]},{"name":"flutter_local_notifications_linux","dependencies":[]},{"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":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]},{"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-12-10 16:24:17.940568","version":"3.24.5","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":"flutter_local_notifications","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/flutter_local_notifications-18.0.1/","native_build":true,"dependencies":[]},{"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":"sqflite_darwin","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1/","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.2/","native_build":true,"dependencies":[]}],"android":[{"name":"flutter_local_notifications","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/flutter_local_notifications-18.0.1/","native_build":true,"dependencies":[]},{"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":"sqflite_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/sqflite_android-2.4.0/","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":"flutter_local_notifications","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/flutter_local_notifications-18.0.1/","native_build":true,"dependencies":[]},{"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":"sqflite_darwin","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1/","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.2/","native_build":true,"dependencies":[]}],"linux":[{"name":"flutter_local_notifications_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/flutter_local_notifications_linux-5.0.0/","native_build":false,"dependencies":[]},{"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":"flutter_local_notifications","dependencies":["flutter_local_notifications_linux"]},{"name":"flutter_local_notifications_linux","dependencies":[]},{"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":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]},{"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-12-12 12:02:16.071350","version":"3.27.0","swift_package_manager_enabled":false} \ No newline at end of file diff --git a/data/lib/apis/google_drive/google_drive_endpoint.dart b/data/lib/apis/google_drive/google_drive_endpoint.dart index 09759e5..a3ace87 100644 --- a/data/lib/apis/google_drive/google_drive_endpoint.dart +++ b/data/lib/apis/google_drive/google_drive_endpoint.dart @@ -101,12 +101,10 @@ class GoogleDriveDownloadEndpoint extends DownloadEndpoint { class GoogleDriveUpdateAppPropertiesEndpoint extends Endpoint { final String id; final String localFileId; - final CancelToken? cancellationToken; const GoogleDriveUpdateAppPropertiesEndpoint({ required this.id, required this.localFileId, - this.cancellationToken, }); @override @@ -116,11 +114,11 @@ class GoogleDriveUpdateAppPropertiesEndpoint extends Endpoint { String get path => '/files/$id'; @override - CancelToken? get cancelToken => cancellationToken; + HttpMethod get method => HttpMethod.patch; @override Object? get data => { - "properties": { + "appProperties": { ProviderConstants.localRefIdKey: localFileId, }, }; diff --git a/data/lib/errors/app_error.dart b/data/lib/errors/app_error.dart index 1d9b6ef..aadd9e3 100644 --- a/data/lib/errors/app_error.dart +++ b/data/lib/errors/app_error.dart @@ -1,6 +1,6 @@ import 'dart:io'; import 'l10n_error_codes.dart'; -import 'package:dio/dio.dart' show DioException, DioExceptionType; +import 'package:dio/dio.dart' show DioException; class AppError implements Exception { final String? message; @@ -20,16 +20,12 @@ class AppError implements Exception { } else if (error is SocketException) { return const NoConnectionError(); } else if (error is DioException) { - if (error.type == DioExceptionType.cancel) { - return const RequestCancelledByUser(); - } return SomethingWentWrongError( message: error.message, statusCode: error.response?.statusCode, ); - } else { - return const SomethingWentWrongError(); } + return SomethingWentWrongError(message: error.toString()); } } @@ -51,13 +47,6 @@ class UserGoogleSignInAccountNotFound extends AppError { ); } -class RequestCancelledByUser extends AppError { - const RequestCancelledByUser() - : super( - message: "Request cancelled.", - ); -} - class BackUpFolderNotFound extends AppError { const BackUpFolderNotFound() : super( @@ -82,12 +71,12 @@ class SomethingWentWrongError extends AppError { ); } -class AuthSessionExpiredError extends AppError { - const AuthSessionExpiredError() +class DropboxAuthSessionExpiredError extends AppError { + const DropboxAuthSessionExpiredError() : super( l10nCode: AppErrorL10nCodes.authSessionExpiredError, message: - "User authentication session expired. Unable to get access token.", + "User authentication session expired. Unable to get dropbox access token.", statusCode: 401, ); } diff --git a/data/lib/extensions/date_time_extension.dart b/data/lib/extensions/date_time_extension.dart deleted file mode 100644 index 2fea8fb..0000000 --- a/data/lib/extensions/date_time_extension.dart +++ /dev/null @@ -1,3 +0,0 @@ -extension DateTimeExtension on DateTime { - int get secondsSinceEpoch => millisecondsSinceEpoch ~/ 1000; -} diff --git a/data/lib/extensions/iterable_extension.dart b/data/lib/extensions/iterable_extension.dart deleted file mode 100644 index 4514dd1..0000000 --- a/data/lib/extensions/iterable_extension.dart +++ /dev/null @@ -1,12 +0,0 @@ -extension ListExtension on List { - void updateWhere({ - required bool Function(E element) where, - required E Function(E element) update, - }) { - for (var i = 0; i < length; i++) { - if (where(elementAt(i))) { - this[i] = update(elementAt(i)); - } - } - } -} diff --git a/data/lib/handlers/connectivity_handler.dart b/data/lib/handlers/connectivity_handler.dart new file mode 100644 index 0000000..4bf038b --- /dev/null +++ b/data/lib/handlers/connectivity_handler.dart @@ -0,0 +1,35 @@ +import 'dart:io'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../errors/app_error.dart'; + +final connectivityHandlerProvider = Provider((ref) { + return ConnectivityHandler(); +}); + +class ConnectivityHandler { + // This method is used to check internet connection + Future hasInternetAccess() async { + try { + final result = await InternetAddress.lookup('example.com'); + if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { + return true; // Internet access + } + } on SocketException catch (_) { + return false; + } + return false; + } + + // This method is used to check internet connection and throw an exception if there is no internet connection + Future checkInternetAccess() async { + try { + final result = await InternetAddress.lookup('example.com'); + if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { + return; // Internet access + } + } on SocketException catch (_) { + throw NoConnectionError(); + } + throw NoConnectionError(); + } +} diff --git a/data/lib/repositories/media_process_repository.dart b/data/lib/repositories/media_process_repository.dart index b2e00a9..dc68bb0 100644 --- a/data/lib/repositories/media_process_repository.dart +++ b/data/lib/repositories/media_process_repository.dart @@ -504,7 +504,7 @@ class MediaProcessRepo extends ChangeNotifier { await clearUploadProcessResponse(id: process.id); } catch (e) { - if (e is RequestCancelledByUser) { + if (e is DioException && e.type == DioExceptionType.cancel) { showNotification('Upload to Google Drive cancelled'); return; } @@ -593,7 +593,7 @@ class MediaProcessRepo extends ChangeNotifier { await clearUploadProcessResponse(id: process.id); } catch (e) { - if (e is RequestCancelledByUser) { + if (e is DioException && e.type == DioExceptionType.cancel) { showNotification('Upload to Dropbox cancelled'); return; } @@ -820,7 +820,7 @@ class MediaProcessRepo extends ChangeNotifier { await clearDownloadProcessResponse(id: process.id); } catch (error) { - if (error is RequestCancelledByUser) { + if (error is DioException && error.type == DioExceptionType.cancel) { showNotification('Download from Google Drive cancelled'); return; } @@ -938,7 +938,7 @@ class MediaProcessRepo extends ChangeNotifier { await clearDownloadProcessResponse(id: process.id); } catch (error) { - if (error is RequestCancelledByUser) { + if (error is DioException && error.type == DioExceptionType.cancel) { showNotification('Download from Dropbox cancelled'); return; } diff --git a/data/lib/services/auth_service.dart b/data/lib/services/auth_service.dart index ce93618..67803c5 100644 --- a/data/lib/services/auth_service.dart +++ b/data/lib/services/auth_service.dart @@ -74,84 +74,66 @@ class AuthService { } Future signInSilently() async { - try { - await _googleSignIn.signInSilently(suppressErrors: true); - } catch (e) { - throw AppError.fromError(e); - } + await _googleSignIn.signInSilently(suppressErrors: true); } Future signInWithGoogle() async { - try { - final googleSignInAccount = await _googleSignIn.signIn(); - if (googleSignInAccount != null) { - await googleSignInAccount.authentication; - } - } catch (e) { - throw AppError.fromError(e); + final googleSignInAccount = await _googleSignIn.signIn(); + if (googleSignInAccount != null) { + await googleSignInAccount.authentication; } } Future signOutWithGoogle() async { - try { - await _googleSignIn.signOut(); - _googleDriveAutoBackUpController.state = false; - } catch (e) { - throw AppError.fromError(e); - } + await _googleSignIn.signOut(); + _googleDriveAutoBackUpController.state = false; } /// 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. + /// Retrieves the code to fetch 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); - } + 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); } + /// Fetch dropbox access token using the code using the Proof of Key Code Exchange (PKCE) flow. 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 (_dropboxCodeVerifierPrefProvider.state == null) { + throw const SomethingWentWrongError( + message: "Dropbox code verifier is missing", ); - if (res.data != null) { - _dropboxTokenController.state = DropboxToken( - access_token: res.data['access_token'], - token_type: res.data['token_type'], - refresh_token: res.data['refresh_token'], - expires_in: - DateTime.now().add(Duration(seconds: res.data['expires_in'])), - account_id: res.data['account_id'], - scope: res.data['scope'], - uid: res.data['uid'], - ); - _dropboxCodeVerifierPrefProvider.state = null; - } - } catch (e) { - throw AppError.fromError(e); + } + final res = await _dio.req( + DropboxTokenEndpoint( + code: code, + codeVerifier: _dropboxCodeVerifierPrefProvider.state!, + clientId: AppSecretes.dropBoxAppKey, + redirectUrl: RedirectURL.auth, + clientSecret: AppSecretes.dropBoxAppSecret, + ), + ); + if (res.statusCode == 200) { + _dropboxTokenController.state = DropboxToken( + access_token: res.data['access_token'], + token_type: res.data['token_type'], + refresh_token: res.data['refresh_token'], + expires_in: + DateTime.now().add(Duration(seconds: res.data['expires_in'])), + account_id: res.data['account_id'], + scope: res.data['scope'], + uid: res.data['uid'], + ); + _dropboxCodeVerifierPrefProvider.state = null; + } else { + throw const DropboxAuthSessionExpiredError(); } } @@ -163,15 +145,15 @@ class AuthService { } 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, - ), - ); + if (_dropboxTokenController.state != null) { + final res = await _dio.req( + DropboxRefreshTokenEndpoint( + refreshToken: _dropboxTokenController.state!.refresh_token, + clientId: AppSecretes.dropBoxAppKey, + clientSecret: AppSecretes.dropBoxAppSecret, + ), + ); + if (res.statusCode == 200) { _dropboxTokenController.state = _dropboxTokenController.state!.copyWith( access_token: res.data['access_token'], expires_in: DateTime.now().add( @@ -181,12 +163,10 @@ class AuthService { ), token_type: res.data['token_type'], ); - } else { - throw const AuthSessionExpiredError(); + return; } - } catch (e) { - throw AppError.fromError(e); } + throw DropboxAuthSessionExpiredError(); } bool get signedInWithGoogle => _googleSignIn.currentUser != null; diff --git a/data/lib/services/cloud_provider_service.dart b/data/lib/services/base/cloud_provider_service.dart similarity index 96% rename from data/lib/services/cloud_provider_service.dart rename to data/lib/services/base/cloud_provider_service.dart index a1f82fa..b1c1804 100644 --- a/data/lib/services/cloud_provider_service.dart +++ b/data/lib/services/base/cloud_provider_service.dart @@ -1,5 +1,5 @@ import 'package:dio/dio.dart'; -import '../models/media/media.dart'; +import '../../models/media/media.dart'; abstract class CloudProviderService { const CloudProviderService(); diff --git a/data/lib/services/dropbox_services.dart b/data/lib/services/dropbox_services.dart index cec601a..b2f454f 100644 --- a/data/lib/services/dropbox_services.dart +++ b/data/lib/services/dropbox_services.dart @@ -12,7 +12,7 @@ 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'; -import 'cloud_provider_service.dart'; +import 'base/cloud_provider_service.dart'; final dropboxServiceProvider = Provider((ref) { return DropboxService( @@ -35,55 +35,67 @@ class DropboxService extends CloudProviderService { ); Future setCurrentUserAccount() async { - try { - final res = await _dropboxAuthenticatedDio - .req(const DropboxGetUserAccountEndpoint()); + final res = await _dropboxAuthenticatedDio + .req(const DropboxGetUserAccountEndpoint()); + if (res.statusCode == 200) { _dropboxAccountController.state = DropboxAccount.fromJson(res.data); - } catch (e) { - AppError.fromError(e); + } else { + throw SomethingWentWrongError( + statusCode: res.statusCode, + message: res.statusMessage, + ); } } Future setFileIdAppPropertyTemplate() async { - try { - // Get all the app property templates - final res = await _dropboxAuthenticatedDio - .req(const DropboxGetAppPropertyTemplate()); + // Get all the app property templates + final res = await _dropboxAuthenticatedDio + .req(const DropboxGetAppPropertyTemplate()); - if (res.statusCode == 200) { - final templateIds = res.data['template_ids'] as List; + if (res.statusCode == 200) { + final templateIds = res.data['template_ids'] as List; - // Find the template id for the app - String? appTemplateId; - for (final templateId in templateIds) { - final res = await _dropboxAuthenticatedDio.req( - DropboxGetAppPropertiesTemplateDetails(templateId), - ); + // Find the template id for the app + String? appTemplateId; + for (final templateId in templateIds) { + final res = await _dropboxAuthenticatedDio.req( + DropboxGetAppPropertiesTemplateDetails(templateId), + ); - if (res.statusCode == 200) { - if (res.data is Map && - res.data['name'] == ProviderConstants.dropboxAppTemplateName) { - appTemplateId = templateId; - break; - } - } + if (res.statusCode == 200 && + res.data?['name'] == ProviderConstants.dropboxAppTemplateName) { + appTemplateId = templateId; + break; + } else { + throw SomethingWentWrongError( + statusCode: res.statusCode, + message: res.statusMessage, + ); } + } - // If the template id is found, set it else create a new one - if (appTemplateId != null) { - _dropboxFileIdAppPropertyTemplateIdController.state = appTemplateId; + // If the template id is found, set it else create a new one + if (appTemplateId != null) { + _dropboxFileIdAppPropertyTemplateIdController.state = appTemplateId; + } else { + final res = await _dropboxAuthenticatedDio.req( + DropboxCreateAppPropertyTemplate(), + ); + if (res.statusCode == 200) { + _dropboxFileIdAppPropertyTemplateIdController.state = + res.data['template_id']; } else { - final res = await _dropboxAuthenticatedDio.req( - DropboxCreateAppPropertyTemplate(), + throw SomethingWentWrongError( + statusCode: res.statusCode, + message: res.statusMessage, ); - if (res.statusCode == 200) { - _dropboxFileIdAppPropertyTemplateIdController.state = - res.data['template_id']; - } } } - } catch (e) { - AppError.fromError(e); + } else { + throw SomethingWentWrongError( + statusCode: res.statusCode, + message: res.statusMessage, + ); } } @@ -120,13 +132,22 @@ class DropboxService extends CloudProviderService { .toList(), ); } else { - throw AppError.fromError(response.statusMessage ?? ''); + throw SomethingWentWrongError( + statusCode: response.statusCode, + message: response.statusMessage, + ); } } return medias; } catch (e) { - throw AppError.fromError(e); + if (e is DioException && + e.response?.statusCode == 409 && + e.response?.data?['error']?['path']?['.tag'] == 'not_found') { + await createFolder(ProviderConstants.backupFolderName); + return getAllMedias(folder: folder); + } + rethrow; } } @@ -196,7 +217,7 @@ class DropboxService extends CloudProviderService { pageSize: pageSize, ); } - throw AppError.fromError(e); + rethrow; } } @@ -210,7 +231,10 @@ class DropboxService extends CloudProviderService { return response.data['metadata']['id']; } - throw AppError.fromError(response.statusMessage ?? ''); + throw SomethingWentWrongError( + statusCode: response.statusCode, + message: response.statusMessage, + ); } @override @@ -226,37 +250,46 @@ class DropboxService extends CloudProviderService { await setFileIdAppPropertyTemplate(); } final localFile = File(path); - try { - final res = await _dropboxAuthenticatedDio.req( - DropboxUploadEndpoint( - appPropertyTemplateId: - _dropboxFileIdAppPropertyTemplateIdController.state!, - localRefId: localRefId, - content: AppMediaContent( - stream: localFile.openRead(), - length: localFile.lengthSync(), - contentType: 'application/octet-stream', - ), - filePath: - "/${ProviderConstants.backupFolderName}/${localFile.path.split('/').last}", - onProgress: onProgress, - cancellationToken: cancelToken, + + final res = await _dropboxAuthenticatedDio.req( + DropboxUploadEndpoint( + appPropertyTemplateId: + _dropboxFileIdAppPropertyTemplateIdController.state!, + localRefId: localRefId, + content: AppMediaContent( + stream: localFile.openRead(), + length: localFile.lengthSync(), + contentType: 'application/octet-stream', ), - ); - final metadata = await _dropboxAuthenticatedDio.req( - DropboxGetFileMetadata(id: res.data['id']), - ); + filePath: + "/${ProviderConstants.backupFolderName}/${localFile.path.split('/').last}", + onProgress: onProgress, + cancellationToken: cancelToken, + ), + ); - if (res.statusCode == 200) { - return AppMedia.fromDropboxJson( - json: res.data, - metadataJson: metadata.data, + if (res.statusCode == 200) { + // Get the metadata of the uploaded file + try { + final metadata = await _dropboxAuthenticatedDio.req( + DropboxGetFileMetadata(id: res.data['id']), ); - } - throw AppError.fromError(res.statusMessage ?? ''); - } catch (error) { - throw AppError.fromError(error); + + if (metadata.statusCode == 200) { + return AppMedia.fromDropboxJson( + json: res.data, + metadataJson: metadata.data, + ); + } + } catch (_) {} + + // If metadata is not available, return the uploaded file + return AppMedia.fromDropboxJson(json: res.data); } + throw SomethingWentWrongError( + statusCode: res.statusCode, + message: res.statusMessage, + ); } @override @@ -266,18 +299,21 @@ class DropboxService extends CloudProviderService { CancelToken? cancelToken, void Function(int sent, int total)? onProgress, }) async { - try { - await _dropboxAuthenticatedDio.downloadReq( - DropboxDownloadEndpoint( - filePath: id, - storagePath: saveLocation, - cancellationToken: cancelToken, - onProgress: onProgress, - ), - ); - } catch (e) { - throw AppError.fromError(e); - } + final res = await _dropboxAuthenticatedDio.downloadReq( + DropboxDownloadEndpoint( + filePath: id, + storagePath: saveLocation, + cancellationToken: cancelToken, + onProgress: onProgress, + ), + ); + + if (res.statusCode == 200) return; + + throw SomethingWentWrongError( + statusCode: res.statusCode, + message: res.statusMessage, + ); } Future updateAppProperties({ @@ -285,19 +321,22 @@ class DropboxService extends CloudProviderService { required String localRefId, CancelToken? cancelToken, }) async { - try { - await _dropboxAuthenticatedDio.req( - DropboxUpdateAppPropertyEndpoint( - id: id, - cancellationToken: cancelToken, - appPropertyTemplateId: - _dropboxFileIdAppPropertyTemplateIdController.state!, - localRefId: localRefId, - ), - ); - } catch (e) { - throw AppError.fromError(e); - } + final res = await _dropboxAuthenticatedDio.req( + DropboxUpdateAppPropertyEndpoint( + id: id, + cancellationToken: cancelToken, + appPropertyTemplateId: + _dropboxFileIdAppPropertyTemplateIdController.state!, + localRefId: localRefId, + ), + ); + + if (res.statusCode == 200) return; + + throw SomethingWentWrongError( + statusCode: res.statusCode, + message: res.statusMessage, + ); } @override @@ -305,28 +344,17 @@ class DropboxService extends CloudProviderService { required String id, CancelToken? cancelToken, }) async { - try { - final res = await _dropboxAuthenticatedDio.req( - DropboxDeleteEndpoint( - id: id, - cancellationToken: cancelToken, - ), - ); - if (res.statusCode == 200) return; + final res = await _dropboxAuthenticatedDio.req( + DropboxDeleteEndpoint( + id: id, + cancellationToken: cancelToken, + ), + ); + if (res.statusCode == 200) return; - throw AppError.fromError(res.statusMessage ?? ''); - } catch (e) { - throw AppError.fromError(e); - } + throw SomethingWentWrongError( + statusCode: res.statusCode, + message: res.statusMessage, + ); } } - -class DropboxMediaListResponse { - final List medias; - final String? cursor; - - const DropboxMediaListResponse({ - required this.medias, - required this.cursor, - }); -} diff --git a/data/lib/services/google_drive_service.dart b/data/lib/services/google_drive_service.dart index caa6760..45033a3 100644 --- a/data/lib/services/google_drive_service.dart +++ b/data/lib/services/google_drive_service.dart @@ -1,5 +1,4 @@ import 'dart:async'; - import 'dart:io'; import '../apis/google_drive/google_drive_endpoint.dart'; import '../apis/network/client.dart'; @@ -13,7 +12,7 @@ import 'package:google_sign_in/google_sign_in.dart'; import 'package:googleapis/drive/v3.dart' as drive; import '../errors/app_error.dart'; import 'auth_service.dart'; -import 'cloud_provider_service.dart'; +import 'base/cloud_provider_service.dart'; final backUpFolderIdProvider = StateNotifierProvider((ref) { @@ -76,68 +75,60 @@ class GoogleDriveService extends CloudProviderService { Future> getAllMedias({ required String folder, }) async { - try { - final driveApi = await _getGoogleDriveAPI(); - - bool hasMore = true; - String? pageToken; - final List medias = []; - - while (hasMore) { - final response = await driveApi.files.list( - q: "'$folder' in parents and trashed=false", - $fields: - "files(id, name, description, mimeType, thumbnailLink, webContentLink, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, appProperties)", - pageSize: 1000, - pageToken: pageToken, - orderBy: "createdTime desc", - ); - hasMore = response.nextPageToken != null; - pageToken = response.nextPageToken; - medias.addAll( - (response.files ?? []) - .map( - (e) => AppMedia.fromGoogleDriveFile(e), - ) - .toList(), - ); - } - - return medias; - } catch (e) { - throw AppError.fromError(e); - } - } + final driveApi = await _getGoogleDriveAPI(); - @override - Future getPaginatedMedias({ - required String folder, - String? nextPageToken, - int pageSize = 30, - }) async { - try { - final driveApi = await _getGoogleDriveAPI(); + bool hasMore = true; + String? pageToken; + final List medias = []; + while (hasMore) { final response = await driveApi.files.list( q: "'$folder' in parents and trashed=false", - orderBy: "createdTime desc", $fields: "files(id, name, description, mimeType, thumbnailLink, webContentLink, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, appProperties)", - pageSize: pageSize, - pageToken: nextPageToken, + pageSize: 1000, + pageToken: pageToken, + orderBy: "createdTime desc", ); - - return GetPaginatedMediasResponse( - nextPageToken: response.nextPageToken, - medias: (response.files ?? []) + hasMore = response.nextPageToken != null; + pageToken = response.nextPageToken; + medias.addAll( + (response.files ?? []) .map( (e) => AppMedia.fromGoogleDriveFile(e), ) .toList(), ); - } catch (e) { - throw AppError.fromError(e); } + + return medias; + } + + @override + Future getPaginatedMedias({ + required String folder, + String? nextPageToken, + int pageSize = 30, + }) async { + final driveApi = await _getGoogleDriveAPI(); + + final response = await driveApi.files.list( + q: "'$folder' in parents and trashed=false", + orderBy: "createdTime desc", + $fields: + "files(id, name, description, mimeType, thumbnailLink, webContentLink, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, appProperties)", + pageSize: pageSize, + pageToken: nextPageToken, + ); + + return GetPaginatedMediasResponse( + nextPageToken: response.nextPageToken, + medias: (response.files ?? []) + .map( + (e) => AppMedia.fromGoogleDriveFile(e), + ) + .toList(), + ); } @override @@ -145,53 +136,41 @@ class GoogleDriveService extends CloudProviderService { required String id, CancelToken? cancelToken, }) async { - try { - final driveApi = await _getGoogleDriveAPI(); - await driveApi.files.delete(id); - } catch (e) { - throw AppError.fromError(e); - } + final driveApi = await _getGoogleDriveAPI(); + await driveApi.files.delete(id); } Future getBackUpFolderId() async { - try { - final driveApi = await _getGoogleDriveAPI(); + final driveApi = await _getGoogleDriveAPI(); - final response = await driveApi.files.list( - q: "name='${ProviderConstants.backupFolderName}' and trashed=false and mimeType='application/vnd.google-apps.folder'", - ); - - if (response.files?.isNotEmpty ?? false) { - return response.files?.first.id; - } else { - final folder = drive.File( - name: ProviderConstants.backupFolderName, - mimeType: 'application/vnd.google-apps.folder', - ); - final googleDriveFolder = await driveApi.files.create(folder); - return googleDriveFolder.id; - } - } catch (e) { - throw AppError.fromError(e); - } - } - - @override - Future createFolder(String folderName) async { - try { - final driveApi = await _getGoogleDriveAPI(); + final response = await driveApi.files.list( + q: "name='${ProviderConstants.backupFolderName}' and trashed=false and mimeType='application/vnd.google-apps.folder'", + ); + if (response.files?.isNotEmpty ?? false) { + return response.files?.first.id; + } else { final folder = drive.File( - name: folderName, + name: ProviderConstants.backupFolderName, mimeType: 'application/vnd.google-apps.folder', ); final googleDriveFolder = await driveApi.files.create(folder); return googleDriveFolder.id; - } catch (e) { - throw AppError.fromError(e); } } + @override + Future createFolder(String folderName) async { + final driveApi = await _getGoogleDriveAPI(); + + final folder = drive.File( + name: folderName, + mimeType: 'application/vnd.google-apps.folder', + ); + final googleDriveFolder = await driveApi.files.create(folder); + return googleDriveFolder.id; + } + @override Future uploadMedia({ required String folderId, @@ -202,36 +181,36 @@ class GoogleDriveService extends CloudProviderService { void Function(int sent, int total)? onProgress, }) async { final localFile = File(path); - try { - final file = drive.File( - name: localFile.path.split('/').last, - mimeType: mimeType, - appProperties: { - ProviderConstants.localRefIdKey: localRefId, - }, - parents: [folderId], - ); + final file = drive.File( + name: localFile.path.split('/').last, + mimeType: mimeType, + appProperties: { + ProviderConstants.localRefIdKey: localRefId, + }, + parents: [folderId], + ); - final res = await _client.req( - GoogleDriveUploadEndpoint( - request: file, - content: AppMediaContent( - stream: localFile.openRead(), - length: localFile.lengthSync(), - contentType: 'application/octet-stream', - ), - onProgress: onProgress, - cancellationToken: cancelToken, + final res = await _client.req( + GoogleDriveUploadEndpoint( + request: file, + content: AppMediaContent( + stream: localFile.openRead(), + length: localFile.lengthSync(), + contentType: 'application/octet-stream', ), - ); + onProgress: onProgress, + cancellationToken: cancelToken, + ), + ); + if (res.statusCode == 200) { return AppMedia.fromGoogleDriveFile(drive.File.fromJson(res.data)); - } catch (error) { - if (error is AppError && error.statusCode == 404) { - throw const BackUpFolderNotFound(); - } - throw AppError.fromError(error); } + + throw SomethingWentWrongError( + statusCode: res.statusCode, + message: res.statusMessage, + ); } @override @@ -241,18 +220,21 @@ class GoogleDriveService extends CloudProviderService { CancelToken? cancelToken, void Function(int sent, int total)? onProgress, }) async { - try { - await _client.downloadReq( - GoogleDriveDownloadEndpoint( - id: id, - cancellationToken: cancelToken, - saveLocation: saveLocation, - onProgress: onProgress, - ), - ); - } catch (e) { - throw AppError.fromError(e); - } + final res = await _client.downloadReq( + GoogleDriveDownloadEndpoint( + id: id, + cancellationToken: cancelToken, + saveLocation: saveLocation, + onProgress: onProgress, + ), + ); + + if (res.statusCode == 200) return; + + throw SomethingWentWrongError( + statusCode: res.statusCode, + message: res.statusMessage, + ); } Future updateAppProperties({ @@ -260,26 +242,18 @@ class GoogleDriveService extends CloudProviderService { required String localRefId, CancelToken? cancelToken, }) async { - try { - await _client.req( - GoogleDriveUpdateAppPropertiesEndpoint( - id: id, - cancellationToken: cancelToken, - localFileId: localRefId, - ), - ); - } catch (e) { - throw AppError.fromError(e); - } - } -} + final res = await _client.req( + GoogleDriveUpdateAppPropertiesEndpoint( + id: id, + localFileId: localRefId, + ), + ); -class GoogleDriveListResponse { - final List medias; - final String? pageToken; + if (res.statusCode == 200) return; - const GoogleDriveListResponse({ - required this.medias, - required this.pageToken, - }); + throw SomethingWentWrongError( + statusCode: res.statusCode, + message: res.statusMessage, + ); + } } diff --git a/data/lib/services/local_media_service.dart b/data/lib/services/local_media_service.dart index 8c2e266..be92d18 100644 --- a/data/lib/services/local_media_service.dart +++ b/data/lib/services/local_media_service.dart @@ -1,10 +1,8 @@ import 'dart:async'; import 'dart:io'; -import 'package:collection/collection.dart'; import '../models/media/media.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:photo_manager/photo_manager.dart'; -import '../errors/app_error.dart'; final localMediaServiceProvider = Provider( (ref) => const LocalMediaService(), @@ -35,55 +33,43 @@ class LocalMediaService { } Future> getAllLocalMedia() async { - try { - final count = await PhotoManager.getAssetCount(); - final assets = await PhotoManager.getAssetListRange( - start: 0, - end: count, - filterOption: FilterOptionGroup( - orders: [const OrderOption(type: OrderOptionType.createDate)], - ), - ); - final files = await Future.wait( - assets.map( - (asset) => AppMedia.fromAssetEntity(asset), - ), - ); - return files.whereNotNull().toList(); - } catch (e) { - throw AppError.fromError(e); - } + final count = await PhotoManager.getAssetCount(); + final assets = await PhotoManager.getAssetListRange( + start: 0, + end: count, + filterOption: FilterOptionGroup( + orders: [const OrderOption(type: OrderOptionType.createDate)], + ), + ); + final files = await Future.wait( + assets.map( + (asset) => AppMedia.fromAssetEntity(asset), + ), + ); + return files.nonNulls.toList(); } Future> getLocalMedia({ required int start, required int end, }) async { - try { - final assets = await PhotoManager.getAssetListRange( - start: start, - end: end, - filterOption: FilterOptionGroup( - orders: [const OrderOption(type: OrderOptionType.createDate)], - ), - ); - final files = await Future.wait( - assets.map( - (asset) => AppMedia.fromAssetEntity(asset), - ), - ); - return files.whereNotNull().toList(); - } catch (e) { - throw AppError.fromError(e); - } + final assets = await PhotoManager.getAssetListRange( + start: start, + end: end, + filterOption: FilterOptionGroup( + orders: [const OrderOption(type: OrderOptionType.createDate)], + ), + ); + final files = await Future.wait( + assets.map( + (asset) => AppMedia.fromAssetEntity(asset), + ), + ); + return files.nonNulls.toList(); } Future> deleteMedias(List medias) async { - try { - return await PhotoManager.editor.deleteWithIds(medias); - } catch (e) { - throw AppError.fromError(e); - } + return await PhotoManager.editor.deleteWithIds(medias); } Future saveInGallery({ @@ -91,21 +77,17 @@ class LocalMediaService { required AppMediaType type, }) async { AssetEntity? asset; - try { - if (type.isVideo) { - asset = await PhotoManager.editor.saveVideo( - File(saveFromLocation), - title: saveFromLocation.split('/').last, - ); - } else if (type.isImage) { - asset = await PhotoManager.editor.saveImageWithPath( - saveFromLocation, - title: saveFromLocation.split('/').last, - ); - } - return asset != null ? AppMedia.fromAssetEntity(asset) : null; - } catch (e) { - throw AppError.fromError(e); + if (type.isVideo) { + asset = await PhotoManager.editor.saveVideo( + File(saveFromLocation), + title: saveFromLocation.split('/').last, + ); + } else if (type.isImage) { + asset = await PhotoManager.editor.saveImageWithPath( + saveFromLocation, + title: saveFromLocation.split('/').last, + ); } + return asset != null ? AppMedia.fromAssetEntity(asset) : null; } } diff --git a/data/lib/storage/provider/preferences_provider.dart b/data/lib/storage/provider/preferences_provider.dart index 647729f..4113279 100644 --- a/data/lib/storage/provider/preferences_provider.dart +++ b/data/lib/storage/provider/preferences_provider.dart @@ -1,5 +1,4 @@ import 'dart:convert'; - import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; diff --git a/style/lib/animations/dismissible_page.dart b/style/lib/animations/dismissible_page.dart index cc77bc6..fcc343a 100644 --- a/style/lib/animations/dismissible_page.dart +++ b/style/lib/animations/dismissible_page.dart @@ -58,7 +58,7 @@ class _DismissiblePageState extends State { child: Stack( children: [ Container( - color: widget.backgroundColor.withOpacity(1 - percentage), + color: widget.backgroundColor.withValues(alpha: 1 - percentage), height: double.infinity, width: double.infinity, ), diff --git a/style/lib/callback/on_visible_callback.dart b/style/lib/callback/on_visible_callback.dart deleted file mode 100644 index be9cae6..0000000 --- a/style/lib/callback/on_visible_callback.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/cupertino.dart'; - -class OnVisibleCallback extends StatefulWidget { - final void Function() onVisible; - final Widget child; - - const OnVisibleCallback({ - super.key, - required this.onVisible, - required this.child, - }); - - @override - State createState() => _OnCreateState(); -} - -class _OnCreateState extends State { - @override - void initState() { - widget.onVisible(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return widget.child; - } -}