[mob][photos] feat: enhance CollectionsFlexiGridViewWidget with selection mode and album creation options

This commit is contained in:
Aman Raj Singh Mourya
2025-04-09 00:12:50 +05:30
parent 9207b0c7b8
commit 227d76db29
2 changed files with 185 additions and 19 deletions

View File

@@ -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<CollectionsFlexiGridViewWidget> createState() =>
_CollectionsFlexiGridViewWidgetState();
}
class _CollectionsFlexiGridViewWidgetState
extends State<CollectionsFlexiGridViewWidget> {
List<Collection> selectedAlbums = [];
bool isAnyAlbumSelected = false;
Future<void> _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<void> _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<Widget> displayItems = [];
if (tag.isEmpty) {
displayItems.add(
NewAlbumIcon(
final List<Widget> 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<Widget> 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),
),
),
);
}
}

View File

@@ -184,6 +184,8 @@ class _UserCollectionsTabState extends State<UserCollectionsTab>
collections,
displayLimitCount: _kOnEnteItemLimitCount,
shrinkWrap: true,
shouldShowCreateAlbum: true,
enableSelectionMode: false,
)
: const SliverToBoxAdapter(child: EmptyState()),
SliverToBoxAdapter(