Merge branch 'main' into novel/font-size

This commit is contained in:
Schnitzel5 2025-01-01 00:35:43 +01:00
commit 380bea2249
25 changed files with 1227 additions and 858 deletions

View file

@ -389,5 +389,15 @@
"n_minutes_ago": "{minutes} minutes ago",
"n_day_ago": "{day} day ago",
"now": "now",
"library_last_updated": "Library last updated: {lastUpdated}"
"library_last_updated": "Library last updated: {lastUpdated}",
"data_and_storage": "Data and storage",
"download_location_info": "Used for chapter downloads",
"storage": "Storage",
"clear_chapter_and_episode_cache": "Clear chapter and episode cache",
"cache_cleared": "Cache cleared",
"clear_chapter_or_episode_cache_on_app_launch": "Clear chapter/episode cache on app launch",
"app_settings": "App settings",
"sources_settings": "Sources settings",
"include_sensitive_settings": "Include sensitive settings (e.g., tracker login tokens)",
"create": "Create"
}

View file

@ -10,6 +10,7 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/modules/more/data_and_storage/providers/storage_usage.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/app_font_family.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
@ -75,6 +76,13 @@ class _MyAppState extends ConsumerState<MyApp> {
@override
void initState() {
iniDateFormatting();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (ref.read(clearChapterCacheOnAppLaunchStateProvider)) {
ref
.read(totalChapterCacheSizeStateProvider.notifier)
.clearCache(showToast: false);
}
});
super.initState();
}

View file

