Fix black sheet/dialog barriers and Details tabs refetching on switch

Every sheet and dialog barrier goes solid black instead of dimming the
screen behind it whenever dynamic color ("Match wallpaper") is on -
Material 3's ColorScheme.scrim role is fully opaque black by spec,
meant to be used at reduced opacity by whoever paints it, but
NooColors.fromDynamicScheme mapped it straight into barrierColor.
Use the fixed palette's already-translucent scrim instead, matching
how success/warning/info already fall back to it since they have no
dynamic equivalent either.

Also fix the Details sheet's Info/Versions/Activity segmented control
refetching a tab's data every time it's selected: switching on the
active tab picked which single widget to build, so Flutter tore down
and recreated whichever tab wasn't showing on every switch, discarding
its fetched data. Keep all three mounted in an IndexedStack instead
(the same pattern the app's own main tab bar already uses for this
exact reason), so each tab's state - and its one-shot fetch - survives
switching back and forth.
This commit is contained in:
2026-09-28 17:49:26 -04:00
parent 1b8bbaa22f
commit 8d17ae1f4b
3 changed files with 37 additions and 8 deletions
+7 -2
View File
@@ -133,7 +133,12 @@ class NooColors extends ThemeExtension<NooColors> {
/// 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<NooColors> {
warningSoft: fixed.warningSoft,
info: fixed.info,
infoSoft: fixed.infoSoft,
scrim: cs.scrim,
scrim: fixed.scrim,
);
}
+14 -6
View File
@@ -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),
],
),
],
);
}
+16
View File
@@ -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,