diff --git a/.idea/.gitignore b/.idea/.gitignore index 26d3352..8f00030 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,3 +1,5 @@ # Default ignored files /shelf/ /workspace.xml +# GitHub Copilot persisted chat sessions +/copilot/chatSessions diff --git a/app/assets/locales/app_en.arb b/app/assets/locales/app_en.arb index d195418..e55366b 100644 --- a/app/assets/locales/app_en.arb +++ b/app/assets/locales/app_en.arb @@ -29,5 +29,13 @@ "system_theme_text": "System", "add_account_title": "Add account", - "version_text": "Version" + "version_text": "Version", + + "greetings_hey_there_text": "Hey There!", + "greetings_hey_text": "Hey", + + "hint_google_sign_in_message": "Sign in with Google and effortlessly link your Google Drive to your Cloud Gallery. Enjoy quick access to all your awesome content in one spot", + "hint_google_auto_backup_message": "Enable Auto Back Up to Google Drive and never lose your precious memories. Your photos and videos will be automatically backed up to your Google Drive", + "hint_action_auto_backup": "Enable Auto Back Up" + } \ No newline at end of file diff --git a/app/lib/ui/flow/accounts/accounts_screen.dart b/app/lib/ui/flow/accounts/accounts_screen.dart index 43b0fa8..5cc6cb4 100644 --- a/app/lib/ui/flow/accounts/accounts_screen.dart +++ b/app/lib/ui/flow/accounts/accounts_screen.dart @@ -3,6 +3,7 @@ import 'package:cloud_gallery/domain/extensions/context_extensions.dart'; import 'package:cloud_gallery/domain/extensions/widget_extensions.dart'; import 'package:cloud_gallery/ui/flow/accounts/accounts_screen_view_model.dart'; import 'package:cloud_gallery/ui/flow/accounts/components/settings_action_list.dart'; +import 'package:data/storage/app_preferences.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -43,7 +44,7 @@ class _AccountsScreenState extends ConsumerState { children: [ if (googleAccount != null) AccountsTab( - name: googleAccount.displayName ?? "Anonymous", + name: googleAccount.displayName ?? googleAccount.email, serviceDescription: context.l10n.common_google_drive, profileImage: googleAccount.photoUrl, actionList: ActionList(buttons: [ @@ -51,11 +52,16 @@ class _AccountsScreenState extends ConsumerState { title: context.l10n.common_auto_back_up, trailing: Consumer( builder: (context, ref, child) { - final autoBackUp = ref.watch(accountsStateNotifierProvider - .select((value) => value.autoBackUp)); + final googleDriveAutoBackUp = ref + .watch(AppPreferences.canTakeAutoBackUpInGoogleDrive); return AppSwitch( - value: autoBackUp, - onChanged: notifier.setAutoBackUp, + value: googleDriveAutoBackUp, + onChanged: (bool value) { + ref + .read(AppPreferences + .canTakeAutoBackUpInGoogleDrive.notifier) + .state = value; + }, ); }, ), diff --git a/app/lib/ui/flow/accounts/accounts_screen_view_model.dart b/app/lib/ui/flow/accounts/accounts_screen_view_model.dart index db35bce..0227ad7 100644 --- a/app/lib/ui/flow/accounts/accounts_screen_view_model.dart +++ b/app/lib/ui/flow/accounts/accounts_screen_view_model.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:data/services/auth_service.dart'; import 'package:data/services/device_service.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -17,20 +18,34 @@ final accountsStateNotifierProvider = class AccountsStateNotifier extends StateNotifier { final DeviceService _deviceService; final AuthService _authService; + StreamSubscription? _googleAccountSubscription; AccountsStateNotifier( this._deviceService, this._authService, - ) : super(AccountsState(googleAccount: _authService.user)); + ) : super(AccountsState(googleAccount: _authService.googleAccount)); Future init() async { _getAppVersion(); + _googleAccountSubscription = + _authService.onGoogleAccountChange.listen((event) { + updateUser(event); + }); + } + + @override + void dispose() { + _googleAccountSubscription?.cancel(); + super.dispose(); + } + + void updateUser(GoogleSignInAccount? account) { + state = state.copyWith(googleAccount: account); } Future signInWithGoogle() async { try { await _authService.signInWithGoogle(); - state = state.copyWith(googleAccount: _authService.user); } catch (e) { state = state.copyWith(error: e); } @@ -39,7 +54,6 @@ class AccountsStateNotifier extends StateNotifier { Future signOutWithGoogle() async { try { await _authService.signOutWithGoogle(); - state = state.copyWith(googleAccount: _authService.user); } catch (e) { state = state.copyWith(error: e); } @@ -49,10 +63,6 @@ class AccountsStateNotifier extends StateNotifier { final version = await _deviceService.version; state = state.copyWith(version: version); } - - void setAutoBackUp(bool value) { - state = state.copyWith(autoBackUp: value); - } } @freezed @@ -61,6 +71,5 @@ class AccountsState with _$AccountsState { String? version, Object? error, GoogleSignInAccount? googleAccount, - @Default(false) bool autoBackUp, }) = _AccountsState; } diff --git a/app/lib/ui/flow/accounts/accounts_screen_view_model.freezed.dart b/app/lib/ui/flow/accounts/accounts_screen_view_model.freezed.dart index dcea795..1535317 100644 --- a/app/lib/ui/flow/accounts/accounts_screen_view_model.freezed.dart +++ b/app/lib/ui/flow/accounts/accounts_screen_view_model.freezed.dart @@ -19,7 +19,6 @@ mixin _$AccountsState { String? get version => throw _privateConstructorUsedError; Object? get error => throw _privateConstructorUsedError; GoogleSignInAccount? get googleAccount => throw _privateConstructorUsedError; - bool get autoBackUp => throw _privateConstructorUsedError; @JsonKey(ignore: true) $AccountsStateCopyWith get copyWith => @@ -33,10 +32,7 @@ abstract class $AccountsStateCopyWith<$Res> { _$AccountsStateCopyWithImpl<$Res, AccountsState>; @useResult $Res call( - {String? version, - Object? error, - GoogleSignInAccount? googleAccount, - bool autoBackUp}); + {String? version, Object? error, GoogleSignInAccount? googleAccount}); } /// @nodoc @@ -55,7 +51,6 @@ class _$AccountsStateCopyWithImpl<$Res, $Val extends AccountsState> Object? version = freezed, Object? error = freezed, Object? googleAccount = freezed, - Object? autoBackUp = null, }) { return _then(_value.copyWith( version: freezed == version @@ -67,10 +62,6 @@ class _$AccountsStateCopyWithImpl<$Res, $Val extends AccountsState> ? _value.googleAccount : googleAccount // ignore: cast_nullable_to_non_nullable as GoogleSignInAccount?, - autoBackUp: null == autoBackUp - ? _value.autoBackUp - : autoBackUp // ignore: cast_nullable_to_non_nullable - as bool, ) as $Val); } } @@ -84,10 +75,7 @@ abstract class _$$AccountsStateImplCopyWith<$Res> @override @useResult $Res call( - {String? version, - Object? error, - GoogleSignInAccount? googleAccount, - bool autoBackUp}); + {String? version, Object? error, GoogleSignInAccount? googleAccount}); } /// @nodoc @@ -104,7 +92,6 @@ class __$$AccountsStateImplCopyWithImpl<$Res> Object? version = freezed, Object? error = freezed, Object? googleAccount = freezed, - Object? autoBackUp = null, }) { return _then(_$AccountsStateImpl( version: freezed == version @@ -116,10 +103,6 @@ class __$$AccountsStateImplCopyWithImpl<$Res> ? _value.googleAccount : googleAccount // ignore: cast_nullable_to_non_nullable as GoogleSignInAccount?, - autoBackUp: null == autoBackUp - ? _value.autoBackUp - : autoBackUp // ignore: cast_nullable_to_non_nullable - as bool, )); } } @@ -127,8 +110,7 @@ class __$$AccountsStateImplCopyWithImpl<$Res> /// @nodoc class _$AccountsStateImpl implements _AccountsState { - const _$AccountsStateImpl( - {this.version, this.error, this.googleAccount, this.autoBackUp = false}); + const _$AccountsStateImpl({this.version, this.error, this.googleAccount}); @override final String? version; @@ -136,13 +118,10 @@ class _$AccountsStateImpl implements _AccountsState { final Object? error; @override final GoogleSignInAccount? googleAccount; - @override - @JsonKey() - final bool autoBackUp; @override String toString() { - return 'AccountsState(version: $version, error: $error, googleAccount: $googleAccount, autoBackUp: $autoBackUp)'; + return 'AccountsState(version: $version, error: $error, googleAccount: $googleAccount)'; } @override @@ -153,14 +132,12 @@ class _$AccountsStateImpl implements _AccountsState { (identical(other.version, version) || other.version == version) && const DeepCollectionEquality().equals(other.error, error) && (identical(other.googleAccount, googleAccount) || - other.googleAccount == googleAccount) && - (identical(other.autoBackUp, autoBackUp) || - other.autoBackUp == autoBackUp)); + other.googleAccount == googleAccount)); } @override int get hashCode => Object.hash(runtimeType, version, - const DeepCollectionEquality().hash(error), googleAccount, autoBackUp); + const DeepCollectionEquality().hash(error), googleAccount); @JsonKey(ignore: true) @override @@ -173,8 +150,7 @@ abstract class _AccountsState implements AccountsState { const factory _AccountsState( {final String? version, final Object? error, - final GoogleSignInAccount? googleAccount, - final bool autoBackUp}) = _$AccountsStateImpl; + final GoogleSignInAccount? googleAccount}) = _$AccountsStateImpl; @override String? get version; @@ -183,8 +159,6 @@ abstract class _AccountsState implements AccountsState { @override GoogleSignInAccount? get googleAccount; @override - bool get autoBackUp; - @override @JsonKey(ignore: true) _$$AccountsStateImplCopyWith<_$AccountsStateImpl> get copyWith => throw _privateConstructorUsedError; 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 7825a95..f1ba4c7 100644 --- a/app/lib/ui/flow/home/components/app_media_item.dart +++ b/app/lib/ui/flow/home/components/app_media_item.dart @@ -95,7 +95,11 @@ class _AppMediaItemState extends State height: 12, width: 12, ), - if (widget.isUploading) const AppCircularProgressIndicator(size: 12), + if (widget.isUploading) + const Padding( + padding: EdgeInsets.all(2), + child: AppCircularProgressIndicator(size: 12), + ), ], ), ); diff --git a/app/lib/ui/flow/home/components/hints.dart b/app/lib/ui/flow/home/components/hints.dart new file mode 100644 index 0000000..677338f --- /dev/null +++ b/app/lib/ui/flow/home/components/hints.dart @@ -0,0 +1,148 @@ +import 'package:cloud_gallery/domain/extensions/context_extensions.dart'; +import 'package:cloud_gallery/ui/flow/home/home_screen_view_model.dart'; +import 'package:data/storage/app_preferences.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:style/extensions/context_extensions.dart'; +import 'package:style/text/app_text_style.dart'; + +class HomeScreenHints extends ConsumerWidget { + const HomeScreenHints({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final googleAccount = + ref.watch(homeViewStateNotifier.select((value) => value.googleAccount)); + final canTakeAutoBackUpInGoogleDrive = + ref.watch(AppPreferences.canTakeAutoBackUpInGoogleDrive); + final googleDriveSignInHintShown = + ref.watch(AppPreferences.googleDriveSignInHintShown); + final googleDriveAutoBackUpHintShown = + ref.watch(AppPreferences.googleDriveAutoBackUpHintShown); + if (!googleDriveSignInHintShown && googleAccount == null) { + return HintView( + title: context.l10n.greetings_hey_there_text, + hint: context.l10n.hint_google_sign_in_message, + onClose: () { + ref.read(AppPreferences.googleDriveSignInHintShown.notifier).state = + true; + }, + actionTitle: context.l10n.add_account_title, + onActionTap: () { + ref.read(homeViewStateNotifier.notifier).signInWithGoogle(); + ref.read(AppPreferences.googleDriveSignInHintShown.notifier).state = + true; + }, + ); + } else if (googleAccount != null && + !googleDriveAutoBackUpHintShown && + !canTakeAutoBackUpInGoogleDrive) { + return HintView( + title: + "${context.l10n.greetings_hey_text} ${googleAccount.displayName?.split(' ').first ?? "There"}!", + hint: context.l10n.hint_google_auto_backup_message, + onClose: () { + ref + .read(AppPreferences.googleDriveAutoBackUpHintShown.notifier) + .state = true; + }, + actionTitle: context.l10n.hint_action_auto_backup, + onActionTap: () { + ref + .read(AppPreferences.canTakeAutoBackUpInGoogleDrive.notifier) + .state = true; + ref + .read(AppPreferences.googleDriveAutoBackUpHintShown.notifier) + .state = true; + }, + ); + } else { + return const SizedBox(); + } + } +} + +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.start, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 16, left: 16), + child: Text( + title, + style: AppTextStyles.subtitle2.copyWith( + color: context.colorScheme.textPrimary, + ), + ), + ), + ), + IconButton( + style: IconButton.styleFrom( + backgroundColor: context.colorScheme.containerNormal, + minimumSize: const Size(28, 28), + ), + onPressed: onClose, + icon: Icon( + Icons.close_rounded, + 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/home_screen.dart b/app/lib/ui/flow/home/home_screen.dart index fe7e25b..2a21319 100644 --- a/app/lib/ui/flow/home/home_screen.dart +++ b/app/lib/ui/flow/home/home_screen.dart @@ -1,5 +1,4 @@ import 'package:cloud_gallery/components/app_page.dart'; -import 'package:cloud_gallery/components/resume_detector.dart'; import 'package:cloud_gallery/domain/extensions/context_extensions.dart'; import 'package:cloud_gallery/ui/flow/home/components/no_local_medias_access_screen.dart'; import 'package:cloud_gallery/ui/flow/home/home_screen_view_model.dart'; @@ -15,6 +14,7 @@ import '../../../components/snack_bar.dart'; import '../../../domain/assets/assets_paths.dart'; import '../../navigation/app_router.dart'; import 'components/app_media_item.dart'; +import 'components/hints.dart'; import 'components/multi_selection_done_button.dart'; import 'package:style/slivers/sticky_header_delegate.dart'; @@ -98,8 +98,10 @@ class _HomeScreenState extends ConsumerState { } else if (medias.isEmpty && !hasLocalMediaAccess) { return const NoLocalMediasAccessScreen(); } - return ResumeDetector( - onResume: notifier.loadMedias, + return RefreshIndicator.adaptive( + onRefresh: () async { + await notifier.loadMedias(); + }, child: Stack( alignment: Alignment.bottomRight, children: [ @@ -127,9 +129,11 @@ class _HomeScreenState extends ConsumerState { return Scrollbar( controller: _scrollController, interactive: true, - child: CustomScrollView( - controller: _scrollController, - slivers: medias.entries + child: CustomScrollView(controller: _scrollController, slivers: [ + const SliverToBoxAdapter( + child: HomeScreenHints(), + ), + ...medias.entries .map( (e) => SliverMainAxisGroup( slivers: [ @@ -185,7 +189,7 @@ class _HomeScreenState extends ConsumerState { ), ) .toList(), - ), + ]), ); } 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 b1cfc88..2fe72c7 100644 --- a/app/lib/ui/flow/home/home_screen_view_model.dart +++ b/app/lib/ui/flow/home/home_screen_view_model.dart @@ -5,38 +5,110 @@ import 'package:data/models/media/media.dart'; import 'package:data/services/auth_service.dart'; import 'package:data/services/google_drive_service.dart'; import 'package:data/services/local_media_service.dart'; +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:style/extensions/list_extensions.dart'; part 'home_screen_view_model.freezed.dart'; final homeViewStateNotifier = StateNotifierProvider.autoDispose( - (ref) => HomeViewStateNotifier( + (ref) { + final homeViewStateNotifier = HomeViewStateNotifier( ref.read(localMediaServiceProvider), ref.read(googleDriveServiceProvider), ref.read(authServiceProvider), - ), -); + ref.read(AppPreferences.canTakeAutoBackUpInGoogleDrive), + ); + final subscription = ref.listen(AppPreferences.canTakeAutoBackUpInGoogleDrive, + (previous, next) { + homeViewStateNotifier.updateAutoBackUpStatus(next); + }); + ref.onDispose(() { + subscription.close(); + }); + return homeViewStateNotifier; +}); class HomeViewStateNotifier extends StateNotifier { final GoogleDriveService _googleDriveService; final AuthService _authService; final LocalMediaService _localMediaService; + StreamSubscription? _googleAccountSubscription; + bool _isAutoBackUpEnabled = false; + bool _isAutoBackUpWorking = false; String? _backUpFolderId; List _localMedias = []; int? _localMediaCount; + bool _loading = false; List _googleDriveMedias = []; - HomeViewStateNotifier( - this._localMediaService, this._googleDriveService, this._authService) + HomeViewStateNotifier(this._localMediaService, this._googleDriveService, + this._authService, this._isAutoBackUpEnabled) : super(const HomeViewState()) { + _googleAccountSubscription = + _authService.onGoogleAccountChange.listen((event) { + state = state.copyWith(googleAccount: event); + loadMedias(); + }); + if (_isAutoBackUpEnabled) { + _autoBackUpMedias(); + } loadMedias(); } + @override + Future dispose() async { + await _googleAccountSubscription?.cancel(); + super.dispose(); + } + + Future _autoBackUpMedias() async { + _backUpFolderId ??= await _googleDriveService.getBackupFolderId(); + _isAutoBackUpWorking = true; + for (final media in state.medias.values.expand((element) => element)) { + if (!_isAutoBackUpEnabled) { + _isAutoBackUpWorking = false; + return; + } + if (!media.sources.contains(AppMediaSource.googleDrive)) { + state = state.copyWith( + uploadingMedias: state.uploadingMedias.toList()..add(media.id)); + await _googleDriveService.uploadInGoogleDrive( + media: media, + folderID: _backUpFolderId!, + ); + state = state.copyWith( + uploadingMedias: state.uploadingMedias.toList()..remove(media.id), + medias: state.medias.map((key, value) { + value.updateElement( + newElement: media.copyWith( + sources: media.sources.toList() + ..add(AppMediaSource.googleDrive)), + oldElement: media); + return MapEntry(key, value); + }), + ); + } + } + _isAutoBackUpWorking = false; + } + + Future updateAutoBackUpStatus(bool status) async { + _isAutoBackUpEnabled = status; + if (_isAutoBackUpEnabled && !_isAutoBackUpWorking) { + _autoBackUpMedias(); + } + } + Future loadMedias() async { + if (_loading == true) return; + _loading = true; + _googleDriveMedias = []; + _localMedias = []; state = state.copyWith(loading: state.medias.isEmpty, error: null); try { _localMediaCount ??= await _getLocalMediaCount(); @@ -55,6 +127,17 @@ class HomeViewStateNotifier extends StateNotifier { ); } catch (error) { state = state.copyWith(loading: false, error: error); + } finally { + _loading = false; + } + } + + Future signInWithGoogle() async { + try { + await _authService.signInWithGoogle(); + state = state.copyWith(googleAccount: _authService.googleAccount); + } catch (e) { + state = state.copyWith(error: e); } } @@ -89,7 +172,7 @@ class HomeViewStateNotifier extends StateNotifier { } Future _getGoogleDriveMedias() async { - if (_authService.hasUserSigned) { + if (_authService.signedInWithGoogle) { _backUpFolderId ??= await _googleDriveService.getBackupFolderId(); _googleDriveMedias = await _googleDriveService.getDriveMedias( backUpFolderId: _backUpFolderId!); @@ -121,7 +204,7 @@ class HomeViewStateNotifier extends StateNotifier { Future uploadMediaOnGoogleDrive() async { try { - if (!_authService.hasUserSigned) { + if (!_authService.signedInWithGoogle) { await _authService.signInWithGoogle(); loadMedias(); } @@ -168,6 +251,7 @@ class HomeViewState with _$HomeViewState { Object? error, @Default(false) bool hasLocalMediaAccess, @Default(false) bool loading, + GoogleSignInAccount? googleAccount, @Default({}) Map> medias, @Default([]) List selectedMedias, @Default([]) List uploadingMedias, 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 ef7ba7a..7f07220 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; bool get hasLocalMediaAccess => throw _privateConstructorUsedError; bool get loading => throw _privateConstructorUsedError; + GoogleSignInAccount? get googleAccount => throw _privateConstructorUsedError; Map> get medias => throw _privateConstructorUsedError; List get selectedMedias => throw _privateConstructorUsedError; @@ -39,6 +40,7 @@ abstract class $HomeViewStateCopyWith<$Res> { {Object? error, bool hasLocalMediaAccess, bool loading, + GoogleSignInAccount? googleAccount, Map> medias, List selectedMedias, List uploadingMedias}); @@ -60,6 +62,7 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState> Object? error = freezed, Object? hasLocalMediaAccess = null, Object? loading = null, + Object? googleAccount = freezed, Object? medias = null, Object? selectedMedias = null, Object? uploadingMedias = null, @@ -74,6 +77,10 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState> ? _value.loading : loading // ignore: cast_nullable_to_non_nullable as bool, + googleAccount: freezed == googleAccount + ? _value.googleAccount + : googleAccount // ignore: cast_nullable_to_non_nullable + as GoogleSignInAccount?, medias: null == medias ? _value.medias : medias // ignore: cast_nullable_to_non_nullable @@ -102,6 +109,7 @@ abstract class _$$HomeViewStateImplCopyWith<$Res> {Object? error, bool hasLocalMediaAccess, bool loading, + GoogleSignInAccount? googleAccount, Map> medias, List selectedMedias, List uploadingMedias}); @@ -121,6 +129,7 @@ class __$$HomeViewStateImplCopyWithImpl<$Res> Object? error = freezed, Object? hasLocalMediaAccess = null, Object? loading = null, + Object? googleAccount = freezed, Object? medias = null, Object? selectedMedias = null, Object? uploadingMedias = null, @@ -135,6 +144,10 @@ class __$$HomeViewStateImplCopyWithImpl<$Res> ? _value.loading : loading // ignore: cast_nullable_to_non_nullable as bool, + googleAccount: freezed == googleAccount + ? _value.googleAccount + : googleAccount // ignore: cast_nullable_to_non_nullable + as GoogleSignInAccount?, medias: null == medias ? _value._medias : medias // ignore: cast_nullable_to_non_nullable @@ -158,6 +171,7 @@ class _$HomeViewStateImpl implements _HomeViewState { {this.error, this.hasLocalMediaAccess = false, this.loading = false, + this.googleAccount, final Map> medias = const {}, final List selectedMedias = const [], final List uploadingMedias = const []}) @@ -173,6 +187,8 @@ class _$HomeViewStateImpl implements _HomeViewState { @override @JsonKey() final bool loading; + @override + final GoogleSignInAccount? googleAccount; final Map> _medias; @override @JsonKey() @@ -202,7 +218,7 @@ class _$HomeViewStateImpl implements _HomeViewState { @override String toString() { - return 'HomeViewState(error: $error, hasLocalMediaAccess: $hasLocalMediaAccess, loading: $loading, medias: $medias, selectedMedias: $selectedMedias, uploadingMedias: $uploadingMedias)'; + return 'HomeViewState(error: $error, hasLocalMediaAccess: $hasLocalMediaAccess, loading: $loading, googleAccount: $googleAccount, medias: $medias, selectedMedias: $selectedMedias, uploadingMedias: $uploadingMedias)'; } @override @@ -214,6 +230,8 @@ class _$HomeViewStateImpl implements _HomeViewState { (identical(other.hasLocalMediaAccess, hasLocalMediaAccess) || other.hasLocalMediaAccess == hasLocalMediaAccess) && (identical(other.loading, loading) || other.loading == loading) && + (identical(other.googleAccount, googleAccount) || + other.googleAccount == googleAccount) && const DeepCollectionEquality().equals(other._medias, _medias) && const DeepCollectionEquality() .equals(other._selectedMedias, _selectedMedias) && @@ -227,6 +245,7 @@ class _$HomeViewStateImpl implements _HomeViewState { const DeepCollectionEquality().hash(error), hasLocalMediaAccess, loading, + googleAccount, const DeepCollectionEquality().hash(_medias), const DeepCollectionEquality().hash(_selectedMedias), const DeepCollectionEquality().hash(_uploadingMedias)); @@ -243,6 +262,7 @@ abstract class _HomeViewState implements HomeViewState { {final Object? error, final bool hasLocalMediaAccess, final bool loading, + final GoogleSignInAccount? googleAccount, final Map> medias, final List selectedMedias, final List uploadingMedias}) = _$HomeViewStateImpl; @@ -254,6 +274,8 @@ abstract class _HomeViewState implements HomeViewState { @override bool get loading; @override + GoogleSignInAccount? get googleAccount; + @override Map> get medias; @override List get selectedMedias; diff --git a/data/lib/services/auth_service.dart b/data/lib/services/auth_service.dart index 68d6954..df9e983 100644 --- a/data/lib/services/auth_service.dart +++ b/data/lib/services/auth_service.dart @@ -2,6 +2,18 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:googleapis/drive/v3.dart' as google_drive; +final googleUserAccountProvider = StateProvider((ref) { + final googleSignIn = ref.read(googleSignInProvider); + googleSignIn.signInSilently(suppressErrors: true); + final subscription = googleSignIn.onCurrentUserChanged.listen((account) { + ref.controller.state = account; + }); + ref.onDispose(() async { + await subscription.cancel(); + }); + return googleSignIn.currentUser; +}); + final googleSignInProvider = Provider( (ref) => GoogleSignIn( scopes: [google_drive.DriveApi.driveScope], @@ -9,14 +21,24 @@ final googleSignInProvider = Provider( ); final authServiceProvider = Provider( - (ref) => AuthService(ref.read(googleSignInProvider)), + (ref) => AuthService( + ref.read(googleSignInProvider), + ), ); class AuthService { final GoogleSignIn _googleSignIn; AuthService(this._googleSignIn) { - _googleSignIn.signInSilently(suppressErrors: true); + signInSilently(); + } + + Future signInSilently() async { + try { + await _googleSignIn.signInSilently(suppressErrors: true); + } catch (_) { + rethrow; + } } Future signInWithGoogle() async { @@ -38,7 +60,10 @@ class AuthService { } } - bool get hasUserSigned => _googleSignIn.currentUser != null; + bool get signedInWithGoogle => _googleSignIn.currentUser != null; + + GoogleSignInAccount? get googleAccount => _googleSignIn.currentUser; - GoogleSignInAccount? get user => _googleSignIn.currentUser; + Stream get onGoogleAccountChange => + _googleSignIn.onCurrentUserChanged; } diff --git a/data/lib/storage/app_preferences.dart b/data/lib/storage/app_preferences.dart index b7d51df..35c6b4d 100644 --- a/data/lib/storage/app_preferences.dart +++ b/data/lib/storage/app_preferences.dart @@ -11,4 +11,22 @@ class AppPreferences { prefKey: "is_dark_mode", defaultValue: null, ); + + static StateProvider canTakeAutoBackUpInGoogleDrive = + createPrefProvider( + prefKey: "google_drive_auto_backup", + defaultValue: false, + ); + + static StateProvider googleDriveSignInHintShown = + createPrefProvider( + prefKey: "google_drive_sign_in_hint_shown", + defaultValue: false, + ); + + static StateProvider googleDriveAutoBackUpHintShown = + createPrefProvider( + prefKey: "google_drive_sign_in_hint_shown", + defaultValue: false, + ); }