@ -23,7 +23,7 @@ class History {
History({
this.id = Isar.autoIncrement,
this.isManga = true,
this.isManga,
required this.itemType,
required this.chapterId,
required this.mangaId,

View file

@ -65,7 +65,7 @@ class Manga {
required this.name,
required this.status,
required this.description,
this.isManga = true,
this.isManga,
this.itemType = ItemType.manga,
this.dateAdded,
this.lastUpdate,

View file

@ -133,7 +133,7 @@ class Settings {
int? backupFrequency;
List<int>? backupFrequencyOptions;
List<int>? backupListOptions;
String? autoBackupLocation;
@ -226,6 +226,8 @@ class Settings {
bool? hideNovel;
bool? clearChapterCacheOnAppLaunch;
Settings(
{this.id = 227,
this.displayType = DisplayType.compactGrid,
@ -282,7 +284,7 @@ class Settings {
this.backgroundColor = BackgroundColor.black,
this.personalPageModeList,
this.backupFrequency,
this.backupFrequencyOptions,
this.backupListOptions,
this.autoBackupLocation,
this.startDatebackup,
this.usePageTapZones = true,
@ -325,7 +327,8 @@ class Settings {
this.novelTextAlign = NovelTextAlign.left,
this.hideManga = false,
this.hideAnime = false,
this.hideNovel = false});
this.hideNovel = false,
this.clearChapterCacheOnAppLaunch = false});
Settings.fromJson(Map<String, dynamic> json) {
animatePageTransitions = json['animatePageTransitions'];
@ -445,7 +448,7 @@ class Settings {
themeIsDark = json['themeIsDark'];
userAgent = json['userAgent'];
backupFrequency = json['backupFrequency'];
backupFrequencyOptions = json['backupFrequencyOptions']?.cast<int>();
backupListOptions = json['backupListOptions']?.cast<int>();
autoBackupLocation = json['autoBackupLocation'];
startDatebackup = json['startDatebackup'];
usePageTapZones = json['usePageTapZones'];
@ -503,6 +506,7 @@ class Settings {
hideManga = json['hideManga'];
hideAnime = json['hideAnime'];
hideNovel = json['hideNovel'];
clearChapterCacheOnAppLaunch = json['clearChapterCacheOnAppLaunch'];
}
Map<String, dynamic> toJson() => {
@ -574,7 +578,7 @@ class Settings {
'themeIsDark': themeIsDark,
'userAgent': userAgent,
'backupFrequency': backupFrequency,
'backupFrequencyOptions': backupFrequencyOptions,
'backupListOptions': backupListOptions,
'autoBackupLocation': autoBackupLocation,
'startDatebackup': startDatebackup,
'usePageTapZones': usePageTapZones,
@ -619,7 +623,8 @@ class Settings {
'novelTextAlign': novelTextAlign.index,
'hideManga': hideManga,
'hideAnime': hideAnime,
'hideNovel': hideNovel
'hideNovel': hideNovel,
'clearChapterCacheOnAppLaunch': clearChapterCacheOnAppLaunch
};
}

File diff suppressed because it is too large Load diff

View file

@ -86,7 +86,7 @@ class Source {
this.versionLast = "0.0.1",
this.sourceCode = '',
this.headers = '',
this.isManga = true,
this.isManga,
this.itemType = ItemType.manga,
this.appMinVerReq = "",
this.additionalParams = "",

View file

@ -14,7 +14,7 @@ import 'package:mangayomi/services/fetch_anime_sources.dart';
import 'package:mangayomi/services/fetch_manga_sources.dart';
import 'package:mangayomi/modules/main_view/providers/migration.dart';
import 'package:mangayomi/modules/more/about/providers/check_for_update.dart';
import 'package:mangayomi/modules/more/backup_and_restore/providers/auto_backup.dart';
import 'package:mangayomi/modules/more/data_and_storage/providers/auto_backup.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/router/router.dart';
import 'package:mangayomi/services/fetch_novel_sources.dart';

View file

@ -1,401 +0,0 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/modules/manga/detail/widgets/chapter_filter_list_tile_widget.dart';
import 'package:mangayomi/modules/more/backup_and_restore/providers/auto_backup.dart';
import 'package:mangayomi/modules/more/backup_and_restore/providers/backup.dart';
import 'package:mangayomi/modules/more/backup_and_restore/providers/restore.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
class BackupAndRestore extends ConsumerWidget {
const BackupAndRestore({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final backupFrequency = ref.watch(backupFrequencyStateProvider);
final backupFrequencyOptions =
ref.watch(backupFrequencyOptionsStateProvider);
final autoBackupLocation = ref.watch(autoBackupLocationStateProvider);
ref.read(autoBackupLocationStateProvider.notifier).refresh();
final l10n = l10nLocalizations(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.backup_and_restore),
),
body: SingleChildScrollView(
child: Column(
children: [
ListTile(
onTap: () {
final list = _getList(context);
List<int> indexList = [];
indexList.addAll(backupFrequencyOptions);
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text(l10n.create_backup_dialog_title),
content: SizedBox(
width: context.width(0.8),
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
return ListTileChapterFilter(
label: list[index],
type: indexList.contains(index) ? 1 : 0,
onTap: () {
if (indexList.contains(index)) {
setState(() {
indexList.remove(index);
});
} else {
setState(() {
indexList.add(index);
});
}
});
},
)),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
l10n.cancel,
style: TextStyle(
color: context.primaryColor),
)),
TextButton(
onPressed: () async {
String? result;
if (Platform.isIOS) {
result = (await StorageProvider()
.getIosBackupDirectory())!
.path;
} else {
result = await FilePicker.platform
.getDirectoryPath();
}
if (result != null && context.mounted) {
ref.watch(doBackUpProvider(
list: indexList,
path: result,
context: context));
}
},
child: Text(
l10n.ok,
style: TextStyle(
color: context.primaryColor),
)),
],
)
],
);
},
);
});
},
title: Text(l10n.create_backup),
subtitle: Text(
l10n.create_backup_subtitle,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
ListTile(
onTap: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(l10n.restore_backup),
content: SizedBox(
width: context.width(0.8),
child: ListView(
shrinkWrap: true,
children: [
Row(
children: [
Icon(Icons.info_outline_rounded,
color: context.secondaryColor),
],
),
Padding(
padding:
const EdgeInsets.symmetric(vertical: 5),
child:
Text(l10n.restore_backup_warning_title),
),
],
)),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
l10n.cancel,
style:
TextStyle(color: context.primaryColor),
)),
TextButton(
onPressed: () async {
try {
FilePickerResult? result =
await FilePicker.platform
.pickFiles(allowMultiple: false);
if (result != null && context.mounted) {
ref.watch(doRestoreProvider(
path: result.files.first.path!,
context: context));
}
if (!context.mounted) return;
Navigator.pop(context);
} catch (_) {
botToast("Error");
Navigator.pop(context);
}
},
child: Text(
l10n.ok,
style:
TextStyle(color: context.primaryColor),
)),
],
)
],
);
});
},
title: Text(l10n.restore_backup),
subtitle: Text(
l10n.restore_backup_subtitle,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
child: Row(
children: [
Text(l10n.automatic_backups,
style:
TextStyle(fontSize: 13, color: context.primaryColor)),
],
),
),
ListTile(
onTap: () {
showDialog(
context: context,
builder: (context) {
final list = _getBackupFrequencyList(context);
return AlertDialog(
title: Text(l10n.backup_frequency),
content: SizedBox(
width: context.width(0.8),
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
return RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: index,
groupValue: backupFrequency,
onChanged: (value) {
ref
.read(backupFrequencyStateProvider
.notifier)
.set(value!);
Navigator.pop(context);
},
title: Row(
children: [Text(list[index])],
),
);
},
)),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
l10n.cancel,
style:
TextStyle(color: context.primaryColor),
)),
],
)
],
);
});
},
title: Text(l10n.backup_frequency),
subtitle: Text(
_getBackupFrequencyList(context)[backupFrequency],
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
if (!Platform.isIOS)
ListTile(
onTap: () async {
String? result = await FilePicker.platform.getDirectoryPath();
if (result != null) {
ref
.read(autoBackupLocationStateProvider.notifier)
.set(result);
}
},
title: Text(l10n.backup_location),
subtitle: Text(
autoBackupLocation.$2.isEmpty
? autoBackupLocation.$1
: autoBackupLocation.$2,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
ListTile(
onTap: () {
final list = _getList(context);
List<int> indexList = [];
indexList.addAll(backupFrequencyOptions);
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text(
l10n.backup_options_subtitle,
),
content: SizedBox(
width: context.width(0.8),
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
return ListTileChapterFilter(
label: list[index],
type: indexList.contains(index) ? 1 : 0,
onTap: () {
if (indexList.contains(index)) {
setState(() {
indexList.remove(index);
});
} else {
setState(() {
indexList.add(index);
});
}
});
},
)),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
l10n.cancel,
style: TextStyle(
color: context.primaryColor),
)),
TextButton(
onPressed: () async {
ref
.read(
backupFrequencyOptionsStateProvider
.notifier)
.set(indexList);
Navigator.pop(context);
},
child: Text(
l10n.ok,
style: TextStyle(
color: context.primaryColor),
)),
],
)
],
);
},
);
});
},
title: Text(l10n.backup_options),
subtitle: Text(
l10n.backup_options_subtitle,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
ListTile(
title: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Icon(Icons.info_outline_rounded,
color: context.secondaryColor),
],
),
),
subtitle: Text(l10n.backup_and_restore_warning_info,
style:
TextStyle(fontSize: 11, color: context.secondaryColor)),
)
],
),
),
);
}
}
List<String> _getList(BuildContext context) {
final l10n = l10nLocalizations(context)!;
return [
l10n.library_entries,
l10n.categories,
l10n.chapters_and_episode,
l10n.tracking,
l10n.history,
l10n.settings,
l10n.extensions,
l10n.updates
];
}
List<String> _getBackupFrequencyList(BuildContext context) {
final l10n = l10nLocalizations(context)!;
return [
l10n.off,
l10n.every_6_hours,
l10n.every_12_hours,
l10n.daily,
l10n.every_2_days,
l10n.weekly
];
}

