diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 0fd20a79bd..e83e1e6677 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -173,6 +173,8 @@ "library_page_sharing": "Sharing", "library_page_sort_created": "Most recently created", "library_page_sort_title": "Album title", + "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Last modified", "login_disabled": "Login has been disabled", "login_form_api_exception": "API exception. Please check the server URL and try again.", "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", diff --git a/mobile/lib/modules/album/views/library_page.dart b/mobile/lib/modules/album/views/library_page.dart index c22129a501..0eb44b82c5 100644 --- a/mobile/lib/modules/album/views/library_page.dart +++ b/mobile/lib/modules/album/views/library_page.dart @@ -47,6 +47,7 @@ class LibraryPage extends HookConsumerWidget { useState(settings.getSetting(AppSettingsEnum.selectedAlbumSortOrder)); List sortedAlbums() { + // Created. if (selectedAlbumSortOrder.value == 0) { return albums .where((a) => a.isRemote) @@ -54,6 +55,34 @@ class LibraryPage extends HookConsumerWidget { .reversed .toList(); } + // Album title. + if (selectedAlbumSortOrder.value == 1) { + return albums.where((a) => a.isRemote).sortedBy((album) => album.name); + } + // Most recent photo, if unset (e.g. empty album, use modifiedAt / updatedAt). + if (selectedAlbumSortOrder.value == 2) { + return albums + .where((a) => a.isRemote) + .sorted( + (a, b) => a.lastModifiedAssetTimestamp != null && + b.lastModifiedAssetTimestamp != null + ? a.lastModifiedAssetTimestamp! + .compareTo(b.lastModifiedAssetTimestamp!) + : a.modifiedAt.compareTo(b.modifiedAt), + ) + .reversed + .toList(); + } + // Last modified. + if (selectedAlbumSortOrder.value == 3) { + return albums + .where((a) => a.isRemote) + .sortedBy((album) => album.modifiedAt) + .reversed + .toList(); + } + + // Fallback: Album title. return albums.where((a) => a.isRemote).sortedBy((album) => album.name); } @@ -61,6 +90,8 @@ class LibraryPage extends HookConsumerWidget { final options = [ "library_page_sort_created".tr(), "library_page_sort_title".tr(), + "library_page_sort_most_recent_photo".tr(), + "library_page_sort_last_modified".tr(), ]; return PopupMenuButton( diff --git a/mobile/lib/shared/models/album.dart b/mobile/lib/shared/models/album.dart index 486647ad1b..94afe1d76d 100644 --- a/mobile/lib/shared/models/album.dart +++ b/mobile/lib/shared/models/album.dart @@ -18,6 +18,7 @@ class Album { required this.name, required this.createdAt, required this.modifiedAt, + this.lastModifiedAssetTimestamp, required this.shared, }); @@ -29,6 +30,7 @@ class Album { String name; DateTime createdAt; DateTime modifiedAt; + DateTime? lastModifiedAssetTimestamp; bool shared; final IsarLink owner = IsarLink(); final IsarLink thumbnail = IsarLink(); @@ -83,12 +85,21 @@ class Album { @override bool operator ==(other) { if (other is! Album) return false; + + final lastModifiedAssetTimestampIsSetAndEqual = + lastModifiedAssetTimestamp != null && + other.lastModifiedAssetTimestamp != null + ? lastModifiedAssetTimestamp! + .isAtSameMomentAs(other.lastModifiedAssetTimestamp!) + : true; + return id == other.id && remoteId == other.remoteId && localId == other.localId && name == other.name && createdAt.isAtSameMomentAs(other.createdAt) && modifiedAt.isAtSameMomentAs(other.modifiedAt) && + lastModifiedAssetTimestampIsSetAndEqual && shared == other.shared && owner.value == other.owner.value && thumbnail.value == other.thumbnail.value && @@ -105,6 +116,7 @@ class Album { name.hashCode ^ createdAt.hashCode ^ modifiedAt.hashCode ^ + lastModifiedAssetTimestamp.hashCode ^ shared.hashCode ^ owner.value.hashCode ^ thumbnail.value.hashCode ^ @@ -130,6 +142,7 @@ class Album { name: dto.albumName, createdAt: dto.createdAt, modifiedAt: dto.updatedAt, + lastModifiedAssetTimestamp: dto.lastModifiedAssetTimestamp, shared: dto.shared, ); a.owner.value = await db.users.getById(dto.ownerId); diff --git a/mobile/lib/shared/models/album.g.dart b/mobile/lib/shared/models/album.g.dart index 8d24155657..63f71f380f 100644 Binary files a/mobile/lib/shared/models/album.g.dart and b/mobile/lib/shared/models/album.g.dart differ diff --git a/mobile/lib/shared/services/sync.service.dart b/mobile/lib/shared/services/sync.service.dart index a11bb8eefc..d12a68ffde 100644 --- a/mobile/lib/shared/services/sync.service.dart +++ b/mobile/lib/shared/services/sync.service.dart @@ -282,6 +282,9 @@ class SyncService { if (!_hasAlbumResponseDtoChanged(dto, album)) { return false; } + // loadDetails (/api/album/:id) will not include lastModifiedAssetTimestamp, + // i.e. it will always be null. Save it here. + final originalDto = dto; dto = await loadDetails(dto); if (dto.assetCount != dto.assets.length) { return false; @@ -321,6 +324,7 @@ class SyncService { album.name = dto.albumName; album.shared = dto.shared; album.modifiedAt = dto.updatedAt; + album.lastModifiedAssetTimestamp = originalDto.lastModifiedAssetTimestamp; if (album.thumbnail.value?.remoteId != dto.albumThumbnailAssetId) { album.thumbnail.value = await _db.assets .where() @@ -808,5 +812,13 @@ bool _hasAlbumResponseDtoChanged(AlbumResponseDto dto, Album a) { dto.albumThumbnailAssetId != a.thumbnail.value?.remoteId || dto.shared != a.shared || dto.sharedUsers.length != a.sharedUsers.length || - !dto.updatedAt.isAtSameMomentAs(a.modifiedAt); + !dto.updatedAt.isAtSameMomentAs(a.modifiedAt) || + (dto.lastModifiedAssetTimestamp == null && + a.lastModifiedAssetTimestamp != null) || + (dto.lastModifiedAssetTimestamp != null && + a.lastModifiedAssetTimestamp == null) || + (dto.lastModifiedAssetTimestamp != null && + a.lastModifiedAssetTimestamp != null && + !dto.lastModifiedAssetTimestamp! + .isAtSameMomentAs(a.lastModifiedAssetTimestamp!)); }