mirror of
https://github.com/immich-app/immich.git
synced 2026-07-28 14:47:30 -07:00
more cleanup
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
@@ -19,35 +19,30 @@ class ActionItem {
|
||||
abstract class ActionBuilder {
|
||||
const ActionBuilder();
|
||||
|
||||
ActionItem? build(BuildContext context, WidgetRef ref);
|
||||
// null when the action is not applicable for the current context
|
||||
ActionItem? create(BuildContext context, WidgetRef ref);
|
||||
}
|
||||
|
||||
typedef AssetsActionState = ({AssetFilter<BaseAsset> assets, AssetFilter<RemoteAsset> ownedAssets});
|
||||
final assetsActionProvider = Provider.family.autoDispose<AssetFilter<BaseAsset>, ActionSource>(
|
||||
(ref, source) => AssetFilter(switch (source) {
|
||||
.timeline => ref.watch(multiSelectProvider.select((s) => s.selectedAssets)),
|
||||
.viewer => switch (ref.watch(assetViewerProvider.select((s) => s.currentAsset))) {
|
||||
BaseAsset asset => {asset},
|
||||
null => const <BaseAsset>{},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
class AssetsActionNotifier extends AutoDisposeFamilyNotifier<AssetsActionState, ActionSource> {
|
||||
@override
|
||||
AssetsActionState build(ActionSource source) {
|
||||
final selected = switch (source) {
|
||||
.timeline => ref.watch(multiSelectProvider.select((s) => s.selectedAssets)),
|
||||
.viewer => switch (ref.watch(assetViewerProvider.select((s) => s.currentAsset))) {
|
||||
BaseAsset asset => {asset},
|
||||
null => const <BaseAsset>{},
|
||||
},
|
||||
};
|
||||
|
||||
final assets = AssetFilter(selected);
|
||||
return (assets: assets, ownedAssets: assets.owned(ref.watch(authUserProvider).id));
|
||||
final clearSelectionProvider = Provider.family.autoDispose<VoidCallback, ActionSource>((ref, source) {
|
||||
if (source == .timeline) {
|
||||
return ref.read(multiSelectProvider.notifier).reset;
|
||||
}
|
||||
|
||||
void clearSelect() {
|
||||
if (arg == .timeline) {
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
return () {};
|
||||
});
|
||||
|
||||
final assetsActionProvider = NotifierProvider.family.autoDispose<AssetsActionNotifier, AssetsActionState, ActionSource>(
|
||||
AssetsActionNotifier.new,
|
||||
final ownedAssetsActionProvider = Provider.family.autoDispose<AssetFilter<RemoteAsset>, ActionSource>(
|
||||
(ref, source) => ref.watch(assetsActionProvider(source)).owned(ref.watch(authUserProvider).id),
|
||||
);
|
||||
|
||||
abstract class AssetActionBuilder extends ActionBuilder {
|
||||
|
||||
@@ -12,12 +12,12 @@ abstract class ActionWidget extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final resolved = action.build(context, ref);
|
||||
if (resolved == null) {
|
||||
final actionItem = action.create(context, ref);
|
||||
if (actionItem == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return builder(context, ref, resolved);
|
||||
return builder(context, ref, actionItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class AssetDebugAction extends AssetActionBuilder {
|
||||
const AssetDebugAction({required super.source});
|
||||
|
||||
@override
|
||||
ActionItem? build(BuildContext context, WidgetRef ref) {
|
||||
ActionItem? create(BuildContext context, WidgetRef ref) {
|
||||
final assets = ref.watch(assetsActionProvider(source)).assets;
|
||||
final troubleshootEnabled = ref.watch(settingsProvider.notifier).get(.advancedTroubleshooting);
|
||||
if (!troubleshootEnabled || assets.length != 1) {
|
||||
|
||||
@@ -10,13 +10,13 @@ import 'package:immich_mobile/utils/error_handler.dart';
|
||||
typedef _State = ({bool shouldFavorite, List<String> assetIds});
|
||||
|
||||
final _stateProvider = Provider.family.autoDispose<_State?, ActionSource>((ref, source) {
|
||||
final AssetsActionState(:ownedAssets) = ref.watch(assetsActionProvider(source));
|
||||
if (ownedAssets.isEmpty) {
|
||||
final assets = ref.watch(ownedAssetsActionProvider(source));
|
||||
if (assets.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final shouldFavorite = ownedAssets.favorite(isFavorite: false).isNotEmpty;
|
||||
final assetIds = ownedAssets.favorite(isFavorite: !shouldFavorite).map((asset) => asset.id).toList(growable: false);
|
||||
final shouldFavorite = assets.favorite(isFavorite: false).isNotEmpty;
|
||||
final assetIds = assets.favorite(isFavorite: !shouldFavorite).map((asset) => asset.id).toList(growable: false);
|
||||
return (shouldFavorite: shouldFavorite, assetIds: assetIds);
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ class FavoriteAction extends AssetActionBuilder {
|
||||
const FavoriteAction({required super.source});
|
||||
|
||||
@override
|
||||
ActionItem? build(BuildContext context, WidgetRef ref) {
|
||||
ActionItem? create(BuildContext context, WidgetRef ref) {
|
||||
final shouldFavorite = ref.watch(_stateProvider(source).select((state) => state?.shouldFavorite));
|
||||
if (shouldFavorite == null) {
|
||||
return null;
|
||||
@@ -47,13 +47,14 @@ class FavoriteAction extends AssetActionBuilder {
|
||||
final message = shouldFavorite
|
||||
? context.t.favorite_action_prompt(count: assetIds.length)
|
||||
: context.t.unfavorite_action_prompt(count: assetIds.length);
|
||||
final toast = ref.read(toastRepositoryProvider);
|
||||
final selection = ref.read(assetsActionProvider(source).notifier);
|
||||
final assertService = ref.read(assetServiceProvider);
|
||||
final toastService = ref.read(toastServiceProvider);
|
||||
final clearSelection = ref.read(clearSelectionProvider(source));
|
||||
|
||||
try {
|
||||
await ref.read(assetServiceProvider).update(assetIds, isFavorite: .some(shouldFavorite));
|
||||
toast.success(message);
|
||||
selection.clearSelect();
|
||||
await assertService.update(assetIds, isFavorite: .some(shouldFavorite));
|
||||
toastService.success(message);
|
||||
clearSelection();
|
||||
} catch (error, stack) {
|
||||
handleError(error, stack: stack, description: "Failed to update favorite status for assets");
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class PartnerAddAction extends ActionBuilder {
|
||||
const PartnerAddAction();
|
||||
|
||||
@override
|
||||
ActionItem build(BuildContext context, WidgetRef ref) =>
|
||||
ActionItem create(BuildContext context, WidgetRef ref) =>
|
||||
ActionItem(icon: Icons.person_add_rounded, label: context.t.add_partner, onAction: () => _add(context, ref));
|
||||
|
||||
Future<void> _add(BuildContext context, WidgetRef ref) async {
|
||||
@@ -40,7 +40,7 @@ class PartnerRemoveAction extends ActionBuilder {
|
||||
final String partnerName;
|
||||
|
||||
@override
|
||||
ActionItem build(BuildContext context, WidgetRef ref) =>
|
||||
ActionItem create(BuildContext context, WidgetRef ref) =>
|
||||
ActionItem(icon: Icons.person_remove_rounded, label: context.t.remove, onAction: () => _remove(context, ref));
|
||||
|
||||
Future<void> _remove(BuildContext context, WidgetRef ref) async {
|
||||
@@ -67,14 +67,9 @@ class PartnerRemoveAction extends ActionBuilder {
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
final candidatesStateProvider = StreamProvider.autoDispose<Iterable<User>>((ref) {
|
||||
final currentUser = ref.watch(currentUserProvider);
|
||||
// TODO: Refactor with a route guard to avoid this check in every provider
|
||||
if (currentUser == null) {
|
||||
return const Stream.empty();
|
||||
}
|
||||
return ref.watch(partnerServiceProvider).getCandidates(currentUser.id);
|
||||
});
|
||||
final candidatesStateProvider = StreamProvider.autoDispose<Iterable<User>>(
|
||||
(ref) => ref.watch(partnerServiceProvider).getCandidates(ref.watch(authUserProvider).id),
|
||||
);
|
||||
|
||||
@visibleForTesting
|
||||
class PartnerSelectionDialog extends ConsumerWidget {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/repositories/toast.repository.dart';
|
||||
import 'package:immich_mobile/services/toast.service.dart';
|
||||
|
||||
final toastRepositoryProvider = Provider<ToastRepository>((ref) => const .new());
|
||||
final toastServiceProvider = Provider<ToastService>((ref) => const .new());
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ class ToastOption {
|
||||
const ToastOption({this.timeout, this.onUndo});
|
||||
}
|
||||
|
||||
class ToastRepository {
|
||||
const ToastRepository();
|
||||
class ToastService {
|
||||
const ToastService();
|
||||
|
||||
FutureOr<void> success(String message, {ToastOption? toast}) {
|
||||
snackbar.success(message, duration: toast?.timeout);
|
||||
@@ -6,7 +6,6 @@ import 'package:immich_mobile/repositories/auth.repository.dart';
|
||||
import 'package:immich_mobile/repositories/auth_api.repository.dart';
|
||||
import 'package:immich_mobile/repositories/download.repository.dart';
|
||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
||||
import 'package:immich_mobile/repositories/toast.repository.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockAssetApiRepository extends Mock implements AssetApiRepository {}
|
||||
@@ -24,5 +23,3 @@ class MockTagService extends Mock implements TagService {}
|
||||
class MockDownloadRepository extends Mock implements DownloadRepository {}
|
||||
|
||||
class MockRemoteExifRepository extends Mock implements RemoteExifRepository {}
|
||||
|
||||
class MockToastRepository extends Mock implements ToastRepository {}
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||
import 'package:immich_mobile/services/gcast.service.dart';
|
||||
import 'package:immich_mobile/services/network.service.dart';
|
||||
import 'package:immich_mobile/services/server_info.service.dart';
|
||||
import 'package:immich_mobile/services/toast.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockApiService extends Mock implements ApiService {}
|
||||
@@ -43,3 +44,5 @@ class MockServerInfoService extends Mock implements ServerInfoService {}
|
||||
class MockCleanupService extends Mock implements CleanupService {}
|
||||
|
||||
class MockBackgroundSyncManager extends Mock implements BackgroundSyncManager {}
|
||||
|
||||
class MockToastService extends Mock implements ToastService {}
|
||||
|
||||
@@ -30,7 +30,6 @@ class RepositoryMocks {
|
||||
final remoteAsset = RemoteAssetRepositoryStub(MockRemoteAssetRepository());
|
||||
final remoteExif = RemoteExifRepositoryStub(MockRemoteExifRepository());
|
||||
final trashedAsset = MockTrashedLocalAssetRepository();
|
||||
final toast = MockToastRepository();
|
||||
final remoteAlbum = MockRemoteAlbumRepository();
|
||||
final albumApi = MockDriftAlbumApiRepository();
|
||||
|
||||
@@ -56,7 +55,6 @@ class RepositoryMocks {
|
||||
assetApi.reset();
|
||||
assetMedia.reset();
|
||||
download.reset();
|
||||
reset(toast);
|
||||
_stubLocalAlbumRepository();
|
||||
_stubLocalAssetRepository();
|
||||
_stubRemoteAssetRepository();
|
||||
@@ -115,6 +113,7 @@ class ServiceMocks {
|
||||
final upload = MockForegroundUploadService();
|
||||
final cast = MockGCastService();
|
||||
final serverInfo = MockServerInfoService();
|
||||
final toast = MockToastService();
|
||||
|
||||
ServiceMocks() {
|
||||
resetAll();
|
||||
@@ -132,6 +131,7 @@ class ServiceMocks {
|
||||
reset(serverInfo);
|
||||
reset(backgroundSync);
|
||||
reset(upload);
|
||||
reset(toast);
|
||||
_stubUserService();
|
||||
_stubPartnerService();
|
||||
_stubAssetService();
|
||||
|
||||
Reference in New Issue
Block a user