fix: system appbar icon color has incorrect color in asset viewer (#29885)

This commit is contained in:
Alex
2026-07-13 14:50:38 -05:00
committed by GitHub
parent 99883096d6
commit f2ddace584
2 changed files with 134 additions and 40 deletions
@@ -81,6 +81,12 @@ class AssetViewer extends ConsumerStatefulWidget {
}
class _AssetViewerState extends ConsumerState<AssetViewer> {
static const _viewerOverlayStyle = SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
statusBarBrightness: Brightness.dark,
systemNavigationBarIconBrightness: Brightness.light,
);
late final _heroOffset = widget.heroOffset ?? TabsRouterScope.of(context)?.controller.activeIndex ?? 0;
late final _pageController = PageController(initialPage: widget.initialIndex);
late final _preloader = AssetPreloader(timelineService: ref.read(timelineServiceProvider), mounted: () => mounted);
@@ -280,49 +286,52 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
_setSystemUIMode(controls, details);
});
return Scaffold(
backgroundColor: backgroundColor,
resizeToAvoidBottomInset: false,
appBar: const ViewerTopAppBar(),
extendBody: true,
extendBodyBehindAppBar: true,
floatingActionButton: IgnorePointer(
ignoring: !showingControls,
child: AnimatedOpacity(
opacity: showingControls ? 1.0 : 0.0,
duration: Durations.short2,
child: const DownloadStatusFloatingButton(),
),
),
bottomNavigationBar: const ViewerBottomAppBar(),
body: Stack(
children: [
NotificationListener<ScrollEndNotification>(
onNotification: _onScrollEnd,
child: PhotoViewGestureDetectorScope(
axis: Axis.horizontal,
child: PageView.builder(
controller: _pageController,
physics: isZoomed
? const NeverScrollableScrollPhysics()
: CurrentPlatform.isIOS
? const FastScrollPhysics()
: const FastClampingScrollPhysics(),
itemCount: _totalAssets,
itemBuilder: (context, index) =>
AssetPage(index: index, heroOffset: _heroOffset, onTapNavigate: _onTapNavigate),
),
),
return AnnotatedRegion(
value: _viewerOverlayStyle,
child: Scaffold(
backgroundColor: backgroundColor,
resizeToAvoidBottomInset: false,
appBar: const ViewerTopAppBar(),
extendBody: true,
extendBodyBehindAppBar: true,
floatingActionButton: IgnorePointer(
ignoring: !showingControls,
child: AnimatedOpacity(
opacity: showingControls ? 1.0 : 0.0,
duration: Durations.short2,
child: const DownloadStatusFloatingButton(),
),
if (!CurrentPlatform.isIOS)
IgnorePointer(
child: AnimatedContainer(
duration: Durations.short2,
color: Colors.black.withValues(alpha: showingDetails ? 0.6 : 0.0),
height: context.padding.top,
),
bottomNavigationBar: const ViewerBottomAppBar(),
body: Stack(
children: [
NotificationListener<ScrollEndNotification>(
onNotification: _onScrollEnd,
child: PhotoViewGestureDetectorScope(
axis: Axis.horizontal,
child: PageView.builder(
controller: _pageController,
physics: isZoomed
? const NeverScrollableScrollPhysics()
: CurrentPlatform.isIOS
? const FastScrollPhysics()
: const FastClampingScrollPhysics(),
itemCount: _totalAssets,
itemBuilder: (context, index) =>
AssetPage(index: index, heroOffset: _heroOffset, onTapNavigate: _onTapNavigate),
),
),
),
],
if (!CurrentPlatform.isIOS)
IgnorePointer(
child: AnimatedContainer(
duration: Durations.short2,
color: Colors.black.withValues(alpha: showingDetails ? 0.6 : 0.0),
height: context.padding.top,
),
),
],
),
),
);
}
@@ -0,0 +1,85 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/locales.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/generated/codegen_loader.g.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import '../../../fixtures/asset.stub.dart';
class _SeededAssetViewerNotifier extends AssetViewerStateNotifier {
@override
AssetViewerState build() {
super.build();
return AssetViewerState(currentAsset: LocalAssetStub.image1);
}
}
TimelineService _stubTimelineService() {
return TimelineService((
assetSource: (index, count) async => [LocalAssetStub.image1],
bucketSource: () => Stream.value(const [Bucket(assetCount: 1)]),
origin: TimelineOrigin.main,
));
}
void main() {
testWidgets('status bar icons are light while the asset viewer is open in light mode', (tester) async {
// Emulate arriving from a light-themed page whose AppBar set dark status
// bar icons (the state the viewer is opened from in light mode).
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
await tester.pumpWidget(
EasyLocalization(
supportedLocales: locales.values.toList(),
path: translationsPath,
startLocale: locales.values.first,
fallbackLocale: locales.values.first,
saveLocale: false,
useFallbackTranslations: true,
assetLoader: const CodegenLoader(),
child: ProviderScope(
overrides: [
timelineServiceProvider.overrideWithValue(_stubTimelineService()),
assetViewerProvider.overrideWith(_SeededAssetViewerNotifier.new),
],
child: Builder(
builder: (context) => MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
home: const Material(child: AssetViewer(initialIndex: 0)),
),
),
),
),
);
// Let post-frame callbacks and the initial frames run without waiting for
// infinite animations (loading spinners) to settle.
await tester.pump();
await tester.pump(const Duration(milliseconds: 600));
// Asset thumbnails cannot load in the test environment (no platform
// channels / real HTTP). Those image errors are irrelevant to the system
// chrome behaviour under test, so drain them.
tester.takeException();
expect(
SystemChrome.latestStyle?.statusBarIconBrightness,
Brightness.light,
reason:
'The asset viewer draws over a black background, so status bar '
'icons must be light regardless of the app theme',
);
});
}