View file

@ -0,0 +1,206 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/modules/manga/detail/widgets/chapter_filter_list_tile_widget.dart';
import 'package:mangayomi/modules/more/data_and_storage/providers/auto_backup.dart';
import 'package:mangayomi/modules/more/data_and_storage/providers/backup.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
class CreateBackup extends ConsumerStatefulWidget {
const CreateBackup({super.key});
@override
ConsumerState<CreateBackup> createState() => _CreateBackupState();
}
class _CreateBackupState extends ConsumerState<CreateBackup> {
late final List<(String, int)> _libraryList = _getLibraryList(context);
late final List<(String, int)> _settingsList = _getSettingsList(context);
late final List<(String, int)> _extensionList = _getExtensionsList(context);
void _set(int index, List<int> indexList) {
if (indexList.contains(index)) {
ref
.read(backupFrequencyOptionsStateProvider.notifier)
.set(indexList.where((e) => e != index).toList());
} else {
ref
.read(backupFrequencyOptionsStateProvider.notifier)
.set([...indexList, index]);
}
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final indexList = ref.watch(backupFrequencyOptionsStateProvider);
return Scaffold(
appBar: AppBar(
title: Text(l10n.create_backup),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Row(
children: [
Text(l10n.library,
style: TextStyle(fontWeight: FontWeight.bold)),
],
),
),
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25)),
child: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 0, vertical: 15),
shrinkWrap: true,
primary: false,
itemCount: _libraryList.length,
itemBuilder: (context, index) {
final (label, idx) = _libraryList[index];
return ListTileChapterFilter(
label: label,
type: indexList.contains(idx) ? 1 : 0,
onTap: () {
_set(idx, indexList);
});
},
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Row(
children: [
Text(l10n.settings,
style: TextStyle(fontWeight: FontWeight.bold)),
],
),
),
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25)),
child: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 0, vertical: 15),
shrinkWrap: true,
primary: false,
itemCount: _settingsList.length,
itemBuilder: (context, index) {
final (label, idx) = _settingsList[index];
return ListTileChapterFilter(
label: label,
type: indexList.contains(idx) ? 1 : 0,
onTap: () {
_set(idx, indexList);
});
},
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Row(
children: [
Text(l10n.extensions,
style: TextStyle(fontWeight: FontWeight.bold)),
],
),
),
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25)),
child: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 0, vertical: 15),
shrinkWrap: true,
primary: false,
itemCount: _extensionList.length,
itemBuilder: (context, index) {
final (label, idx) = _extensionList[index];
return ListTileChapterFilter(
label: label,
type: indexList.contains(idx) ? 1 : 0,
onTap: () {
_set(idx, indexList);
});
},
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Divider(color: context.primaryColor, height: 0.3),
),
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: context.primaryColor),
onPressed: () async {
String? result;
if (Platform.isIOS) {
result = (await StorageProvider()
.getIosBackupDirectory())!
.path;
} else {
result =
await FilePicker.platform.getDirectoryPath();
}
if (result != null && context.mounted) {
ref.watch(doBackUpProvider(
list: indexList,
path: result,
context: context));
}
},
child: Text(l10n.create,
style: TextStyle(
color: context.dynamicBlackWhiteColor))),
),
),
],
)
],
),
),
),
);
}
}
List<(String, int)> _getLibraryList(BuildContext context) {
final l10n = context.l10n;
return [
(l10n.library_entries, 0),
(l10n.categories, 1),
(l10n.chapters_and_episode, 2),
(l10n.tracking, 3),
(l10n.history, 4),
(l10n.updates, 5)
];
}
List<(String, int)> _getSettingsList(BuildContext context) {
final l10n = context.l10n;
return [
(l10n.app_settings, 6),
(l10n.sources_settings, 7),
(l10n.include_sensitive_settings, 8),
];
}
List<(String, int)> _getExtensionsList(BuildContext context) {
final l10n = context.l10n;
return [(l10n.extensions, 9)];
}

