mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 20:25:32 -04:00
feat(mobile): unify partner assets on timeline (#4974)
* feat(mobile): unify partner assets on timeline * skip non-owned assets in bulk actions * add message when trying to delete partner assets
This commit is contained in:
parent
0c482960ce
commit
9fa9ad05b1
@ -175,6 +175,10 @@
|
|||||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||||
|
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||||
|
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||||
|
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||||
|
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||||
"image_viewer_page_state_provider_share_error": "Share Error",
|
"image_viewer_page_state_provider_share_error": "Share Error",
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
|
||||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/utils/renderlist_generator.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
final archiveProvider = StreamProvider<RenderList>((ref) async* {
|
final archiveProvider = StreamProvider<RenderList>((ref) {
|
||||||
final user = ref.watch(currentUserProvider);
|
final user = ref.watch(currentUserProvider);
|
||||||
if (user == null) return;
|
if (user == null) return const Stream.empty();
|
||||||
final query = ref
|
final query = ref
|
||||||
.watch(dbProvider)
|
.watch(dbProvider)
|
||||||
.assets
|
.assets
|
||||||
@ -19,11 +18,5 @@ final archiveProvider = StreamProvider<RenderList>((ref) async* {
|
|||||||
.isArchivedEqualTo(true)
|
.isArchivedEqualTo(true)
|
||||||
.isTrashedEqualTo(false)
|
.isTrashedEqualTo(false)
|
||||||
.sortByFileCreatedAt();
|
.sortByFileCreatedAt();
|
||||||
final settings = ref.watch(appSettingsServiceProvider);
|
return renderListGenerator(query, ref);
|
||||||
final groupBy =
|
|
||||||
GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)];
|
|
||||||
yield await RenderList.fromQuery(query, groupBy);
|
|
||||||
await for (final _ in query.watchLazy()) {
|
|
||||||
yield await RenderList.fromQuery(query, groupBy);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@ import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structu
|
|||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/utils/renderlist_generator.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
final renderListProvider =
|
final renderListProvider =
|
||||||
@ -17,16 +18,6 @@ final renderListProvider =
|
|||||||
|
|
||||||
final renderListQueryProvider = StreamProvider.family<RenderList,
|
final renderListQueryProvider = StreamProvider.family<RenderList,
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy>?>(
|
QueryBuilder<Asset, Asset, QAfterSortBy>?>(
|
||||||
(ref, query) async* {
|
(ref, query) =>
|
||||||
if (query == null) {
|
query == null ? const Stream.empty() : renderListGenerator(query, ref),
|
||||||
return;
|
|
||||||
}
|
|
||||||
final settings = ref.watch(appSettingsServiceProvider);
|
|
||||||
final groupBy = GroupAssetsBy
|
|
||||||
.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)];
|
|
||||||
yield await RenderList.fromQuery(query, groupBy);
|
|
||||||
await for (final _ in query.watchLazy()) {
|
|
||||||
yield await RenderList.fromQuery(query, groupBy);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
|
||||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/utils/renderlist_generator.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
final favoriteAssetsProvider = StreamProvider<RenderList>((ref) async* {
|
final favoriteAssetsProvider = StreamProvider<RenderList>((ref) {
|
||||||
final user = ref.watch(currentUserProvider);
|
final user = ref.watch(currentUserProvider);
|
||||||
if (user == null) return;
|
if (user == null) return const Stream.empty();
|
||||||
final query = ref
|
final query = ref
|
||||||
.watch(dbProvider)
|
.watch(dbProvider)
|
||||||
.assets
|
.assets
|
||||||
@ -19,11 +18,5 @@ final favoriteAssetsProvider = StreamProvider<RenderList>((ref) async* {
|
|||||||
.isFavoriteEqualTo(true)
|
.isFavoriteEqualTo(true)
|
||||||
.isTrashedEqualTo(false)
|
.isTrashedEqualTo(false)
|
||||||
.sortByFileCreatedAt();
|
.sortByFileCreatedAt();
|
||||||
final settings = ref.watch(appSettingsServiceProvider);
|
return renderListGenerator(query, ref);
|
||||||
final groupBy =
|
|
||||||
GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)];
|
|
||||||
yield await RenderList.fromQuery(query, groupBy);
|
|
||||||
await for (final _ in query.watchLazy()) {
|
|
||||||
yield await RenderList.fromQuery(query, groupBy);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
@ -44,6 +44,7 @@ class HomePage extends HookConsumerWidget {
|
|||||||
final sharedAlbums = ref.watch(sharedAlbumProvider);
|
final sharedAlbums = ref.watch(sharedAlbumProvider);
|
||||||
final albumService = ref.watch(albumServiceProvider);
|
final albumService = ref.watch(albumServiceProvider);
|
||||||
final currentUser = ref.watch(currentUserProvider);
|
final currentUser = ref.watch(currentUserProvider);
|
||||||
|
final timelineUsers = ref.watch(timelineUsersIdsProvider);
|
||||||
final trashEnabled =
|
final trashEnabled =
|
||||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||||
|
|
||||||
@ -55,6 +56,7 @@ class HomePage extends HookConsumerWidget {
|
|||||||
() {
|
() {
|
||||||
ref.read(websocketProvider.notifier).connect();
|
ref.read(websocketProvider.notifier).connect();
|
||||||
Future(() => ref.read(assetProvider.notifier).getAllAsset());
|
Future(() => ref.read(assetProvider.notifier).getAllAsset());
|
||||||
|
ref.read(assetProvider.notifier).getPartnerAssets();
|
||||||
ref.read(albumProvider.notifier).getAllAlbums();
|
ref.read(albumProvider.notifier).getAllAlbums();
|
||||||
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
||||||
ref.read(serverInfoProvider.notifier).getServerInfo();
|
ref.read(serverInfoProvider.notifier).getServerInfo();
|
||||||
@ -84,20 +86,49 @@ class HomePage extends HookConsumerWidget {
|
|||||||
SelectionAssetState.fromSelection(selectedAssets);
|
SelectionAssetState.fromSelection(selectedAssets);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Asset> remoteOnlySelection({String? localErrorMessage}) {
|
errorBuilder(String? msg) => msg != null && msg.isNotEmpty
|
||||||
final Set<Asset> assets = selection.value;
|
? () => ImmichToast.show(
|
||||||
|
context: context,
|
||||||
|
msg: msg,
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
Iterable<Asset> remoteOnly(
|
||||||
|
Iterable<Asset> assets, {
|
||||||
|
void Function()? errorCallback,
|
||||||
|
}) {
|
||||||
final bool onlyRemote = assets.every((e) => e.isRemote);
|
final bool onlyRemote = assets.every((e) => e.isRemote);
|
||||||
if (!onlyRemote) {
|
if (!onlyRemote) {
|
||||||
if (localErrorMessage != null && localErrorMessage.isNotEmpty) {
|
if (errorCallback != null) errorCallback();
|
||||||
ImmichToast.show(
|
return assets.where((a) => a.isRemote);
|
||||||
context: context,
|
|
||||||
msg: localErrorMessage,
|
|
||||||
gravity: ToastGravity.BOTTOM,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return assets.where((a) => a.isRemote).toList();
|
|
||||||
}
|
}
|
||||||
return assets.toList();
|
return assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<Asset> ownedOnly(
|
||||||
|
Iterable<Asset> assets, {
|
||||||
|
void Function()? errorCallback,
|
||||||
|
}) {
|
||||||
|
if (currentUser == null) return [];
|
||||||
|
final userId = currentUser.isarId;
|
||||||
|
final bool onlyOwned = assets.every((e) => e.ownerId == userId);
|
||||||
|
if (!onlyOwned) {
|
||||||
|
if (errorCallback != null) errorCallback();
|
||||||
|
return assets.where((a) => a.ownerId == userId);
|
||||||
|
}
|
||||||
|
return assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<Asset> ownedRemoteSelection({
|
||||||
|
String? localErrorMessage,
|
||||||
|
String? ownerErrorMessage,
|
||||||
|
}) {
|
||||||
|
final assets = selection.value;
|
||||||
|
return remoteOnly(
|
||||||
|
ownedOnly(assets, errorCallback: errorBuilder(ownerErrorMessage)),
|
||||||
|
errorCallback: errorBuilder(localErrorMessage),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onShareAssets(bool shareLocal) {
|
void onShareAssets(bool shareLocal) {
|
||||||
@ -105,7 +136,7 @@ class HomePage extends HookConsumerWidget {
|
|||||||
if (shareLocal) {
|
if (shareLocal) {
|
||||||
handleShareAssets(ref, context, selection.value.toList());
|
handleShareAssets(ref, context, selection.value.toList());
|
||||||
} else {
|
} else {
|
||||||
final ids = remoteOnlySelection().map((e) => e.remoteId!);
|
final ids = ownedRemoteSelection().map((e) => e.remoteId!);
|
||||||
context.autoPush(SharedLinkEditRoute(assetsList: ids.toList()));
|
context.autoPush(SharedLinkEditRoute(assetsList: ids.toList()));
|
||||||
}
|
}
|
||||||
processing.value = false;
|
processing.value = false;
|
||||||
@ -115,11 +146,12 @@ class HomePage extends HookConsumerWidget {
|
|||||||
void onFavoriteAssets() async {
|
void onFavoriteAssets() async {
|
||||||
processing.value = true;
|
processing.value = true;
|
||||||
try {
|
try {
|
||||||
final remoteAssets = remoteOnlySelection(
|
final remoteAssets = ownedRemoteSelection(
|
||||||
localErrorMessage: 'home_page_favorite_err_local'.tr(),
|
localErrorMessage: 'home_page_favorite_err_local'.tr(),
|
||||||
|
ownerErrorMessage: 'home_page_favorite_err_partner'.tr(),
|
||||||
);
|
);
|
||||||
if (remoteAssets.isNotEmpty) {
|
if (remoteAssets.isNotEmpty) {
|
||||||
await handleFavoriteAssets(ref, context, remoteAssets);
|
await handleFavoriteAssets(ref, context, remoteAssets.toList());
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
processing.value = false;
|
processing.value = false;
|
||||||
@ -130,10 +162,11 @@ class HomePage extends HookConsumerWidget {
|
|||||||
void onArchiveAsset() async {
|
void onArchiveAsset() async {
|
||||||
processing.value = true;
|
processing.value = true;
|
||||||
try {
|
try {
|
||||||
final remoteAssets = remoteOnlySelection(
|
final remoteAssets = ownedRemoteSelection(
|
||||||
localErrorMessage: 'home_page_archive_err_local'.tr(),
|
localErrorMessage: 'home_page_archive_err_local'.tr(),
|
||||||
|
ownerErrorMessage: 'home_page_archive_err_partner'.tr(),
|
||||||
);
|
);
|
||||||
await handleArchiveAssets(ref, context, remoteAssets);
|
await handleArchiveAssets(ref, context, remoteAssets.toList());
|
||||||
} finally {
|
} finally {
|
||||||
processing.value = false;
|
processing.value = false;
|
||||||
selectionEnabledHook.value = false;
|
selectionEnabledHook.value = false;
|
||||||
@ -143,12 +176,16 @@ class HomePage extends HookConsumerWidget {
|
|||||||
void onDelete() async {
|
void onDelete() async {
|
||||||
processing.value = true;
|
processing.value = true;
|
||||||
try {
|
try {
|
||||||
|
final toDelete = ownedOnly(
|
||||||
|
selection.value,
|
||||||
|
errorCallback: errorBuilder('home_page_delete_err_partner'.tr()),
|
||||||
|
).toList();
|
||||||
await ref
|
await ref
|
||||||
.read(assetProvider.notifier)
|
.read(assetProvider.notifier)
|
||||||
.deleteAssets(selection.value, force: !trashEnabled);
|
.deleteAssets(toDelete, force: !trashEnabled);
|
||||||
|
|
||||||
final hasRemote = selection.value.any((a) => a.isRemote);
|
final hasRemote = toDelete.any((a) => a.isRemote);
|
||||||
final assetOrAssets = selection.value.length > 1 ? 'assets' : 'asset';
|
final assetOrAssets = toDelete.length > 1 ? 'assets' : 'asset';
|
||||||
final trashOrRemoved =
|
final trashOrRemoved =
|
||||||
!trashEnabled ? 'deleted permanently' : 'trashed';
|
!trashEnabled ? 'deleted permanently' : 'trashed';
|
||||||
if (hasRemote) {
|
if (hasRemote) {
|
||||||
@ -180,8 +217,9 @@ class HomePage extends HookConsumerWidget {
|
|||||||
void onAddToAlbum(Album album) async {
|
void onAddToAlbum(Album album) async {
|
||||||
processing.value = true;
|
processing.value = true;
|
||||||
try {
|
try {
|
||||||
final Iterable<Asset> assets = remoteOnlySelection(
|
final Iterable<Asset> assets = ownedRemoteSelection(
|
||||||
localErrorMessage: "home_page_add_to_album_err_local".tr(),
|
localErrorMessage: "home_page_add_to_album_err_local".tr(),
|
||||||
|
ownerErrorMessage: "home_page_album_err_partner".tr(),
|
||||||
);
|
);
|
||||||
if (assets.isEmpty) {
|
if (assets.isEmpty) {
|
||||||
return;
|
return;
|
||||||
@ -228,8 +266,9 @@ class HomePage extends HookConsumerWidget {
|
|||||||
void onCreateNewAlbum() async {
|
void onCreateNewAlbum() async {
|
||||||
processing.value = true;
|
processing.value = true;
|
||||||
try {
|
try {
|
||||||
final Iterable<Asset> assets = remoteOnlySelection(
|
final Iterable<Asset> assets = ownedRemoteSelection(
|
||||||
localErrorMessage: "home_page_add_to_album_err_local".tr(),
|
localErrorMessage: "home_page_add_to_album_err_local".tr(),
|
||||||
|
ownerErrorMessage: "home_page_album_err_partner".tr(),
|
||||||
);
|
);
|
||||||
if (assets.isEmpty) {
|
if (assets.isEmpty) {
|
||||||
return;
|
return;
|
||||||
@ -270,6 +309,9 @@ class HomePage extends HookConsumerWidget {
|
|||||||
Future<void> refreshAssets() async {
|
Future<void> refreshAssets() async {
|
||||||
final fullRefresh = refreshCount.value > 0;
|
final fullRefresh = refreshCount.value > 0;
|
||||||
await ref.read(assetProvider.notifier).getAllAsset(clear: fullRefresh);
|
await ref.read(assetProvider.notifier).getAllAsset(clear: fullRefresh);
|
||||||
|
if (timelineUsers.length > 1) {
|
||||||
|
await ref.read(assetProvider.notifier).getPartnerAssets();
|
||||||
|
}
|
||||||
if (fullRefresh) {
|
if (fullRefresh) {
|
||||||
// refresh was forced: user requested another refresh within 2 seconds
|
// refresh was forced: user requested another refresh within 2 seconds
|
||||||
refreshCount.value = 0;
|
refreshCount.value = 0;
|
||||||
@ -330,7 +372,13 @@ class HomePage extends HookConsumerWidget {
|
|||||||
bottom: false,
|
bottom: false,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
ref.watch(assetsProvider(currentUser?.isarId)).when(
|
ref
|
||||||
|
.watch(
|
||||||
|
timelineUsers.length > 1
|
||||||
|
? multiUserAssetsProvider(timelineUsers)
|
||||||
|
: assetsProvider(currentUser?.isarId),
|
||||||
|
)
|
||||||
|
.when(
|
||||||
data: (data) => data.isEmpty
|
data: (data) => data.isEmpty
|
||||||
? buildLoadingIndicator()
|
? buildLoadingIndicator()
|
||||||
: ImmichAssetGrid(
|
: ImmichAssetGrid(
|
||||||
@ -338,11 +386,10 @@ class HomePage extends HookConsumerWidget {
|
|||||||
listener: selectionListener,
|
listener: selectionListener,
|
||||||
selectionActive: selectionEnabledHook.value,
|
selectionActive: selectionEnabledHook.value,
|
||||||
onRefresh: refreshAssets,
|
onRefresh: refreshAssets,
|
||||||
topWidget: (currentUser != null &&
|
topWidget:
|
||||||
currentUser.memoryEnabled != null &&
|
(currentUser != null && currentUser.memoryEnabled)
|
||||||
currentUser.memoryEnabled!)
|
? const MemoryLane()
|
||||||
? const MemoryLane()
|
: const SizedBox(),
|
||||||
: const SizedBox(),
|
|
||||||
showStack: true,
|
showStack: true,
|
||||||
),
|
),
|
||||||
error: (error, _) => Center(child: Text(error.toString())),
|
error: (error, _) => Center(child: Text(error.toString())),
|
||||||
|
@ -2,21 +2,31 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart';
|
import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/partner/services/partner.service.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
class PartnerSharedWithNotifier extends StateNotifier<List<User>> {
|
class PartnerSharedWithNotifier extends StateNotifier<List<User>> {
|
||||||
PartnerSharedWithNotifier(Isar db) : super([]) {
|
PartnerSharedWithNotifier(Isar db, this._ps) : super([]) {
|
||||||
final query = db.users.filter().isPartnerSharedWithEqualTo(true);
|
final query = db.users.filter().isPartnerSharedWithEqualTo(true);
|
||||||
query.findAll().then((partners) => state = partners);
|
query.findAll().then((partners) => state = partners);
|
||||||
query.watch().listen((partners) => state = partners);
|
query.watch().listen((partners) => state = partners);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> updatePartner(User partner, {required bool inTimeline}) {
|
||||||
|
return _ps.updatePartner(partner, inTimeline: inTimeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
final PartnerService _ps;
|
||||||
}
|
}
|
||||||
|
|
||||||
final partnerSharedWithProvider =
|
final partnerSharedWithProvider =
|
||||||
StateNotifierProvider<PartnerSharedWithNotifier, List<User>>((ref) {
|
StateNotifierProvider<PartnerSharedWithNotifier, List<User>>((ref) {
|
||||||
return PartnerSharedWithNotifier(ref.watch(dbProvider));
|
return PartnerSharedWithNotifier(
|
||||||
|
ref.watch(dbProvider),
|
||||||
|
ref.watch(partnerServiceProvider),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
class PartnerSharedByNotifier extends StateNotifier<List<User>> {
|
class PartnerSharedByNotifier extends StateNotifier<List<User>> {
|
||||||
|
@ -5,6 +5,7 @@ import 'package:immich_mobile/shared/providers/db.provider.dart';
|
|||||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
final partnerServiceProvider = Provider(
|
final partnerServiceProvider = Provider(
|
||||||
(ref) => PartnerService(
|
(ref) => PartnerService(
|
||||||
@ -69,4 +70,19 @@ class PartnerService {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> updatePartner(User partner, {required bool inTimeline}) async {
|
||||||
|
try {
|
||||||
|
final dto = await _apiService.partnerApi
|
||||||
|
.updatePartner(partner.id, UpdatePartnerDto(inTimeline: inTimeline));
|
||||||
|
if (dto != null) {
|
||||||
|
partner.inTimeline = dto.inTimeline ?? partner.inTimeline;
|
||||||
|
await _db.writeTxn(() => _db.users.put(partner));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_log.warning("failed to update partner ${partner.id}:\n$e");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
||||||
|
import 'package:immich_mobile/modules/partner/providers/partner.provider.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||||
|
|
||||||
class PartnerDetailPage extends HookConsumerWidget {
|
class PartnerDetailPage extends HookConsumerWidget {
|
||||||
const PartnerDetailPage({Key? key, required this.partner}) : super(key: key);
|
const PartnerDetailPage({Key? key, required this.partner}) : super(key: key);
|
||||||
@ -14,6 +16,8 @@ class PartnerDetailPage extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final assets = ref.watch(assetsProvider(partner.isarId));
|
final assets = ref.watch(assetsProvider(partner.isarId));
|
||||||
|
final inTimeline = useState(partner.inTimeline);
|
||||||
|
bool toggleInProcess = false;
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
@ -23,11 +27,49 @@ class PartnerDetailPage extends HookConsumerWidget {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void toggleInTimeline() async {
|
||||||
|
if (toggleInProcess) return;
|
||||||
|
toggleInProcess = true;
|
||||||
|
try {
|
||||||
|
final ok = await ref
|
||||||
|
.read(partnerSharedWithProvider.notifier)
|
||||||
|
.updatePartner(partner, inTimeline: !inTimeline.value);
|
||||||
|
if (ok) {
|
||||||
|
inTimeline.value = !inTimeline.value;
|
||||||
|
final action = inTimeline.value ? "shown on" : "hidden from";
|
||||||
|
ImmichToast.show(
|
||||||
|
context: context,
|
||||||
|
toastType: ToastType.success,
|
||||||
|
durationInSecond: 1,
|
||||||
|
msg: "${partner.name}'s assets $action your timeline",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ImmichToast.show(
|
||||||
|
context: context,
|
||||||
|
toastType: ToastType.error,
|
||||||
|
durationInSecond: 1,
|
||||||
|
msg: "Failed to toggle the timeline setting",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
toggleInProcess = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(partner.name),
|
title: Text(partner.name),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: toggleInTimeline,
|
||||||
|
icon: Icon(
|
||||||
|
inTimeline.value ? Icons.collections : Icons.collections_outlined,
|
||||||
|
),
|
||||||
|
tooltip: "Show/hide photos on your main timeline",
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: assets.when(
|
body: assets.when(
|
||||||
data: (renderList) => renderList.isEmpty
|
data: (renderList) => renderList.isEmpty
|
||||||
|
@ -5,6 +5,7 @@ import 'package:immich_mobile/shared/models/asset.dart';
|
|||||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/sync.service.dart';
|
import 'package:immich_mobile/shared/services/sync.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/renderlist_generator.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
@ -107,9 +108,9 @@ final trashProvider = StateNotifierProvider<TrashNotifier, bool>((ref) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
final trashedAssetsProvider = StreamProvider<RenderList>((ref) async* {
|
final trashedAssetsProvider = StreamProvider<RenderList>((ref) {
|
||||||
final user = ref.read(currentUserProvider);
|
final user = ref.read(currentUserProvider);
|
||||||
if (user == null) return;
|
if (user == null) return const Stream.empty();
|
||||||
final query = ref
|
final query = ref
|
||||||
.watch(dbProvider)
|
.watch(dbProvider)
|
||||||
.assets
|
.assets
|
||||||
@ -117,9 +118,5 @@ final trashedAssetsProvider = StreamProvider<RenderList>((ref) async* {
|
|||||||
.ownerIdEqualTo(user.isarId)
|
.ownerIdEqualTo(user.isarId)
|
||||||
.isTrashedEqualTo(true)
|
.isTrashedEqualTo(true)
|
||||||
.sortByFileCreatedAt();
|
.sortByFileCreatedAt();
|
||||||
const groupBy = GroupAssetsBy.none;
|
return renderListGeneratorWithGroupBy(query, GroupAssetsBy.none);
|
||||||
yield await RenderList.fromQuery(query, groupBy);
|
|
||||||
await for (final _ in query.watchLazy()) {
|
|
||||||
yield await RenderList.fromQuery(query, groupBy);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
@ -31,7 +31,8 @@ class User {
|
|||||||
isPartnerSharedWith = false,
|
isPartnerSharedWith = false,
|
||||||
profileImagePath = dto.profileImagePath,
|
profileImagePath = dto.profileImagePath,
|
||||||
isAdmin = dto.isAdmin,
|
isAdmin = dto.isAdmin,
|
||||||
memoryEnabled = dto.memoriesEnabled;
|
memoryEnabled = dto.memoriesEnabled ?? false,
|
||||||
|
inTimeline = false;
|
||||||
|
|
||||||
User.fromPartnerDto(PartnerResponseDto dto)
|
User.fromPartnerDto(PartnerResponseDto dto)
|
||||||
: id = dto.id,
|
: id = dto.id,
|
||||||
@ -42,8 +43,8 @@ class User {
|
|||||||
isPartnerSharedWith = false,
|
isPartnerSharedWith = false,
|
||||||
profileImagePath = dto.profileImagePath,
|
profileImagePath = dto.profileImagePath,
|
||||||
isAdmin = dto.isAdmin,
|
isAdmin = dto.isAdmin,
|
||||||
memoryEnabled = dto.memoriesEnabled,
|
memoryEnabled = dto.memoriesEnabled ?? false,
|
||||||
inTimeline = dto.inTimeline;
|
inTimeline = dto.inTimeline ?? false;
|
||||||
|
|
||||||
@Index(unique: true, replace: false, type: IndexType.hash)
|
@Index(unique: true, replace: false, type: IndexType.hash)
|
||||||
String id;
|
String id;
|
||||||
@ -54,8 +55,8 @@ class User {
|
|||||||
bool isPartnerSharedWith;
|
bool isPartnerSharedWith;
|
||||||
bool isAdmin;
|
bool isAdmin;
|
||||||
String profileImagePath;
|
String profileImagePath;
|
||||||
bool? memoryEnabled;
|
bool memoryEnabled;
|
||||||
bool? inTimeline;
|
bool inTimeline;
|
||||||
|
|
||||||
@Backlink(to: 'owner')
|
@Backlink(to: 'owner')
|
||||||
final IsarLinks<Album> albums = IsarLinks<Album>();
|
final IsarLinks<Album> albums = IsarLinks<Album>();
|
||||||
|
@ -151,11 +151,11 @@ User _userDeserialize(
|
|||||||
final object = User(
|
final object = User(
|
||||||
email: reader.readString(offsets[0]),
|
email: reader.readString(offsets[0]),
|
||||||
id: reader.readString(offsets[1]),
|
id: reader.readString(offsets[1]),
|
||||||
inTimeline: reader.readBoolOrNull(offsets[2]),
|
inTimeline: reader.readBoolOrNull(offsets[2]) ?? false,
|
||||||
isAdmin: reader.readBool(offsets[3]),
|
isAdmin: reader.readBool(offsets[3]),
|
||||||
isPartnerSharedBy: reader.readBoolOrNull(offsets[4]) ?? false,
|
isPartnerSharedBy: reader.readBoolOrNull(offsets[4]) ?? false,
|
||||||
isPartnerSharedWith: reader.readBoolOrNull(offsets[5]) ?? false,
|
isPartnerSharedWith: reader.readBoolOrNull(offsets[5]) ?? false,
|
||||||
memoryEnabled: reader.readBoolOrNull(offsets[6]),
|
memoryEnabled: reader.readBoolOrNull(offsets[6]) ?? true,
|
||||||
name: reader.readString(offsets[7]),
|
name: reader.readString(offsets[7]),
|
||||||
profileImagePath: reader.readStringOrNull(offsets[8]) ?? '',
|
profileImagePath: reader.readStringOrNull(offsets[8]) ?? '',
|
||||||
updatedAt: reader.readDateTime(offsets[9]),
|
updatedAt: reader.readDateTime(offsets[9]),
|
||||||
@ -175,7 +175,7 @@ P _userDeserializeProp<P>(
|
|||||||
case 1:
|
case 1:
|
||||||
return (reader.readString(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
case 2:
|
case 2:
|
||||||
return (reader.readBoolOrNull(offset)) as P;
|
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||||
case 3:
|
case 3:
|
||||||
return (reader.readBool(offset)) as P;
|
return (reader.readBool(offset)) as P;
|
||||||
case 4:
|
case 4:
|
||||||
@ -183,7 +183,7 @@ P _userDeserializeProp<P>(
|
|||||||
case 5:
|
case 5:
|
||||||
return (reader.readBoolOrNull(offset) ?? false) as P;
|
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||||
case 6:
|
case 6:
|
||||||
return (reader.readBoolOrNull(offset)) as P;
|
return (reader.readBoolOrNull(offset) ?? true) as P;
|
||||||
case 7:
|
case 7:
|
||||||
return (reader.readString(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
case 8:
|
case 8:
|
||||||
@ -638,24 +638,8 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> inTimelineIsNull() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(const FilterCondition.isNull(
|
|
||||||
property: r'inTimeline',
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> inTimelineIsNotNull() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
|
||||||
property: r'inTimeline',
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> inTimelineEqualTo(
|
QueryBuilder<User, User, QAfterFilterCondition> inTimelineEqualTo(
|
||||||
bool? value) {
|
bool value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.equalTo(
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
property: r'inTimeline',
|
property: r'inTimeline',
|
||||||
@ -745,24 +729,8 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> memoryEnabledIsNull() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(const FilterCondition.isNull(
|
|
||||||
property: r'memoryEnabled',
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> memoryEnabledIsNotNull() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
|
||||||
property: r'memoryEnabled',
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> memoryEnabledEqualTo(
|
QueryBuilder<User, User, QAfterFilterCondition> memoryEnabledEqualTo(
|
||||||
bool? value) {
|
bool value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.equalTo(
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
property: r'memoryEnabled',
|
property: r'memoryEnabled',
|
||||||
@ -1540,7 +1508,7 @@ extension UserQueryProperty on QueryBuilder<User, User, QQueryProperty> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, bool?, QQueryOperations> inTimelineProperty() {
|
QueryBuilder<User, bool, QQueryOperations> inTimelineProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'inTimeline');
|
return query.addPropertyName(r'inTimeline');
|
||||||
});
|
});
|
||||||
@ -1564,7 +1532,7 @@ extension UserQueryProperty on QueryBuilder<User, User, QQueryProperty> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, bool?, QQueryOperations> memoryEnabledProperty() {
|
QueryBuilder<User, bool, QQueryOperations> memoryEnabledProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'memoryEnabled');
|
return query.addPropertyName(r'memoryEnabled');
|
||||||
});
|
});
|
||||||
|
@ -54,6 +54,7 @@ class AppStateNotiifer extends StateNotifier<AppStateEnum> {
|
|||||||
switch (ref.read(tabProvider)) {
|
switch (ref.read(tabProvider)) {
|
||||||
case TabEnum.home:
|
case TabEnum.home:
|
||||||
ref.read(assetProvider.notifier).getAllAsset();
|
ref.read(assetProvider.notifier).getAllAsset();
|
||||||
|
ref.read(assetProvider.notifier).getPartnerAssets();
|
||||||
case TabEnum.search:
|
case TabEnum.search:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
case TabEnum.sharing:
|
case TabEnum.sharing:
|
||||||
|
@ -8,12 +8,11 @@ import 'package:immich_mobile/shared/providers/db.provider.dart';
|
|||||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/asset.service.dart';
|
import 'package:immich_mobile/shared/services/asset.service.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
|
||||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/services/sync.service.dart';
|
import 'package:immich_mobile/shared/services/sync.service.dart';
|
||||||
import 'package:immich_mobile/shared/services/user.service.dart';
|
import 'package:immich_mobile/shared/services/user.service.dart';
|
||||||
import 'package:immich_mobile/utils/db.dart';
|
import 'package:immich_mobile/utils/db.dart';
|
||||||
|
import 'package:immich_mobile/utils/renderlist_generator.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
@ -251,26 +250,23 @@ final assetWatcher =
|
|||||||
return db.assets.watchObject(asset.id, fireImmediately: true);
|
return db.assets.watchObject(asset.id, fireImmediately: true);
|
||||||
});
|
});
|
||||||
|
|
||||||
final assetsProvider =
|
final assetsProvider = StreamProvider.family<RenderList, int?>((ref, userId) {
|
||||||
StreamProvider.family<RenderList, int?>((ref, userId) async* {
|
if (userId == null) return const Stream.empty();
|
||||||
if (userId == null) return;
|
final query = _commonFilterAndSort(
|
||||||
final query = ref
|
_assets(ref).where().ownerIdEqualToAnyChecksum(userId),
|
||||||
.watch(dbProvider)
|
);
|
||||||
.assets
|
return renderListGenerator(query, ref);
|
||||||
.where()
|
});
|
||||||
.ownerIdEqualToAnyChecksum(userId)
|
|
||||||
.filter()
|
final multiUserAssetsProvider =
|
||||||
.isArchivedEqualTo(false)
|
StreamProvider.family<RenderList, List<int>>((ref, userIds) {
|
||||||
.isTrashedEqualTo(false)
|
if (userIds.isEmpty) return const Stream.empty();
|
||||||
.stackParentIdIsNull()
|
final query = _commonFilterAndSort(
|
||||||
.sortByFileCreatedAtDesc();
|
_assets(ref)
|
||||||
final settings = ref.watch(appSettingsServiceProvider);
|
.where()
|
||||||
final groupBy =
|
.anyOf(userIds, (q, u) => q.ownerIdEqualToAnyChecksum(u)),
|
||||||
GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)];
|
);
|
||||||
yield await RenderList.fromQuery(query, groupBy);
|
return renderListGenerator(query, ref);
|
||||||
await for (final _ in query.watchLazy()) {
|
|
||||||
yield await RenderList.fromQuery(query, groupBy);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy>? getRemoteAssetQuery(WidgetRef ref) {
|
QueryBuilder<Asset, Asset, QAfterSortBy>? getRemoteAssetQuery(WidgetRef ref) {
|
||||||
@ -289,3 +285,17 @@ QueryBuilder<Asset, Asset, QAfterSortBy>? getRemoteAssetQuery(WidgetRef ref) {
|
|||||||
.stackParentIdIsNull()
|
.stackParentIdIsNull()
|
||||||
.sortByFileCreatedAtDesc();
|
.sortByFileCreatedAtDesc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IsarCollection<Asset> _assets(StreamProviderRef<RenderList> ref) =>
|
||||||
|
ref.watch(dbProvider).assets;
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterSortBy> _commonFilterAndSort(
|
||||||
|
QueryBuilder<Asset, Asset, QAfterWhereClause> query,
|
||||||
|
) {
|
||||||
|
return query
|
||||||
|
.filter()
|
||||||
|
.isArchivedEqualTo(false)
|
||||||
|
.isTrashedEqualTo(false)
|
||||||
|
.stackParentIdIsNull()
|
||||||
|
.sortByFileCreatedAtDesc();
|
||||||
|
}
|
||||||
|
@ -3,6 +3,8 @@ import 'dart:async';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
class CurrentUserProvider extends StateNotifier<User?> {
|
class CurrentUserProvider extends StateNotifier<User?> {
|
||||||
CurrentUserProvider() : super(null) {
|
CurrentUserProvider() : super(null) {
|
||||||
@ -24,3 +26,32 @@ final currentUserProvider =
|
|||||||
StateNotifierProvider<CurrentUserProvider, User?>((ref) {
|
StateNotifierProvider<CurrentUserProvider, User?>((ref) {
|
||||||
return CurrentUserProvider();
|
return CurrentUserProvider();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
class TimelineUserIdsProvider extends StateNotifier<List<int>> {
|
||||||
|
TimelineUserIdsProvider(Isar db, User? currentUser) : super([]) {
|
||||||
|
final query = db.users
|
||||||
|
.filter()
|
||||||
|
.inTimelineEqualTo(true)
|
||||||
|
.or()
|
||||||
|
.isarIdEqualTo(currentUser?.isarId ?? Isar.autoIncrement)
|
||||||
|
.isarIdProperty();
|
||||||
|
query.findAll().then((users) => state = users);
|
||||||
|
streamSub = query.watch().listen((users) => state = users);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final StreamSubscription<List<int>> streamSub;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
streamSub.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final timelineUsersIdsProvider =
|
||||||
|
StateNotifierProvider<TimelineUserIdsProvider, List<int>>((ref) {
|
||||||
|
return TimelineUserIdsProvider(
|
||||||
|
ref.watch(dbProvider),
|
||||||
|
ref.watch(currentUserProvider),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -99,7 +99,11 @@ class UserService {
|
|||||||
users,
|
users,
|
||||||
sharedWith,
|
sharedWith,
|
||||||
compare: (User a, User b) => a.id.compareTo(b.id),
|
compare: (User a, User b) => a.id.compareTo(b.id),
|
||||||
both: (User a, User b) => a.isPartnerSharedWith = true,
|
both: (User a, User b) {
|
||||||
|
a.isPartnerSharedWith = true;
|
||||||
|
a.inTimeline = b.inTimeline;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
onlyFirst: (_) {},
|
onlyFirst: (_) {},
|
||||||
onlySecond: (_) {},
|
onlySecond: (_) {},
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,8 @@ Future<void> migrateDatabaseIfNeeded(Isar db) async {
|
|||||||
await _migrateTo(db, 2);
|
await _migrateTo(db, 2);
|
||||||
case 2:
|
case 2:
|
||||||
await _migrateTo(db, 3);
|
await _migrateTo(db, 3);
|
||||||
|
case 3:
|
||||||
|
await _migrateTo(db, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
mobile/lib/utils/renderlist_generator.dart
Normal file
26
mobile/lib/utils/renderlist_generator.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
||||||
|
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
Stream<RenderList> renderListGenerator(
|
||||||
|
QueryBuilder<Asset, Asset, QAfterSortBy> query,
|
||||||
|
StreamProviderRef<RenderList> ref,
|
||||||
|
) {
|
||||||
|
final settings = ref.watch(appSettingsServiceProvider);
|
||||||
|
final groupBy =
|
||||||
|
GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)];
|
||||||
|
return renderListGeneratorWithGroupBy(query, groupBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<RenderList> renderListGeneratorWithGroupBy(
|
||||||
|
QueryBuilder<Asset, Asset, QAfterSortBy> query,
|
||||||
|
GroupAssetsBy groupBy,
|
||||||
|
) async* {
|
||||||
|
yield await RenderList.fromQuery(query, groupBy);
|
||||||
|
await for (final _ in query.watchLazy()) {
|
||||||
|
yield await RenderList.fromQuery(query, groupBy);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user