Release 1.0.3: refine tablet layouts, pointer selection and account sync cleanup
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../noo/lists/noo_settings_row.dart';
|
||||
import 'settings_section.dart';
|
||||
|
||||
class SettingsAboutSection extends StatefulWidget {
|
||||
const SettingsAboutSection({super.key});
|
||||
|
||||
@override
|
||||
State<SettingsAboutSection> createState() => _SettingsAboutSectionState();
|
||||
}
|
||||
|
||||
class _SettingsAboutSectionState extends State<SettingsAboutSection> {
|
||||
late final Future<PackageInfo> _info = PackageInfo.fromPlatform();
|
||||
|
||||
Future<void> _openLink(String url) async {
|
||||
try {
|
||||
if (await launchUrl(
|
||||
Uri.parse(url),
|
||||
mode: LaunchMode.externalApplication,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
} catch (_) {}
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Could not open link. Please try again.')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => FutureBuilder<PackageInfo>(
|
||||
future: _info,
|
||||
builder: (context, snapshot) {
|
||||
final info = snapshot.data;
|
||||
final pending = snapshot.hasError ? 'Unavailable' : 'Loading…';
|
||||
return SettingsSection(
|
||||
title: 'About',
|
||||
children: [
|
||||
const NooSettingsRow(
|
||||
label: Text('Noo'),
|
||||
subtitle: Text('An independent Nextcloud client'),
|
||||
),
|
||||
NooSettingsRow(
|
||||
label: const Text('Version'),
|
||||
trailing: Text(info?.version ?? pending),
|
||||
),
|
||||
NooSettingsRow(
|
||||
label: const Text('Build number'),
|
||||
trailing: Text(info?.buildNumber ?? pending),
|
||||
),
|
||||
NooSettingsRow(
|
||||
label: const Text('App identifier'),
|
||||
subtitle: Text(info?.packageName ?? pending),
|
||||
),
|
||||
for (final link in {
|
||||
'Website': 'https://noo.ayushya.dev',
|
||||
'GitHub': 'https://github.com/ayushyamitabh/noo',
|
||||
'Privacy policy': 'https://noo.ayushya.dev/privacy',
|
||||
}.entries)
|
||||
NooSettingsRow(
|
||||
label: Text(link.key),
|
||||
value: '',
|
||||
onTap: () => _openLink(link.value),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import '../noo/core/noo_chip.dart';
|
||||
import '../noo/core/noo_progress_bar.dart';
|
||||
import '../noo/noo_layout.dart';
|
||||
import '../shell/shell_common.dart';
|
||||
import '../shell/tablet_account_menu.dart';
|
||||
import 'settings_dialogs.dart';
|
||||
|
||||
/// Settings section 1: the account card (DESIGN_SYSTEM.md 4 - "NooAvatar
|
||||
@@ -18,7 +19,9 @@ import 'settings_dialogs.dart';
|
||||
/// the pre-rework card and have no other home, so they stay here as a
|
||||
/// button row under the storage meter.
|
||||
class SettingsAccountCard extends StatelessWidget {
|
||||
const SettingsAccountCard({super.key});
|
||||
final bool compact;
|
||||
final VoidCallback? onManage;
|
||||
const SettingsAccountCard({super.key, this.compact = false, this.onManage});
|
||||
|
||||
Future<void> _refresh(BuildContext context, FilesController files) async {
|
||||
await files.refreshData();
|
||||
@@ -48,7 +51,7 @@ class SettingsAccountCard extends StatelessWidget {
|
||||
context,
|
||||
session,
|
||||
accountId: id,
|
||||
username: session.username,
|
||||
username: session.displayName,
|
||||
host: serverHost(session.serverUrl),
|
||||
isActive: true,
|
||||
);
|
||||
@@ -60,52 +63,67 @@ class SettingsAccountCard extends StatelessWidget {
|
||||
final session = context.watch<SessionController>();
|
||||
final files = context.watch<FilesController>();
|
||||
final quota = files.quota;
|
||||
final name = quota?.userName ?? session.username;
|
||||
final name = session.displayName;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(NooSpace.lg),
|
||||
padding: EdgeInsets.all(compact ? 10 : NooSpace.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface,
|
||||
borderRadius: BorderRadius.circular(NooRadii.card),
|
||||
border: NooLayout.isDesktop(context) ? Border.all(color: colors.line) : null,
|
||||
border: NooLayout.isDesktop(context)
|
||||
? Border.all(color: colors.line)
|
||||
: null,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
NooAvatar(initials: accountInitial(name), current: true, size: 56),
|
||||
const SizedBox(width: NooSpace.sm),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: NooText.cardTitle.copyWith(color: colors.fg1),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
serverHost(session.serverUrl),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: NooText.meta.copyWith(color: colors.fg3),
|
||||
),
|
||||
],
|
||||
if (compact)
|
||||
TabletAccountMenu(framed: false, onManage: onManage)
|
||||
else
|
||||
Row(
|
||||
children: [
|
||||
NooAvatar(
|
||||
initials: accountInitial(name),
|
||||
current: true,
|
||||
size: 56,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: NooSpace.sm),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: NooText.cardTitle.copyWith(color: colors.fg1),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
serverHost(session.serverUrl),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: NooText.meta.copyWith(color: colors.fg3),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: NooSpace.lg),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Storage', style: NooText.label.copyWith(color: colors.fg2)),
|
||||
Text(
|
||||
quota != null ? quotaDetail(quota) : 'Loading…',
|
||||
style: NooText.meta.copyWith(color: colors.fg3),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
quota != null ? quotaDetail(quota) : 'Loading…',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end,
|
||||
style: NooText.meta.copyWith(color: colors.fg3),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -116,7 +134,9 @@ class SettingsAccountCard extends StatelessWidget {
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [for (final group in quota.groups) NooChip(child: Text(group))],
|
||||
children: [
|
||||
for (final group in quota.groups) NooChip(child: Text(group)),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: NooSpace.md),
|
||||
|
||||
@@ -34,7 +34,7 @@ class SettingsAccountsSection extends StatelessWidget {
|
||||
context,
|
||||
session,
|
||||
accountId: account.id,
|
||||
username: account.username,
|
||||
username: account.label,
|
||||
host: serverHost(account.serverUrl),
|
||||
isActive: account.id == session.activeAccountId,
|
||||
),
|
||||
@@ -45,7 +45,9 @@ class SettingsAccountsSection extends StatelessWidget {
|
||||
label: const Text('Add account'),
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const LoginView(isAddingAccount: true)),
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const LoginView(isAddingAccount: true),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -79,11 +81,18 @@ class _SavedAccountRow extends StatelessWidget {
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: NooSpace.md, vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: NooSpace.md,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
NooAvatar(initials: accountInitial(account.username), current: isActive, size: 36),
|
||||
NooAvatar(
|
||||
initials: accountInitial(account.label),
|
||||
current: isActive,
|
||||
size: 36,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -108,15 +117,27 @@ class _SavedAccountRow extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isActive) Icon(LucideIcons.checkCircle, size: 20, color: colors.accentText),
|
||||
if (isActive)
|
||||
Icon(
|
||||
LucideIcons.checkCircle,
|
||||
size: 20,
|
||||
color: colors.accentText,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: onRemove,
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: colors.surface2, shape: BoxShape.circle),
|
||||
child: Icon(LucideIcons.trash2, size: 16, color: colors.danger),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface2,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.trash2,
|
||||
size: 16,
|
||||
color: colors.danger,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../noo/lists/noo_settings_row.dart';
|
||||
import '../noo/lists/noo_grouped_list.dart';
|
||||
import '../noo/core/noo_segmented_control.dart';
|
||||
import '../noo/noo_layout.dart';
|
||||
import 'settings_section.dart';
|
||||
|
||||
/// Settings section: the priority order bulk actions (favorite, share,
|
||||
/// download, ...) show in on the Files/Photos/Favorites multi-select
|
||||
@@ -77,10 +78,11 @@ class SettingsActionBarSection extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Action bar',
|
||||
style: NooText.cardTitle.copyWith(color: colors.fg1),
|
||||
),
|
||||
if (SettingsCategoryHeading.shows(context, 'Action bar'))
|
||||
Text(
|
||||
'Action bar',
|
||||
style: NooText.cardTitle.copyWith(color: colors.fg1),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(_subtitle, style: NooText.meta.copyWith(color: colors.fg3)),
|
||||
const SizedBox(height: NooSpace.md),
|
||||
|
||||
@@ -43,22 +43,23 @@ class SettingsAppearanceSection extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final settings = context.watch<SettingsController>();
|
||||
final tablet = NooLayout.isDesktop(context);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SettingsSection(
|
||||
title: 'Appearance',
|
||||
notice:
|
||||
'Bottom bar style, menu style and search in bottom bar have no effect on wide tablets, which use a sidebar.',
|
||||
children: [
|
||||
_ThemeRow(settings: settings),
|
||||
_BottomBarStyleRow(settings: settings),
|
||||
if (!tablet) _BottomBarStyleRow(settings: settings),
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.glassWater,
|
||||
label: const Text('Frosted glass'),
|
||||
subtitle: const Text(
|
||||
'Blurred bottom navigation, avatar menus and media viewer panels',
|
||||
subtitle: Text(
|
||||
tablet
|
||||
? 'Blurred media viewer panels'
|
||||
: 'Blurred bottom navigation, avatar menus and media viewer panels',
|
||||
),
|
||||
trailing: NooToggle(
|
||||
checked: settings.bottomBarFrosted,
|
||||
@@ -66,8 +67,8 @@ class SettingsAppearanceSection extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
if (settings.bottomBarFrosted) _FrostedGlassRow(settings: settings),
|
||||
_NavMenuStyleRow(settings: settings),
|
||||
if (settings.navMenuStyle == NooNavMenuStyle.avatarMenu)
|
||||
if (!tablet) _NavMenuStyleRow(settings: settings),
|
||||
if (!tablet && settings.navMenuStyle == NooNavMenuStyle.avatarMenu)
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.userRound,
|
||||
label: const Text('Avatar position'),
|
||||
@@ -84,27 +85,29 @@ class SettingsAppearanceSection extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.search,
|
||||
label: const Text('Search in bottom bar'),
|
||||
subtitle: const Text(
|
||||
'Adds Search to the bottom bar and removes it from the top '
|
||||
'bar - leaves room for one fewer regular tab',
|
||||
if (!tablet)
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.search,
|
||||
label: const Text('Search in bottom bar'),
|
||||
subtitle: const Text(
|
||||
'Adds Search to the bottom bar and removes it from the top '
|
||||
'bar - leaves room for one fewer regular tab',
|
||||
),
|
||||
trailing: NooToggle(
|
||||
checked: settings.searchInBottomBar,
|
||||
onChanged: settings.setSearchInBottomBar,
|
||||
),
|
||||
),
|
||||
trailing: NooToggle(
|
||||
checked: settings.searchInBottomBar,
|
||||
onChanged: settings.setSearchInBottomBar,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: NooSpace.xl),
|
||||
SettingsSection(
|
||||
title: 'Upload button',
|
||||
notice:
|
||||
'Auto shows the label on Files and Photos and shrinks to an icon on other tabs. Mini and Expanded stay the same on every tab. Has no effect on wide tablets.',
|
||||
children: [_FabStyleRow(settings: settings)],
|
||||
),
|
||||
if (!tablet) const SizedBox(height: NooSpace.xl),
|
||||
if (!tablet)
|
||||
SettingsSection(
|
||||
title: 'Upload button',
|
||||
notice:
|
||||
'Auto shows the label on Files and Photos and shrinks to an icon on other tabs. Mini and Expanded stay the same on every tab.',
|
||||
children: [_FabStyleRow(settings: settings)],
|
||||
),
|
||||
const SizedBox(height: NooSpace.xl),
|
||||
SettingsSection(
|
||||
title: 'Advanced appearance',
|
||||
|
||||
@@ -22,8 +22,8 @@ Future<void> confirmRemoveAccount(
|
||||
}) async {
|
||||
final hasOtherAccounts = session.accounts.length > 1;
|
||||
final message = isActive && hasOtherAccounts
|
||||
? 'Remove $username ($host)? Another saved account will become active.'
|
||||
: 'Remove $username ($host)? You can add it again later.';
|
||||
? 'Remove $username ($host)? Its synced files will be removed from this device. Another saved account will become active.'
|
||||
: 'Remove $username ($host)? Its synced files will be removed from this device. You can add it again later.';
|
||||
|
||||
var confirmed = false;
|
||||
void cancel() => Navigator.pop(context);
|
||||
@@ -80,7 +80,20 @@ Future<void> confirmRemoveAccount(
|
||||
if (!confirmed) return;
|
||||
if (!context.mounted) return;
|
||||
final navigator = Navigator.of(context);
|
||||
await session.removeAccount(accountId);
|
||||
try {
|
||||
await session.removeAccount(accountId);
|
||||
} catch (error) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Could not remove local account data. Please try again.',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Only when this was the last saved account does isLoggedIn drop to false
|
||||
// and main.dart swap the root route to LoginView underneath - pop back to
|
||||
// it then, rather than leaving Settings stranded on top. Removing a
|
||||
|
||||
@@ -6,6 +6,24 @@ import '../noo/noo_layout.dart';
|
||||
import '../noo/overlays/noo_dialog.dart';
|
||||
import '../noo/overlays/noo_sheet.dart';
|
||||
|
||||
/// Category heading already supplied by the tablet toolbar.
|
||||
class SettingsCategoryHeading extends InheritedWidget {
|
||||
final String title;
|
||||
const SettingsCategoryHeading({
|
||||
super.key,
|
||||
required this.title,
|
||||
required super.child,
|
||||
});
|
||||
static bool shows(BuildContext context, String title) =>
|
||||
context
|
||||
.dependOnInheritedWidgetOfExactType<SettingsCategoryHeading>()
|
||||
?.title !=
|
||||
title;
|
||||
@override
|
||||
bool updateShouldNotify(SettingsCategoryHeading oldWidget) =>
|
||||
title != oldWidget.title;
|
||||
}
|
||||
|
||||
/// One block of Settings (DESIGN_SYSTEM.md 4's 9-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
|
||||
@@ -34,7 +52,7 @@ class SettingsSection extends StatelessWidget {
|
||||
final colors = context.nooColors;
|
||||
if (!NooLayout.isDesktop(context)) {
|
||||
return NooGroupedList(
|
||||
label: title,
|
||||
label: SettingsCategoryHeading.shows(context, title) ? title : null,
|
||||
notice: notice != null ? NooInfoNote(message: notice!) : null,
|
||||
footer: subtitle != null ? Text(subtitle!) : null,
|
||||
children: children,
|
||||
@@ -50,7 +68,8 @@ class SettingsSection extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: NooText.cardTitle.copyWith(color: colors.fg1)),
|
||||
if (SettingsCategoryHeading.shows(context, title))
|
||||
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)),
|
||||
|
||||
@@ -47,7 +47,6 @@ class SettingsSwipeSection extends StatelessWidget {
|
||||
return SettingsSection(
|
||||
title: 'Swipe on a file',
|
||||
subtitle: 'Choose what swiping a file left or right does in list view',
|
||||
notice: 'Swipe actions have no effect on wide tablets.',
|
||||
children: [
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.chevronsRight,
|
||||
|
||||
@@ -78,17 +78,18 @@ class SettingsTabsSection extends StatelessWidget {
|
||||
final desktop = NooLayout.isDesktop(context);
|
||||
|
||||
final optionRows = [
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.arrowUpDown,
|
||||
label: const Text('Tap tab to scroll to top'),
|
||||
subtitle: const Text(
|
||||
'Tapping the current bottom bar tab scrolls its list back to the top',
|
||||
if (!desktop)
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.arrowUpDown,
|
||||
label: const Text('Tap tab to scroll to top'),
|
||||
subtitle: const Text(
|
||||
'Tapping the current bottom bar tab scrolls its list back to the top',
|
||||
),
|
||||
trailing: NooToggle(
|
||||
checked: settings.tapTabToScrollTop,
|
||||
onChanged: settings.setTapTabToScrollTop,
|
||||
),
|
||||
),
|
||||
trailing: NooToggle(
|
||||
checked: settings.tapTabToScrollTop,
|
||||
onChanged: settings.setTapTabToScrollTop,
|
||||
),
|
||||
),
|
||||
NooSettingsRow(
|
||||
icon: LucideIcons.star,
|
||||
label: const Text('Default tab'),
|
||||
@@ -136,7 +137,8 @@ class SettingsTabsSection extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Tabs', style: NooText.cardTitle.copyWith(color: colors.fg1)),
|
||||
if (SettingsCategoryHeading.shows(context, 'Tabs'))
|
||||
Text('Tabs', style: NooText.cardTitle.copyWith(color: colors.fg1)),
|
||||
const SizedBox(height: 4),
|
||||
Text(_subtitle, style: NooText.meta.copyWith(color: colors.fg3)),
|
||||
const SizedBox(height: NooSpace.md),
|
||||
|
||||
Reference in New Issue
Block a user