mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-27 15:07:45 +00:00
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:
parent
19ff39c2b9
commit
5d0ad853f4
18 changed files with 363 additions and 62 deletions
|
|
@ -19,6 +19,7 @@ class Album {
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.modifiedAt,
|
required this.modifiedAt,
|
||||||
|
this.description,
|
||||||
this.startDate,
|
this.startDate,
|
||||||
this.endDate,
|
this.endDate,
|
||||||
this.lastModifiedAssetTimestamp,
|
this.lastModifiedAssetTimestamp,
|
||||||
|
|
@ -34,6 +35,7 @@ class Album {
|
||||||
@Index(unique: false, replace: false, type: IndexType.hash)
|
@Index(unique: false, replace: false, type: IndexType.hash)
|
||||||
String? localId;
|
String? localId;
|
||||||
String name;
|
String name;
|
||||||
|
String? description;
|
||||||
DateTime createdAt;
|
DateTime createdAt;
|
||||||
DateTime modifiedAt;
|
DateTime modifiedAt;
|
||||||
DateTime? startDate;
|
DateTime? startDate;
|
||||||
|
|
@ -108,6 +110,7 @@ class Album {
|
||||||
remoteId == other.remoteId &&
|
remoteId == other.remoteId &&
|
||||||
localId == other.localId &&
|
localId == other.localId &&
|
||||||
name == other.name &&
|
name == other.name &&
|
||||||
|
description == other.description &&
|
||||||
createdAt.isAtSameMomentAs(other.createdAt) &&
|
createdAt.isAtSameMomentAs(other.createdAt) &&
|
||||||
modifiedAt.isAtSameMomentAs(other.modifiedAt) &&
|
modifiedAt.isAtSameMomentAs(other.modifiedAt) &&
|
||||||
isAtSameMomentAs(startDate, other.startDate) &&
|
isAtSameMomentAs(startDate, other.startDate) &&
|
||||||
|
|
@ -135,6 +138,7 @@ class Album {
|
||||||
modifiedAt.hashCode ^
|
modifiedAt.hashCode ^
|
||||||
startDate.hashCode ^
|
startDate.hashCode ^
|
||||||
endDate.hashCode ^
|
endDate.hashCode ^
|
||||||
|
description.hashCode ^
|
||||||
lastModifiedAssetTimestamp.hashCode ^
|
lastModifiedAssetTimestamp.hashCode ^
|
||||||
shared.hashCode ^
|
shared.hashCode ^
|
||||||
activityEnabled.hashCode ^
|
activityEnabled.hashCode ^
|
||||||
|
|
@ -150,6 +154,7 @@ class Album {
|
||||||
name: dto.albumName,
|
name: dto.albumName,
|
||||||
createdAt: dto.createdAt,
|
createdAt: dto.createdAt,
|
||||||
modifiedAt: dto.updatedAt,
|
modifiedAt: dto.updatedAt,
|
||||||
|
description: dto.description,
|
||||||
lastModifiedAssetTimestamp: dto.lastModifiedAssetTimestamp,
|
lastModifiedAssetTimestamp: dto.lastModifiedAssetTimestamp,
|
||||||
shared: dto.shared,
|
shared: dto.shared,
|
||||||
startDate: dto.startDate,
|
startDate: dto.startDate,
|
||||||
|
|
@ -184,7 +189,8 @@ class Album {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => name;
|
String toString() =>
|
||||||
|
'remoteId: $remoteId name: $name description: $description';
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AssetsHelper on IsarCollection<Album> {
|
extension AssetsHelper on IsarCollection<Album> {
|
||||||
|
|
|
||||||
BIN
mobile/lib/entities/album.entity.g.dart
generated
BIN
mobile/lib/entities/album.entity.g.dart
generated
Binary file not shown.
|
|
@ -3,18 +3,23 @@ import 'dart:convert';
|
||||||
class AlbumViewerPageState {
|
class AlbumViewerPageState {
|
||||||
final bool isEditAlbum;
|
final bool isEditAlbum;
|
||||||
final String editTitleText;
|
final String editTitleText;
|
||||||
|
final String editDescriptionText;
|
||||||
|
|
||||||
AlbumViewerPageState({
|
AlbumViewerPageState({
|
||||||
required this.isEditAlbum,
|
required this.isEditAlbum,
|
||||||
required this.editTitleText,
|
required this.editTitleText,
|
||||||
|
required this.editDescriptionText,
|
||||||
});
|
});
|
||||||
|
|
||||||
AlbumViewerPageState copyWith({
|
AlbumViewerPageState copyWith({
|
||||||
bool? isEditAlbum,
|
bool? isEditAlbum,
|
||||||
String? editTitleText,
|
String? editTitleText,
|
||||||
|
String? editDescriptionText,
|
||||||
}) {
|
}) {
|
||||||
return AlbumViewerPageState(
|
return AlbumViewerPageState(
|
||||||
isEditAlbum: isEditAlbum ?? this.isEditAlbum,
|
isEditAlbum: isEditAlbum ?? this.isEditAlbum,
|
||||||
editTitleText: editTitleText ?? this.editTitleText,
|
editTitleText: editTitleText ?? this.editTitleText,
|
||||||
|
editDescriptionText: editDescriptionText ?? this.editDescriptionText,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,6 +28,7 @@ class AlbumViewerPageState {
|
||||||
|
|
||||||
result.addAll({'isEditAlbum': isEditAlbum});
|
result.addAll({'isEditAlbum': isEditAlbum});
|
||||||
result.addAll({'editTitleText': editTitleText});
|
result.addAll({'editTitleText': editTitleText});
|
||||||
|
result.addAll({'editDescriptionText': editDescriptionText});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -31,6 +37,7 @@ class AlbumViewerPageState {
|
||||||
return AlbumViewerPageState(
|
return AlbumViewerPageState(
|
||||||
isEditAlbum: map['isEditAlbum'] ?? false,
|
isEditAlbum: map['isEditAlbum'] ?? false,
|
||||||
editTitleText: map['editTitleText'] ?? '',
|
editTitleText: map['editTitleText'] ?? '',
|
||||||
|
editDescriptionText: map['editDescriptionText'] ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,7 +48,7 @@ class AlbumViewerPageState {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'AlbumViewerPageState(isEditAlbum: $isEditAlbum, editTitleText: $editTitleText)';
|
'AlbumViewerPageState(isEditAlbum: $isEditAlbum, editTitleText: $editTitleText, editDescriptionText: $editDescriptionText)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
|
|
@ -49,9 +56,13 @@ class AlbumViewerPageState {
|
||||||
|
|
||||||
return other is AlbumViewerPageState &&
|
return other is AlbumViewerPageState &&
|
||||||
other.isEditAlbum == isEditAlbum &&
|
other.isEditAlbum == isEditAlbum &&
|
||||||
other.editTitleText == editTitleText;
|
other.editTitleText == editTitleText &&
|
||||||
|
other.editDescriptionText == editDescriptionText;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => isEditAlbum.hashCode ^ editTitleText.hashCode;
|
int get hashCode =>
|
||||||
|
isEditAlbum.hashCode ^
|
||||||
|
editTitleText.hashCode ^
|
||||||
|
editDescriptionText.hashCode;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,9 @@ class AlbumControlButton extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 16),
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 40,
|
height: 36,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,12 @@ class AlbumDateRange extends ConsumerWidget {
|
||||||
final (startDate, endDate, shared) = data;
|
final (startDate, endDate, shared) = data;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: shared
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
? const EdgeInsets.only(
|
|
||||||
left: 16.0,
|
|
||||||
bottom: 0.0,
|
|
||||||
)
|
|
||||||
: const EdgeInsets.only(left: 16.0, bottom: 8.0),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
_getDateRangeText(startDate, endDate),
|
_getDateRangeText(startDate, endDate),
|
||||||
style: context.textTheme.labelLarge,
|
style: context.textTheme.labelLarge?.copyWith(
|
||||||
|
color: context.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
45
mobile/lib/pages/album/album_description.dart
Normal file
45
mobile/lib/pages/album/album_description.dart
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
||||||
|
import 'package:immich_mobile/widgets/album/album_viewer_editable_description.dart';
|
||||||
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
|
|
||||||
|
class AlbumDescription extends ConsumerWidget {
|
||||||
|
const AlbumDescription({super.key, required this.descriptionFocusNode});
|
||||||
|
|
||||||
|
final FocusNode descriptionFocusNode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final userId = ref.watch(authProvider).userId;
|
||||||
|
final (isOwner, isRemote, albumDescription) = ref.watch(
|
||||||
|
currentAlbumProvider.select((album) {
|
||||||
|
if (album == null) {
|
||||||
|
return const (false, false, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (album.ownerId == userId, album.isRemote, album.description);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isOwner && isRemote) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8, right: 8),
|
||||||
|
child: AlbumViewerEditableDescription(
|
||||||
|
albumDescription: albumDescription ?? 'add_a_description'.tr(),
|
||||||
|
descriptionFocusNode: descriptionFocusNode,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 8),
|
||||||
|
child: Text(
|
||||||
|
albumDescription ?? 'add_a_description'.tr(),
|
||||||
|
style: context.textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ class AlbumSharedUserIcons extends HookConsumerWidget {
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 50,
|
height: 50,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
padding: const EdgeInsets.only(left: 16),
|
padding: const EdgeInsets.only(left: 16, bottom: 8),
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemBuilder: ((context, index) {
|
itemBuilder: ((context, index) {
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,11 @@ class AlbumTitle extends ConsumerWidget {
|
||||||
return const (false, false, '');
|
return const (false, false, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (album.ownerId == userId, album.isRemote, album.name);
|
return (
|
||||||
|
album.ownerId == userId,
|
||||||
|
album.isRemote,
|
||||||
|
album.name,
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -35,7 +39,12 @@ class AlbumTitle extends ConsumerWidget {
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 16, right: 8),
|
padding: const EdgeInsets.only(left: 16, right: 8),
|
||||||
child: Text(albumName, style: context.textTheme.headlineMedium),
|
child: Text(
|
||||||
|
albumName,
|
||||||
|
style: context.textTheme.headlineLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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/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_control_button.dart';
|
||||||
import 'package:immich_mobile/pages/album/album_date_range.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_shared_user_icons.dart';
|
||||||
import 'package:immich_mobile/pages/album/album_title.dart';
|
import 'package:immich_mobile/pages/album/album_title.dart';
|
||||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
|
|
@ -36,6 +37,7 @@ class AlbumViewer extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
final titleFocusNode = useFocusNode();
|
final titleFocusNode = useFocusNode();
|
||||||
|
final descriptionFocusNode = useFocusNode();
|
||||||
final userId = ref.watch(authProvider).userId;
|
final userId = ref.watch(authProvider).userId;
|
||||||
final isMultiselecting = ref.watch(multiselectProvider);
|
final isMultiselecting = ref.watch(multiselectProvider);
|
||||||
final isProcessing = useProcessingOverlay();
|
final isProcessing = useProcessingOverlay();
|
||||||
|
|
@ -106,23 +108,44 @@ class AlbumViewer extends HookConsumerWidget {
|
||||||
MultiselectGrid(
|
MultiselectGrid(
|
||||||
key: const ValueKey("albumViewerMultiselectGrid"),
|
key: const ValueKey("albumViewerMultiselectGrid"),
|
||||||
renderListProvider: albumTimelineProvider(album.id),
|
renderListProvider: albumTimelineProvider(album.id),
|
||||||
topWidget: Column(
|
topWidget: Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
decoration: BoxDecoration(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
gradient: LinearGradient(
|
||||||
children: [
|
begin: Alignment.topCenter,
|
||||||
AlbumTitle(
|
end: Alignment.bottomCenter,
|
||||||
key: const ValueKey("albumTitle"),
|
colors: [
|
||||||
titleFocusNode: titleFocusNode,
|
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(),
|
child: Column(
|
||||||
if (album.isRemote)
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
AlbumControlButton(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
key: const ValueKey("albumControlButton"),
|
children: [
|
||||||
onAddPhotosPressed: onAddPhotosPressed,
|
const SizedBox(height: 32),
|
||||||
onAddUsersPressed: onAddUsersPressed,
|
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,
|
onRemoveFromAlbum: onRemoveFromAlbumPressed,
|
||||||
editEnabled: album.ownerId == userId,
|
editEnabled: album.ownerId == userId,
|
||||||
|
|
@ -136,6 +159,7 @@ class AlbumViewer extends HookConsumerWidget {
|
||||||
child: AlbumViewerAppbar(
|
child: AlbumViewerAppbar(
|
||||||
key: const ValueKey("albumViewerAppbar"),
|
key: const ValueKey("albumViewerAppbar"),
|
||||||
titleFocusNode: titleFocusNode,
|
titleFocusNode: titleFocusNode,
|
||||||
|
descriptionFocusNode: descriptionFocusNode,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
onAddPhotos: onAddPhotosPressed,
|
onAddPhotos: onAddPhotosPressed,
|
||||||
onAddUsers: onAddUsersPressed,
|
onAddUsers: onAddUsersPressed,
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,11 @@ 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/models/albums/asset_selection_page_result.model.dart';
|
||||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/album/album_title.provider.dart';
|
import 'package:immich_mobile/providers/album/album_title.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/album/album_viewer.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/widgets/album/album_action_filled_button.dart';
|
import 'package:immich_mobile/widgets/album/album_action_filled_button.dart';
|
||||||
import 'package:immich_mobile/widgets/album/album_title_text_field.dart';
|
import 'package:immich_mobile/widgets/album/album_title_text_field.dart';
|
||||||
|
import 'package:immich_mobile/widgets/album/album_viewer_editable_description.dart';
|
||||||
import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
|
import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
|
|
@ -28,6 +30,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||||
final albumTitleController =
|
final albumTitleController =
|
||||||
useTextEditingController.fromValue(TextEditingValue.empty);
|
useTextEditingController.fromValue(TextEditingValue.empty);
|
||||||
final albumTitleTextFieldFocusNode = useFocusNode();
|
final albumTitleTextFieldFocusNode = useFocusNode();
|
||||||
|
final albumDescriptionTextFieldFocusNode = useFocusNode();
|
||||||
final isAlbumTitleTextFieldFocus = useState(false);
|
final isAlbumTitleTextFieldFocus = useState(false);
|
||||||
final isAlbumTitleEmpty = useState(true);
|
final isAlbumTitleEmpty = useState(true);
|
||||||
final selectedAssets = useState<Set<Asset>>(
|
final selectedAssets = useState<Set<Asset>>(
|
||||||
|
|
@ -36,6 +39,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||||
|
|
||||||
void onBackgroundTapped() {
|
void onBackgroundTapped() {
|
||||||
albumTitleTextFieldFocusNode.unfocus();
|
albumTitleTextFieldFocusNode.unfocus();
|
||||||
|
albumDescriptionTextFieldFocusNode.unfocus();
|
||||||
isAlbumTitleTextFieldFocus.value = false;
|
isAlbumTitleTextFieldFocus.value = false;
|
||||||
|
|
||||||
if (albumTitleController.text.isEmpty) {
|
if (albumTitleController.text.isEmpty) {
|
||||||
|
|
@ -77,6 +81,19 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildDescriptionInputField() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
right: 10,
|
||||||
|
left: 10,
|
||||||
|
),
|
||||||
|
child: AlbumViewerEditableDescription(
|
||||||
|
albumDescription: '',
|
||||||
|
descriptionFocusNode: albumDescriptionTextFieldFocusNode,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
buildTitle() {
|
buildTitle() {
|
||||||
if (selectedAssets.value.isEmpty) {
|
if (selectedAssets.value.isEmpty) {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
|
|
@ -178,18 +195,18 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||||
return const SliverToBoxAdapter();
|
return const SliverToBoxAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
createNonSharedAlbum() async {
|
Future<void> createAlbum() async {
|
||||||
onBackgroundTapped();
|
onBackgroundTapped();
|
||||||
var newAlbum = await ref.watch(albumProvider.notifier).createAlbum(
|
var newAlbum = await ref.watch(albumProvider.notifier).createAlbum(
|
||||||
ref.watch(albumTitleProvider),
|
ref.read(albumTitleProvider),
|
||||||
selectedAssets.value,
|
selectedAssets.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (newAlbum != null) {
|
if (newAlbum != null) {
|
||||||
ref.watch(albumProvider.notifier).refreshRemoteAlbums();
|
ref.read(albumProvider.notifier).refreshRemoteAlbums();
|
||||||
selectedAssets.value = {};
|
selectedAssets.value = {};
|
||||||
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
|
ref.read(albumTitleProvider.notifier).clearAlbumTitle();
|
||||||
|
ref.read(albumViewerProvider.notifier).disableEditAlbum();
|
||||||
context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id));
|
context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -211,9 +228,8 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||||
).tr(),
|
).tr(),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: albumTitleController.text.isNotEmpty
|
onPressed:
|
||||||
? createNonSharedAlbum
|
albumTitleController.text.isNotEmpty ? createAlbum : null,
|
||||||
: null,
|
|
||||||
child: Text(
|
child: Text(
|
||||||
'create'.tr(),
|
'create'.tr(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
@ -237,10 +253,11 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||||
pinned: true,
|
pinned: true,
|
||||||
floating: false,
|
floating: false,
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(96.0),
|
preferredSize: const Size.fromHeight(125.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
buildTitleInputField(),
|
buildTitleInputField(),
|
||||||
|
buildDescriptionInputField(),
|
||||||
if (selectedAssets.value.isNotEmpty) buildControlButton(),
|
if (selectedAssets.value.isNotEmpty) buildControlButton(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,13 @@ import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
|
|
||||||
class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
|
class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
|
||||||
AlbumViewerNotifier(this.ref)
|
AlbumViewerNotifier(this.ref)
|
||||||
: super(AlbumViewerPageState(editTitleText: "", isEditAlbum: false));
|
: super(
|
||||||
|
AlbumViewerPageState(
|
||||||
|
editTitleText: "",
|
||||||
|
isEditAlbum: false,
|
||||||
|
editDescriptionText: "",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final Ref ref;
|
final Ref ref;
|
||||||
|
|
||||||
|
|
@ -21,12 +27,24 @@ class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
|
||||||
state = state.copyWith(editTitleText: newTitle);
|
state = state.copyWith(editTitleText: newTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setEditDescriptionText(String newDescription) {
|
||||||
|
state = state.copyWith(editDescriptionText: newDescription);
|
||||||
|
}
|
||||||
|
|
||||||
void remoteEditTitleText() {
|
void remoteEditTitleText() {
|
||||||
state = state.copyWith(editTitleText: "");
|
state = state.copyWith(editTitleText: "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void remoteEditDescriptionText() {
|
||||||
|
state = state.copyWith(editDescriptionText: "");
|
||||||
|
}
|
||||||
|
|
||||||
void resetState() {
|
void resetState() {
|
||||||
state = state.copyWith(editTitleText: "", isEditAlbum: false);
|
state = state.copyWith(
|
||||||
|
editTitleText: "",
|
||||||
|
isEditAlbum: false,
|
||||||
|
editDescriptionText: "",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> changeAlbumTitle(
|
Future<bool> changeAlbumTitle(
|
||||||
|
|
@ -46,6 +64,28 @@ class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
|
||||||
state = state.copyWith(editTitleText: "", isEditAlbum: false);
|
state = state.copyWith(editTitleText: "", isEditAlbum: false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> changeAlbumDescription(
|
||||||
|
Album album,
|
||||||
|
String newAlbumDescription,
|
||||||
|
) async {
|
||||||
|
AlbumService service = ref.watch(albumServiceProvider);
|
||||||
|
|
||||||
|
bool isSuccess = await service.changeDescriptionAlbum(
|
||||||
|
album,
|
||||||
|
newAlbumDescription,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
state = state.copyWith(editDescriptionText: "", isEditAlbum: false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = state.copyWith(editDescriptionText: "", isEditAlbum: false);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final albumViewerProvider =
|
final albumViewerProvider =
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
|
||||||
String name, {
|
String name, {
|
||||||
required Iterable<String> assetIds,
|
required Iterable<String> assetIds,
|
||||||
Iterable<String> sharedUserIds = const [],
|
Iterable<String> sharedUserIds = const [],
|
||||||
|
String? description,
|
||||||
}) async {
|
}) async {
|
||||||
final users = sharedUserIds.map(
|
final users = sharedUserIds.map(
|
||||||
(id) => AlbumUserCreateDto(userId: id, role: AlbumUserRole.editor),
|
(id) => AlbumUserCreateDto(userId: id, role: AlbumUserRole.editor),
|
||||||
|
|
@ -44,6 +45,7 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
|
||||||
_api.createAlbum(
|
_api.createAlbum(
|
||||||
CreateAlbumDto(
|
CreateAlbumDto(
|
||||||
albumName: name,
|
albumName: name,
|
||||||
|
description: description,
|
||||||
assetIds: assetIds.toList(),
|
assetIds: assetIds.toList(),
|
||||||
albumUsers: users.toList(),
|
albumUsers: users.toList(),
|
||||||
),
|
),
|
||||||
|
|
@ -161,6 +163,7 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
|
||||||
lastModifiedAssetTimestamp: dto.lastModifiedAssetTimestamp,
|
lastModifiedAssetTimestamp: dto.lastModifiedAssetTimestamp,
|
||||||
shared: dto.shared,
|
shared: dto.shared,
|
||||||
startDate: dto.startDate,
|
startDate: dto.startDate,
|
||||||
|
description: dto.description,
|
||||||
endDate: dto.endDate,
|
endDate: dto.endDate,
|
||||||
activityEnabled: dto.isActivityEnabled,
|
activityEnabled: dto.isActivityEnabled,
|
||||||
sortOrder: dto.order == AssetOrder.asc ? SortOrder.asc : SortOrder.desc,
|
sortOrder: dto.order == AssetOrder.asc ? SortOrder.asc : SortOrder.desc,
|
||||||
|
|
@ -174,6 +177,7 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
|
||||||
album.sharedUsers.addAll(users.map(entity.User.fromDto));
|
album.sharedUsers.addAll(users.map(entity.User.fromDto));
|
||||||
final assets = dto.assets.map(Asset.remote).toList();
|
final assets = dto.assets.map(Asset.remote).toList();
|
||||||
album.assets.addAll(assets);
|
album.assets.addAll(assets);
|
||||||
|
|
||||||
return album;
|
return album;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,25 @@ class AlbumService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> changeDescriptionAlbum(
|
||||||
|
Album album,
|
||||||
|
String newAlbumDescription,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final updatedAlbum = await _albumApiRepository.update(
|
||||||
|
album.remoteId!,
|
||||||
|
description: newAlbumDescription,
|
||||||
|
);
|
||||||
|
|
||||||
|
album.description = updatedAlbum.description;
|
||||||
|
await _albumRepository.update(album);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error changeDescriptionAlbum ${e.toString()}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Album?> getAlbumByName(
|
Future<Album?> getAlbumByName(
|
||||||
String name, {
|
String name, {
|
||||||
bool? remote,
|
bool? remote,
|
||||||
|
|
|
||||||
|
|
@ -451,6 +451,7 @@ class SyncService {
|
||||||
final usersToLink = await _userRepository.getByUserIds(userIdsToAdd);
|
final usersToLink = await _userRepository.getByUserIds(userIdsToAdd);
|
||||||
|
|
||||||
album.name = dto.name;
|
album.name = dto.name;
|
||||||
|
album.description = dto.description;
|
||||||
album.shared = dto.shared;
|
album.shared = dto.shared;
|
||||||
album.createdAt = dto.createdAt;
|
album.createdAt = dto.createdAt;
|
||||||
album.modifiedAt = dto.modifiedAt;
|
album.modifiedAt = dto.modifiedAt;
|
||||||
|
|
@ -643,6 +644,7 @@ class SyncService {
|
||||||
toUpdate.isEmpty &&
|
toUpdate.isEmpty &&
|
||||||
toDelete.isEmpty &&
|
toDelete.isEmpty &&
|
||||||
dbAlbum.name == deviceAlbum.name &&
|
dbAlbum.name == deviceAlbum.name &&
|
||||||
|
dbAlbum.description == deviceAlbum.description &&
|
||||||
dbAlbum.modifiedAt.isAtSameMomentAs(deviceAlbum.modifiedAt)) {
|
dbAlbum.modifiedAt.isAtSameMomentAs(deviceAlbum.modifiedAt)) {
|
||||||
// changes only affeted excluded albums
|
// changes only affeted excluded albums
|
||||||
_log.info(
|
_log.info(
|
||||||
|
|
@ -670,6 +672,7 @@ class SyncService {
|
||||||
deleteCandidates.addAll(toDelete);
|
deleteCandidates.addAll(toDelete);
|
||||||
existing.addAll(existingInDb);
|
existing.addAll(existingInDb);
|
||||||
dbAlbum.name = deviceAlbum.name;
|
dbAlbum.name = deviceAlbum.name;
|
||||||
|
dbAlbum.description = deviceAlbum.description;
|
||||||
dbAlbum.modifiedAt = deviceAlbum.modifiedAt;
|
dbAlbum.modifiedAt = deviceAlbum.modifiedAt;
|
||||||
if (dbAlbum.thumbnail.value != null &&
|
if (dbAlbum.thumbnail.value != null &&
|
||||||
toDelete.contains(dbAlbum.thumbnail.value)) {
|
toDelete.contains(dbAlbum.thumbnail.value)) {
|
||||||
|
|
@ -943,6 +946,7 @@ class SyncService {
|
||||||
Album dbAlbum,
|
Album dbAlbum,
|
||||||
) async {
|
) async {
|
||||||
return deviceAlbum.name != dbAlbum.name ||
|
return deviceAlbum.name != dbAlbum.name ||
|
||||||
|
deviceAlbum.description != dbAlbum.description ||
|
||||||
!deviceAlbum.modifiedAt.isAtSameMomentAs(dbAlbum.modifiedAt) ||
|
!deviceAlbum.modifiedAt.isAtSameMomentAs(dbAlbum.modifiedAt) ||
|
||||||
await _albumMediaRepository.getAssetCount(deviceAlbum.localId!) !=
|
await _albumMediaRepository.getAssetCount(deviceAlbum.localId!) !=
|
||||||
(await _eTagRepository.getById(deviceAlbum.eTagKeyAssetCount))
|
(await _eTagRepository.getById(deviceAlbum.eTagKeyAssetCount))
|
||||||
|
|
@ -1101,6 +1105,7 @@ class SyncService {
|
||||||
bool _hasRemoteAlbumChanged(Album remoteAlbum, Album dbAlbum) {
|
bool _hasRemoteAlbumChanged(Album remoteAlbum, Album dbAlbum) {
|
||||||
return remoteAlbum.remoteAssetCount != dbAlbum.assetCount ||
|
return remoteAlbum.remoteAssetCount != dbAlbum.assetCount ||
|
||||||
remoteAlbum.name != dbAlbum.name ||
|
remoteAlbum.name != dbAlbum.name ||
|
||||||
|
remoteAlbum.description != dbAlbum.description ||
|
||||||
remoteAlbum.remoteThumbnailAssetId != dbAlbum.thumbnail.value?.remoteId ||
|
remoteAlbum.remoteThumbnailAssetId != dbAlbum.thumbnail.value?.remoteId ||
|
||||||
remoteAlbum.shared != dbAlbum.shared ||
|
remoteAlbum.shared != dbAlbum.shared ||
|
||||||
remoteAlbum.remoteUsers.length != dbAlbum.sharedUsers.length ||
|
remoteAlbum.remoteUsers.length != dbAlbum.sharedUsers.length ||
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class AlbumActionFilledButton extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(right: 16.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
|
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
|
||||||
|
|
@ -32,9 +32,7 @@ class AlbumActionFilledButton extends StatelessWidget {
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
labelText,
|
labelText,
|
||||||
style: context.textTheme.labelMedium?.copyWith(
|
style: context.textTheme.labelLarge?.copyWith(),
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||||
super.key,
|
super.key,
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.titleFocusNode,
|
required this.titleFocusNode,
|
||||||
|
required this.descriptionFocusNode,
|
||||||
this.onAddPhotos,
|
this.onAddPhotos,
|
||||||
this.onAddUsers,
|
this.onAddUsers,
|
||||||
required this.onActivities,
|
required this.onActivities,
|
||||||
|
|
@ -25,6 +26,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||||
|
|
||||||
final String userId;
|
final String userId;
|
||||||
final FocusNode titleFocusNode;
|
final FocusNode titleFocusNode;
|
||||||
|
final FocusNode descriptionFocusNode;
|
||||||
final void Function()? onAddPhotos;
|
final void Function()? onAddPhotos;
|
||||||
final void Function()? onAddUsers;
|
final void Function()? onAddUsers;
|
||||||
final void Function() onActivities;
|
final void Function() onActivities;
|
||||||
|
|
@ -48,6 +50,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||||
|
|
||||||
final albumViewer = ref.watch(albumViewerProvider);
|
final albumViewer = ref.watch(albumViewerProvider);
|
||||||
final newAlbumTitle = albumViewer.editTitleText;
|
final newAlbumTitle = albumViewer.editTitleText;
|
||||||
|
final newAlbumDescription = albumViewer.editDescriptionText;
|
||||||
final isEditAlbum = albumViewer.isEditAlbum;
|
final isEditAlbum = albumViewer.isEditAlbum;
|
||||||
|
|
||||||
final comments = album.shared
|
final comments = album.shared
|
||||||
|
|
@ -277,20 +280,37 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||||
if (isEditAlbum) {
|
if (isEditAlbum) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
bool isSuccess = await ref
|
if (newAlbumTitle.isNotEmpty) {
|
||||||
.watch(albumViewerProvider.notifier)
|
bool isSuccess = await ref
|
||||||
.changeAlbumTitle(album, newAlbumTitle);
|
.watch(albumViewerProvider.notifier)
|
||||||
|
.changeAlbumTitle(album, newAlbumTitle);
|
||||||
if (!isSuccess) {
|
if (!isSuccess) {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
msg: "album_viewer_appbar_share_err_title".tr(),
|
msg: "album_viewer_appbar_share_err_title".tr(),
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
toastType: ToastType.error,
|
toastType: ToastType.error,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
titleFocusNode.unfocus();
|
||||||
|
} else if (newAlbumDescription.isNotEmpty) {
|
||||||
|
bool isSuccessDescription = await ref
|
||||||
|
.watch(albumViewerProvider.notifier)
|
||||||
|
.changeAlbumDescription(album, newAlbumDescription);
|
||||||
|
if (!isSuccessDescription) {
|
||||||
|
ImmichToast.show(
|
||||||
|
context: context,
|
||||||
|
msg: "album_viewer_appbar_share_err_description".tr(),
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
toastType: ToastType.error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
descriptionFocusNode.unfocus();
|
||||||
|
} else {
|
||||||
|
titleFocusNode.unfocus();
|
||||||
|
descriptionFocusNode.unfocus();
|
||||||
|
ref.read(albumViewerProvider.notifier).disableEditAlbum();
|
||||||
}
|
}
|
||||||
|
|
||||||
titleFocusNode.unfocus();
|
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.check_rounded),
|
icon: const Icon(Icons.check_rounded),
|
||||||
splashRadius: 25,
|
splashRadius: 25,
|
||||||
|
|
|
||||||
102
mobile/lib/widgets/album/album_viewer_editable_description.dart
Normal file
102
mobile/lib/widgets/album/album_viewer_editable_description.dart
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/album/album_viewer.provider.dart';
|
||||||
|
|
||||||
|
class AlbumViewerEditableDescription extends HookConsumerWidget {
|
||||||
|
final String albumDescription;
|
||||||
|
final FocusNode descriptionFocusNode;
|
||||||
|
const AlbumViewerEditableDescription({
|
||||||
|
super.key,
|
||||||
|
required this.albumDescription,
|
||||||
|
required this.descriptionFocusNode,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final albumViewerState = ref.watch(albumViewerProvider);
|
||||||
|
|
||||||
|
final descriptionTextEditController = useTextEditingController(
|
||||||
|
text: albumViewerState.isEditAlbum &&
|
||||||
|
albumViewerState.editDescriptionText.isNotEmpty
|
||||||
|
? albumViewerState.editDescriptionText
|
||||||
|
: albumDescription,
|
||||||
|
);
|
||||||
|
|
||||||
|
void onFocusModeChange() {
|
||||||
|
if (!descriptionFocusNode.hasFocus &&
|
||||||
|
descriptionTextEditController.text.isEmpty) {
|
||||||
|
ref.watch(albumViewerProvider.notifier).setEditDescriptionText("");
|
||||||
|
descriptionTextEditController.text = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() {
|
||||||
|
descriptionFocusNode.addListener(onFocusModeChange);
|
||||||
|
return () {
|
||||||
|
descriptionFocusNode.removeListener(onFocusModeChange);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value.isEmpty) {
|
||||||
|
} else {
|
||||||
|
ref
|
||||||
|
.watch(albumViewerProvider.notifier)
|
||||||
|
.setEditDescriptionText(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
focusNode: descriptionFocusNode,
|
||||||
|
style: context.textTheme.bodyMedium,
|
||||||
|
maxLines: 3,
|
||||||
|
minLines: 1,
|
||||||
|
controller: descriptionTextEditController,
|
||||||
|
onTap: () {
|
||||||
|
context.focusScope.requestFocus(descriptionFocusNode);
|
||||||
|
|
||||||
|
ref
|
||||||
|
.watch(albumViewerProvider.notifier)
|
||||||
|
.setEditDescriptionText(albumDescription);
|
||||||
|
ref.watch(albumViewerProvider.notifier).enableEditAlbum();
|
||||||
|
|
||||||
|
if (descriptionTextEditController.text == '') {
|
||||||
|
descriptionTextEditController.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
contentPadding: const EdgeInsets.all(8),
|
||||||
|
suffixIcon: descriptionFocusNode.hasFocus
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
descriptionTextEditController.clear();
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.cancel_rounded,
|
||||||
|
color: context.primaryColor,
|
||||||
|
),
|
||||||
|
splashRadius: 10,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
enabledBorder: const OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Colors.transparent),
|
||||||
|
),
|
||||||
|
focusedBorder: const OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Colors.transparent),
|
||||||
|
),
|
||||||
|
focusColor: Colors.grey[300],
|
||||||
|
fillColor: context.scaffoldBackgroundColor,
|
||||||
|
filled: descriptionFocusNode.hasFocus,
|
||||||
|
hintText: 'add_a_description'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -52,7 +52,9 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
focusNode: titleFocusNode,
|
focusNode: titleFocusNode,
|
||||||
style: context.textTheme.headlineMedium,
|
style: context.textTheme.headlineLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
controller: titleTextEditController,
|
controller: titleTextEditController,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.focusScope.requestFocus(titleFocusNode);
|
context.focusScope.requestFocus(titleFocusNode);
|
||||||
|
|
@ -65,8 +67,10 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
horizontal: 8,
|
||||||
|
vertical: 0,
|
||||||
|
),
|
||||||
suffixIcon: titleFocusNode.hasFocus
|
suffixIcon: titleFocusNode.hasFocus
|
||||||
? IconButton(
|
? IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue