1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

feat(mobile): shared album activity disable handling (#4890)

* feat(mobile): shared album activity disable handling

* not show comment/like option on non-shared album, alternative text when activity is disabled

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
shenlong 2023-11-08 03:07:43 +00:00 committed by GitHub
parent bb28cae671
commit 664b7106ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 175 additions and 88 deletions

View File

@ -376,5 +376,8 @@
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"shared_album_activities_input_hint": "Say something", "shared_album_activities_input_hint": "Say something",
"shared_album_activity_remove_title": "Delete Activity", "shared_album_activity_remove_title": "Delete Activity",
"shared_album_activity_remove_content": "Do you want to delete this activity?" "shared_album_activity_remove_content": "Do you want to delete this activity?",
"shared_album_activity_setting_title": "Comments & likes",
"shared_album_activity_setting_subtitle": "Let others respond",
"shared_album_activities_input_disable": "Comment is disabled"
} }

View File

@ -169,4 +169,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382 PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382
COCOAPODS: 1.12.1 COCOAPODS: 1.11.3

View File

@ -19,12 +19,14 @@ class ActivitiesPage extends HookConsumerWidget {
final bool withAssetThumbs; final bool withAssetThumbs;
final String appBarTitle; final String appBarTitle;
final bool isOwner; final bool isOwner;
final bool isReadOnly;
const ActivitiesPage( const ActivitiesPage(
this.albumId, { this.albumId, {
this.appBarTitle = "", this.appBarTitle = "",
this.assetId, this.assetId,
this.withAssetThumbs = true, this.withAssetThumbs = true,
this.isOwner = false, this.isOwner = false,
this.isReadOnly = false,
super.key, super.key,
}); });
@ -45,6 +47,7 @@ class ActivitiesPage extends HookConsumerWidget {
}, },
[], [],
); );
buildTitleWithTimestamp(Activity activity, {bool leftAlign = true}) { buildTitleWithTimestamp(Activity activity, {bool leftAlign = true}) {
final textColor = Theme.of(context).brightness == Brightness.dark final textColor = Theme.of(context).brightness == Brightness.dark
? Colors.white ? Colors.white
@ -116,6 +119,7 @@ class ActivitiesPage extends HookConsumerWidget {
padding: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.only(bottom: 10),
child: TextField( child: TextField(
controller: inputController, controller: inputController,
enabled: !isReadOnly,
focusNode: inputFocusNode, focusNode: inputFocusNode,
textInputAction: TextInputAction.send, textInputAction: TextInputAction.send,
autofocus: false, autofocus: false,
@ -150,7 +154,9 @@ class ActivitiesPage extends HookConsumerWidget {
), ),
), ),
suffixIconColor: liked ? Colors.red[700] : null, suffixIconColor: liked ? Colors.red[700] : null,
hintText: 'shared_album_activities_input_hint'.tr(), hintText: isReadOnly
? 'shared_album_activities_input_disable'.tr()
: 'shared_album_activities_input_hint'.tr(),
hintStyle: TextStyle( hintStyle: TextStyle(
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
fontSize: 14, fontSize: 14,
@ -240,70 +246,72 @@ class ActivitiesPage extends HookConsumerWidget {
a.assetId == assetId, a.assetId == assetId,
); );
return Stack( return SafeArea(
children: [ child: Stack(
ListView.builder( children: [
controller: listViewScrollController, ListView.builder(
itemCount: data.length + 1, controller: listViewScrollController,
itemBuilder: (context, index) { itemCount: data.length + 1,
// Vertical gap after the last element itemBuilder: (context, index) {
if (index == data.length) { // Vertical gap after the last element
return const SizedBox( if (index == data.length) {
height: 80, return const SizedBox(
); height: 80,
} );
}
final activity = data[index]; final activity = data[index];
final canDelete = final canDelete =
activity.user.id == currentUser?.id || isOwner; activity.user.id == currentUser?.id || isOwner;
return Padding( return Padding(
padding: const EdgeInsets.all(5), padding: const EdgeInsets.all(5),
child: activity.type == ActivityType.comment child: activity.type == ActivityType.comment
? getDismissibleWidget( ? getDismissibleWidget(
ListTile( ListTile(
minVerticalPadding: 15, minVerticalPadding: 15,
leading: UserCircleAvatar(user: activity.user), leading: UserCircleAvatar(user: activity.user),
title: buildTitleWithTimestamp( title: buildTitleWithTimestamp(
activity, activity,
leftAlign: leftAlign: withAssetThumbs &&
withAssetThumbs && activity.assetId != null, activity.assetId != null,
),
titleAlignment: ListTileTitleAlignment.top,
trailing: buildAssetThumbnail(activity),
subtitle: Text(activity.comment!),
),
activity,
canDelete,
)
: getDismissibleWidget(
ListTile(
minVerticalPadding: 15,
leading: Container(
width: 44,
alignment: Alignment.center,
child: Icon(
Icons.favorite_rounded,
color: Colors.red[700],
), ),
titleAlignment: ListTileTitleAlignment.top,
trailing: buildAssetThumbnail(activity),
subtitle: Text(activity.comment!),
), ),
title: buildTitleWithTimestamp(activity), activity,
trailing: buildAssetThumbnail(activity), canDelete,
)
: getDismissibleWidget(
ListTile(
minVerticalPadding: 15,
leading: Container(
width: 44,
alignment: Alignment.center,
child: Icon(
Icons.favorite_rounded,
color: Colors.red[700],
),
),
title: buildTitleWithTimestamp(activity),
trailing: buildAssetThumbnail(activity),
),
activity,
canDelete,
), ),
activity, );
canDelete, },
),
);
},
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: buildTextField(liked?.id),
), ),
), Align(
], alignment: Alignment.bottomCenter,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: buildTextField(liked?.id),
),
),
],
),
); );
}, },
), ),

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/asset.dart';
@ -10,7 +11,7 @@ import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
class SharedAlbumNotifier extends StateNotifier<List<Album>> { class SharedAlbumNotifier extends StateNotifier<List<Album>> {
SharedAlbumNotifier(this._albumService, Isar db) : super([]) { SharedAlbumNotifier(this._albumService, Isar db, this._ref) : super([]) {
final query = db.albums.filter().sharedEqualTo(true).sortByCreatedAtDesc(); final query = db.albums.filter().sharedEqualTo(true).sortByCreatedAtDesc();
query.findAll().then((value) => state = value); query.findAll().then((value) => state = value);
_streamSub = query.watch().listen((data) => state = data); _streamSub = query.watch().listen((data) => state = data);
@ -18,6 +19,7 @@ class SharedAlbumNotifier extends StateNotifier<List<Album>> {
final AlbumService _albumService; final AlbumService _albumService;
late final StreamSubscription<List<Album>> _streamSub; late final StreamSubscription<List<Album>> _streamSub;
final Ref _ref;
Future<Album?> createSharedAlbum( Future<Album?> createSharedAlbum(
String albumName, String albumName,
@ -66,6 +68,17 @@ class SharedAlbumNotifier extends StateNotifier<List<Album>> {
return result; return result;
} }
Future<bool> setActivityEnabled(Album album, bool activityEnabled) async {
final result =
await _albumService.setActivityEnabled(album, activityEnabled);
if (result) {
_ref.invalidate(albumDetailProvider(album.id));
}
return result;
}
@override @override
void dispose() { void dispose() {
_streamSub.cancel(); _streamSub.cancel();
@ -78,5 +91,6 @@ final sharedAlbumProvider =
return SharedAlbumNotifier( return SharedAlbumNotifier(
ref.watch(albumServiceProvider), ref.watch(albumServiceProvider),
ref.watch(dbProvider), ref.watch(dbProvider),
ref,
); );
}); });

View File

@ -284,6 +284,23 @@ class AlbumService {
return false; return false;
} }
Future<bool> setActivityEnabled(Album album, bool enabled) async {
try {
final result = await _apiService.albumApi.updateAlbumInfo(
album.remoteId!,
UpdateAlbumDto(isActivityEnabled: enabled),
);
if (result != null) {
album.activityEnabled = enabled;
await _db.writeTxn(() => _db.albums.put(album));
return true;
}
} catch (e) {
debugPrint("Error setActivityEnabled ${e.toString()}");
}
return false;
}
Future<bool> deleteAlbum(Album album) async { Future<bool> deleteAlbum(Album album) async {
try { try {
final userId = Store.get(StoreKey.currentUser).isarId; final userId = Store.get(StoreKey.currentUser).isarId;

View File

@ -216,32 +216,36 @@ class AlbumViewerAppbar extends HookConsumerWidget
).tr(), ).tr(),
onTap: () => onShareAssetsTo(), onTap: () => onShareAssetsTo(),
), ),
album.ownerId == userId ? ListTile( album.ownerId == userId
leading: const Icon(Icons.delete_sweep_rounded), ? ListTile(
title: const Text( leading: const Icon(Icons.delete_sweep_rounded),
'album_viewer_appbar_share_remove', title: const Text(
style: TextStyle(fontWeight: FontWeight.bold), 'album_viewer_appbar_share_remove',
).tr(), style: TextStyle(fontWeight: FontWeight.bold),
onTap: () => onRemoveFromAlbumPressed(), ).tr(),
) : const SizedBox(), onTap: () => onRemoveFromAlbumPressed(),
)
: const SizedBox(),
]; ];
} else { } else {
return [ return [
album.ownerId == userId ? ListTile( album.ownerId == userId
leading: const Icon(Icons.delete_forever_rounded), ? ListTile(
title: const Text( leading: const Icon(Icons.delete_forever_rounded),
'album_viewer_appbar_share_delete', title: const Text(
style: TextStyle(fontWeight: FontWeight.bold), 'album_viewer_appbar_share_delete',
).tr(), style: TextStyle(fontWeight: FontWeight.bold),
onTap: () => onDeleteAlbumPressed(), ).tr(),
) : ListTile( onTap: () => onDeleteAlbumPressed(),
leading: const Icon(Icons.person_remove_rounded), )
title: const Text( : ListTile(
'album_viewer_appbar_share_leave', leading: const Icon(Icons.person_remove_rounded),
style: TextStyle(fontWeight: FontWeight.bold), title: const Text(
).tr(), 'album_viewer_appbar_share_leave',
onTap: () => onLeaveAlbumPressed(), style: TextStyle(fontWeight: FontWeight.bold),
), ).tr(),
onTap: () => onLeaveAlbumPressed(),
),
]; ];
} }
} }
@ -390,7 +394,8 @@ class AlbumViewerAppbar extends HookConsumerWidget
title: selected.isNotEmpty ? Text('${selected.length}') : null, title: selected.isNotEmpty ? Text('${selected.length}') : null,
centerTitle: false, centerTitle: false,
actions: [ actions: [
if (album.shared) buildActivitiesButton(), if (album.shared && (album.activityEnabled || comments != 0))
buildActivitiesButton(),
if (album.isRemote) if (album.isRemote)
IconButton( IconButton(
splashRadius: 25, splashRadius: 25,

View File

@ -23,6 +23,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
final sharedUsers = useState(album.sharedUsers.toList()); final sharedUsers = useState(album.sharedUsers.toList());
final owner = album.owner.value; final owner = album.owner.value;
final userId = ref.watch(authenticationProvider).userId; final userId = ref.watch(authenticationProvider).userId;
final activityEnabled = useState(album.activityEnabled);
final isOwner = owner?.id == userId; final isOwner = owner?.id == userId;
void showErrorMessage() { void showErrorMessage() {
@ -195,6 +196,31 @@ class AlbumOptionsPage extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (isOwner && album.shared)
SwitchListTile.adaptive(
value: activityEnabled.value,
onChanged: (bool value) async {
activityEnabled.value = value;
if (await ref
.read(sharedAlbumProvider.notifier)
.setActivityEnabled(album, value)) {
album.activityEnabled = value;
}
},
activeColor: activityEnabled.value
? Theme.of(context).primaryColor
: Theme.of(context).disabledColor,
dense: true,
title: Text(
"shared_album_activity_setting_title",
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(fontWeight: FontWeight.bold),
).tr(),
subtitle:
const Text("shared_album_activity_setting_subtitle").tr(),
),
buildSectionTitle("PEOPLE"), buildSectionTitle("PEOPLE"),
buildOwnerInfo(), buildOwnerInfo(),
buildSharedUsersList(), buildSharedUsersList(),

View File

@ -239,6 +239,7 @@ class AlbumViewerPage extends HookConsumerWidget {
albumId: album.remoteId!, albumId: album.remoteId!,
appBarTitle: album.name, appBarTitle: album.name,
isOwner: userId == album.ownerId, isOwner: userId == album.ownerId,
isReadOnly: !album.activityEnabled,
), ),
); );
} }
@ -279,7 +280,8 @@ class AlbumViewerPage extends HookConsumerWidget {
], ],
), ),
isOwner: userId == data.ownerId, isOwner: userId == data.ownerId,
sharedAlbumId: data.remoteId, sharedAlbumId:
data.shared && data.activityEnabled ? data.remoteId : null,
), ),
), ),
), ),

