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>
133 lines
4.8 KiB
Dart
133 lines
4.8 KiB
Dart
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'),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|