feat(mobile): add album description functionality (#18886)

* feat(mobile): add album description functionality

- Introduced a new optional `description` field in the `Album` entity.
- Updated `AlbumViewerPageState` to manage `editDescriptionText`.
- Created `AlbumDescription` and `AlbumViewerEditableDescription` widgets for displaying and editing album descriptions.
- Enhanced `CreateAlbumPage` to include a description input field.
- Implemented backend support for updating album descriptions in `AlbumApiRepository` and `AlbumService`.
- Updated sync logic to handle album descriptions during data synchronization.
- Adjusted UI components to accommodate the new description feature.

* fix dart analysis error

* remove comment that shouldn't be there

* Album header styling

* fix: disable edit after album creation

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
JobiJoba
2025-06-05 00:41:28 +07:00
committed by GitHub
parent 19ff39c2b9
commit 5d0ad853f4
18 changed files with 598 additions and 98 deletions

View File

@@ -10,6 +10,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
import 'package:immich_mobile/pages/album/album_control_button.dart';
import 'package:immich_mobile/pages/album/album_date_range.dart';
import 'package:immich_mobile/pages/album/album_description.dart';
import 'package:immich_mobile/pages/album/album_shared_user_icons.dart';
import 'package:immich_mobile/pages/album/album_title.dart';
import 'package:immich_mobile/providers/album/album.provider.dart';
@@ -36,6 +37,7 @@ class AlbumViewer extends HookConsumerWidget {
}
final titleFocusNode = useFocusNode();
final descriptionFocusNode = useFocusNode();
final userId = ref.watch(authProvider).userId;
final isMultiselecting = ref.watch(multiselectProvider);
final isProcessing = useProcessingOverlay();
@@ -106,23 +108,44 @@ class AlbumViewer extends HookConsumerWidget {
MultiselectGrid(
key: const ValueKey("albumViewerMultiselectGrid"),
renderListProvider: albumTimelineProvider(album.id),
topWidget: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AlbumTitle(
key: const ValueKey("albumTitle"),
titleFocusNode: titleFocusNode,
topWidget: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
context.primaryColor.withValues(alpha: 0.04),
context.primaryColor.withValues(alpha: 0.02),
Colors.orange.withValues(alpha: 0.02),
Colors.transparent,
],
stops: const [0.0, 0.3, 0.7, 1.0],
),
const AlbumDateRange(),
const AlbumSharedUserIcons(),
if (album.isRemote)
AlbumControlButton(
key: const ValueKey("albumControlButton"),
onAddPhotosPressed: onAddPhotosPressed,
onAddUsersPressed: onAddUsersPressed,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 32),
const AlbumDateRange(),
AlbumTitle(
key: const ValueKey("albumTitle"),
titleFocusNode: titleFocusNode,
),
],
AlbumDescription(
key: const ValueKey("albumDescription"),
descriptionFocusNode: descriptionFocusNode,
),
const AlbumSharedUserIcons(),
if (album.isRemote)
AlbumControlButton(
key: const ValueKey("albumControlButton"),
onAddPhotosPressed: onAddPhotosPressed,
onAddUsersPressed: onAddUsersPressed,
),
const SizedBox(height: 8),
],
),
),
onRemoveFromAlbum: onRemoveFromAlbumPressed,
editEnabled: album.ownerId == userId,
@@ -136,6 +159,7 @@ class AlbumViewer extends HookConsumerWidget {
child: AlbumViewerAppbar(
key: const ValueKey("albumViewerAppbar"),
titleFocusNode: titleFocusNode,
descriptionFocusNode: descriptionFocusNode,
userId: userId,
onAddPhotos: onAddPhotosPressed,
onAddUsers: onAddUsersPressed,