mirror of
https://github.com/immich-app/immich.git
synced 2025-07-31 15:08:44 -04:00
chore: finish drift locked page (#20013)
* feat: overlay mechanism * handle merged asset local id extraction * locked view asset viewer actions * pr feedback
This commit is contained in:
parent
dcfe8d5ade
commit
5d244c6fec
@ -1,13 +1,14 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart' show useState;
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/local_auth.provider.dart';
|
import 'package:immich_mobile/providers/local_auth.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/widgets/forms/pin_registration_form.dart';
|
import 'package:immich_mobile/widgets/forms/pin_registration_form.dart';
|
||||||
import 'package:immich_mobile/widgets/forms/pin_verification_form.dart';
|
import 'package:immich_mobile/widgets/forms/pin_verification_form.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class PinAuthPage extends HookConsumerWidget {
|
class PinAuthPage extends HookConsumerWidget {
|
||||||
@ -19,6 +20,7 @@ class PinAuthPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final localAuthState = ref.watch(localAuthProvider);
|
final localAuthState = ref.watch(localAuthProvider);
|
||||||
final showPinRegistrationForm = useState(createPinCode);
|
final showPinRegistrationForm = useState(createPinCode);
|
||||||
|
final isBetaTimeline = Store.isBetaTimelineEnabled;
|
||||||
|
|
||||||
Future<void> registerBiometric(String pinCode) async {
|
Future<void> registerBiometric(String pinCode) async {
|
||||||
final isRegistered =
|
final isRegistered =
|
||||||
@ -39,9 +41,13 @@ class PinAuthPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isBetaTimeline) {
|
||||||
|
context.replaceRoute(const DriftLockedFolderRoute());
|
||||||
|
} else {
|
||||||
context.replaceRoute(const LockedRoute());
|
context.replaceRoute(const LockedRoute());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enableBiometricAuth() {
|
enableBiometricAuth() {
|
||||||
showDialog(
|
showDialog(
|
||||||
@ -93,8 +99,14 @@ class PinAuthPage extends HookConsumerWidget {
|
|||||||
Center(
|
Center(
|
||||||
child: PinVerificationForm(
|
child: PinVerificationForm(
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
onSuccess: (_) =>
|
onSuccess: (_) {
|
||||||
context.replaceRoute(const LockedRoute()),
|
if (isBetaTimeline) {
|
||||||
|
context
|
||||||
|
.replaceRoute(const DriftLockedFolderRoute());
|
||||||
|
} else {
|
||||||
|
context.replaceRoute(const LockedRoute());
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
@ -95,9 +95,13 @@ class _DriftAlbumsPageState extends ConsumerState<DriftAlbumsPage> {
|
|||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: onRefresh,
|
onRefresh: onRefresh,
|
||||||
|
edgeOffset: 100,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
ImmichSliverAppBar(
|
ImmichSliverAppBar(
|
||||||
|
snap: false,
|
||||||
|
floating: false,
|
||||||
|
pinned: true,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
|
@ -4,14 +4,45 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||||
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
|
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class DriftLockedFolderPage extends StatelessWidget {
|
class DriftLockedFolderPage extends ConsumerStatefulWidget {
|
||||||
const DriftLockedFolderPage({super.key});
|
const DriftLockedFolderPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<DriftLockedFolderPage> createState() =>
|
||||||
|
_DriftLockedFolderPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DriftLockedFolderPageState extends ConsumerState<DriftLockedFolderPage>
|
||||||
|
with WidgetsBindingObserver {
|
||||||
|
bool _showOverlay = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_showOverlay = state != AppLifecycleState.resumed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ProviderScope(
|
return ProviderScope(
|
||||||
@ -30,12 +61,18 @@ class DriftLockedFolderPage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
child: _showOverlay
|
||||||
|
? const SizedBox()
|
||||||
|
: PopScope(
|
||||||
|
onPopInvokedWithResult: (didPop, _) =>
|
||||||
|
didPop ? ref.read(authProvider.notifier).lockPinCode() : null,
|
||||||
child: Timeline(
|
child: Timeline(
|
||||||
appBar: MesmerizingSliverAppBar(
|
appBar: MesmerizingSliverAppBar(
|
||||||
title: 'locked_folder'.t(context: context),
|
title: 'locked_folder'.t(context: context),
|
||||||
),
|
),
|
||||||
bottomSheet: const LockedFolderBottomSheet(),
|
bottomSheet: const LockedFolderBottomSheet(),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider
|
|||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/photo_view_gallery.dart';
|
import 'package:immich_mobile/widgets/photo_view/photo_view_gallery.dart';
|
||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
@ -638,6 +639,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||||
|
|
||||||
// Currently it is not possible to scroll the asset when the bottom sheet is open all the way.
|
// Currently it is not possible to scroll the asset when the bottom sheet is open all the way.
|
||||||
// Issue: https://github.com/flutter/flutter/issues/109037
|
// Issue: https://github.com/flutter/flutter/issues/109037
|
||||||
// TODO: Add a custom scrum builder once the fix lands on stable
|
// TODO: Add a custom scrum builder once the fix lands on stable
|
||||||
@ -666,13 +669,13 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
),
|
),
|
||||||
bottomNavigationBar: showingBottomSheet
|
bottomNavigationBar: showingBottomSheet
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
: const Column(
|
: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
AssetStackRow(),
|
const AssetStackRow(),
|
||||||
ViewerBottomBar(),
|
if (!isInLockedView) const ViewerBottomBar(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -18,6 +18,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_
|
|||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/location_details.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/location_details.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||||
|
|
||||||
@ -44,6 +45,8 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
serverInfoProvider.select((state) => state.serverFeatures.trash),
|
serverInfoProvider.select((state) => state.serverFeatures.trash),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||||
|
|
||||||
final actions = <Widget>[
|
final actions = <Widget>[
|
||||||
const ShareActionButton(source: ActionSource.viewer),
|
const ShareActionButton(source: ActionSource.viewer),
|
||||||
if (asset.hasRemote) ...[
|
if (asset.hasRemote) ...[
|
||||||
@ -63,8 +66,10 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
final lockedViewActions = <Widget>[];
|
||||||
|
|
||||||
return BaseBottomSheet(
|
return BaseBottomSheet(
|
||||||
actions: actions,
|
actions: isInLockedView ? lockedViewActions : actions,
|
||||||
slivers: const [_AssetDetailBottomSheet()],
|
slivers: const [_AssetDetailBottomSheet()],
|
||||||
controller: controller,
|
controller: controller,
|
||||||
initialChildSize: initialChildSize,
|
initialChildSize: initialChildSize,
|
||||||
|
@ -12,6 +12,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_act
|
|||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
|
|
||||||
final user = ref.watch(currentUserProvider);
|
final user = ref.watch(currentUserProvider);
|
||||||
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
|
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
|
||||||
|
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||||
|
|
||||||
final isShowingSheet = ref
|
final isShowingSheet = ref
|
||||||
.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
|
.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
|
||||||
@ -62,6 +64,14 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
const _KebabMenu(),
|
const _KebabMenu(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
final lockedViewActions = <Widget>[
|
||||||
|
if (isCasting || (asset.hasRemote && websocketConnected))
|
||||||
|
const CastActionButton(
|
||||||
|
menuItem: true,
|
||||||
|
),
|
||||||
|
const _KebabMenu(),
|
||||||
|
];
|
||||||
|
|
||||||
return IgnorePointer(
|
return IgnorePointer(
|
||||||
ignoring: opacity < 255,
|
ignoring: opacity < 255,
|
||||||
child: AnimatedOpacity(
|
child: AnimatedOpacity(
|
||||||
@ -74,7 +84,11 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
iconTheme: const IconThemeData(size: 22, color: Colors.white),
|
iconTheme: const IconThemeData(size: 22, color: Colors.white),
|
||||||
actionsIconTheme: const IconThemeData(size: 22, color: Colors.white),
|
actionsIconTheme: const IconThemeData(size: 22, color: Colors.white),
|
||||||
shape: const Border(),
|
shape: const Border(),
|
||||||
actions: isShowingSheet ? null : actions,
|
actions: isShowingSheet
|
||||||
|
? null
|
||||||
|
: isInLockedView
|
||||||
|
? lockedViewActions
|
||||||
|
: actions,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -47,7 +47,18 @@ class ActionNotifier extends Notifier<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<String> _getLocalIdsForSource(ActionSource source) {
|
List<String> _getLocalIdsForSource(ActionSource source) {
|
||||||
return _getIdsForSource<LocalAsset>(source).toIds().toList(growable: false);
|
final Set<BaseAsset> assets = _getAssets(source);
|
||||||
|
final List<String> localIds = [];
|
||||||
|
|
||||||
|
for (final asset in assets) {
|
||||||
|
if (asset is LocalAsset) {
|
||||||
|
localIds.add(asset.id);
|
||||||
|
} else if (asset is RemoteAsset && asset.localId != null) {
|
||||||
|
localIds.add(asset.localId!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return localIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _getOwnedRemoteIdsForSource(ActionSource source) {
|
List<String> _getOwnedRemoteIdsForSource(ActionSource source) {
|
||||||
@ -162,8 +173,9 @@ class ActionNotifier extends Notifier<void> {
|
|||||||
|
|
||||||
Future<ActionResult> moveToLockFolder(ActionSource source) async {
|
Future<ActionResult> moveToLockFolder(ActionSource source) async {
|
||||||
final ids = _getOwnedRemoteIdsForSource(source);
|
final ids = _getOwnedRemoteIdsForSource(source);
|
||||||
|
final localIds = _getLocalIdsForSource(source);
|
||||||
try {
|
try {
|
||||||
await _service.moveToLockFolder(ids);
|
await _service.moveToLockFolder(ids, localIds);
|
||||||
return ActionResult(count: ids.length, success: true);
|
return ActionResult(count: ids.length, success: true);
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
_logger.severe('Failed to move assets to lock folder', error, stack);
|
_logger.severe('Failed to move assets to lock folder', error, stack);
|
||||||
@ -329,7 +341,3 @@ extension on Iterable<RemoteAsset> {
|
|||||||
return whereType<RemoteAsset>().where((a) => a.ownerId == ownerId);
|
return whereType<RemoteAsset>().where((a) => a.ownerId == ownerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension on Iterable<LocalAsset> {
|
|
||||||
Iterable<String> toIds() => map((e) => e.id);
|
|
||||||
}
|
|
||||||
|
@ -25,7 +25,7 @@ class AppNavigationObserver extends AutoRouterObserver {
|
|||||||
@override
|
@override
|
||||||
void didPush(Route route, Route? previousRoute) {
|
void didPush(Route route, Route? previousRoute) {
|
||||||
_handleLockedViewState(route, previousRoute);
|
_handleLockedViewState(route, previousRoute);
|
||||||
|
_handleDriftLockedFolderState(route, previousRoute);
|
||||||
Future(
|
Future(
|
||||||
() => ref.read(currentRouteNameProvider.notifier).state =
|
() => ref.read(currentRouteNameProvider.notifier).state =
|
||||||
route.settings.name,
|
route.settings.name,
|
||||||
@ -54,4 +54,27 @@ class AppNavigationObserver extends AutoRouterObserver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleDriftLockedFolderState(Route route, Route? previousRoute) {
|
||||||
|
final isInLockedView = ref.read(inLockedViewProvider);
|
||||||
|
final isFromLockedViewToDetailView =
|
||||||
|
route.settings.name == AssetViewerRoute.name &&
|
||||||
|
previousRoute?.settings.name == DriftLockedFolderRoute.name;
|
||||||
|
|
||||||
|
final isFromDetailViewToInfoPanelView = route.settings.name == null &&
|
||||||
|
previousRoute?.settings.name == AssetViewerRoute.name &&
|
||||||
|
isInLockedView;
|
||||||
|
|
||||||
|
if (route.settings.name == DriftLockedFolderRoute.name ||
|
||||||
|
isFromLockedViewToDetailView ||
|
||||||
|
isFromDetailViewToInfoPanelView) {
|
||||||
|
Future(
|
||||||
|
() => ref.read(inLockedViewProvider.notifier).state = true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Future(
|
||||||
|
() => ref.read(inLockedViewProvider.notifier).state = false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -427,7 +427,7 @@ class AppRouter extends RootStackRouter {
|
|||||||
),
|
),
|
||||||
AutoRoute(
|
AutoRoute(
|
||||||
page: DriftLockedFolderRoute.page,
|
page: DriftLockedFolderRoute.page,
|
||||||
guards: [_authGuard, _duplicateGuard],
|
guards: [_authGuard, _lockedGuard, _duplicateGuard],
|
||||||
),
|
),
|
||||||
AutoRoute(
|
AutoRoute(
|
||||||
page: DriftVideoRoute.page,
|
page: DriftVideoRoute.page,
|
||||||
|
@ -83,7 +83,10 @@ class ActionService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> moveToLockFolder(List<String> remoteIds) async {
|
Future<void> moveToLockFolder(
|
||||||
|
List<String> remoteIds,
|
||||||
|
List<String> localIds,
|
||||||
|
) async {
|
||||||
await _assetApiRepository.updateVisibility(
|
await _assetApiRepository.updateVisibility(
|
||||||
remoteIds,
|
remoteIds,
|
||||||
AssetVisibilityEnum.locked,
|
AssetVisibilityEnum.locked,
|
||||||
@ -92,6 +95,15 @@ class ActionService {
|
|||||||
remoteIds,
|
remoteIds,
|
||||||
AssetVisibility.locked,
|
AssetVisibility.locked,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Ask user if they want to delete local copies
|
||||||
|
if (localIds.isNotEmpty) {
|
||||||
|
final deletedIds = await _assetMediaRepository.deleteAll(localIds);
|
||||||
|
|
||||||
|
if (deletedIds.isNotEmpty) {
|
||||||
|
await _localAssetRepository.delete(deletedIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeFromLockFolder(List<String> remoteIds) async {
|
Future<void> removeFromLockFolder(List<String> remoteIds) async {
|
||||||
|
@ -22,7 +22,6 @@ class MesmerizingSliverAppBar extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<MesmerizingSliverAppBar> createState() =>
|
ConsumerState<MesmerizingSliverAppBar> createState() =>
|
||||||
_MesmerizingSliverAppBarState();
|
_MesmerizingSliverAppBarState();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user