From f578ca6d47d379eef1758e0c4320b78788b5dcc5 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 9 Feb 2022 12:41:02 -0600 Subject: [PATCH] Implemented bottom app bar with control buttons for asset's operation (#15) --- README.md | 4 +- .../home/providers/asset.provider.dart | 2 +- .../home/ui/control_bottom_app_bar.dart | 75 +++++++++++++++++++ .../lib/modules/home/ui/daily_title_text.dart | 60 ++++++++------- .../lib/modules/home/ui/delete_diaglog.dart | 33 ++++++++ .../home/ui/disable_multi_select_button.dart | 47 ++++++++++++ .../lib/modules/home/ui/profile_drawer.dart | 2 +- .../lib/modules/home/ui/thumbnail_image.dart | 1 + mobile/lib/modules/home/views/home_page.dart | 66 +++++++++++----- mobile/pubspec.lock | 16 ++-- mobile/pubspec.yaml | 1 + 11 files changed, 252 insertions(+), 55 deletions(-) create mode 100644 mobile/lib/modules/home/ui/control_bottom_app_bar.dart create mode 100644 mobile/lib/modules/home/ui/delete_diaglog.dart create mode 100644 mobile/lib/modules/home/ui/disable_multi_select_button.dart diff --git a/README.md b/README.md index 4bed65f181..b3921a2cdb 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,10 @@ Loading ~4000 images/videos # Note -This project is under heavy development, there will be continous functions, features and api changes. - **!! NOT READY FOR PRODUCTION! DO NOT USE TO STORE YOUR ASSETS !!** +This project is under heavy development, there will be continous functions, features and api changes. + # Features [x] Upload assets(videos/images) diff --git a/mobile/lib/modules/home/providers/asset.provider.dart b/mobile/lib/modules/home/providers/asset.provider.dart index 92ec188a25..e4f72188f7 100644 --- a/mobile/lib/modules/home/providers/asset.provider.dart +++ b/mobile/lib/modules/home/providers/asset.provider.dart @@ -14,7 +14,7 @@ class AssetNotifier extends StateNotifier> { bool isFetching = false; // Get All assets - getImmichAssets() async { + getAllAssets() async { GetAllAssetResponse? res = await _assetService.getAllAsset(); nextPageKey = res?.nextPageKey; diff --git a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart new file mode 100644 index 0000000000..7ad8f4119a --- /dev/null +++ b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart'; + +class ControlBottomAppBar extends StatelessWidget { + const ControlBottomAppBar({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Positioned( + bottom: 0, + left: 0, + child: Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height * 0.15, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)), + color: Colors.grey[300]?.withOpacity(0.98), + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ControlBoxButton( + iconData: Icons.delete_forever_rounded, + label: "Delete", + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return const DeleteDialog(); + }, + ); + }, + ), + ], + ), + ) + ], + ), + ), + ); + } +} + +class ControlBoxButton extends StatelessWidget { + const ControlBoxButton({Key? key, required this.label, required this.iconData, required this.onPressed}) + : super(key: key); + + final String label; + final IconData iconData; + final Function onPressed; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 60, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconButton( + onPressed: () { + onPressed(); + }, + icon: Icon(iconData, size: 30), + ), + Text(label) + ], + ), + ); + } +} diff --git a/mobile/lib/modules/home/ui/daily_title_text.dart b/mobile/lib/modules/home/ui/daily_title_text.dart index add168c70a..bffe496b8c 100644 --- a/mobile/lib/modules/home/ui/daily_title_text.dart +++ b/mobile/lib/modules/home/ui/daily_title_text.dart @@ -24,6 +24,31 @@ class DailyTitleText extends ConsumerWidget { var selectedDateGroup = ref.watch(homePageStateProvider).selectedDateGroup; var selectedItems = ref.watch(homePageStateProvider).selectedItems; + void _handleTitleIconClick() { + if (isMultiSelectEnable && + selectedDateGroup.contains(dateText) && + selectedDateGroup.length == 1 && + selectedItems.length <= assetGroup.length) { + // Multi select is active - click again on the icon while it is the only active group -> disable multi select + ref.watch(homePageStateProvider.notifier).disableMultiSelect(); + } else if (isMultiSelectEnable && + selectedDateGroup.contains(dateText) && + selectedItems.length != assetGroup.length) { + // Multi select is active - click again on the icon while it is not the only active group -> remove that group from selected group/items + ref.watch(homePageStateProvider.notifier).removeSelectedDateGroup(dateText); + ref.watch(homePageStateProvider.notifier).removeMultipleSelectedItem(assetGroup); + } else if (isMultiSelectEnable && selectedDateGroup.contains(dateText) && selectedDateGroup.length > 1) { + ref.watch(homePageStateProvider.notifier).removeSelectedDateGroup(dateText); + ref.watch(homePageStateProvider.notifier).removeMultipleSelectedItem(assetGroup); + } else if (isMultiSelectEnable && !selectedDateGroup.contains(dateText)) { + ref.watch(homePageStateProvider.notifier).addSelectedDateGroup(dateText); + ref.watch(homePageStateProvider.notifier).addMultipleSelectedItems(assetGroup); + } else { + ref.watch(homePageStateProvider.notifier).enableMultiSelect(assetGroup.toSet()); + ref.watch(homePageStateProvider.notifier).addSelectedDateGroup(dateText); + } + } + return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.only(top: 29.0, bottom: 29.0, left: 12.0, right: 12.0), @@ -39,33 +64,16 @@ class DailyTitleText extends ConsumerWidget { ), const Spacer(), GestureDetector( - onTap: () { - if (isMultiSelectEnable && - selectedDateGroup.contains(dateText) && - selectedDateGroup.length == 1 && - selectedItems.length == assetGroup.length) { - ref.watch(homePageStateProvider.notifier).disableMultiSelect(); - } else if (isMultiSelectEnable && - selectedDateGroup.contains(dateText) && - selectedItems.length != assetGroup.length) { - ref.watch(homePageStateProvider.notifier).removeSelectedDateGroup(dateText); - ref.watch(homePageStateProvider.notifier).removeMultipleSelectedItem(assetGroup); - } else if (isMultiSelectEnable && - selectedDateGroup.contains(dateText) && - selectedDateGroup.length > 1) { - ref.watch(homePageStateProvider.notifier).removeSelectedDateGroup(dateText); - ref.watch(homePageStateProvider.notifier).removeMultipleSelectedItem(assetGroup); - } else if (isMultiSelectEnable && !selectedDateGroup.contains(dateText)) { - ref.watch(homePageStateProvider.notifier).addSelectedDateGroup(dateText); - ref.watch(homePageStateProvider.notifier).addMultipleSelectedItems(assetGroup); - } else { - ref.watch(homePageStateProvider.notifier).enableMultiSelect(assetGroup.toSet()); - ref.watch(homePageStateProvider.notifier).addSelectedDateGroup(dateText); - } - }, + onTap: _handleTitleIconClick, child: isMultiSelectEnable && selectedDateGroup.contains(dateText) - ? const Icon(Icons.check_circle_rounded) - : const Icon(Icons.check_circle_outline_rounded), + ? Icon( + Icons.check_circle_rounded, + color: Theme.of(context).primaryColor, + ) + : const Icon( + Icons.check_circle_outline_rounded, + color: Colors.grey, + ), ) ], ), diff --git a/mobile/lib/modules/home/ui/delete_diaglog.dart b/mobile/lib/modules/home/ui/delete_diaglog.dart new file mode 100644 index 0000000000..8613760a22 --- /dev/null +++ b/mobile/lib/modules/home/ui/delete_diaglog.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class DeleteDialog extends StatelessWidget { + const DeleteDialog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: Colors.grey[200], + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + title: const Text("Delete Permanently"), + content: const Text("These items will be permanently deleted from Immich and from your device"), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text( + "Cancel", + style: TextStyle(color: Colors.blueGrey), + ), + ), + TextButton( + onPressed: () {}, + child: Text( + "Delete", + style: TextStyle(color: Colors.red[400]), + ), + ), + ], + ); + } +} diff --git a/mobile/lib/modules/home/ui/disable_multi_select_button.dart b/mobile/lib/modules/home/ui/disable_multi_select_button.dart new file mode 100644 index 0000000000..bb1094a594 --- /dev/null +++ b/mobile/lib/modules/home/ui/disable_multi_select_button.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart'; + +class DisableMultiSelectButton extends ConsumerWidget { + const DisableMultiSelectButton({ + Key? key, + required this.onPressed, + required this.selectedItemCount, + }) : super(key: key); + + final Function onPressed; + final int selectedItemCount; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Positioned( + top: 0, + left: 0, + child: Padding( + padding: const EdgeInsets.only(left: 16.0, top: 46), + child: Material( + elevation: 20, + borderRadius: BorderRadius.circular(35), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(35), + color: Colors.grey[100], + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: TextButton.icon( + onPressed: () { + onPressed(); + }, + icon: const Icon(Icons.close_rounded), + label: Text( + selectedItemCount.toString(), + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18), + )), + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/modules/home/ui/profile_drawer.dart b/mobile/lib/modules/home/ui/profile_drawer.dart index 1d92742a9b..a62f813272 100644 --- a/mobile/lib/modules/home/ui/profile_drawer.dart +++ b/mobile/lib/modules/home/ui/profile_drawer.dart @@ -57,8 +57,8 @@ class ProfileDrawer extends ConsumerWidget { bool res = await ref.read(authenticationProvider.notifier).logout(); if (res) { + ref.watch(assetProvider.notifier).clearAllAsset(); AutoRouter.of(context).popUntilRoot(); - ref.read(assetProvider.notifier).clearAllAsset(); } }, ) diff --git a/mobile/lib/modules/home/ui/thumbnail_image.dart b/mobile/lib/modules/home/ui/thumbnail_image.dart index 6d35749933..ae8208186c 100644 --- a/mobile/lib/modules/home/ui/thumbnail_image.dart +++ b/mobile/lib/modules/home/ui/thumbnail_image.dart @@ -49,6 +49,7 @@ class ThumbnailImage extends HookConsumerWidget { } else if (isMultiSelectEnable && !selectedAsset.contains(asset)) { ref.watch(homePageStateProvider.notifier).addSingleSelectedItem(asset); } else { + print(asset.id); if (asset.type == 'IMAGE') { AutoRouter.of(context).push( ImageViewerRoute( diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart index f302eeb36c..e5ea1ebe27 100644 --- a/mobile/lib/modules/home/views/home_page.dart +++ b/mobile/lib/modules/home/views/home_page.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart'; +import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart'; import 'package:immich_mobile/modules/home/ui/daily_title_text.dart'; +import 'package:immich_mobile/modules/home/ui/disable_multi_select_button.dart'; import 'package:immich_mobile/modules/home/ui/draggable_scrollbar.dart'; import 'package:immich_mobile/modules/home/ui/image_grid.dart'; import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart'; @@ -9,6 +12,7 @@ import 'package:immich_mobile/modules/home/ui/monthly_title_text.dart'; import 'package:immich_mobile/modules/home/ui/profile_drawer.dart'; import 'package:immich_mobile/modules/home/models/get_all_asset_respose.model.dart'; import 'package:immich_mobile/modules/home/providers/asset.provider.dart'; +import 'package:sliver_tools/sliver_tools.dart'; class HomePage extends HookConsumerWidget { const HomePage({Key? key}) : super(key: key); @@ -18,6 +22,8 @@ class HomePage extends HookConsumerWidget { ScrollController _scrollController = useScrollController(); List _assetGroup = ref.watch(assetProvider); List _imageGridGroup = []; + var isMultiSelectEnable = ref.watch(homePageStateProvider).isMultiSelectEnable; + var homePageState = ref.watch(homePageStateProvider); _scrollControllerCallback() { var endOfPage = _scrollController.position.maxScrollExtent; @@ -28,10 +34,9 @@ class HomePage extends HookConsumerWidget { } useEffect(() { - ref.read(assetProvider.notifier).getImmichAssets(); + ref.read(assetProvider.notifier).getAllAssets(); _scrollController.addListener(_scrollControllerCallback); - return () { _scrollController.removeListener(_scrollControllerCallback); }; @@ -45,7 +50,7 @@ class HomePage extends HookConsumerWidget { if (_imageGridGroup.isNotEmpty && _imageGridGroup.length < 20) { ref.read(assetProvider.notifier).getOlderAsset(); } else if (_imageGridGroup.isEmpty) { - ref.read(assetProvider.notifier).getImmichAssets(); + ref.read(assetProvider.notifier).getAllAssets(); } } @@ -72,7 +77,10 @@ class HomePage extends HookConsumerWidget { // Add Daily Title Group _imageGridGroup.add( - DailyTitleText(isoDate: dateTitle, assetGroup: assetGroup), + DailyTitleText( + isoDate: dateTitle, + assetGroup: assetGroup, + ), ); // Add Image Group @@ -85,25 +93,49 @@ class HomePage extends HookConsumerWidget { } return SafeArea( - child: DraggableScrollbar.semicircle( - backgroundColor: Theme.of(context).primaryColor, - controller: _scrollController, - heightScrollThumb: 48.0, - child: CustomScrollView( - controller: _scrollController, - slivers: [ - ImmichSliverAppBar( - imageGridGroup: _imageGridGroup, - onPopBack: onPopBackFromBackupPage, + bottom: !isMultiSelectEnable, + top: !isMultiSelectEnable, + child: Stack( + children: [ + DraggableScrollbar.semicircle( + backgroundColor: Theme.of(context).primaryColor, + controller: _scrollController, + heightScrollThumb: 48.0, + child: CustomScrollView( + controller: _scrollController, + slivers: [ + SliverAnimatedSwitcher( + child: isMultiSelectEnable + ? const SliverToBoxAdapter( + child: SizedBox( + height: 70, + child: null, + ), + ) + : ImmichSliverAppBar( + imageGridGroup: _imageGridGroup, + onPopBack: onPopBackFromBackupPage, + ), + duration: const Duration(milliseconds: 350), + ), + ..._imageGridGroup + ], ), - ..._imageGridGroup, - ], - ), + ), + isMultiSelectEnable + ? DisableMultiSelectButton( + onPressed: ref.watch(homePageStateProvider.notifier).disableMultiSelect, + selectedItemCount: homePageState.selectedItems.length, + ) + : Container(), + isMultiSelectEnable ? const ControlBottomAppBar() : Container(), + ], ), ); } return Scaffold( + // key: _scaffoldKey, drawer: const ProfileDrawer(), body: _buildBody(), ); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index dad4be5230..9df915f441 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -513,13 +513,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.12.11" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.3" meta: dependency: transitive description: @@ -721,6 +714,13 @@ packages: description: flutter source: sdk version: "0.0.99" + sliver_tools: + dependency: "direct main" + description: + name: sliver_tools + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.5" source_gen: dependency: transitive description: @@ -818,7 +818,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.3" timing: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index abf1410ac6..e92b81f4bd 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -30,6 +30,7 @@ dependencies: fluttertoast: ^8.0.8 video_player: ^2.2.18 chewie: ^1.2.2 + sliver_tools: ^0.2.5 dev_dependencies: flutter_test: