mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-02 14:19:45 +02:00
fix(mornye): preserve navbar blur during collapse animations
This commit is contained in:
1 parent
d1eba29a4a
commit
fe30871e36
3 files changed
+323
-159
No files matched your search
@@ -61,7 +61,7 @@ class MornyeChromeController extends ValueNotifier<bool> {
|
||||
|
||||
/// A single mini-player survives the transition, preserving its artwork Hero,
|
||||
/// playback controls and swipe-to-dismiss state as the tabs fold away.
|
||||
class MornyeBottomBar extends ConsumerWidget {
|
||||
class MornyeBottomBar extends ConsumerStatefulWidget {
|
||||
const MornyeBottomBar({
|
||||
super.key,
|
||||
required this.collapsed,
|
||||
@@ -82,7 +82,59 @@ class MornyeBottomBar extends ConsumerWidget {
|
||||
final bool blurEnabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
ConsumerState<MornyeBottomBar> createState() => _MornyeBottomBarState();
|
||||
}
|
||||
|
||||
class _MornyeBottomBarState extends ConsumerState<MornyeBottomBar>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final _controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 380),
|
||||
value: widget.collapsed ? 1 : 0,
|
||||
);
|
||||
late final _collapse = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeInOutCubic,
|
||||
);
|
||||
late final _tabOpacity = ReverseAnimation(_collapse);
|
||||
|
||||
void _updateCollapse() {
|
||||
final target = widget.collapsed ? 1.0 : 0.0;
|
||||
if (MediaQuery.disableAnimationsOf(context)) {
|
||||
_controller.value = target;
|
||||
} else {
|
||||
_controller.animateTo(target);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_updateCollapse();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(MornyeBottomBar oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.collapsed != widget.collapsed) _updateCollapse();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_collapse.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final collapsed = widget.collapsed;
|
||||
final destinations = widget.destinations;
|
||||
final selectedIndex = widget.selectedIndex;
|
||||
final onSelected = widget.onSelected;
|
||||
final onHome = widget.onHome;
|
||||
final onSearch = widget.onSearch;
|
||||
final blurEnabled = widget.blurEnabled;
|
||||
final hasPlayer = ref.watch(
|
||||
currentMediaItemProvider.select((item) => item.value != null),
|
||||
);
|
||||
@@ -95,14 +147,18 @@ class MornyeBottomBar extends ConsumerWidget {
|
||||
);
|
||||
final tabGap = glassTabs ? 0.0 : 8.0;
|
||||
// These contents do not depend on animation progress. Retain their widget
|
||||
// instances so folding only updates size/opacity wrappers each frame.
|
||||
Widget sideSurface() => RepaintBoundary(
|
||||
child: MornyeGlass.navigation(
|
||||
blurEnabled: blurEnabled,
|
||||
strongTint: true,
|
||||
tintOpacity: MornyeTheme.navigationOpacity(context),
|
||||
radius: 26,
|
||||
child: const SizedBox.expand(),
|
||||
// instances so folding only updates geometry and foreground opacity. A
|
||||
// backdrop inside a fading layer loses access to the page behind the bar.
|
||||
Widget sideSurface() => ScaleTransition(
|
||||
scale: _collapse,
|
||||
child: RepaintBoundary(
|
||||
child: MornyeGlass.navigation(
|
||||
blurEnabled: blurEnabled,
|
||||
strongTint: true,
|
||||
tintOpacity: MornyeTheme.navigationOpacity(context),
|
||||
radius: 26,
|
||||
child: const SizedBox.expand(),
|
||||
),
|
||||
),
|
||||
);
|
||||
final leadingSurface = sideSurface();
|
||||
@@ -122,6 +178,7 @@ class MornyeBottomBar extends ConsumerWidget {
|
||||
index == destinations.length - 1 ? onSearch() : onSelected(index),
|
||||
blurEnabled: blurEnabled,
|
||||
liquidGlass: liquidGlass,
|
||||
contentOpacity: _tabOpacity,
|
||||
hiddenIconIndices: hideMovingIcons
|
||||
? {leadingIndex, destinations.length - 1}
|
||||
: const {},
|
||||
@@ -158,13 +215,10 @@ class MornyeBottomBar extends ConsumerWidget {
|
||||
(constraints.maxWidth - tabInset * 2) /
|
||||
destinations.length *
|
||||
(index + 0.5);
|
||||
return TweenAnimationBuilder<double>(
|
||||
tween: Tween(end: collapsed ? 1 : 0),
|
||||
duration: MediaQuery.disableAnimationsOf(context)
|
||||
? Duration.zero
|
||||
: const Duration(milliseconds: 380),
|
||||
curve: Curves.easeInOutCubic,
|
||||
builder: (context, amount, _) {
|
||||
return AnimatedBuilder(
|
||||
animation: _collapse,
|
||||
builder: (context, _) {
|
||||
final amount = _collapse.value;
|
||||
Widget movingIcon({
|
||||
required bool leading,
|
||||
required Widget surface,
|
||||
@@ -188,9 +242,7 @@ class MornyeBottomBar extends ConsumerWidget {
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Opacity(opacity: amount, child: surface),
|
||||
),
|
||||
Positioned.fill(child: surface),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
@@ -269,10 +321,7 @@ class MornyeBottomBar extends ConsumerWidget {
|
||||
ignoring: amount > 0.5,
|
||||
child: ExcludeSemantics(
|
||||
excluding: amount > 0.5,
|
||||
child: Opacity(
|
||||
opacity: 1 - amount,
|
||||
child: amount == 0 ? fullTabs : foldingTabs,
|
||||
),
|
||||
child: amount == 0 ? fullTabs : foldingTabs,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+147
-135
@@ -612,6 +612,7 @@ class MornyeTabBar extends StatelessWidget {
|
||||
required this.blurEnabled,
|
||||
this.liquidGlass = true,
|
||||
this.hiddenIconIndices = const {},
|
||||
this.contentOpacity = const AlwaysStoppedAnimation(1),
|
||||
});
|
||||
|
||||
/// Shader capsule and travelling pill; otherwise the frosted row below.
|
||||
@@ -638,6 +639,10 @@ class MornyeTabBar extends StatelessWidget {
|
||||
// The active and Search icons move independently while the capsule folds.
|
||||
final Set<int> hiddenIconIndices;
|
||||
|
||||
/// Fade icons and labels while the capsule folds. Its backdrop must stay
|
||||
/// outside the fade layer so it can still sample the page during motion.
|
||||
final Animation<double> contentOpacity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
@@ -673,79 +678,83 @@ class MornyeTabBar extends StatelessWidget {
|
||||
child: const SizedBox.expand(),
|
||||
),
|
||||
),
|
||||
NativeGlassMetrics(
|
||||
child: LiquidGlassTabBar.withImpeller(
|
||||
width: constraints.maxWidth,
|
||||
height: 64,
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
selectedIndex: selectedIndex < 0 ? 0 : selectedIndex,
|
||||
onChanged: onSelected,
|
||||
style: LiquidGlassTabBar.defaultStyle.copyWith(
|
||||
// The frosted base owns the subtle outline. Disable the
|
||||
// package's default specular rim around the whole capsule.
|
||||
shape: const LiquidGlassShape.continuousRoundedRectangle(
|
||||
cornerRadius: 32,
|
||||
borderWidth: 0,
|
||||
lightIntensity: 0,
|
||||
),
|
||||
// The sibling surface already supplies tint and blur. Keep
|
||||
// the lens clear so the moving pill can refract the icons.
|
||||
appearance: const LiquidGlassAppearance(),
|
||||
refraction: const LiquidGlassRefraction(
|
||||
distortion: 0,
|
||||
chromaticAberration: 0,
|
||||
),
|
||||
),
|
||||
itemStyle: LiquidGlassTabItemStyle(
|
||||
selectedColor: scheme.primary,
|
||||
unselectedColor: scheme.onSurface,
|
||||
iconSize: 25,
|
||||
labelFontSize: 11,
|
||||
),
|
||||
pillStyle: LiquidGlassTabPillStyle(
|
||||
mode: LiquidGlassPillMode.impellerOnly,
|
||||
show: selectedIndex >= 0,
|
||||
color: selectionFill,
|
||||
animated: true,
|
||||
// Keep the moving refractive pill, without stacking the
|
||||
// package's second magnifier lens beneath it.
|
||||
magnifierPill: const LiquidGlassTabMagnifierPillStyle(
|
||||
enabled: false,
|
||||
),
|
||||
),
|
||||
items: [
|
||||
for (final (index, destination) in destinations.indexed)
|
||||
LiquidGlassTabBarItem(
|
||||
label: destination.label,
|
||||
iconBuilder: (context, icon) => IconTheme(
|
||||
data: IconThemeData(
|
||||
size: icon.size,
|
||||
color: icon.selected && selectedIndex >= 0
|
||||
? scheme.primary
|
||||
: inactiveIconColor,
|
||||
FadeTransition(
|
||||
opacity: contentOpacity,
|
||||
child: NativeGlassMetrics(
|
||||
child: LiquidGlassTabBar.withImpeller(
|
||||
width: constraints.maxWidth,
|
||||
height: 64,
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
selectedIndex: selectedIndex < 0 ? 0 : selectedIndex,
|
||||
onChanged: onSelected,
|
||||
style: LiquidGlassTabBar.defaultStyle.copyWith(
|
||||
// The frosted base owns the subtle outline. Disable the
|
||||
// package's default specular rim around the whole capsule.
|
||||
shape:
|
||||
const LiquidGlassShape.continuousRoundedRectangle(
|
||||
cornerRadius: 32,
|
||||
borderWidth: 0,
|
||||
lightIntensity: 0,
|
||||
),
|
||||
child: Opacity(
|
||||
opacity: hiddenIconIndices.contains(index)
|
||||
? 0
|
||||
: 1,
|
||||
child: destination.icon,
|
||||
),
|
||||
),
|
||||
labelBuilder: (context, label) => Text(
|
||||
destination.label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.labelSmall
|
||||
?.copyWith(
|
||||
fontSize: label.textStyle.fontSize,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: selectedIndex < 0
|
||||
? scheme.onSurface
|
||||
: label.textStyle.color,
|
||||
),
|
||||
),
|
||||
// The sibling surface already supplies tint and blur. Keep
|
||||
// the lens clear so the moving pill can refract the icons.
|
||||
appearance: const LiquidGlassAppearance(),
|
||||
refraction: const LiquidGlassRefraction(
|
||||
distortion: 0,
|
||||
chromaticAberration: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
itemStyle: LiquidGlassTabItemStyle(
|
||||
selectedColor: scheme.primary,
|
||||
unselectedColor: scheme.onSurface,
|
||||
iconSize: 25,
|
||||
labelFontSize: 11,
|
||||
),
|
||||
pillStyle: LiquidGlassTabPillStyle(
|
||||
mode: LiquidGlassPillMode.impellerOnly,
|
||||
show: selectedIndex >= 0,
|
||||
color: selectionFill,
|
||||
animated: true,
|
||||
// Keep the moving refractive pill, without stacking the
|
||||
// package's second magnifier lens beneath it.
|
||||
magnifierPill: const LiquidGlassTabMagnifierPillStyle(
|
||||
enabled: false,
|
||||
),
|
||||
),
|
||||
items: [
|
||||
for (final (index, destination) in destinations.indexed)
|
||||
LiquidGlassTabBarItem(
|
||||
label: destination.label,
|
||||
iconBuilder: (context, icon) => IconTheme(
|
||||
data: IconThemeData(
|
||||
size: icon.size,
|
||||
color: icon.selected && selectedIndex >= 0
|
||||
? scheme.primary
|
||||
: inactiveIconColor,
|
||||
),
|
||||
child: Opacity(
|
||||
opacity: hiddenIconIndices.contains(index)
|
||||
? 0
|
||||
: 1,
|
||||
child: destination.icon,
|
||||
),
|
||||
),
|
||||
labelBuilder: (context, label) => Text(
|
||||
destination.label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.labelSmall
|
||||
?.copyWith(
|
||||
fontSize: label.textStyle.fontSize,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: selectedIndex < 0
|
||||
? scheme.onSurface
|
||||
: label.textStyle.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// With no selected tab the package still needs an internal
|
||||
@@ -784,77 +793,80 @@ class MornyeTabBar extends StatelessWidget {
|
||||
blurEnabled: blurEnabled,
|
||||
strongTint: true,
|
||||
tintOpacity: MornyeTheme.navigationOpacity(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Row(
|
||||
children: [
|
||||
for (var index = 0; index < destinations.length; index++)
|
||||
Expanded(
|
||||
child: Semantics(
|
||||
selected: index == selectedIndex,
|
||||
button: true,
|
||||
label: destinations[index].label,
|
||||
excludeSemantics: true,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
onTap: () => onSelected(index),
|
||||
child: AnimatedContainer(
|
||||
duration: MediaQuery.disableAnimationsOf(context)
|
||||
? Duration.zero
|
||||
: const Duration(milliseconds: 220),
|
||||
constraints: const BoxConstraints(minHeight: 54),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: index == selectedIndex
|
||||
? selectionFill
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
size: 25,
|
||||
color: index == selectedIndex
|
||||
? scheme.primary
|
||||
: inactiveIconColor,
|
||||
child: FadeTransition(
|
||||
opacity: contentOpacity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Row(
|
||||
children: [
|
||||
for (var index = 0; index < destinations.length; index++)
|
||||
Expanded(
|
||||
child: Semantics(
|
||||
selected: index == selectedIndex,
|
||||
button: true,
|
||||
label: destinations[index].label,
|
||||
excludeSemantics: true,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
onTap: () => onSelected(index),
|
||||
child: AnimatedContainer(
|
||||
duration: MediaQuery.disableAnimationsOf(context)
|
||||
? Duration.zero
|
||||
: const Duration(milliseconds: 220),
|
||||
constraints: const BoxConstraints(minHeight: 54),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: index == selectedIndex
|
||||
? selectionFill
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
size: 25,
|
||||
color: index == selectedIndex
|
||||
? scheme.primary
|
||||
: inactiveIconColor,
|
||||
),
|
||||
// Keep tab selection quiet, as in Mornye. Badges
|
||||
// stay live without the Material bounce/spin.
|
||||
child: Opacity(
|
||||
opacity: hiddenIconIndices.contains(index)
|
||||
? 0
|
||||
: 1,
|
||||
child: destinations[index].icon,
|
||||
),
|
||||
),
|
||||
// Keep tab selection quiet, as in Mornye. Badges
|
||||
// stay live without the Material bounce/spin.
|
||||
child: Opacity(
|
||||
opacity: hiddenIconIndices.contains(index)
|
||||
? 0
|
||||
: 1,
|
||||
child: destinations[index].icon,
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
destinations[index].label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.labelSmall
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: index == selectedIndex
|
||||
? scheme.primary
|
||||
: scheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
destinations[index].label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.labelSmall
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: index == selectedIndex
|
||||
? scheme.primary
|
||||
: scheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -39,6 +39,7 @@ void main() {
|
||||
WidgetTester tester, {
|
||||
bool withPlayer = true,
|
||||
bool blur = false,
|
||||
bool liquidGlass = true,
|
||||
Brightness brightness = Brightness.light,
|
||||
Color? backdrop,
|
||||
Color? chromeSurface,
|
||||
@@ -67,6 +68,7 @@ void main() {
|
||||
playbackStateProvider.overrideWith((ref) => const Stream.empty()),
|
||||
lowEndDeviceProvider.overrideWithValue(!blur),
|
||||
backdropBlurEnabledProvider.overrideWithValue(blur),
|
||||
mornyeLiquidGlassProvider.overrideWithValue(liquidGlass),
|
||||
],
|
||||
child: MaterialApp(
|
||||
theme: MornyeTheme.build(
|
||||
@@ -135,6 +137,91 @@ void main() {
|
||||
|
||||
const albumBlue = Color(0xff464566);
|
||||
|
||||
for (final liquidGlass in [false, true]) {
|
||||
testWidgets(
|
||||
'navbar keeps its backdrop blurred throughout motion (liquid: $liquidGlass)',
|
||||
(tester) async {
|
||||
await pumpShell(
|
||||
tester,
|
||||
blur: true,
|
||||
liquidGlass: liquidGlass,
|
||||
brightness: Brightness.dark,
|
||||
glassClarity: 1,
|
||||
body: const SizedBox.expand(
|
||||
child: CustomPaint(painter: _StripedBackdrop()),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> expectBlur(Offset center, String phase) async {
|
||||
final variation = await tester.runAsync(() async {
|
||||
final boundary = tester.renderObject<RenderRepaintBoundary>(
|
||||
find.byKey(capture),
|
||||
);
|
||||
final image = await boundary.toImage();
|
||||
final bytes = (await image.toByteData(
|
||||
format: ui.ImageByteFormat.rawRgba,
|
||||
))!;
|
||||
final values = <int>[];
|
||||
for (var x = -9; x <= 9; x++) {
|
||||
final offset =
|
||||
(center.dy.floor() * image.width + center.dx.floor() + x) * 4;
|
||||
values.add(bytes.getUint8(offset));
|
||||
}
|
||||
image.dispose();
|
||||
values.sort();
|
||||
return values.last - values.first;
|
||||
});
|
||||
expect(variation, lessThan(20), reason: phase);
|
||||
}
|
||||
|
||||
const capsuleSample = Offset(196, 745);
|
||||
await expectBlur(capsuleSample, 'Expanded capsule');
|
||||
final gesture = await tester.startGesture(
|
||||
tester.getCenter(find.text('Home').first),
|
||||
);
|
||||
await gesture.moveBy(const Offset(100, 0));
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
await expectBlur(capsuleSample, 'Dragging the selected tab');
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
for (final collapsed in [true, false]) {
|
||||
chrome.value = collapsed;
|
||||
await tester.pump();
|
||||
if (collapsed) {
|
||||
await tester.pump(const Duration(milliseconds: 190));
|
||||
await expectBlur(
|
||||
capsuleSample,
|
||||
'Capsule halfway to collapsed=$collapsed',
|
||||
);
|
||||
}
|
||||
// Sample below the icons once the growing circles are large enough to
|
||||
// expose their backdrop; the page stripes must stay blurred while moving.
|
||||
await tester.pump(const Duration(milliseconds: 95));
|
||||
for (final key in [
|
||||
'mornye-compact-leading',
|
||||
'mornye-compact-search',
|
||||
]) {
|
||||
final center = tester.getCenter(find.byKey(ValueKey(key)));
|
||||
await expectBlur(
|
||||
center + const Offset(0, 18),
|
||||
'$key moving to collapsed=$collapsed',
|
||||
);
|
||||
}
|
||||
if (!collapsed) {
|
||||
await tester.pump(const Duration(milliseconds: 95));
|
||||
await expectBlur(
|
||||
capsuleSample,
|
||||
'Capsule halfway to collapsed=$collapsed',
|
||||
);
|
||||
}
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (final blur in [false, true]) {
|
||||
testWidgets('active edge icons and tabs stay synchronized (glass: $blur)', (
|
||||
tester,
|
||||
@@ -718,3 +805,19 @@ void main() {
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
|
||||
class _StripedBackdrop extends CustomPainter {
|
||||
const _StripedBackdrop();
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint();
|
||||
for (var x = 0.0; x < size.width; x += 12) {
|
||||
paint.color = (x / 12).round().isEven ? Colors.white : Colors.black;
|
||||
canvas.drawRect(Rect.fromLTWH(x, 0, 12, size.height), paint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_StripedBackdrop oldDelegate) => false;
|
||||
}
|
||||
Reference in new issue
Block a user