View file

@ -0,0 +1,379 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/modules/more/data_and_storage/providers/auto_backup.dart';
import 'package:mangayomi/modules/more/data_and_storage/providers/restore.dart';
import 'package:mangayomi/modules/more/data_and_storage/providers/storage_usage.dart';
import 'package:mangayomi/modules/more/settings/downloads/providers/downloads_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
class DataAndStorage extends ConsumerWidget {
const DataAndStorage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final backupFrequency = ref.watch(backupFrequencyStateProvider);
final autoBackupLocation = ref.watch(autoBackupLocationStateProvider);
final downloadLocationState = ref.watch(downloadLocationStateProvider);
final totalChapterCacheSize = ref.watch(totalChapterCacheSizeStateProvider);
final clearChapterCacheOnAppLaunch =
ref.watch(clearChapterCacheOnAppLaunchStateProvider);
final l10n = l10nLocalizations(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.data_and_storage),
),
body: SingleChildScrollView(
child: Column(
children: [
ListTile(
onTap: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(l10n.download_location),
content: SizedBox(
width: context.width(0.8),
child: ListView(
shrinkWrap: true,
children: [
RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: downloadLocationState.$2.isEmpty
? downloadLocationState.$1
: downloadLocationState.$2,
groupValue: downloadLocationState.$1,
onChanged: (value) {
ref
.read(downloadLocationStateProvider
.notifier)
.set("");
Navigator.pop(context);
},
title: Text(downloadLocationState.$1)),
RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: downloadLocationState.$2.isEmpty
? downloadLocationState.$1
: downloadLocationState.$2,
groupValue: downloadLocationState.$2,
onChanged: (value) async {
String? result = await FilePicker.platform
.getDirectoryPath();
if (result != null) {
ref
.read(downloadLocationStateProvider
.notifier)
.set(result);
} else {}
if (!context.mounted) return;
Navigator.pop(context);
},
title: Text(l10n.custom_location)),
],
)),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
l10n.cancel,
style:
TextStyle(color: context.primaryColor),
)),
],
)
],
);
});
},
title: Text(l10n.download_location),
subtitle: Text(
downloadLocationState.$2.isEmpty
? downloadLocationState.$1
: downloadLocationState.$2,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
ListTile(
title: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Icon(Icons.info_outline_rounded,
color: context.secondaryColor),
],
),
),
subtitle: Text(l10n.download_location_info,
style:
TextStyle(fontSize: 11, color: context.secondaryColor)),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
child: Row(
children: [
Text(l10n.backup_and_restore,
style:
TextStyle(fontSize: 13, color: context.primaryColor)),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Row(
children: [
Expanded(
child: SegmentedButton(
emptySelectionAllowed: true,
showSelectedIcon: false,
style: TextButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50))),
segments: [
ButtonSegment(
value: 'create',
label: Padding(
padding: const EdgeInsets.all(12),
child: Text(l10n.create_backup),
),
),
ButtonSegment(
value: 'restore',
label: Padding(
padding: const EdgeInsets.all(12),
child: Text(l10n.restore_backup),
),
),
],
selected: {},
onSelectionChanged: (newSelection) {
if (newSelection.contains('create')) {
context.push('/createBackup');
} else if (newSelection.contains('restore')) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(l10n.restore_backup),
content: SizedBox(
width: context.width(0.8),
child: ListView(
shrinkWrap: true,
children: [
Row(
children: [
Icon(Icons.info_outline_rounded,
color:
context.secondaryColor),
],
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 5),
child: Text(l10n
.restore_backup_warning_title),
),
],
)),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
l10n.cancel,
style: TextStyle(
color: context.primaryColor),
)),
TextButton(
onPressed: () async {
try {
FilePickerResult? result =
await FilePicker.platform
.pickFiles(
allowMultiple:
false);
if (result != null &&
context.mounted) {
ref.watch(doRestoreProvider(
path: result
.files.first.path!,
context: context));
}
if (!context.mounted) return;
Navigator.pop(context);
} catch (_) {
botToast("Error");
Navigator.pop(context);
}
},
child: Text(
l10n.ok,
style: TextStyle(
color: context.primaryColor),
)),
],
)
],
);
});
}
},
),
),
],
),
),
ListTile(
onTap: () {
showDialog(
context: context,
builder: (context) {
final list = _getBackupFrequencyList(context);
return AlertDialog(
title: Text(l10n.backup_frequency),
content: SizedBox(
width: context.width(0.8),
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
return RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: index,
groupValue: backupFrequency,
onChanged: (value) {
ref
.read(backupFrequencyStateProvider
.notifier)
.set(value!);
Navigator.pop(context);
},
title: Row(
children: [Text(list[index])],
),
);
},
)),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
l10n.cancel,
style:
TextStyle(color: context.primaryColor),
)),
],
)
],
);
});
},
title: Text(l10n.backup_frequency),
subtitle: Text(
_getBackupFrequencyList(context)[backupFrequency],
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
if (!Platform.isIOS)
ListTile(
onTap: () async {
String? result = await FilePicker.platform.getDirectoryPath();
if (result != null) {
ref
.read(autoBackupLocationStateProvider.notifier)
.set(result);
}
},
title: Text(l10n.backup_location),
subtitle: Text(
autoBackupLocation.$2.isEmpty
? autoBackupLocation.$1
: autoBackupLocation.$2,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
ListTile(
title: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Icon(Icons.info_outline_rounded,
color: context.secondaryColor),
],
),
),
subtitle: Text(l10n.backup_and_restore_warning_info,
style:
TextStyle(fontSize: 11, color: context.secondaryColor)),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
child: Row(
children: [
Text(l10n.storage,
style:
TextStyle(fontSize: 13, color: context.primaryColor)),
],
),
),
ListTile(
title: Text(l10n.clear_chapter_and_episode_cache),
onTap: () => ref
.read(totalChapterCacheSizeStateProvider.notifier)
.clearCache(),
subtitle: Text(totalChapterCacheSize,
style:
TextStyle(fontSize: 11, color: context.secondaryColor)),
),
SwitchListTile(
value: clearChapterCacheOnAppLaunch,
title: Text(
context.l10n.clear_chapter_or_episode_cache_on_app_launch),
onChanged: (value) {
ref
.read(clearChapterCacheOnAppLaunchStateProvider.notifier)
.set(value);
}),
],
),
),
);
}
}
List<String> _getBackupFrequencyList(BuildContext context) {
final l10n = l10nLocalizations(context)!;
return [
l10n.off,
l10n.every_6_hours,
l10n.every_12_hours,
l10n.daily,
l10n.every_2_days,
l10n.weekly
];
}

