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,132 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../models/app_tab.dart';
|
||||
import '../../providers/session_controller.dart';
|
||||
import '../../providers/sync_status_controller.dart';
|
||||
import '../../services/sync_service.dart';
|
||||
import '../../theme/design_tokens.dart';
|
||||
import '../app_tab_view_builder.dart';
|
||||
import '../noo/core/noo_button.dart';
|
||||
import '../noo/core/noo_toggle.dart';
|
||||
import '../noo/lists/noo_settings_row.dart';
|
||||
import '../noo/nav/noo_top_bar.dart';
|
||||
import '../noo/nav/noo_toolbar.dart';
|
||||
import '../noo/noo_layout.dart';
|
||||
import 'settings_section.dart';
|
||||
|
||||
/// Settings section 4: device sync (which folders/files mirror locally,
|
||||
/// notifications, cellular use, and a manual "Sync now").
|
||||
class SettingsFileSyncSection extends StatefulWidget {
|
||||
const SettingsFileSyncSection({super.key});
|
||||
|
||||
@override
|
||||
State<SettingsFileSyncSection> createState() => _SettingsFileSyncSectionState();
|
||||
}
|
||||
|
||||
class _SettingsFileSyncSectionState extends State<SettingsFileSyncSection> {
|
||||
bool _syncingNow = false;
|
||||
|
||||
Future<void> _syncNow(SessionController session, SyncStatusController sync) async {
|
||||
setState(() => _syncingNow = true);
|
||||
try {
|
||||
await SyncService.syncNow(session, sync);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Could not start sync: $e'), behavior: SnackBarBehavior.floating),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _syncingNow = false);
|
||||
}
|
||||
}
|
||||
|
||||
/// The Offline tab has its own chrome-free content (see the shared
|
||||
/// rebuild contract's "division of chrome" - only the shell draws top
|
||||
/// bars for a tab), so opening it as a pushed screen from here needs a
|
||||
/// minimal top bar of its own to stay navigable.
|
||||
void _openOfflineFiles(BuildContext context) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => Scaffold(
|
||||
appBar: NooLayout.isDesktop(context)
|
||||
? const NooToolbar(title: 'Offline')
|
||||
: NooTopBar(
|
||||
style: NooLayout.navStyle(context),
|
||||
title: 'Offline',
|
||||
leading: const NooTopBarBack(),
|
||||
),
|
||||
body: buildAppTabView(AppTab.offline, ScrollController()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final session = context.watch<SessionController>();
|
||||
final sync = context.watch<SyncStatusController>();
|
||||
final colors = context.nooColors;
|
||||
final folders = sync.syncedPaths;
|
||||
final everything = sync.syncEverything;
|
||||
final hasSyncScope = everything || folders.isNotEmpty;
|
||||
|
||||
return SettingsSection(
|
||||
title: 'File sync',
|
||||
children: [
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.cloud,
|
||||
label: const Text('Sync everything'),
|
||||
subtitle: const Text('Mirror the whole account instead of picking folders'),
|
||||
trailing: NooToggle(checked: everything, onChanged: sync.setSyncEverything),
|
||||
),
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.hardDriveDownload,
|
||||
label: const Text('View offline files'),
|
||||
subtitle: Text(
|
||||
everything
|
||||
? 'Every folder in this account is synced'
|
||||
: folders.isEmpty
|
||||
? 'Nothing synced yet'
|
||||
: '${folders.length} synced folder${folders.length == 1 ? '' : 's'}/file${folders.length == 1 ? '' : 's'}',
|
||||
),
|
||||
onTap: () => _openOfflineFiles(context),
|
||||
),
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.bellRing,
|
||||
label: const Text('Background sync notifications'),
|
||||
subtitle: const Text(
|
||||
'Show a notification when a background sync updates files. '
|
||||
'Conflicts and "Sync now" always notify.',
|
||||
),
|
||||
trailing: NooToggle(checked: sync.syncNotifications, onChanged: sync.setSyncNotifications),
|
||||
),
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.signal,
|
||||
label: const Text('Sync on cellular'),
|
||||
subtitle: const Text('Off = background sync only runs on Wi-Fi'),
|
||||
trailing: NooToggle(checked: sync.syncOnCellular, onChanged: sync.setSyncOnCellular),
|
||||
),
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.refreshCw,
|
||||
label: const Text('Sync now'),
|
||||
trailing: _syncingNow
|
||||
? SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: colors.accentText),
|
||||
)
|
||||
: NooButton(
|
||||
variant: NooButtonVariant.tonal,
|
||||
size: NooButtonSize.compact,
|
||||
disabled: !hasSyncScope,
|
||||
onTap: () => _syncNow(session, sync),
|
||||
child: const Text('Sync now'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user