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
+40
View File
@@ -369,6 +369,46 @@ void main() {
const Size(78, 34),
);
});
testNooWidgets(
'labelOnlySelected shows every icon but only the selected label',
(tester, theme, c) async {
var value = 'all';
await pumpNoo(
tester,
StatefulBuilder(
builder: (context, setState) => NooSegmentedControl<String>(
fill: true,
labelOnlySelected: true,
value: value,
onChanged: (v) => setState(() => value = v),
options: const [
NooSegmentOption(
value: 'all',
icon: LucideIcons.layoutGrid,
label: 'All',
),
NooSegmentOption(
value: 'files',
icon: LucideIcons.file,
label: 'Files',
),
],
),
),
theme: theme,
);
expect(find.byIcon(LucideIcons.layoutGrid), findsOneWidget);
expect(find.byIcon(LucideIcons.file), findsOneWidget);
expect(find.text('All'), findsOneWidget);
expect(find.text('Files'), findsNothing);
await tester.tap(find.byIcon(LucideIcons.file));
await tester.pump();
expect(find.text('Files'), findsOneWidget);
expect(find.text('All'), findsNothing);
},
);
});
group('NooToggle', () {
+63
View File
@@ -105,6 +105,69 @@ void main() {
// Rows paint their own surface; the gaps show the list's line fill.
expect(decorationOf(tester, find.byKey(const Key('r1'))).color, c.line);
});
testNooWidgets('collapsible starts expanded, tapping the label hides '
'and re-shows the card', (tester, theme, c) async {
await pumpNoo(
tester,
SizedBox(
width: 360,
child: NooGroupedList(
label: 'Appearance',
collapsible: true,
children: [
Container(key: const Key('r1'), height: 52, color: c.surface),
],
),
),
theme: theme,
);
expect(find.byIcon(LucideIcons.chevronDown), findsOneWidget);
expect(tester.getSize(find.byKey(const Key('r1'))).height, 52);
AnimatedCrossFade crossFade() =>
tester.widget<AnimatedCrossFade>(find.byType(AnimatedCrossFade));
expect(crossFade().crossFadeState, CrossFadeState.showFirst);
await tester.tap(find.text('Appearance'));
await tester.pump();
expect(crossFade().crossFadeState, CrossFadeState.showSecond);
expect(
tester.widget<AnimatedRotation>(find.byType(AnimatedRotation)).turns,
0,
);
await tester.tap(find.text('Appearance'));
await tester.pump();
expect(crossFade().crossFadeState, CrossFadeState.showFirst);
expect(
tester.widget<AnimatedRotation>(find.byType(AnimatedRotation)).turns,
0.5,
);
});
testNooWidgets('non-collapsible has no chevron and ignores label taps', (
tester,
theme,
c,
) async {
await pumpNoo(
tester,
SizedBox(
width: 360,
child: NooGroupedList(
label: 'Accounts',
children: [
Container(key: const Key('r1'), height: 52, color: c.surface),
],
),
),
theme: theme,
);
expect(find.byIcon(LucideIcons.chevronDown), findsNothing);
await tester.tap(find.text('Accounts'));
await tester.pumpAndSettle();
expect(tester.getSize(find.byKey(const Key('r1'))).height, 52);
});
});
group('NooSelectionBar', () {