Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter_checklist/checklist.dart';
import 'package:flutter_fgbg/flutter_fgbg.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'common/actions/backup.dart';
import 'common/constants/constants.dart';
import 'common/enums/supported_language.dart';
import 'common/extensions/locale_extension.dart';
Expand All @@ -21,6 +22,7 @@ import 'providers/labels/labels_list/labels_list_provider.dart';
import 'providers/labels/labels_navigation/labels_navigation_provider.dart';
import 'providers/notifiers/notifiers.dart';
import 'providers/preferences/preferences_provider.dart';
import 'services/backup/auto_backup_service.dart';

/// MaterialNotes application.
class App extends ConsumerStatefulWidget {
Expand Down Expand Up @@ -53,9 +55,14 @@ class _AppState extends ConsumerState<App> with AfterLayoutMixin<App> {
}

@override
FutureOr<void> afterFirstLayout(BuildContext context) {
Future<void> afterFirstLayout(BuildContext context) async {
// Using the context provided by afterFirstLayout doesn't work
SystemUtils().setQuickActions(rootNavigatorKey.currentContext!);

// If the backup directory is still the default, ask to select it
if (await AutoExportUtils().isAutoExportDirectoryDefault) {
await requireBackupDirectory(rootNavigatorKey.currentContext!);
}
}

@override
Expand Down
33 changes: 33 additions & 0 deletions lib/common/actions/backup.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';

import '../../services/backup/auto_backup_service.dart';
import '../dialogs/require_backup_dialog.dart';
import '../files/files_utils.dart';
import '../preferences/preference_key.dart';

/// Requires the user to select a backup directory.
Future<void> requireBackupDirectory(BuildContext context) async {
final select = await showAdaptiveDialog<bool>(
context: context,
useRootNavigator: false,
builder: (context) => RequireBackupDialog(),
);

if (select == null || !select) {
return;
}

await selectBackupDirectory();
}

/// Asks the user to select a backup directory.
Future<void> selectBackupDirectory() async {
final autoExportDirectory = await selectDirectory();

if (autoExportDirectory == null) {
return;
}

await PreferenceKey.autoExportDirectory.set(autoExportDirectory);
await AutoExportUtils().setAutoExportDirectory();
}
28 changes: 28 additions & 0 deletions lib/common/dialogs/require_backup_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:flutter/material.dart';

import '../extensions/build_context_extension.dart';

/// Require backup dialog.
class RequireBackupDialog extends StatelessWidget {
/// Dialog to require the user to select a backup location.
const RequireBackupDialog({super.key});

@override
Widget build(BuildContext context) {
return AlertDialog.adaptive(
title: Text(context.l.dialog_require_backup_title),
content: SingleChildScrollView(child: Column(children: [Text(context.l.dialog_require_backup_description)])),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.error),
child: Text(context.l.dialog_require_backup_ignore),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: Text(context.l.dialog_require_backup_select),
),
],
);
}
}
16 changes: 16 additions & 0 deletions lib/l10n/translations/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,22 @@
"@hint_label_name": {
"description": "Hint for the text field of the name of a tag."
},
"dialog_require_backup_title": "Select a backup location",
"@dialog_require_backup_title": {
"description": "Title of the dialog to select a backup location."
},
"dialog_require_backup_description": "As your notes are only stored locally on your device, any issue could lead to losing them all. Uninstalling the application would also delete all your notes.\n\nSelecting a backup location will ensure you can always retrieve your notes. For example, you could create a folder named \"Material Notes backups\" under your existing \"Documents\" folder.",
"@dialog_require_backup_description": {
"description": "Description of the dialog to select a backup location."
},
"dialog_require_backup_ignore": "Ignore",
"@dialog_require_backup_ignore": {
"description": "Button to ignore the dialog to select a backup location."
},
"dialog_require_backup_select": "Select",
"@dialog_require_backup_select": {
"description": "Button to select a backup location in the related dialog."
},
"dialog_export_encryption_password": "Password",
"@dialog_export_encryption_password": {
"description": "Hint for the password text field in the dialog to configure the encryption of an automatic or manual export."
Expand Down
43 changes: 18 additions & 25 deletions lib/pages/settings/pages/settings_backup_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:settings_tiles/settings_tiles.dart';
import 'package:simple_icons/simple_icons.dart';

import '../../../common/actions/backup.dart';
import '../../../common/constants/constants.dart';
import '../../../common/constants/paddings.dart';
import '../../../common/extensions/build_context_extension.dart';
import '../../../common/extensions/string_extension.dart';
import '../../../common/files/files_utils.dart';
import '../../../common/navigation/app_bars/basic_app_bar.dart';
import '../../../common/navigation/top_navigation.dart';
import '../../../common/preferences/preference_key.dart';
Expand Down Expand Up @@ -40,7 +40,7 @@ class _SettingsBackupPageState extends ConsumerState<SettingsBackupPage> {
/// Asks the user to choose a JSON file to import.
///
/// If the file is encrypted, asks for the password used to encrypt it.
Future<void> _import() async {
Future<void> import() async {
try {
final imported = await ManualBackupService().import(context);

Expand Down Expand Up @@ -70,7 +70,7 @@ class _SettingsBackupPageState extends ConsumerState<SettingsBackupPage> {
/// Asks the user to configure the immediate export as JSON.
///
/// Asks whether to encrypt and where to store the export file.
Future<void> _exportAsJson() async {
Future<void> exportAsJson() async {
await showAdaptiveDialog<(bool, String?)?>(
context: context,
useRootNavigator: false,
Expand Down Expand Up @@ -102,7 +102,7 @@ class _SettingsBackupPageState extends ConsumerState<SettingsBackupPage> {
/// Asks the user to configure the immediate export as Markdown.
///
/// Asks where to store the export file.
Future<void> _exportAsMarkdown() async {
Future<void> exportAsMarkdown() async {
try {
final exported = await ManualBackupService().exportAsMarkdown();

Expand All @@ -119,7 +119,7 @@ class _SettingsBackupPageState extends ConsumerState<SettingsBackupPage> {
}

/// Toggles the setting to enable the automatic export.
Future<void> _toggleEnableAutoExport(bool toggled) async {
Future<void> toggleEnableAutoExport(bool toggled) async {
await PreferenceKey.enableAutoExport.set(toggled);

setState(() {});
Expand All @@ -139,7 +139,7 @@ class _SettingsBackupPageState extends ConsumerState<SettingsBackupPage> {
/// Toggles the setting to enable the automatic export encryption.
///
/// If enabled, asks the user for the password used for the encryption.
Future<void> _toggleAutoExportEncryption(bool toggled) async {
Future<void> toggleAutoExportEncryption(bool toggled) async {
if (!toggled) {
await PreferenceKey.autoExportPassword.remove();
await PreferenceKey.autoExportEncryption.set(false);
Expand Down Expand Up @@ -170,28 +170,21 @@ class _SettingsBackupPageState extends ConsumerState<SettingsBackupPage> {
}

/// Sets automatic export frequency to [frequency].
Future<void> _submittedAutoExportFrequency(double frequency) async {
Future<void> submittedAutoExportFrequency(double frequency) async {
setState(() {
PreferenceKey.autoExportFrequency.set(frequency.toInt());
});
}

/// Asks the user to choose a directory for the automatic export.
Future<void> _setAutoExportDirectory() async {
final autoExportDirectory = await selectDirectory();

if (autoExportDirectory == null) {
return;
}

await PreferenceKey.autoExportDirectory.set(autoExportDirectory);
await AutoExportUtils().setAutoExportDirectory();
Future<void> setAutoExportDirectory() async {
await selectBackupDirectory();

setState(() {});
}

/// Resets the directory of the automatic export to its default value.
Future<void> _resetAutoExportDirectory() async {
Future<void> resetAutoExportDirectory() async {
await PreferenceKey.autoExportDirectory.remove();

await AutoExportUtils().setAutoExportDirectory();
Expand Down Expand Up @@ -220,7 +213,7 @@ class _SettingsBackupPageState extends ConsumerState<SettingsBackupPage> {
icon: SettingTileIcon(Icons.file_upload),
title: Text(context.l.settings_import),
description: Text(context.l.settings_import_description),
onTap: _import,
onTap: import,
),
],
),
Expand All @@ -231,13 +224,13 @@ class _SettingsBackupPageState extends ConsumerState<SettingsBackupPage> {
icon: SettingTileIcon(SimpleIcons.json),
title: Text(context.l.settings_export_json),
description: Text(context.l.settings_export_json_description),
onTap: _exportAsJson,
onTap: exportAsJson,
),
SettingActionTile(
icon: SettingTileIcon(SimpleIcons.markdown),
title: Text(context.l.settings_export_markdown),
description: Text(context.l.settings_export_markdown_description),
onTap: _exportAsMarkdown,
onTap: exportAsMarkdown,
),
],
),
Expand All @@ -249,15 +242,15 @@ class _SettingsBackupPageState extends ConsumerState<SettingsBackupPage> {
title: Text(context.l.settings_auto_export),
description: Text(context.l.settings_auto_export_description),
toggled: enableAutoExport,
onChanged: _toggleEnableAutoExport,
onChanged: toggleEnableAutoExport,
),
SettingSwitchTile(
enabled: enableAutoExport,
icon: SettingTileIcon(Icons.enhanced_encryption),
title: Text(context.l.settings_auto_export_encryption),
description: Text(context.l.settings_auto_export_encryption_description),
toggled: autoExportEncryption,
onChanged: _toggleAutoExportEncryption,
onChanged: toggleAutoExportEncryption,
),
SettingCustomSliderTile(
enabled: enableAutoExport,
Expand All @@ -272,7 +265,7 @@ class _SettingsBackupPageState extends ConsumerState<SettingsBackupPage> {
label: (frequency) => context.l.settings_auto_export_frequency_value(frequency.toInt().toString()),
values: automaticExportFrequenciesValues,
initialValue: autoExportFrequency.toDouble(),
onSubmitted: _submittedAutoExportFrequency,
onSubmitted: submittedAutoExportFrequency,
),
SettingActionTile(
enabled: enableAutoExport,
Expand All @@ -283,9 +276,9 @@ class _SettingsBackupPageState extends ConsumerState<SettingsBackupPage> {
trailing: IconButton(
icon: const Icon(Symbols.reset_settings),
tooltip: context.l.tooltip_reset,
onPressed: enableAutoExport ? _resetAutoExportDirectory : null,
onPressed: enableAutoExport ? resetAutoExportDirectory : null,
),
onTap: _setAutoExportDirectory,
onTap: setAutoExportDirectory,
),
],
),
Expand Down
7 changes: 6 additions & 1 deletion lib/services/backup/auto_backup_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,18 @@ class AutoExportUtils {
await createDirectory(autoExportDirectory);
}

/// Returns the default automatic export directory.
/// The default automatic export directory.
Future<String> get autoExportDirectoryDefault async {
final baseDirectory = (await getApplicationDocumentsDirectory()).path;

return join(baseDirectory, 'backups');
}

/// Whether the auto export directory is the default one.
Future<bool> get isAutoExportDirectoryDefault async {
return PreferenceKey.autoExportDirectory.preferenceOrDefault.isEmpty;
}

/// Checks if an automatic export should be performed.
///
/// An automatic export should be performed if it is enabled and either if no automatic export has been performed yet,
Expand Down
Loading