diff --git a/mobile/lib/ui/collections/flex_grid_view.dart b/mobile/lib/ui/collections/flex_grid_view.dart index 4d244423b0..646fd7a359 100644 --- a/mobile/lib/ui/collections/flex_grid_view.dart +++ b/mobile/lib/ui/collections/flex_grid_view.dart @@ -1,11 +1,22 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import "package:flutter/services.dart"; +import "package:logging/logging.dart"; +import "package:photos/generated/l10n.dart"; import 'package:photos/models/collection/collection.dart'; +import "package:photos/models/collection/collection_items.dart"; +import "package:photos/services/collections_service.dart"; +import "package:photos/ui/collections/album/list_item.dart"; +import "package:photos/ui/collections/album/new_list_item.dart"; +import "package:photos/ui/collections/album/new_row_item.dart"; import "package:photos/ui/collections/album/row_item.dart"; -import "package:photos/ui/collections/new_album_icon.dart"; +import "package:photos/ui/viewer/gallery/collection_page.dart"; +import "package:photos/utils/dialog_util.dart"; +import "package:photos/utils/local_settings.dart"; +import "package:photos/utils/navigation_util.dart"; -class CollectionsFlexiGridViewWidget extends StatelessWidget { +class CollectionsFlexiGridViewWidget extends StatefulWidget { /* Aspect ratio 1:1 Max width 224 Fixed gap 8 Width changes dynamically with screen width such that we can fit 2 in one row. @@ -24,59 +35,125 @@ class CollectionsFlexiGridViewWidget extends StatelessWidget { final bool shrinkWrap; final String tag; + final AlbumViewType albumViewType; + final bool enableSelectionMode; + final bool shouldShowCreateAlbum; + const CollectionsFlexiGridViewWidget( this.collections, { this.displayLimitCount = 9, this.shrinkWrap = false, this.tag = "", + this.enableSelectionMode = false, super.key, + this.albumViewType = AlbumViewType.grid, + this.shouldShowCreateAlbum = false, }); + @override + State createState() => + _CollectionsFlexiGridViewWidgetState(); +} + +class _CollectionsFlexiGridViewWidgetState + extends State { + List selectedAlbums = []; + bool isAnyAlbumSelected = false; + + Future _toggleAlbumSelection(Collection c) async { + if (selectedAlbums.contains(c)) { + selectedAlbums.remove(c); + } else { + selectedAlbums.isEmpty ? await HapticFeedback.vibrate() : null; + selectedAlbums.add(c); + } + setState(() { + isAnyAlbumSelected = selectedAlbums.isNotEmpty; + }); + } + + Future _navigateToCollectionPage(Collection c) async { + final thumbnail = await CollectionsService.instance.getCover(c); + // ignore: unawaited_futures + routeToPage( + context, + CollectionPage( + CollectionWithThumbnail(c, thumbnail), + ), + ); + } + @override Widget build(BuildContext context) { + return widget.albumViewType == AlbumViewType.grid + ? _buildGridView(context, const ValueKey("grid_view")) + : _buildListView(context, const ValueKey("list_view")); + } + + Widget _buildGridView(BuildContext context, Key key) { final double screenWidth = MediaQuery.of(context).size.width; - final int albumsCountInOneRow = max(screenWidth ~/ maxThumbnailWidth, 3); - final double gapBetweenAlbums = - (albumsCountInOneRow - 1) * fixedGapBetweenAlbum; + final int albumsCountInOneRow = + max(screenWidth ~/ CollectionsFlexiGridViewWidget.maxThumbnailWidth, 3); + final double gapBetweenAlbums = (albumsCountInOneRow - 1) * + CollectionsFlexiGridViewWidget.fixedGapBetweenAlbum; // gapOnSizeOfAlbums will be - final double gapOnSizeOfAlbums = minGapForHorizontalPadding + - (screenWidth - gapBetweenAlbums - (2 * minGapForHorizontalPadding)) % - albumsCountInOneRow; + final double gapOnSizeOfAlbums = + CollectionsFlexiGridViewWidget.minGapForHorizontalPadding + + (screenWidth - + gapBetweenAlbums - + (2 * + CollectionsFlexiGridViewWidget + .minGapForHorizontalPadding)) % + albumsCountInOneRow; final double sideOfThumbnail = (screenWidth - gapOnSizeOfAlbums - gapBetweenAlbums) / albumsCountInOneRow; - final List displayItems = []; - if (tag.isEmpty) { - displayItems.add( - NewAlbumIcon( + final List gridItems = []; + if (widget.shouldShowCreateAlbum) { + gridItems.add( + NewAlbumRowItemWidget( height: sideOfThumbnail, width: sideOfThumbnail, ), ); } - if (collections != null && collections!.isNotEmpty) { - for (int i = 0; i < collections!.length; i++) { - displayItems.add( + if (widget.collections != null && widget.collections!.isNotEmpty) { + for (int i = 0; i < widget.collections!.length; i++) { + gridItems.add( AlbumRowItemWidget( - collections![i], + widget.collections![i], sideOfThumbnail, - tag: tag, + tag: widget.tag, + selectedAlbums: selectedAlbums, + onTapCallback: (c) { + isAnyAlbumSelected + ? _toggleAlbumSelection(c) + : _navigateToCollectionPage(c); + }, + onLongPressCallback: widget.enableSelectionMode + ? (c) { + isAnyAlbumSelected + ? _navigateToCollectionPage(c) + : _toggleAlbumSelection(c); + } + : null, ), ); } } return SliverPadding( + key: key, padding: const EdgeInsets.all(8), sliver: SliverGrid( delegate: SliverChildBuilderDelegate( (context, index) { - return displayItems[index]; + return gridItems[index]; }, - childCount: min(displayItems.length, displayLimitCount), + childCount: min(gridItems.length, widget.displayLimitCount), ), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: albumsCountInOneRow, @@ -87,4 +164,91 @@ class CollectionsFlexiGridViewWidget extends StatelessWidget { ), ); } + + Widget _buildListView(BuildContext context, Key key) { + final List listItems = []; + + if (widget.shouldShowCreateAlbum) { + listItems.add( + GestureDetector( + onTap: () async { + final result = await showTextInputDialog( + context, + title: S.of(context).newAlbum, + submitButtonLabel: S.of(context).create, + hintText: S.of(context).enterAlbumName, + alwaysShowSuccessState: false, + initialValue: "", + textCapitalization: TextCapitalization.words, + popnavAfterSubmission: false, + onSubmit: (String text) async { + if (text.trim() == "") { + return; + } + + try { + final Collection c = + await CollectionsService.instance.createAlbum(text); + // ignore: unawaited_futures + await routeToPage( + context, + CollectionPage(CollectionWithThumbnail(c, null)), + ); + Navigator.of(context).pop(); + } catch (e, s) { + Logger("CreateNewAlbumIcon") + .severe("Failed to rename album", e, s); + rethrow; + } + }, + ); + + if (result is Exception) { + await showGenericErrorDialog(context: context, error: result); + } + }, + child: const NewAlbumListItemWidget(), + ), + ); + } + + if (widget.collections != null && widget.collections!.isNotEmpty) { + for (var collection in widget.collections!) { + listItems.add( + AlbumListItemWidget( + collection, + selectedAlbums: selectedAlbums, + onTapCallback: (c) { + isAnyAlbumSelected + ? _toggleAlbumSelection(c) + : _navigateToCollectionPage(c); + }, + onLongPressCallback: widget.enableSelectionMode + ? (c) { + isAnyAlbumSelected + ? _navigateToCollectionPage(c) + : _toggleAlbumSelection(c); + } + : null, + ), + ); + } + } + + return SliverPadding( + key: key, + padding: const EdgeInsets.all(8), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: listItems[index], + ); + }, + childCount: min(listItems.length, widget.displayLimitCount), + ), + ), + ); + } } diff --git a/mobile/lib/ui/tabs/user_collections_tab.dart b/mobile/lib/ui/tabs/user_collections_tab.dart index aa82e2c4d3..568f4238f3 100644 --- a/mobile/lib/ui/tabs/user_collections_tab.dart +++ b/mobile/lib/ui/tabs/user_collections_tab.dart @@ -184,6 +184,8 @@ class _UserCollectionsTabState extends State collections, displayLimitCount: _kOnEnteItemLimitCount, shrinkWrap: true, + shouldShowCreateAlbum: true, + enableSelectionMode: false, ) : const SliverToBoxAdapter(child: EmptyState()), SliverToBoxAdapter(