[mob][photos] feat: update selection logic to exclude favorite collections from actions

This commit is contained in:
Aman Raj Singh Mourya
2025-04-22 12:42:40 +05:30
parent 7f96a11e07
commit 2449dbe0cd
3 changed files with 225 additions and 208 deletions

View File

@@ -30,30 +30,31 @@ class AlbumListItemWidget extends StatelessWidget {
final textTheme = getEnteTextTheme(context);
final colorScheme = getEnteColorScheme(context);
const sideOfThumbnail = 60.0;
final bool isFavCollection = collection.type == CollectionType.favorites;
return ListenableBuilder(
listenable: selectedAlbums!,
builder: (context, _) {
final isSelected = selectedAlbums?.isAlbumSelected(collection) ?? false;
return GestureDetector(
onTap: () {
if (onTapCallback != null) {
onTapCallback!(collection);
}
},
onLongPress: () {
if (onLongPressCallback != null) {
onLongPressCallback!(collection);
}
},
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
return GestureDetector(
onTap: () {
if (onTapCallback != null) {
onTapCallback!(collection);
}
},
onLongPress: () {
if (onLongPressCallback != null) {
onLongPressCallback!(collection);
}
},
behavior: HitTestBehavior.opaque,
child: ListenableBuilder(
listenable: selectedAlbums!,
builder: (context, _) {
final isSelected =
selectedAlbums?.isAlbumSelected(collection) ?? false;
return AnimatedContainer(
curve: Curves.easeOut,
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
border: Border.all(
color: isSelected
color: isSelected & !isFavCollection
? colorScheme.strokeMuted
: colorScheme.strokeFainter,
),
@@ -81,7 +82,6 @@ class AlbumListItemWidget extends StatelessWidget {
builder: (context, snapshot) {
if (snapshot.hasData) {
final thumbnail = snapshot.data!;
return ThumbnailWidget(
thumbnail,
showFavForAlbumOnly: true,
@@ -143,29 +143,37 @@ class AlbumListItemWidget extends StatelessWidget {
),
Flexible(
flex: 1,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeIn,
child: isSelected
? IconButtonWidget(
key: const ValueKey("selected"),
icon: Icons.check_circle_rounded,
iconButtonType: IconButtonType.secondary,
iconColor: colorScheme.blurStrokeBase,
)
: const IconButtonWidget(
key: ValueKey("unselected"),
icon: Icons.chevron_right_outlined,
iconButtonType: IconButtonType.secondary,
),
child: ListenableBuilder(
listenable: selectedAlbums!,
builder: (context, _) {
final isSelected =
selectedAlbums?.isAlbumSelected(collection) ?? false;
return AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeIn,
child: isSelected & !isFavCollection
? IconButtonWidget(
key: const ValueKey("selected"),
icon: Icons.check_circle_rounded,
iconButtonType: IconButtonType.secondary,
iconColor: colorScheme.blurStrokeBase,
)
: const IconButtonWidget(
key: ValueKey("unselected"),
icon: Icons.chevron_right_outlined,
iconButtonType: IconButtonType.secondary,
),
);
},
),
),
],
),
),
);
},
);
},
),
);
}
}

View File

