Rebuild UI on the Noo design system
Replaces the Material 3 screens with the Noo design system: warm neutrals, one violet accent, pill controls, Schibsted Grotesk/ Instrument Sans, Lucide icons, no gradients/shadows. - New lib/widgets/noo/ component kit (core, lists, files, media, nav, overlays) plus lib/theme/design_tokens.dart for the color/type/space/ radius/motion tokens. - Rebuilt the app shell (top/bottom bars, drawer, desktop sidebar and toolbar), every tab (Files/Offline, Photos, Favorites, Recent, Activity, Trash, Shares), Settings, the lock and login screens, the media viewer, search, the details/share sheets, and the share-upload and move/copy destination pickers. - Added a design canvas (linked from DESIGN_SYSTEM.md) covering the screens the spec didn't already describe, with matching Android and iOS chrome; wrote up the approved recipes into DESIGN_SYSTEM.md §4. - Removed now-dead legacy widgets (media_grid_tile, swipeable_item, sync_status_badge, selectable_thumbnail) and updated architecture.md/styling.md/standards.md to describe the new structure and component/testing conventions. - Added widget tests for the noo/ component kit. This is a UI-only rework: no provider/model/service behavior changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../theme/design_tokens.dart';
|
||||
import '../noo/lists/noo_grouped_list.dart';
|
||||
import '../noo/noo_layout.dart';
|
||||
import '../noo/overlays/noo_dialog.dart';
|
||||
import '../noo/overlays/noo_sheet.dart';
|
||||
|
||||
/// One block of Settings (DESIGN_SYSTEM.md 4's 8-part order): a
|
||||
/// [NooGroupedList] on mobile (label above a radius-20 card), or a titled,
|
||||
/// bordered radius-20 card holding a flat row group on desktop
|
||||
/// ("Mobile uses one column of grouped lists. Desktop uses a 2-column grid
|
||||
/// of cards with a 1px line and radius 20."). Shared by every
|
||||
/// `lib/widgets/settings/*.dart` section so both layouts stay in sync from
|
||||
/// one place.
|
||||
class SettingsSection extends StatelessWidget {
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final List<Widget> children;
|
||||
|
||||
const SettingsSection({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
required this.children,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = context.nooColors;
|
||||
if (!NooLayout.isDesktop(context)) {
|
||||
return NooGroupedList(
|
||||
label: title,
|
||||
footer: subtitle != null ? Text(subtitle!) : null,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface,
|
||||
border: Border.all(color: colors.line),
|
||||
borderRadius: BorderRadius.circular(NooRadii.card),
|
||||
),
|
||||
padding: const EdgeInsets.all(NooSpace.lg),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: NooText.cardTitle.copyWith(color: colors.fg1)),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(subtitle!, style: NooText.meta.copyWith(color: colors.fg3)),
|
||||
],
|
||||
const SizedBox(height: NooSpace.md),
|
||||
FlatRowGroup(children: children),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The line-gapped row stack [NooGroupedList] draws internally, without its
|
||||
/// own outer card/label - used for [SettingsSection]'s desktop card body,
|
||||
/// which already supplies the surrounding card chrome, so nesting a second
|
||||
/// radius-20 card there would double up the framing. Also reused directly
|
||||
/// by `settings_tabs.dart`, whose desktop card mixes plain rows with a
|
||||
/// reorderable list under one shared title/border instead of going through
|
||||
/// [SettingsSection] itself.
|
||||
class FlatRowGroup extends StatelessWidget {
|
||||
final List<Widget> children;
|
||||
const FlatRowGroup({super.key, required this.children});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = context.nooColors;
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(NooRadii.input),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(color: colors.line),
|
||||
child: Column(
|
||||
children: [
|
||||
for (var i = 0; i < children.length; i++) ...[
|
||||
if (i > 0) const SizedBox(height: 1),
|
||||
children[i],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The picker pattern for every "value + chevron" settings row (cache
|
||||
/// policy, swipe actions, default tab...): a titled option list, a
|
||||
/// [NooGroupedList] sheet on mobile or a [NooDialog] on desktop (mirrors
|
||||
/// `files_controls_row.dart`'s sort/filter sheets). Each option row should
|
||||
/// call `Navigator.pop(context)` itself after applying its choice.
|
||||
void showSettingsPicker(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required List<Widget> options,
|
||||
}) {
|
||||
if (NooLayout.isDesktop(context)) {
|
||||
showNooDialog(context, title: title, children: [Column(children: options)]);
|
||||
} else {
|
||||
showNooSheet(context, children: [NooGroupedList(label: title, children: options)]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user