View File

@ -348,6 +348,7 @@ class _$AppRouter extends RootStackRouter {
assetId: args.assetId, assetId: args.assetId,
withAssetThumbs: args.withAssetThumbs, withAssetThumbs: args.withAssetThumbs,
isOwner: args.isOwner, isOwner: args.isOwner,
isReadOnly: args.isReadOnly,
key: args.key, key: args.key,
), ),
transitionsBuilder: TransitionsBuilders.slideLeft, transitionsBuilder: TransitionsBuilders.slideLeft,
@ -1568,6 +1569,7 @@ class ActivitiesRoute extends PageRouteInfo<ActivitiesRouteArgs> {
String? assetId, String? assetId,
bool withAssetThumbs = true, bool withAssetThumbs = true,
bool isOwner = false, bool isOwner = false,
bool isReadOnly = false,
Key? key, Key? key,
}) : super( }) : super(
ActivitiesRoute.name, ActivitiesRoute.name,
@ -1578,6 +1580,7 @@ class ActivitiesRoute extends PageRouteInfo<ActivitiesRouteArgs> {
assetId: assetId, assetId: assetId,
withAssetThumbs: withAssetThumbs, withAssetThumbs: withAssetThumbs,
isOwner: isOwner, isOwner: isOwner,
isReadOnly: isReadOnly,
key: key, key: key,
), ),
); );
@ -1592,6 +1595,7 @@ class ActivitiesRouteArgs {
this.assetId, this.assetId,
this.withAssetThumbs = true, this.withAssetThumbs = true,
this.isOwner = false, this.isOwner = false,
this.isReadOnly = false,
this.key, this.key,
}); });
@ -1605,11 +1609,13 @@ class ActivitiesRouteArgs {
final bool isOwner; final bool isOwner;
final bool isReadOnly;
final Key? key; final Key? key;
@override @override
String toString() { String toString() {
return 'ActivitiesRouteArgs{albumId: $albumId, appBarTitle: $appBarTitle, assetId: $assetId, withAssetThumbs: $withAssetThumbs, isOwner: $isOwner, key: $key}'; return 'ActivitiesRouteArgs{albumId: $albumId, appBarTitle: $appBarTitle, assetId: $assetId, withAssetThumbs: $withAssetThumbs, isOwner: $isOwner, isReadOnly: $isReadOnly, key: $key}';
} }
} }

