mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
fix: shared album control permissions (#22435)
* fix: shared album control permissions * fix: properly display "add photos" * fix: dont allow modification of album order * fix: album title/description edit from app bar * chore: code review changes * chore: format translations * chore: lintings
This commit is contained in:
parent
146973b072
commit
8473dab684
8 changed files with 99 additions and 54 deletions
|
|
@ -43,7 +43,7 @@ class ViewerBottomBar extends ConsumerWidget {
|
|||
final actions = <Widget>[
|
||||
const ShareActionButton(source: ActionSource.viewer),
|
||||
if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer),
|
||||
if (asset.type == AssetType.image) const EditImageActionButton(),
|
||||
if (asset.type == AssetType.image && isOwner) const EditImageActionButton(),
|
||||
if (isOwner) ...[
|
||||
if (asset.hasRemote && isOwner && isArchived)
|
||||
const UnArchiveActionButton(source: ActionSource.viewer)
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||
|
||||
final exifInfo = ref.watch(currentAssetExifProvider).valueOrNull;
|
||||
final cameraTitle = _getCameraInfoTitle(exifInfo);
|
||||
final isOwner = ref.watch(currentUserProvider)?.id == (asset is RemoteAsset ? asset.ownerId : null);
|
||||
|
||||
return SliverList.list(
|
||||
children: [
|
||||
|
|
@ -147,10 +148,10 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||
_SheetTile(
|
||||
title: _getDateTime(context, asset),
|
||||
titleStyle: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
trailing: asset.hasRemote ? const Icon(Icons.edit, size: 18) : null,
|
||||
onTap: asset.hasRemote ? () async => await _editDateTime(context, ref) : null,
|
||||
trailing: asset.hasRemote && isOwner ? const Icon(Icons.edit, size: 18) : null,
|
||||
onTap: asset.hasRemote && isOwner ? () async => await _editDateTime(context, ref) : null,
|
||||
),
|
||||
if (exifInfo != null) _SheetAssetDescription(exif: exifInfo),
|
||||
if (exifInfo != null) _SheetAssetDescription(exif: exifInfo, isEditable: isOwner),
|
||||
const SheetPeopleDetails(),
|
||||
const SheetLocationDetails(),
|
||||
// Details header
|
||||
|
|
@ -265,8 +266,9 @@ class _SheetTile extends ConsumerWidget {
|
|||
|
||||
class _SheetAssetDescription extends ConsumerStatefulWidget {
|
||||
final ExifInfo exif;
|
||||
final bool isEditable;
|
||||
|
||||
const _SheetAssetDescription({required this.exif});
|
||||
const _SheetAssetDescription({required this.exif, this.isEditable = true});
|
||||
|
||||
@override
|
||||
ConsumerState<_SheetAssetDescription> createState() => _SheetAssetDescriptionState();
|
||||
|
|
@ -312,27 +314,33 @@ class _SheetAssetDescriptionState extends ConsumerState<_SheetAssetDescription>
|
|||
|
||||
// Update controller text when EXIF data changes
|
||||
final currentDescription = currentExifInfo?.description ?? '';
|
||||
final hintText = (widget.isEditable ? 'exif_bottom_sheet_description' : 'exif_bottom_sheet_no_description').t(
|
||||
context: context,
|
||||
);
|
||||
if (_controller.text != currentDescription && !_descriptionFocus.hasFocus) {
|
||||
_controller.text = currentDescription;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
keyboardType: TextInputType.multiline,
|
||||
focusNode: _descriptionFocus,
|
||||
maxLines: null, // makes it grow as text is added
|
||||
decoration: InputDecoration(
|
||||
hintText: 'exif_bottom_sheet_description'.t(context: context),
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
focusedErrorBorder: InputBorder.none,
|
||||
child: IgnorePointer(
|
||||
ignoring: !widget.isEditable,
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
keyboardType: TextInputType.multiline,
|
||||
focusNode: _descriptionFocus,
|
||||
maxLines: null, // makes it grow as text is added
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
focusedErrorBorder: InputBorder.none,
|
||||
),
|
||||
onTapOutside: (_) => saveDescription(currentExifInfo?.description),
|
||||
),
|
||||
onTapOutside: (_) => saveDescription(currentExifInfo?.description),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||
(previousRouteName != TabShellRoute.name || tabRoute == TabEnum.search) &&
|
||||
previousRouteName != AssetViewerRoute.name &&
|
||||
previousRouteName != null &&
|
||||
previousRouteName != LocalTimelineRoute.name;
|
||||
previousRouteName != LocalTimelineRoute.name &&
|
||||
isOwner;
|
||||
|
||||
final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
|
||||
int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity));
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_shee
|
|||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
class RemoteAlbumBottomSheet extends ConsumerStatefulWidget {
|
||||
|
|
@ -53,6 +54,7 @@ class _RemoteAlbumBottomSheetState extends ConsumerState<RemoteAlbumBottomSheet>
|
|||
Widget build(BuildContext context) {
|
||||
final multiselect = ref.watch(multiSelectProvider);
|
||||
final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash));
|
||||
final ownsAlbum = ref.watch(currentUserProvider)?.id == widget.album.ownerId;
|
||||
|
||||
Future<void> addAssetsToAlbum(RemoteAlbum album) async {
|
||||
final selectedAssets = multiselect.selectedAssets;
|
||||
|
|
@ -93,28 +95,35 @@ class _RemoteAlbumBottomSheetState extends ConsumerState<RemoteAlbumBottomSheet>
|
|||
const ShareActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasRemote) ...[
|
||||
const ShareLinkActionButton(source: ActionSource.timeline),
|
||||
const ArchiveActionButton(source: ActionSource.timeline),
|
||||
const FavoriteActionButton(source: ActionSource.timeline),
|
||||
|
||||
if (ownsAlbum) ...[
|
||||
const ArchiveActionButton(source: ActionSource.timeline),
|
||||
const FavoriteActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
const DownloadActionButton(source: ActionSource.timeline),
|
||||
isTrashEnable
|
||||
? const TrashActionButton(source: ActionSource.timeline)
|
||||
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
||||
const EditDateTimeActionButton(source: ActionSource.timeline),
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
if (ownsAlbum) ...[
|
||||
isTrashEnable
|
||||
? const TrashActionButton(source: ActionSource.timeline)
|
||||
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
||||
const EditDateTimeActionButton(source: ActionSource.timeline),
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
],
|
||||
if (multiselect.hasLocal) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
const UploadActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
RemoveFromAlbumActionButton(source: ActionSource.timeline, albumId: widget.album.id),
|
||||
],
|
||||
slivers: [
|
||||
const AddToAlbumHeader(),
|
||||
AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand),
|
||||
if (ownsAlbum) RemoveFromAlbumActionButton(source: ActionSource.timeline, albumId: widget.album.id),
|
||||
],
|
||||
slivers: ownsAlbum
|
||||
? [
|
||||
const AddToAlbumHeader(),
|
||||
AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand),
|
||||
]
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue