fix(mobile): keep header image when starting selection (#32356)

* fix(mobile): keep header image when starting selection

* fix(mobile): fade out header actions during selection

Hide the back button and person options menu with the same fade as the
timeline app bar, and block taps on the person name and birthday while
assets are selected.

---------

Co-authored-by: Santo Shakil <santoshakil@users.noreply.github.com>
(cherry picked from commit 745bdd2edf72ce0f75e748e2d68c37b6ea196cc5)
This commit is contained in:
Clément Delestre
2026-10-22 05:25:45 +03:00
committed by Codex
parent f1f7f82e91
commit 7c07c001cd
2 changed files with 119 additions and 116 deletions
@@ -36,64 +36,63 @@ class _MesmerizingSliverAppBarState extends ConsumerState<MesmerizingSliverAppBa
@override
Widget build(BuildContext context) {
final isMultiSelectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled));
Widget hideWhenSelecting(Widget child) => IgnorePointer(
ignoring: isMultiSelectEnabled,
child: AnimatedOpacity(duration: Durations.medium1, opacity: isMultiSelectEnabled ? 0 : 1, child: child),
);
return isMultiSelectEnabled
? SliverToBoxAdapter(
child: switch (_scrollProgress) {
< 0.8 => const SizedBox(height: 120),
_ => const SizedBox(height: 352),
},
)
: SliverAppBar(
expandedHeight: 300.0,
floating: false,
pinned: true,
snap: false,
elevation: 0,
leading: IconButton(
icon: Icon(
Platform.isIOS ? Icons.arrow_back_ios_new_rounded : Icons.arrow_back,
color: Color.lerp(Colors.white, context.primaryColor, _scrollProgress),
shadows: [
_scrollProgress < 0.95
? Shadow(offset: const Offset(0, 2), blurRadius: 5, color: Colors.black.withValues(alpha: 0.5))
: const Shadow(offset: Offset(0, 2), blurRadius: 0, color: Colors.transparent),
],
),
onPressed: () {
context.pop();
},
),
flexibleSpace: Builder(
builder: (context) {
final settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
final scrollProgress = _calculateScrollProgress(settings);
return SliverAppBar(
expandedHeight: 300.0,
floating: false,
pinned: true,
snap: false,
elevation: 0,
leading: hideWhenSelecting(
IconButton(
icon: Icon(
Platform.isIOS ? Icons.arrow_back_ios_new_rounded : Icons.arrow_back,
color: Color.lerp(Colors.white, context.primaryColor, _scrollProgress),
shadows: [
_scrollProgress < 0.95
? Shadow(offset: const Offset(0, 2), blurRadius: 5, color: Colors.black.withValues(alpha: 0.5))
: const Shadow(offset: Offset(0, 2), blurRadius: 0, color: Colors.transparent),
],
),
onPressed: () {
context.pop();
},
),
),
flexibleSpace: Builder(
builder: (context) {
final settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
final scrollProgress = _calculateScrollProgress(settings);
// Update scroll progress for the leading button
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && _scrollProgress != scrollProgress) {
setState(() {
_scrollProgress = scrollProgress;
});
}
});
// Update scroll progress for the leading button
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && _scrollProgress != scrollProgress) {
setState(() {
_scrollProgress = scrollProgress;
});
}
});
return FlexibleSpaceBar(
centerTitle: true,
title: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: scrollProgress > 0.95
? Text(
widget.title,
style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.w600, fontSize: 18),
)
: null,
),
background: _ExpandedBackground(scrollProgress: scrollProgress, title: widget.title),
);
},
return FlexibleSpaceBar(
centerTitle: true,
title: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: scrollProgress > 0.95
? Text(
widget.title,
style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.w600, fontSize: 18),
)
: null,
),
background: _ExpandedBackground(scrollProgress: scrollProgress, title: widget.title),
);
},
),
);
}
}
@@ -54,6 +54,10 @@ class _MesmerizingSliverAppBarState extends ConsumerState<PersonSliverAppBar> {
@override
Widget build(BuildContext context) {
final isMultiSelectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled));
Widget hideWhenSelecting(Widget child) => IgnorePointer(
ignoring: isMultiSelectEnabled,
child: AnimatedOpacity(duration: Durations.medium1, opacity: isMultiSelectEnabled ? 0 : 1, child: child),
);
final Color? actionIconColor = Color.lerp(Colors.white, context.primaryColor, _scrollProgress);
final List<Shadow> actionIconShadows = [
if (_scrollProgress < 0.95)
@@ -62,74 +66,74 @@ class _MesmerizingSliverAppBarState extends ConsumerState<PersonSliverAppBar> {
const Shadow(offset: Offset(0, 2), blurRadius: 0, color: Colors.transparent),
];
return isMultiSelectEnabled
? SliverToBoxAdapter(
child: switch (_scrollProgress) {
< 0.8 => const SizedBox(height: 120),
_ => const SizedBox(height: 352),
},
)
: SliverAppBar(
expandedHeight: 300.0,
floating: false,
pinned: true,
snap: false,
elevation: 0,
leading: IconButton(
icon: Icon(
Platform.isIOS ? Icons.arrow_back_ios_new_rounded : Icons.arrow_back,
color: Color.lerp(Colors.white, context.primaryColor, _scrollProgress),
shadows: [
_scrollProgress < 0.95
? Shadow(offset: const Offset(0, 2), blurRadius: 5, color: Colors.black.withValues(alpha: 0.5))
: const Shadow(offset: Offset(0, 2), blurRadius: 0, color: Colors.transparent),
],
),
onPressed: () {
context.pop();
},
),
actions: [
IconButton(
icon: Icon(Icons.more_vert, color: actionIconColor, shadows: actionIconShadows),
onPressed: widget.onShowOptions,
),
return SliverAppBar(
expandedHeight: 300.0,
floating: false,
pinned: true,
snap: false,
elevation: 0,
leading: hideWhenSelecting(
IconButton(
icon: Icon(
Platform.isIOS ? Icons.arrow_back_ios_new_rounded : Icons.arrow_back,
color: Color.lerp(Colors.white, context.primaryColor, _scrollProgress),
shadows: [
_scrollProgress < 0.95
? Shadow(offset: const Offset(0, 2), blurRadius: 5, color: Colors.black.withValues(alpha: 0.5))
: const Shadow(offset: Offset(0, 2), blurRadius: 0, color: Colors.transparent),
],
flexibleSpace: Builder(
builder: (context) {
final settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
final scrollProgress = _calculateScrollProgress(settings);
),
onPressed: () {
context.pop();
},
),
),
actions: [
hideWhenSelecting(
IconButton(
icon: Icon(Icons.more_vert, color: actionIconColor, shadows: actionIconShadows),
onPressed: widget.onShowOptions,
),
),
],
flexibleSpace: Builder(
builder: (context) {
final settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
final scrollProgress = _calculateScrollProgress(settings);
// Update scroll progress for the leading button
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && _scrollProgress != scrollProgress) {
setState(() {
_scrollProgress = scrollProgress;
});
}
});
// Update scroll progress for the leading button
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && _scrollProgress != scrollProgress) {
setState(() {
_scrollProgress = scrollProgress;
});
}
});
return FlexibleSpaceBar(
centerTitle: true,
title: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: scrollProgress > 0.95
? Text(
widget.person.name,
style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.w600, fontSize: 18),
)
: null,
),
background: _ExpandedBackground(
scrollProgress: scrollProgress,
person: widget.person,
onNameTap: widget.onNameTap,
onBirthdayTap: widget.onBirthdayTap,
),
);
},
return FlexibleSpaceBar(
centerTitle: true,
title: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: scrollProgress > 0.95
? Text(
widget.person.name,
style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.w600, fontSize: 18),
)
: null,
),
background: IgnorePointer(
ignoring: isMultiSelectEnabled,
child: _ExpandedBackground(
scrollProgress: scrollProgress,
person: widget.person,
onNameTap: widget.onNameTap,
onBirthdayTap: widget.onBirthdayTap,
),
),
);
},
),
);
}
}