diff --git a/lib/theme/design_tokens.dart b/lib/theme/design_tokens.dart index 1cc7b1c..6cb33e8 100644 --- a/lib/theme/design_tokens.dart +++ b/lib/theme/design_tokens.dart @@ -133,7 +133,12 @@ class NooColors extends ThemeExtension { /// palettes. `success`/`successSoft`/`warning`/`warningSoft`/`info`/ /// `infoSoft` have no Material dynamic equivalent and are content-identity /// colors regardless (file-type tiles) rather than brand colors, so they - /// always come from the fixed palette matching [cs]'s brightness. + /// always come from the fixed palette matching [cs]'s brightness. Same + /// for `scrim`: Material 3's `ColorScheme.scrim` role is fully *opaque* + /// black (`0xFF000000`), meant to be used at reduced opacity by whoever + /// paints it, not painted directly - using it as-is for a sheet/dialog's + /// `barrierColor` hid the screen behind completely instead of dimming it, + /// so this uses the fixed palette's already-translucent scrim instead. factory NooColors.fromDynamicScheme(ColorScheme cs) { final fixed = cs.brightness == Brightness.dark ? dark : light; return NooColors( @@ -157,7 +162,7 @@ class NooColors extends ThemeExtension { warningSoft: fixed.warningSoft, info: fixed.info, infoSoft: fixed.infoSoft, - scrim: cs.scrim, + scrim: fixed.scrim, ); } diff --git a/lib/widgets/details/details_sheet.dart b/lib/widgets/details/details_sheet.dart index 7ffa9e9..740f1d0 100644 --- a/lib/widgets/details/details_sheet.dart +++ b/lib/widgets/details/details_sheet.dart @@ -96,7 +96,12 @@ class DetailsSheet extends StatelessWidget { /// [StatefulWidget] rather than three fields on [DetailsSheet] so the /// selection survives independently of how the header/container around it /// is built (mobile embeds this once, desktop hands it to [showNooDialog] -/// directly). +/// directly). All three tabs are built once and kept mounted in an +/// [IndexedStack] rather than swapped in/out of the tree - Versions and +/// Activity each fetch on their own first build (see their `_requested` +/// guard), so switching tabs via a bare `switch` on the selected type would +/// tear down and rebuild whichever tab isn't showing, discarding its +/// fetched data and re-requesting it every time the user switched back. class _DetailsBody extends StatefulWidget { final NextcloudItem item; @@ -138,11 +143,14 @@ class _DetailsBodyState extends State<_DetailsBody> { ], ), const SizedBox(height: NooSpace.lg), - switch (_tab) { - _DetailsTab.info => DetailsInfoTab(item: widget.item), - _DetailsTab.versions => DetailsVersionsTab(item: widget.item), - _DetailsTab.activity => DetailsActivityTab(item: widget.item), - }, + IndexedStack( + index: _tab.index, + children: [ + DetailsInfoTab(item: widget.item), + DetailsVersionsTab(item: widget.item), + DetailsActivityTab(item: widget.item), + ], + ), ], ); } diff --git a/test/widgets/noo/core_test.dart b/test/widgets/noo/core_test.dart index 564b9c4..30f04d2 100644 --- a/test/widgets/noo/core_test.dart +++ b/test/widgets/noo/core_test.dart @@ -57,6 +57,22 @@ void main() { }); }); + group('NooColors.fromDynamicScheme', () { + test('scrim comes from the fixed palette, not the opaque dynamic role', () { + // Material 3's ColorScheme.scrim is fully opaque black - using it + // directly as a sheet/dialog barrierColor hid the screen behind + // completely instead of dimming it. + final dynamicScheme = ColorScheme.fromSeed( + seedColor: const Color(0xFF009688), + brightness: Brightness.light, + ); + expect(dynamicScheme.scrim.a, 1.0); + final nooColors = NooColors.fromDynamicScheme(dynamicScheme); + expect(nooColors.scrim, NooColors.light.scrim); + expect(nooColors.scrim.a, lessThan(1.0)); + }); + }); + group('NooAvatar', () { testNooWidgets('current user uses accent-soft/accent-text', ( tester,