diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index a29bb00..b294abb 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -30,6 +30,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/lib/components/app_page.dart b/app/lib/components/app_page.dart index 8b2da12..aa2704d 100644 --- a/app/lib/components/app_page.dart +++ b/app/lib/components/app_page.dart @@ -92,6 +92,8 @@ class AppPage extends StatelessWidget { : AppBar( centerTitle: true, backgroundColor: barBackgroundColor, + scrolledUnderElevation: 0.5, + shadowColor: context.colorScheme.textDisabled, title: titleWidget ?? _title(context), actions: [...?actions, const SizedBox(width: 16)], leading: leading, diff --git a/app/lib/ui/app.dart b/app/lib/ui/app.dart index 102180e..b60a23b 100644 --- a/app/lib/ui/app.dart +++ b/app/lib/ui/app.dart @@ -44,7 +44,7 @@ class _CloudGalleryAppState extends ConsumerState { routes: $appRoutes, redirect: (context, state) { if (state.uri.path.contains('/auth')) { - return '/'; + return AppRoutePath.accounts; } return null; }, diff --git a/app/lib/ui/flow/albums/add/add_album_screen.dart b/app/lib/ui/flow/albums/add/add_album_screen.dart index 14407f7..cd7000e 100644 --- a/app/lib/ui/flow/albums/add/add_album_screen.dart +++ b/app/lib/ui/flow/albums/add/add_album_screen.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:style/buttons/action_button.dart'; import 'package:style/extensions/context_extensions.dart'; +import 'package:style/indicators/circular_progress_indicator.dart'; import 'package:style/text/app_text_field.dart'; import 'package:style/text/app_text_style.dart'; import '../../../../components/app_page.dart'; @@ -66,16 +67,22 @@ class _AddAlbumScreenState extends ConsumerState { title: context.l10n.add_album_screen_title, body: _body(context: context, state: state), actions: [ - ActionButton( - onPressed: state.allowSave ? _notifier.createAlbum : null, - icon: Icon( - Icons.check, - size: 24, - color: state.allowSave - ? context.colorScheme.textPrimary - : context.colorScheme.textDisabled, - ), - ), + state.loading + ? const SizedBox( + height: 24, + width: 24, + child: AppCircularProgressIndicator(), + ) + : ActionButton( + onPressed: state.allowSave ? _notifier.createAlbum : null, + icon: Icon( + Icons.check, + size: 24, + color: state.allowSave + ? context.colorScheme.textPrimary + : context.colorScheme.textDisabled, + ), + ), ], ); } diff --git a/app/lib/ui/flow/albums/add/add_album_state_notifier.dart b/app/lib/ui/flow/albums/add/add_album_state_notifier.dart index 51d5730..5654556 100644 --- a/app/lib/ui/flow/albums/add/add_album_state_notifier.dart +++ b/app/lib/ui/flow/albums/add/add_album_state_notifier.dart @@ -63,6 +63,8 @@ class AddAlbumStateNotifier extends StateNotifier { AddAlbumsState( albumNameController: TextEditingController(text: editAlbum?.name), mediaSource: editAlbum?.source ?? AppMediaSource.local, + googleAccount: googleAccount, + dropboxAccount: dropboxAccount, ), ); diff --git a/app/lib/ui/flow/albums/albums_screen.dart b/app/lib/ui/flow/albums/albums_screen.dart index cbef8f3..2f969dd 100644 --- a/app/lib/ui/flow/albums/albums_screen.dart +++ b/app/lib/ui/flow/albums/albums_screen.dart @@ -3,6 +3,7 @@ import 'package:data/models/media/media.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:style/animations/fade_in_switcher.dart'; import 'package:style/animations/on_tap_scale.dart'; @@ -18,6 +19,7 @@ import '../../../components/place_holder_screen.dart'; import '../../../components/snack_bar.dart'; import '../../../components/thumbnail_builder.dart'; import '../../../domain/extensions/context_extensions.dart'; +import '../../../gen/assets.gen.dart'; import '../../navigation/app_route.dart'; import 'albums_view_notifier.dart'; @@ -74,7 +76,7 @@ class _AlbumsScreenState extends ConsumerState { Widget _body({required BuildContext context}) { final state = ref.watch(albumStateNotifierProvider); - if (state.loading) { + if (state.loading && state.albums.isEmpty) { return const Center(child: AppCircularProgressIndicator()); } else if (state.error != null) { return ErrorScreen( @@ -94,21 +96,22 @@ class _AlbumsScreenState extends ConsumerState { } return GridView( - padding: EdgeInsets.all(16), + padding: EdgeInsets.all(8), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 0.9, - crossAxisSpacing: 16, + crossAxisSpacing: 8, mainAxisSpacing: 16, ), children: state.albums .map( (album) => AlbumItem( album: album, - onTap: () { - AlbumMediaListRoute( + onTap: () async { + await AlbumMediaListRoute( $extra: album, ).push(context); + _notifier.loadAlbums(); }, onLongTap: () { showAppSheet( @@ -204,10 +207,36 @@ class AlbumItem extends StatelessWidget { ), ), const SizedBox(height: 10), - Text( - album.name, - style: AppTextStyles.subtitle1.copyWith( - color: context.colorScheme.textPrimary, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Row( + children: [ + if (album.source == AppMediaSource.dropbox) ...[ + SvgPicture.asset( + Assets.images.icDropbox, + width: 18, + height: 18, + ), + const SizedBox(width: 4), + ], + if (album.source == AppMediaSource.googleDrive) ...[ + SvgPicture.asset( + Assets.images.icGoogleDrive, + width: 18, + height: 18, + ), + const SizedBox(width: 4), + ], + Expanded( + child: Text( + album.name, + style: AppTextStyles.subtitle1.copyWith( + color: context.colorScheme.textPrimary, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], ), ), ], diff --git a/app/lib/ui/flow/albums/albums_view_notifier.dart b/app/lib/ui/flow/albums/albums_view_notifier.dart index 9ee5c92..0f0e9f1 100644 --- a/app/lib/ui/flow/albums/albums_view_notifier.dart +++ b/app/lib/ui/flow/albums/albums_view_notifier.dart @@ -48,7 +48,12 @@ class AlbumStateNotifier extends StateNotifier { this._logger, GoogleSignInAccount? googleAccount, DropboxAccount? dropboxAccount, - ) : super(const AlbumsState()) { + ) : super( + AlbumsState( + googleAccount: googleAccount, + dropboxAccount: dropboxAccount, + ), + ) { loadAlbums(); } diff --git a/app/lib/ui/flow/albums/media_list/album_media_list_screen.dart b/app/lib/ui/flow/albums/media_list/album_media_list_screen.dart index 75fa6b8..7bcbdb4 100644 --- a/app/lib/ui/flow/albums/media_list/album_media_list_screen.dart +++ b/app/lib/ui/flow/albums/media_list/album_media_list_screen.dart @@ -63,7 +63,7 @@ class _AlbumMediaListScreenState extends ConsumerState { .push(context); if (res != null && res is List) { - await _notifier.addMedias(res); + await _notifier.updateAlbumMedias(medias: res); } }, icon: Icon( @@ -76,7 +76,7 @@ class _AlbumMediaListScreenState extends ConsumerState { title: context.l10n.common_edit, onPressed: () async { context.pop(); - final res = await AddAlbumRoute($extra: widget.album) + final res = await AddAlbumRoute($extra: state.album) .push(context); if (res == true) { await _notifier.reloadAlbum(); diff --git a/app/lib/ui/flow/albums/media_list/album_media_list_state_notifier.dart b/app/lib/ui/flow/albums/media_list/album_media_list_state_notifier.dart index ea8f614..608a60d 100644 --- a/app/lib/ui/flow/albums/media_list/album_media_list_state_notifier.dart +++ b/app/lib/ui/flow/albums/media_list/album_media_list_state_notifier.dart @@ -112,16 +112,16 @@ class AlbumMediaListStateNotifier extends StateNotifier { } } - Future addMedias(List medias) async { + Future updateAlbumMedias({ + required List medias, + bool append = true, + }) async { try { state = state.copyWith(actionError: null); if (state.album.source == AppMediaSource.local) { await _localMediaService.updateAlbum( state.album.copyWith( - medias: [ - ...state.album.medias, - ...medias, - ], + medias: append ? [...state.album.medias, ...medias] : medias, ), ); } else if (state.album.source == AppMediaSource.googleDrive) { @@ -132,19 +132,13 @@ class AlbumMediaListStateNotifier extends StateNotifier { await _googleDriveService.updateAlbum( folderId: _backupFolderId!, album: state.album.copyWith( - medias: [ - ...state.album.medias, - ...medias, - ], + medias: append ? [...state.album.medias, ...medias] : medias, ), ); } else if (state.album.source == AppMediaSource.dropbox) { await _dropboxService.updateAlbum( state.album.copyWith( - medias: [ - ...state.album.medias, - ...medias, - ], + medias: append ? [...state.album.medias, ...medias] : medias, ), ); } @@ -160,6 +154,7 @@ class AlbumMediaListStateNotifier extends StateNotifier { } Future loadMedia({bool reload = false}) async { + ///TODO: remove deleted media try { if (state.loading) return; @@ -175,14 +170,13 @@ class AlbumMediaListStateNotifier extends StateNotifier { final loadedMediaIds = state.medias.map((e) => e.id).toList(); final moreMediaIds = state.album.medias .where((element) => !loadedMediaIds.contains(element)) + .take(30) .toList(); medias = await Future.wait( - moreMediaIds - .take(moreMediaIds.length > 30 ? 30 : moreMediaIds.length) - .map( - (id) => _localMediaService.getMedia(id: id), - ), + moreMediaIds.map( + (id) => _localMediaService.getMedia(id: id), + ), ).then( (value) => value.nonNulls.toList(), ); @@ -191,13 +185,12 @@ class AlbumMediaListStateNotifier extends StateNotifier { state.medias.map((e) => e.driveMediaRefId).nonNulls.toList(); final moreMediaIds = state.album.medias .where((element) => !loadedMediaIds.contains(element)) + .take(30) .toList(); medias = await Future.wait( - moreMediaIds - .take(moreMediaIds.length > 30 ? 30 : moreMediaIds.length) - .map( - (id) => _googleDriveService.getMedia(id: id), - ), + moreMediaIds.map( + (id) => _googleDriveService.getMedia(id: id), + ), ).then( (value) => value.nonNulls.toList(), ); @@ -206,13 +199,12 @@ class AlbumMediaListStateNotifier extends StateNotifier { state.medias.map((e) => e.dropboxMediaRefId).nonNulls.toList(); final moreMediaIds = state.album.medias .where((element) => !loadedMediaIds.contains(element)) + .take(30) .toList(); medias = await Future.wait( - moreMediaIds - .take(moreMediaIds.length > 30 ? 30 : moreMediaIds.length) - .map( - (id) => _dropboxService.getMedia(id: id), - ), + moreMediaIds.map( + (id) => _dropboxService.getMedia(id: id), + ), ).then( (value) => value.nonNulls.toList(), ); diff --git a/app/lib/ui/flow/main/main_screen.dart b/app/lib/ui/flow/main/main_screen.dart index 688fab5..a1375dc 100644 --- a/app/lib/ui/flow/main/main_screen.dart +++ b/app/lib/ui/flow/main/main_screen.dart @@ -44,60 +44,34 @@ class _MainScreenState extends State { ), ]; - return Column( - children: [ - Expanded(child: widget.navigationShell), - (!kIsWeb && Platform.isIOS) - ? CupertinoTabBar( - currentIndex: widget.navigationShell.currentIndex, - activeColor: context.colorScheme.primary, - inactiveColor: context.colorScheme.textDisabled, - onTap: (index) => _goBranch( - index: index, - context: context, - ), - backgroundColor: context.colorScheme.surface, - border: Border( - top: BorderSide( - color: context.colorScheme.outline, - width: 1, + return Material( + color: context.colorScheme.surface, + child: Column( + children: [ + Expanded(child: widget.navigationShell), + (!kIsWeb && Platform.isIOS) + ? CupertinoTabBar( + currentIndex: widget.navigationShell.currentIndex, + activeColor: context.colorScheme.primary, + inactiveColor: context.colorScheme.textDisabled, + onTap: (index) => _goBranch( + index: index, + context: context, ), - ), - items: tabs - .map( - (e) => BottomNavigationBarItem( - icon: Icon( - e.icon, - color: context.colorScheme.textDisabled, - size: 22, - ), - label: e.label, - activeIcon: Icon( - e.activeIcon, - color: context.colorScheme.primary, - size: 24, - ), - ), - ) - .toList(), - ) - : Container( - decoration: BoxDecoration( + backgroundColor: context.colorScheme.surface, border: Border( top: BorderSide( color: context.colorScheme.outline, width: 1, ), ), - ), - child: BottomNavigationBar( items: tabs .map( (e) => BottomNavigationBarItem( icon: Icon( e.icon, color: context.colorScheme.textDisabled, - size: 24, + size: 22, ), label: e.label, activeIcon: Icon( @@ -108,21 +82,50 @@ class _MainScreenState extends State { ), ) .toList(), - currentIndex: widget.navigationShell.currentIndex, - selectedItemColor: context.colorScheme.primary, - unselectedItemColor: context.colorScheme.textDisabled, - backgroundColor: context.colorScheme.surface, - type: BottomNavigationBarType.fixed, - selectedFontSize: 12, - unselectedFontSize: 12, - elevation: 0, - onTap: (index) => _goBranch( - index: index, - context: context, + ) + : Container( + decoration: BoxDecoration( + color: context.colorScheme.surface, + border: Border( + top: BorderSide( + color: context.colorScheme.outline, + ), + ), + ), + child: BottomNavigationBar( + items: tabs + .map( + (e) => BottomNavigationBarItem( + icon: Icon( + e.icon, + color: context.colorScheme.textDisabled, + size: 24, + ), + label: e.label, + activeIcon: Icon( + e.activeIcon, + color: context.colorScheme.primary, + size: 24, + ), + ), + ) + .toList(), + currentIndex: widget.navigationShell.currentIndex, + selectedItemColor: context.colorScheme.primary, + unselectedItemColor: context.colorScheme.textDisabled, + backgroundColor: context.colorScheme.surface, + type: BottomNavigationBarType.fixed, + selectedFontSize: 12, + unselectedFontSize: 12, + elevation: 0, + onTap: (index) => _goBranch( + index: index, + context: context, + ), ), ), - ), - ], + ], + ), ); } diff --git a/app/lib/ui/flow/media_selection/media_selection_state_notifier.dart b/app/lib/ui/flow/media_selection/media_selection_state_notifier.dart index ffa25b9..3caa9dd 100644 --- a/app/lib/ui/flow/media_selection/media_selection_state_notifier.dart +++ b/app/lib/ui/flow/media_selection/media_selection_state_notifier.dart @@ -181,12 +181,14 @@ class MediaSelectionStateNotifier extends StateNotifier { } void toggleMediaSelection(AppMedia media) { - String id = media.id; + String id; if (_source == AppMediaSource.googleDrive) { id = media.driveMediaRefId!; } else if (_source == AppMediaSource.dropbox) { id = media.dropboxMediaRefId!; + } else { + id = media.id; } if (state.selectedMedias.contains(id)) { @@ -199,7 +201,7 @@ class MediaSelectionStateNotifier extends StateNotifier { state = state.copyWith( selectedMedias: [ ...state.selectedMedias, - media.id, + id, ], ); } diff --git a/data/lib/services/dropbox_services.dart b/data/lib/services/dropbox_services.dart index 4c2b5ba..11b8321 100644 --- a/data/lib/services/dropbox_services.dart +++ b/data/lib/services/dropbox_services.dart @@ -131,7 +131,11 @@ class DropboxService extends CloudProviderService { nextPageToken = response.data['cursor']; medias.addAll( (response.data['entries'] as List) - .where((element) => element['.tag'] == 'file') + .where( + (element) => + element['.tag'] == 'file' && + element['name'] != 'Albums.json', + ) .map((e) => AppMedia.fromDropboxJson(json: e)) .toList(), ); @@ -177,7 +181,8 @@ class DropboxService extends CloudProviderService { ); if (response.statusCode == 200) { final files = (response.data['entries'] as List).where( - (element) => element['.tag'] == 'file', + (element) => + element['.tag'] == 'file' && element['name'] != 'Albums.json', ); final metadataResponses = await Future.wait( @@ -378,6 +383,8 @@ class DropboxService extends CloudProviderService { ); } + // ALBUM --------------------------------------------------------------------- + Future> getAlbums() async { try { final res = await _dropboxAuthenticatedDio.req( @@ -417,8 +424,8 @@ class DropboxService extends CloudProviderService { mode: 'overwrite', autoRename: false, content: AppMediaContent( - stream: Stream.value(utf8.encode(jsonEncode(album))), - length: utf8.encode(jsonEncode(album)).length, + stream: Stream.value(utf8.encode(jsonEncode(albums))), + length: utf8.encode(jsonEncode(albums)).length, contentType: 'application/octet-stream', ), filePath: "/${ProviderConstants.backupFolderName}/Albums.json", @@ -444,7 +451,7 @@ class DropboxService extends CloudProviderService { content: AppMediaContent( stream: Stream.value(utf8.encode(jsonEncode(albums))), length: utf8.encode(jsonEncode(albums)).length, - contentType: 'application/json', + contentType: 'application/octet-stream', ), filePath: "/${ProviderConstants.backupFolderName}/Albums.json", ), @@ -478,7 +485,7 @@ class DropboxService extends CloudProviderService { content: AppMediaContent( stream: Stream.value(utf8.encode(jsonEncode(albums))), length: utf8.encode(jsonEncode(albums)).length, - contentType: 'application/json', + contentType: 'application/octet-stream', ), filePath: "/${ProviderConstants.backupFolderName}/Albums.json", ),