@@ -53,80 +53,82 @@ class AlbumRowItemWidget extends StatelessWidget {
color: c.publicURLs.first.isExpired ? warning500 : strokeBaseDark,
)
: null;
return ListenableBuilder(
listenable: selectedAlbums ?? ValueNotifier(false),
builder: (context, _) {
final bool isSelected = selectedAlbums?.isAlbumSelected(c) ?? false;
return GestureDetector(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(1),
child: SizedBox(
height: sideOfThumbnail,
width: sideOfThumbnail,
child: Stack(
children: [
FutureBuilder<EnteFile?>(
future: CollectionsService.instance.getCover(c),
builder: (context, snapshot) {
EnteFile? thumbnail;
if (snapshot.hasData) {
thumbnail = snapshot.data!;
} else {
//Need to use cached thumbnail so that the hero
//animation works as expected.
thumbnail = CollectionsService.instance
.getCoverCache(c);
}
if (thumbnail != null) {
final String heroTag =
tagPrefix + thumbnail.tag;
return Hero(
tag: heroTag,
transitionOnUserGestures: true,
child: ThumbnailWidget(
thumbnail,
shouldShowArchiveStatus: isOwner
? c.isArchived()
: c.hasShareeArchived(),
showFavForAlbumOnly: true,
shouldShowSyncStatus: false,
shouldShowPinIcon: isOwner && c.isPinned,
key: Key(heroTag),
),
);
} else {
return const NoThumbnailWidget();
}
},
),
if (isOwner && (c.hasSharees || c.hasLink))
Hero(
tag: tagPrefix + "_sharees",
final bool isFavCollection = c.type == CollectionType.favorites;
return GestureDetector(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(1),
child: SizedBox(
height: sideOfThumbnail,
width: sideOfThumbnail,
child: Stack(
children: [
FutureBuilder<EnteFile?>(
future: CollectionsService.instance.getCover(c),
builder: (context, snapshot) {
EnteFile? thumbnail;
if (snapshot.hasData) {
thumbnail = snapshot.data!;
} else {
//Need to use cached thumbnail so that the hero
//animation works as expected.
thumbnail =
CollectionsService.instance.getCoverCache(c);
}
if (thumbnail != null) {
final String heroTag = tagPrefix + thumbnail.tag;
return Hero(
tag: heroTag,
transitionOnUserGestures: true,
child: Align(
alignment: Alignment.topLeft,
child: AlbumSharesIcons(
sharees: c.getSharees(),
type: AvatarType.mini,
trailingWidget: linkIcon,
),
child: ThumbnailWidget(
thumbnail,
shouldShowArchiveStatus: isOwner
? c.isArchived()
: c.hasShareeArchived(),
showFavForAlbumOnly: true,
shouldShowSyncStatus: false,
shouldShowPinIcon: isOwner && c.isPinned,
key: Key(heroTag),
),
);
} else {
return const NoThumbnailWidget();
}
},
),
if (isOwner && (c.hasSharees || c.hasLink))
Hero(
tag: tagPrefix + "_sharees",
transitionOnUserGestures: true,
child: Align(
alignment: Alignment.topLeft,
child: AlbumSharesIcons(
sharees: c.getSharees(),
type: AvatarType.mini,
trailingWidget: linkIcon,
),
Align(
alignment: Alignment.topRight,
child: Hero(
tag: tagPrefix + "_album_selection",
transitionOnUserGestures: true,
child: AnimatedSwitcher(
),
),
Align(
alignment: Alignment.topRight,
child: Hero(
tag: tagPrefix + "_album_selection",
transitionOnUserGestures: true,
child: ListenableBuilder(
listenable: selectedAlbums ?? ValueNotifier(false),
builder: (context, _) {
final bool isSelected =
selectedAlbums?.isAlbumSelected(c) ?? false;
return AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeIn,
child: isSelected
child: isSelected && !isFavCollection
? IconButtonWidget(
key: const ValueKey("selected"),
icon: Icons.check_circle_rounded,
@@ -135,117 +137,116 @@ class AlbumRowItemWidget extends StatelessWidget {
iconColor: colorScheme.blurStrokeBase,
)
: null,
);
},
),
),
),
if (!isOwner)
Align(
alignment: Alignment.bottomRight,
child: Hero(
tag: tagPrefix + "_owner_other",
transitionOnUserGestures: true,
child: Padding(
padding: const EdgeInsets.only(
right: 8.0,
bottom: 8.0,
),
child: UserAvatarWidget(
c.owner,
thumbnailView: true,
),
),
),
if (!isOwner)
Align(
alignment: Alignment.bottomRight,
child: Hero(
tag: tagPrefix + "_owner_other",
transitionOnUserGestures: true,
child: Padding(
padding: const EdgeInsets.only(
right: 8.0,
bottom: 8.0,
),
child: UserAvatarWidget(
c.owner,
thumbnailView: true,
),
),
),
),
],
),
),
),
],
),
const SizedBox(height: 4),
Hero(
tag: tagPrefix + "_title",
transitionOnUserGestures: true,
child: SizedBox(
width: sideOfThumbnail,
child: FutureBuilder<int>(
future: showFileCount
? CollectionsService.instance.getFileCount(c)
: Future.value(0),
builder: (context, snapshot) {
int? cachedCount;
if (showFileCount) {
if (snapshot.hasData) {
cachedCount = snapshot.data;
} else {
//Need to use cached count so that the hero
//animation works as expected without flickering.
cachedCount =
CollectionsService.instance.getCachedFileCount(c);
}
}
if (cachedCount != null && cachedCount > 0) {
final String textCount =
NumberFormat().format(cachedCount);
return Row(
children: [
Container(
constraints: BoxConstraints(
maxWidth: sideOfThumbnail -
((textCount.length + 3) * 10),
),
child: Text(
c.displayName,
style: enteTextTheme.small,
overflow: TextOverflow.ellipsis,
),
),
RichText(
text: TextSpan(
style: enteTextTheme.smallMuted,
children: [
TextSpan(text: ' \u2022 $textCount'),
],
),
),
],
);
} else {
return Text(
c.displayName,
style: enteTextTheme.small,
overflow: TextOverflow.ellipsis,
);
}
},
),
],
),
),
),
],
),
onTap: () async {
if (onTapCallback != null) {
onTapCallback!(c);
return;
}
final thumbnail = await CollectionsService.instance.getCover(c);
// ignore: unawaited_futures
routeToPage(
context,
CollectionPage(
CollectionWithThumbnail(c, thumbnail),
tagPrefix: tagPrefix,
hasVerifiedLock: hasVerifiedLock,
const SizedBox(height: 4),
Hero(
tag: tagPrefix + "_title",
transitionOnUserGestures: true,
child: SizedBox(
width: sideOfThumbnail,
child: FutureBuilder<int>(
future: showFileCount
? CollectionsService.instance.getFileCount(c)
: Future.value(0),
builder: (context, snapshot) {
int? cachedCount;
if (showFileCount) {
if (snapshot.hasData) {
cachedCount = snapshot.data;
} else {
//Need to use cached count so that the hero
//animation works as expected without flickering.
cachedCount =
CollectionsService.instance.getCachedFileCount(c);
}
}
if (cachedCount != null && cachedCount > 0) {
final String textCount = NumberFormat().format(cachedCount);
return Row(
children: [
Container(
constraints: BoxConstraints(
maxWidth:
sideOfThumbnail - ((textCount.length + 3) * 10),
),
child: Text(
c.displayName,
style: enteTextTheme.small,
overflow: TextOverflow.ellipsis,
),
),
RichText(
text: TextSpan(
style: enteTextTheme.smallMuted,
children: [
TextSpan(text: ' \u2022 $textCount'),
],
),
),
],
);
} else {
return Text(
c.displayName,
style: enteTextTheme.small,
overflow: TextOverflow.ellipsis,
);
}
},
),
);
},
onLongPress: () {
if (onLongPressCallback != null) {
onLongPressCallback!(c);
}
},
),
),
],
),
onTap: () async {
if (onTapCallback != null) {
onTapCallback!(c);
return;
}
final thumbnail = await CollectionsService.instance.getCover(c);
// ignore: unawaited_futures
routeToPage(
context,
CollectionPage(
CollectionWithThumbnail(c, thumbnail),
tagPrefix: tagPrefix,
hasVerifiedLock: hasVerifiedLock,
),
);
},
onLongPress: () {
if (onLongPressCallback != null) {
onLongPressCallback!(c);
}
},
);
}
}

View File

@@ -122,6 +122,8 @@ class _AlbumSelectionActionWidgetState
_logger.warning("failed to trash collection", e, s);
await showGenericErrorDialog(context: context, error: e);
}
} else if (collection.type == CollectionType.favorites) {
continue;
} else {
nonEmptyCollection.add(collection);
}
@@ -143,7 +145,10 @@ class _AlbumSelectionActionWidgetState
Future<void> _onPinClick() async {
for (final collection in widget.selectedAlbums.albums) {
if (collection.isPinned) continue;
if (collection.type == CollectionType.favorites || collection.isPinned) {
continue;
}
await updateOrder(
context,
collection,
@@ -155,6 +160,9 @@ class _AlbumSelectionActionWidgetState
Future<void> _onHideClick() async {
for (final collection in widget.selectedAlbums.albums) {
if (collection.type == CollectionType.favorites) {
continue;
}
final isHidden = collection.isHidden();
final int prevVisiblity = isHidden ? hiddenVisibility : visibleVisibility;
final int newVisiblity = isHidden ? visibleVisibility : hiddenVisibility;