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
+7 -1
View File
@@ -19,6 +19,7 @@ class Album {
required this.name,
required this.createdAt,
required this.modifiedAt,
this.description,
this.startDate,
this.endDate,
this.lastModifiedAssetTimestamp,
@@ -34,6 +35,7 @@ class Album {
@Index(unique: false, replace: false, type: IndexType.hash)
String? localId;
String name;
String? description;
DateTime createdAt;
DateTime modifiedAt;
DateTime? startDate;
@@ -108,6 +110,7 @@ class Album {
remoteId == other.remoteId &&
localId == other.localId &&
name == other.name &&
description == other.description &&
createdAt.isAtSameMomentAs(other.createdAt) &&
modifiedAt.isAtSameMomentAs(other.modifiedAt) &&
isAtSameMomentAs(startDate, other.startDate) &&
@@ -135,6 +138,7 @@ class Album {
modifiedAt.hashCode ^
startDate.hashCode ^
endDate.hashCode ^
description.hashCode ^
lastModifiedAssetTimestamp.hashCode ^
shared.hashCode ^
activityEnabled.hashCode ^
@@ -150,6 +154,7 @@ class Album {
name: dto.albumName,
createdAt: dto.createdAt,
modifiedAt: dto.updatedAt,
description: dto.description,
lastModifiedAssetTimestamp: dto.lastModifiedAssetTimestamp,
shared: dto.shared,
startDate: dto.startDate,
@@ -184,7 +189,8 @@ class Album {
}
@override
String toString() => name;
String toString() =>
'remoteId: $remoteId name: $name description: $description';
}
extension AssetsHelper on IsarCollection<Album> {
+235 -36
View File
@@ -27,49 +27,54 @@ const AlbumSchema = CollectionSchema(
name: r'createdAt',
type: IsarType.dateTime,
),
r'endDate': PropertySchema(
r'description': PropertySchema(
id: 2,
name: r'description',
type: IsarType.string,
),
r'endDate': PropertySchema(
id: 3,
name: r'endDate',
type: IsarType.dateTime,
),
r'lastModifiedAssetTimestamp': PropertySchema(
id: 3,
id: 4,
name: r'lastModifiedAssetTimestamp',
type: IsarType.dateTime,
),
r'localId': PropertySchema(
id: 4,
id: 5,
name: r'localId',
type: IsarType.string,
),
r'modifiedAt': PropertySchema(
id: 5,
id: 6,
name: r'modifiedAt',
type: IsarType.dateTime,
),
r'name': PropertySchema(
id: 6,
id: 7,
name: r'name',
type: IsarType.string,
),
r'remoteId': PropertySchema(
id: 7,
id: 8,
name: r'remoteId',
type: IsarType.string,
),
r'shared': PropertySchema(
id: 8,
id: 9,
name: r'shared',
type: IsarType.bool,
),
r'sortOrder': PropertySchema(
id: 9,
id: 10,
name: r'sortOrder',
type: IsarType.byte,
enumMap: _AlbumsortOrderEnumValueMap,
),
r'startDate': PropertySchema(
id: 10,
id: 11,
name: r'startDate',
type: IsarType.dateTime,
)
@@ -146,6 +151,12 @@ int _albumEstimateSize(
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
{
final value = object.description;
if (value != null) {
bytesCount += 3 + value.length * 3;
}
}
{
final value = object.localId;
if (value != null) {
@@ -170,15 +181,16 @@ void _albumSerialize(
) {
writer.writeBool(offsets[0], object.activityEnabled);
writer.writeDateTime(offsets[1], object.createdAt);
writer.writeDateTime(offsets[2], object.endDate);
writer.writeDateTime(offsets[3], object.lastModifiedAssetTimestamp);
writer.writeString(offsets[4], object.localId);
writer.writeDateTime(offsets[5], object.modifiedAt);
writer.writeString(offsets[6], object.name);
writer.writeString(offsets[7], object.remoteId);
writer.writeBool(offsets[8], object.shared);
writer.writeByte(offsets[9], object.sortOrder.index);
writer.writeDateTime(offsets[10], object.startDate);
writer.writeString(offsets[2], object.description);
writer.writeDateTime(offsets[3], object.endDate);
writer.writeDateTime(offsets[4], object.lastModifiedAssetTimestamp);
writer.writeString(offsets[5], object.localId);
writer.writeDateTime(offsets[6], object.modifiedAt);
writer.writeString(offsets[7], object.name);
writer.writeString(offsets[8], object.remoteId);
writer.writeBool(offsets[9], object.shared);
writer.writeByte(offsets[10], object.sortOrder.index);
writer.writeDateTime(offsets[11], object.startDate);
}
Album _albumDeserialize(
@@ -190,16 +202,18 @@ Album _albumDeserialize(
final object = Album(
activityEnabled: reader.readBool(offsets[0]),
createdAt: reader.readDateTime(offsets[1]),
endDate: reader.readDateTimeOrNull(offsets[2]),
lastModifiedAssetTimestamp: reader.readDateTimeOrNull(offsets[3]),
localId: reader.readStringOrNull(offsets[4]),
modifiedAt: reader.readDateTime(offsets[5]),
name: reader.readString(offsets[6]),
remoteId: reader.readStringOrNull(offsets[7]),
shared: reader.readBool(offsets[8]),
sortOrder: _AlbumsortOrderValueEnumMap[reader.readByteOrNull(offsets[9])] ??
SortOrder.desc,
startDate: reader.readDateTimeOrNull(offsets[10]),
description: reader.readStringOrNull(offsets[2]),
endDate: reader.readDateTimeOrNull(offsets[3]),
lastModifiedAssetTimestamp: reader.readDateTimeOrNull(offsets[4]),
localId: reader.readStringOrNull(offsets[5]),
modifiedAt: reader.readDateTime(offsets[6]),
name: reader.readString(offsets[7]),
remoteId: reader.readStringOrNull(offsets[8]),
shared: reader.readBool(offsets[9]),
sortOrder:
_AlbumsortOrderValueEnumMap[reader.readByteOrNull(offsets[10])] ??
SortOrder.desc,
startDate: reader.readDateTimeOrNull(offsets[11]),
);
object.id = id;
return object;
@@ -217,23 +231,25 @@ P _albumDeserializeProp<P>(
case 1:
return (reader.readDateTime(offset)) as P;
case 2:
return (reader.readDateTimeOrNull(offset)) as P;
return (reader.readStringOrNull(offset)) as P;
case 3:
return (reader.readDateTimeOrNull(offset)) as P;
case 4:
return (reader.readStringOrNull(offset)) as P;
return (reader.readDateTimeOrNull(offset)) as P;
case 5:
return (reader.readDateTime(offset)) as P;
case 6:
return (reader.readString(offset)) as P;
case 7:
return (reader.readStringOrNull(offset)) as P;
case 6:
return (reader.readDateTime(offset)) as P;
case 7:
return (reader.readString(offset)) as P;
case 8:
return (reader.readBool(offset)) as P;
return (reader.readStringOrNull(offset)) as P;
case 9:
return (reader.readBool(offset)) as P;
case 10:
return (_AlbumsortOrderValueEnumMap[reader.readByteOrNull(offset)] ??
SortOrder.desc) as P;
case 10:
case 11:
return (reader.readDateTimeOrNull(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
@@ -535,6 +551,152 @@ extension AlbumQueryFilter on QueryBuilder<Album, Album, QFilterCondition> {
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'description',
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'description',
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionEqualTo(
String? value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'description',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionGreaterThan(
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'description',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionLessThan(
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'description',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionBetween(
String? lower,
String? upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'description',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'description',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'description',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionContains(
String value,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'description',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionMatches(
String pattern,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'description',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'description',
value: '',
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> descriptionIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'description',
value: '',
));
});
}
QueryBuilder<Album, Album, QAfterFilterCondition> endDateIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
@@ -1502,6 +1664,18 @@ extension AlbumQuerySortBy on QueryBuilder<Album, Album, QSortBy> {
});
}
QueryBuilder<Album, Album, QAfterSortBy> sortByDescription() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'description', Sort.asc);
});
}
QueryBuilder<Album, Album, QAfterSortBy> sortByDescriptionDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'description', Sort.desc);
});
}
QueryBuilder<Album, Album, QAfterSortBy> sortByEndDate() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'endDate', Sort.asc);
@@ -1637,6 +1811,18 @@ extension AlbumQuerySortThenBy on QueryBuilder<Album, Album, QSortThenBy> {
});
}
QueryBuilder<Album, Album, QAfterSortBy> thenByDescription() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'description', Sort.asc);
});
}
QueryBuilder<Album, Album, QAfterSortBy> thenByDescriptionDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'description', Sort.desc);
});
}
QueryBuilder<Album, Album, QAfterSortBy> thenByEndDate() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'endDate', Sort.asc);
@@ -1772,6 +1958,13 @@ extension AlbumQueryWhereDistinct on QueryBuilder<Album, Album, QDistinct> {
});
}
QueryBuilder<Album, Album, QDistinct> distinctByDescription(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'description', caseSensitive: caseSensitive);
});
}
QueryBuilder<Album, Album, QDistinct> distinctByEndDate() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'endDate');
@@ -1849,6 +2042,12 @@ extension AlbumQueryProperty on QueryBuilder<Album, Album, QQueryProperty> {
});
}
QueryBuilder<Album, String?, QQueryOperations> descriptionProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'description');
});
}
QueryBuilder<Album, DateTime?, QQueryOperations> endDateProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'endDate');