View file

@ -1,7 +1,7 @@
import 'dart:io';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/more/backup_and_restore/providers/backup.dart';
import 'package:mangayomi/modules/more/data_and_storage/providers/backup.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -25,14 +25,15 @@ class BackupFrequencyState extends _$BackupFrequencyState {
class BackupFrequencyOptionsState extends _$BackupFrequencyOptionsState {
@override
List<int> build() {
return isar.settings.getSync(227)!.backupFrequencyOptions ?? [0, 1, 2, 3];
return isar.settings.getSync(227)!.backupListOptions ??
[0, 1, 2, 3, 4, 5, 6, 7];
}
void set(List<int> values) {
final settings = isar.settings.getSync(227);
state = values;
isar.writeTxnSync(() =>
isar.settings.putSync(settings!..backupFrequencyOptions = values));
isar.writeTxnSync(
() => isar.settings.putSync(settings!..backupListOptions = values));
}
}
@ -40,6 +41,7 @@ class BackupFrequencyOptionsState extends _$BackupFrequencyOptionsState {
class AutoBackupLocationState extends _$AutoBackupLocationState {
@override
(String, String) build() {
_refresh();
return ("", isar.settings.getSync(227)!.autoBackupLocation ?? "");
}
@ -52,7 +54,7 @@ class AutoBackupLocationState extends _$AutoBackupLocationState {
Directory? _storageProvider;
Future refresh() async {
Future _refresh() async {
_storageProvider = Platform.isIOS
? await StorageProvider().getIosBackupDirectory()
: await StorageProvider().getDefaultDirectory();

View file

@ -41,7 +41,7 @@ final backupFrequencyStateProvider =
typedef _$BackupFrequencyState = AutoDisposeNotifier<int>;
String _$backupFrequencyOptionsStateHash() =>
r'79d93411a02867c8882d2d0f2143f5da6c107075';
r'dd5aa850bc250e584973496fee05214b22eed9b1';
/// See also [BackupFrequencyOptionsState].
@ProviderFor(BackupFrequencyOptionsState)
@ -58,7 +58,7 @@ final backupFrequencyOptionsStateProvider = AutoDisposeNotifierProvider<
typedef _$BackupFrequencyOptionsState = AutoDisposeNotifier<List<int>>;
String _$autoBackupLocationStateHash() =>
r'80c41bb34c4ed4d07dce626dd03bb20f01672aec';
r'be78346bb300287ae9b8581e27ee6814483400a7';
/// See also [AutoBackupLocationState].
@ProviderFor(AutoBackupLocationState)

View file

@ -74,13 +74,6 @@ Future<void> doBackUp(Ref ref,
.map((e) => e.toJson())
.toList();
datas.addAll({"tracks": res});
final res_ = isar.trackPreferences
.filter()
.syncIdIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.toList();
datas.addAll({"trackPreferences": res_});
}
if (list.contains(4)) {
final res = isar.historys
@ -92,6 +85,15 @@ Future<void> doBackUp(Ref ref,
datas.addAll({"history": res});
}
if (list.contains(5)) {
final res = isar.updates
.filter()
.idIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.toList();
datas.addAll({"updates": res});
}
if (list.contains(6)) {
final res = isar.settings
.filter()
.idIsNotNull()
@ -100,7 +102,25 @@ Future<void> doBackUp(Ref ref,
.toList();
datas.addAll({"settings": res});
}
if (list.contains(6)) {
if (list.contains(7)) {
final res = isar.sourcePreferences
.filter()
.idIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.toList();
datas.addAll({"extensions_preferences": res});
}
if (list.contains(8)) {
final res_ = isar.trackPreferences
.filter()
.syncIdIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.toList();
datas.addAll({"trackPreferences": res_});
}
if (list.contains(9)) {
final res = isar.sources
.filter()
.idIsNotNull()
@ -108,23 +128,6 @@ Future<void> doBackUp(Ref ref,
.map((e) => e.toJson())
.toList();
datas.addAll({"extensions": res});
final resSourePref = isar.sourcePreferences
.filter()
.idIsNotNull()
.keyIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.toList();
datas.addAll({"extensions_preferences": resSourePref});
}
if (list.contains(7)) {
final res = isar.updates
.filter()
.idIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.toList();
datas.addAll({"updates": res});
}
final regExp = RegExp(r'[^a-zA-Z0-9 .()\-\s]');
final name =

View file

@ -6,7 +6,7 @@ part of 'backup.dart';
// RiverpodGenerator
// **************************************************************************
String _$doBackUpHash() => r'50189cb247f37cadd4d64ddc0377774987859681';
String _$doBackUpHash() => r'ad907e7ff4cd9f05bb3fa2da0fd1a1f1d2c23258';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -84,7 +84,7 @@ void restoreBackup(Ref ref, Map<String, dynamic> backup) {
final extensions = (backup["extensions"] as List?)
?.map((e) => Source.fromJson(e)..itemType = _convertToItemType(e))
.toList();
final extensionsPref = (backup["extensions_preferences"] as List?)
final sourcesPrefs = (backup["extensions_preferences"] as List?)
?.map((e) => SourcePreference.fromJson(e))
.toList();
final updates =
@ -166,8 +166,8 @@ void restoreBackup(Ref ref, Map<String, dynamic> backup) {
}
isar.sourcePreferences.clearSync();
if (extensionsPref != null) {
isar.sourcePreferences.putAllSync(extensionsPref);
if (sourcesPrefs != null) {
isar.sourcePreferences.putAllSync(sourcesPrefs);
}
isar.settings.clearSync();
if (settings != null) {

View file

@ -173,7 +173,7 @@ class _DoRestoreProviderElement extends AutoDisposeProviderElement<void>
BuildContext get context => (origin as DoRestoreProvider).context;
}
String _$restoreBackupHash() => r'834ba688f122cabe7969752ce8be06552c2e70c5';
String _$restoreBackupHash() => r'726b88cc165ac6cae83a2bbbb5d8b5533c3a1f46';
/// See also [restoreBackup].
@ProviderFor(restoreBackup)

View file

@ -0,0 +1,101 @@
import 'dart:io';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/router/router.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'storage_usage.g.dart';
@riverpod
class TotalChapterCacheSizeState extends _$TotalChapterCacheSizeState {
@override
String build() {
_getTotalDiskSpace().then((value) => state = _formatBytes(value));
return "0.00 B";
}
final String _cacheImageMangaPath = join('Mangayomi', 'cacheimagemanga');
final String _cacheDownloadPath = join('Mangayomi', 'downloads');
Future<void> clearCache({bool showToast = true}) async {
final tempPath = (await getTemporaryDirectory()).path;
String? msg;
try {
final dir = Directory(join(tempPath, _cacheImageMangaPath));
if (dir.existsSync()) {
await dir.delete(recursive: true);
}
msg = "0.00 B";
} catch (_) {}
try {
final dir = Directory(join(tempPath, _cacheDownloadPath));
if (dir.existsSync()) {
await dir.delete(recursive: true);
}
msg = "0.00 B";
} catch (_) {}
if (msg != null && showToast) {
state = msg;
botToast(
navigatorKey.currentContext?.l10n.cache_cleared ?? "Cache cleared");
}
}
Future<int> _getTotalDiskSpace() async {
final tempPath = (await getTemporaryDirectory()).path;
try {
return await _getdirectorySize(
Directory(join(tempPath, _cacheImageMangaPath))) +
await _getdirectorySize(
Directory(join(tempPath, _cacheDownloadPath)));
} catch (_) {}
return 0;
}
Future<int> _getdirectorySize(Directory directory) async {
try {
if (await directory.exists()) {
return directory
.list(recursive: true, followLinks: false)
.where((entity) => entity is File)
.cast<File>()
.fold(0, (total, file) {
return total + file.lengthSync();
});
}
} catch (_) {}
return 0;
}
String _formatBytes(int bytes) {
const units = ['B', 'KB', 'MB', 'GB'];
int unitIndex = 0;
double size = bytes.toDouble();
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return '${size.toStringAsFixed(2)} ${units[unitIndex]}';
}
}
@riverpod
class ClearChapterCacheOnAppLaunchState
extends _$ClearChapterCacheOnAppLaunchState {
@override
bool build() {
return isar.settings.getSync(227)!.clearChapterCacheOnAppLaunch ?? false;
}
void set(bool value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(() =>
isar.settings.putSync(settings!..clearChapterCacheOnAppLaunch = value));
}
}

View file

@ -0,0 +1,44 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'storage_usage.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$totalChapterCacheSizeStateHash() =>
r'83bf78de5ef674d61d3a9311061ef0933e59109b';
/// See also [TotalChapterCacheSizeState].
@ProviderFor(TotalChapterCacheSizeState)
final totalChapterCacheSizeStateProvider =
AutoDisposeNotifierProvider<TotalChapterCacheSizeState, String>.internal(
TotalChapterCacheSizeState.new,
name: r'totalChapterCacheSizeStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$totalChapterCacheSizeStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$TotalChapterCacheSizeState = AutoDisposeNotifier<String>;
String _$clearChapterCacheOnAppLaunchStateHash() =>
r'6fdefd7a3bdc4309dd596dad43891c654ec2ba70';
/// See also [ClearChapterCacheOnAppLaunchState].
@ProviderFor(ClearChapterCacheOnAppLaunchState)
final clearChapterCacheOnAppLaunchStateProvider = AutoDisposeNotifierProvider<
ClearChapterCacheOnAppLaunchState, bool>.internal(
ClearChapterCacheOnAppLaunchState.new,
name: r'clearChapterCacheOnAppLaunchStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$clearChapterCacheOnAppLaunchStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ClearChapterCacheOnAppLaunchState = AutoDisposeNotifier<bool>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -57,10 +57,10 @@ class MoreScreen extends StatelessWidget {
),
ListTileWidget(
onTap: () {
context.push('/backupAndRestore');
context.push('/dataAndStorage');
},
icon: Icons.settings_backup_restore_sharp,
title: l10n.backup_and_restore,
icon: Icons.storage,
title: l10n.data_and_storage,
),
const Divider(),
ListTileWidget(

View file

@ -1,9 +1,7 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/modules/more/settings/downloads/providers/downloads_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
class DownloadsScreen extends ConsumerStatefulWidget {
const DownloadsScreen({super.key});
@ -19,8 +17,6 @@ class _DownloadsScreenState extends ConsumerState<DownloadsScreen> {
) {
final saveAsCBZArchiveState = ref.watch(saveAsCBZArchiveStateProvider);
final onlyOnWifiState = ref.watch(onlyOnWifiStateProvider);
final downloadLocationState = ref.watch(downloadLocationStateProvider);
ref.read(downloadLocationStateProvider.notifier).refresh();
final l10n = l10nLocalizations(context);
return Scaffold(
appBar: AppBar(
@ -29,83 +25,6 @@ class _DownloadsScreenState extends ConsumerState<DownloadsScreen> {
body: SingleChildScrollView(
child: Column(
children: [
ListTile(
onTap: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(l10n.download_location),
content: SizedBox(
width: context.width(0.8),
child: ListView(
shrinkWrap: true,
children: [
RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: downloadLocationState.$2.isEmpty
? downloadLocationState.$1
: downloadLocationState.$2,
groupValue: downloadLocationState.$1,
onChanged: (value) {
ref
.read(downloadLocationStateProvider
.notifier)
.set("");
Navigator.pop(context);
},
title: Text(downloadLocationState.$1)),
RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: downloadLocationState.$2.isEmpty
? downloadLocationState.$1
: downloadLocationState.$2,
groupValue: downloadLocationState.$2,
onChanged: (value) async {
String? result = await FilePicker.platform
.getDirectoryPath();
if (result != null) {
ref
.read(downloadLocationStateProvider
.notifier)
.set(result);
} else {}
if (!context.mounted) return;
Navigator.pop(context);
},
title: Text(l10n.custom_location)),
],
)),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
l10n.cancel,
style:
TextStyle(color: context.primaryColor),
)),
],
)
],
);
});
},
title: Text(l10n.download_location),
subtitle: Text(
downloadLocationState.$2.isEmpty
? downloadLocationState.$1
: downloadLocationState.$2,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
SwitchListTile(
value: onlyOnWifiState,
title: Text(l10n.only_on_wifi),

View file

@ -40,6 +40,7 @@ class SaveAsCBZArchiveState extends _$SaveAsCBZArchiveState {
class DownloadLocationState extends _$DownloadLocationState {
@override
(String, String) build() {
_refresh();
return ("", isar.settings.getSync(227)!.downloadLocation ?? "");
}
@ -52,7 +53,7 @@ class DownloadLocationState extends _$DownloadLocationState {
Directory? _storageProvider;
Future refresh() async {
Future _refresh() async {
_storageProvider = await StorageProvider().getDefaultDirectory();
final settings = isar.settings.getSync(227);
state = (

View file

@ -40,7 +40,7 @@ final saveAsCBZArchiveStateProvider =
typedef _$SaveAsCBZArchiveState = AutoDisposeNotifier<bool>;
String _$downloadLocationStateHash() =>
r'e4c2215366c10f850b7d52c96c03545a9413b752';
r'dba216dd8de1206c1c272d0f76684e016e10417f';
/// See also [DownloadLocationState].
@ProviderFor(DownloadLocationState)

View file

@ -11,9 +11,10 @@ import 'package:mangayomi/modules/browse/extension/edit_code.dart';
import 'package:mangayomi/modules/browse/extension/extension_detail.dart';
import 'package:mangayomi/modules/browse/extension/widgets/create_extension.dart';
import 'package:mangayomi/modules/browse/sources/sources_filter_screen.dart';
import 'package:mangayomi/modules/more/data_and_storage/create_backup.dart';
import 'package:mangayomi/modules/more/data_and_storage/data_and_storage.dart';
import 'package:mangayomi/modules/novel/novel_reader_view.dart';
import 'package:mangayomi/modules/updates/updates_screen.dart';
import 'package:mangayomi/modules/more/backup_and_restore/backup_and_restore.dart';
import 'package:mangayomi/modules/more/categories/categories_screen.dart';
import 'package:mangayomi/modules/more/settings/downloads/downloads_screen.dart';
import 'package:mangayomi/modules/more/settings/player/player_screen.dart';
@ -500,15 +501,15 @@ class RouterNotifier extends ChangeNotifier {
},
),
GoRoute(
path: "/backupAndRestore",
name: "backupAndRestore",
path: "/dataAndStorage",
name: "dataAndStorage",
builder: (context, state) {
return const BackupAndRestore();
return const DataAndStorage();
},
pageBuilder: (context, state) {
return transitionPage(
key: state.pageKey,
child: const BackupAndRestore(),
child: const DataAndStorage(),
);
},
),
@ -581,6 +582,19 @@ class RouterNotifier extends ChangeNotifier {
);
},
),
GoRoute(
path: "/createBackup",
name: "createBackup",
builder: (context, state) {
return const CreateBackup();
},
pageBuilder: (context, state) {
return transitionPage(
key: state.pageKey,
child: const CreateBackup(),
);
},
),
];
}

View file

@ -11,7 +11,7 @@ import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/history.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/backup_and_restore/providers/restore.dart';
import 'package:mangayomi/modules/more/data_and_storage/providers/restore.dart';
import 'package:mangayomi/modules/more/settings/sync/models/jwt.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'dart:convert';