View File

@ -22,6 +22,7 @@ class Album {
this.endDate, this.endDate,
this.lastModifiedAssetTimestamp, this.lastModifiedAssetTimestamp,
required this.shared, required this.shared,
required this.activityEnabled,
}); });
Id id = Isar.autoIncrement; Id id = Isar.autoIncrement;
@ -36,6 +37,7 @@ class Album {
DateTime? endDate; DateTime? endDate;
DateTime? lastModifiedAssetTimestamp; DateTime? lastModifiedAssetTimestamp;
bool shared; bool shared;
bool activityEnabled;
final IsarLink<User> owner = IsarLink<User>(); final IsarLink<User> owner = IsarLink<User>();
final IsarLink<Asset> thumbnail = IsarLink<Asset>(); final IsarLink<Asset> thumbnail = IsarLink<Asset>();
final IsarLinks<User> sharedUsers = IsarLinks<User>(); final IsarLinks<User> sharedUsers = IsarLinks<User>();
@ -106,6 +108,7 @@ class Album {
modifiedAt.isAtSameMomentAs(other.modifiedAt) && modifiedAt.isAtSameMomentAs(other.modifiedAt) &&
lastModifiedAssetTimestampIsSetAndEqual && lastModifiedAssetTimestampIsSetAndEqual &&
shared == other.shared && shared == other.shared &&
activityEnabled == other.activityEnabled &&
owner.value == other.owner.value && owner.value == other.owner.value &&
thumbnail.value == other.thumbnail.value && thumbnail.value == other.thumbnail.value &&
sharedUsers.length == other.sharedUsers.length && sharedUsers.length == other.sharedUsers.length &&
@ -123,6 +126,7 @@ class Album {
modifiedAt.hashCode ^ modifiedAt.hashCode ^
lastModifiedAssetTimestamp.hashCode ^ lastModifiedAssetTimestamp.hashCode ^
shared.hashCode ^ shared.hashCode ^
activityEnabled.hashCode ^
owner.value.hashCode ^ owner.value.hashCode ^
thumbnail.value.hashCode ^ thumbnail.value.hashCode ^
sharedUsers.length.hashCode ^ sharedUsers.length.hashCode ^
@ -134,6 +138,7 @@ class Album {
createdAt: ape.lastModified?.toUtc() ?? DateTime.now().toUtc(), createdAt: ape.lastModified?.toUtc() ?? DateTime.now().toUtc(),
modifiedAt: ape.lastModified?.toUtc() ?? DateTime.now().toUtc(), modifiedAt: ape.lastModified?.toUtc() ?? DateTime.now().toUtc(),
shared: false, shared: false,
activityEnabled: false,
); );
a.owner.value = Store.get(StoreKey.currentUser); a.owner.value = Store.get(StoreKey.currentUser);
a.localId = ape.id; a.localId = ape.id;
@ -151,6 +156,7 @@ class Album {
shared: dto.shared, shared: dto.shared,
startDate: dto.startDate, startDate: dto.startDate,
endDate: dto.endDate, endDate: dto.endDate,
activityEnabled: dto.isActivityEnabled,
); );
a.owner.value = await db.users.getById(dto.ownerId); a.owner.value = await db.users.getById(dto.ownerId);
if (dto.albumThumbnailAssetId != null) { if (dto.albumThumbnailAssetId != null) {