Fix share copy-link, destination-picker filters, and type-filter UX

- SharesController's fetchShares() built NextcloudShare objects through
  its own thin inline parser instead of _shareFromJson, so url/token/
  permissions/expireDate were always null - a public link share could
  never show "Copy link" in the Shares tab. Reuses _shareFromJson.
- Upload/Move/Copy's destination picker hid External storage in its
  filter sheet (showStorageScope: false) unlike Files' own controls
  row, despite the design doc already calling for full parity.
- Files' and Photos' type filter (All/Files/Folders, All/Photos/Videos)
  now render as one consistent style in both filter sheets: every icon
  always shown, label only on the selected segment
  (NooSegmentedControl's new labelOnlySelected), replacing Files' old
  checkmark list and Photos' own always-labelled track.
- Fixes Photos' type filter only visually updating after closing and
  reopening the filter sheet - it lived on PhotosView's own State, so
  Listenable.merge([photos, files]) never rebuilt the sheet when it
  changed; a StatefulBuilder now gives it that trigger.
- Replaces Settings' trailing section jump rail with per-section
  collapsible cards (NooGroupedList's new collapsible param, expanded
  by default) - one less parallel way to navigate a long screen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 03:49:24 -04:00
co-authored by Claude Sonnet 5
parent 4e6477a3ae
commit f9653bc0f2
14 changed files with 417 additions and 326 deletions
@@ -11,7 +11,11 @@ class NooSegmentOption<T> {
enum NooSegmentedSize { md, sm, xs }
const _heights = {NooSegmentedSize.md: 34.0, NooSegmentedSize.sm: 30.0, NooSegmentedSize.xs: 28.0};
const _heights = {
NooSegmentedSize.md: 34.0,
NooSegmentedSize.sm: 30.0,
NooSegmentedSize.xs: 28.0,
};
/// Pill track with an accent-soft active segment - List/Grid, Shares scope,
/// Theme (Noo Design System project, `components/core/SegmentedControl.jsx`).
@@ -21,12 +25,22 @@ class NooSegmentedControl<T> extends StatelessWidget {
final List<NooSegmentOption<T>> options;
final T value;
final ValueChanged<T>? onChanged;
/// Use surface on bg, surface-2 on surface.
final bool onSurface;
final NooSegmentedSize size;
final bool fill;
final bool iconOnly;
/// Every segment always shows its icon, but only the *selected* one also
/// shows its label - a middle ground between `iconOnly` (no segment is
/// ever distinguishable without a label) and the default (every segment's
/// label always visible, crowding a 3+-option control). Used for Files'/
/// Photos' type filter (All/Files/Folders, All/Photos/Videos) so both use
/// the same List/Grid-toggle-style pill instead of Files' old checkmark
/// list and Photos' own always-labelled control.
final bool labelOnlySelected;
const NooSegmentedControl({
super.key,
required this.options,
@@ -36,6 +50,7 @@ class NooSegmentedControl<T> extends StatelessWidget {
this.size = NooSegmentedSize.md,
this.fill = false,
this.iconOnly = false,
this.labelOnlySelected = false,
});
@override
@@ -44,10 +59,13 @@ class NooSegmentedControl<T> extends StatelessWidget {
final h = iconOnly ? 28.0 : (_heights[size] ?? 34.0);
final segments = options.map((o) {
final on = o.value == value;
final showLabel = labelOnlySelected ? on : !iconOnly;
final child = Container(
height: h,
width: iconOnly ? 36 : null,
padding: iconOnly || fill ? EdgeInsets.zero : const EdgeInsets.symmetric(horizontal: 12),
padding: iconOnly || fill
? EdgeInsets.zero
: const EdgeInsets.symmetric(horizontal: 12),
alignment: Alignment.center,
decoration: BoxDecoration(
color: on ? colors.accentSoft : Colors.transparent,
@@ -62,8 +80,9 @@ class NooSegmentedControl<T> extends StatelessWidget {
size: iconOnly ? (size == NooSegmentedSize.md ? 18 : 16) : 14,
color: on ? colors.accentText : colors.fg2,
),
if (o.icon != null && !iconOnly && o.label != null) const SizedBox(width: 6),
if (!iconOnly && o.label != null)
if (o.icon != null && showLabel && o.label != null)
const SizedBox(width: 6),
if (showLabel && o.label != null)
Text(
o.label!,
style: NooText.body.copyWith(
@@ -89,7 +108,10 @@ class NooSegmentedControl<T> extends StatelessWidget {
color: onSurface ? colors.surface2 : colors.surface,
borderRadius: BorderRadius.circular(NooRadii.pill),
),
child: Row(mainAxisSize: fill ? MainAxisSize.max : MainAxisSize.min, children: segments),
child: Row(
mainAxisSize: fill ? MainAxisSize.max : MainAxisSize.min,
children: segments,
),
);
}
}