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:
Alex 2025-07-18 13:16:22 -05:00 committed by GitHub
parent dcfe8d5ade
commit 5d244c6fec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 143 additions and 26 deletions

View file

@ -95,9 +95,13 @@ class _DriftAlbumsPageState extends ConsumerState<DriftAlbumsPage> {
return RefreshIndicator(
onRefresh: onRefresh,
edgeOffset: 100,
child: CustomScrollView(
slivers: [
ImmichSliverAppBar(
snap: false,
floating: false,
pinned: true,
actions: [
IconButton(
icon: const Icon(

View file

@ -4,14 +4,45 @@ import 'package:hooks_riverpod/hooks_riverpod.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/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/user.provider.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
@RoutePage()
class DriftLockedFolderPage extends StatelessWidget {
class DriftLockedFolderPage extends ConsumerStatefulWidget {
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
Widget build(BuildContext context) {
return ProviderScope(
@ -30,12 +61,18 @@ class DriftLockedFolderPage extends StatelessWidget {
},
),
],
child: Timeline(
appBar: MesmerizingSliverAppBar(
title: 'locked_folder'.t(context: context),
),
bottomSheet: const LockedFolderBottomSheet(),
),
child: _showOverlay
? const SizedBox()
: PopScope(
onPopInvokedWithResult: (didPop, _) =>
didPop ? ref.read(authProvider.notifier).lockPinCode() : null,
child: Timeline(
appBar: MesmerizingSliverAppBar(
title: 'locked_folder'.t(context: context),
),
bottomSheet: const LockedFolderBottomSheet(),
),
),
);
}
}

View file

@ -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/infrastructure/asset_viewer/current_asset.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_gallery.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.
// Issue: https://github.com/flutter/flutter/issues/109037
// TODO: Add a custom scrum builder once the fix lands on stable
@ -666,13 +669,13 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
),
bottomNavigationBar: showingBottomSheet
? const SizedBox.shrink()
: const Column(
: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AssetStackRow(),
ViewerBottomBar(),
const AssetStackRow(),
if (!isInLockedView) const ViewerBottomBar(),
],
),
),

View file

@ -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/bottom_sheet/base_bottom_sheet.widget.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/utils/bytes_units.dart';
@ -44,6 +45,8 @@ class AssetDetailBottomSheet extends ConsumerWidget {
serverInfoProvider.select((state) => state.serverFeatures.trash),
);
final isInLockedView = ref.watch(inLockedViewProvider);
final actions = <Widget>[
const ShareActionButton(source: ActionSource.viewer),
if (asset.hasRemote) ...[
@ -63,8 +66,10 @@ class AssetDetailBottomSheet extends ConsumerWidget {
],
];
final lockedViewActions = <Widget>[];
return BaseBottomSheet(
actions: actions,
actions: isInLockedView ? lockedViewActions : actions,
slivers: const [_AssetDetailBottomSheet()],
controller: controller,
initialChildSize: initialChildSize,

View file

@ -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/providers/cast.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/websocket.provider.dart';
@ -27,6 +28,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
final user = ref.watch(currentUserProvider);
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
final isInLockedView = ref.watch(inLockedViewProvider);
final isShowingSheet = ref
.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
@ -62,6 +64,14 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
const _KebabMenu(),
];
final lockedViewActions = <Widget>[
if (isCasting || (asset.hasRemote && websocketConnected))
const CastActionButton(
menuItem: true,
),
const _KebabMenu(),
];
return IgnorePointer(
ignoring: opacity < 255,
child: AnimatedOpacity(
@ -74,7 +84,11 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
iconTheme: const IconThemeData(size: 22, color: Colors.white),
actionsIconTheme: const IconThemeData(size: 22, color: Colors.white),
shape: const Border(),
actions: isShowingSheet ? null : actions,
actions: isShowingSheet
? null
: isInLockedView
? lockedViewActions
: actions,
),
),
);