enhanced sync feature

This commit is contained in:
Schnitzel5 2025-02-12 23:59:39 +01:00
parent fc20f87870
commit 00229ef5d0
20 changed files with 425 additions and 290 deletions

View file

@ -42,6 +42,7 @@ enum ActionType {
updateHistory(name: "UPDATE_HISTORY"),
clearUpdates(name: "CLEAR_UPDATES"),
addUpdate(name: "ADD_UPDATE"),
clearExtension(name: "CLEAR_EXTENSION"),
addExtension(name: "ADD_EXTENSION"),
removeExtension(name: "REMOVE_EXTENSION"),
updateExtension(name: "UPDATE_EXTENSION"),

View file

@ -131,12 +131,13 @@ const _ChangedPartactionTypeEnumValueMap = {
'updateHistory': 12,
'clearUpdates': 13,
'addUpdate': 14,
'addExtension': 15,
'removeExtension': 16,
'updateExtension': 17,
'addTrack': 18,
'removeTrack': 19,
'updateTrack': 20,
'clearExtension': 15,
'addExtension': 16,
'removeExtension': 17,
'updateExtension': 18,
'addTrack': 19,
'removeTrack': 20,
'updateTrack': 21,
};
const _ChangedPartactionTypeValueEnumMap = {
0: ActionType.addItem,
@ -154,12 +155,13 @@ const _ChangedPartactionTypeValueEnumMap = {
12: ActionType.updateHistory,
13: ActionType.clearUpdates,
14: ActionType.addUpdate,
15: ActionType.addExtension,
16: ActionType.removeExtension,
17: ActionType.updateExtension,
18: ActionType.addTrack,
19: ActionType.removeTrack,
20: ActionType.updateTrack,
15: ActionType.clearExtension,
16: ActionType.addExtension,
17: ActionType.removeExtension,
18: ActionType.updateExtension,
19: ActionType.addTrack,
20: ActionType.removeTrack,
21: ActionType.updateTrack,
};
Id _changedPartGetId(ChangedPart object) {

View file

@ -517,19 +517,25 @@ class Settings {
}
clearChapterCacheOnAppLaunch = json['clearChapterCacheOnAppLaunch'];
if (json['mangaExtensionsRepo'] != null) {
mangaExtensionsRepo = (json['mangaExtensionsRepo'] as List)
.map((e) => Repo.fromJson(json))
.toList();
mangaExtensionsRepo = json['mangaExtensionsRepo'] is String
? [Repo(jsonUrl: json['mangaExtensionsRepo'])]
: (json['mangaExtensionsRepo'] as List)
.map((e) => Repo.fromJson(json))
.toList();
}
if (json['animeExtensionsRepo'] != null) {
animeExtensionsRepo = (json['animeExtensionsRepo'] as List)
.map((e) => Repo.fromJson(json))
.toList();
animeExtensionsRepo = json['animeExtensionsRepo'] is String
? [Repo(jsonUrl: json['animeExtensionsRepo'])]
: (json['animeExtensionsRepo'] as List)
.map((e) => Repo.fromJson(json))
.toList();
}
if (json['novelExtensionsRepo'] != null) {
novelExtensionsRepo = (json['novelExtensionsRepo'] as List)
.map((e) => Repo.fromJson(json))
.toList();
novelExtensionsRepo = json['novelExtensionsRepo'] is String
? [Repo(jsonUrl: json['novelExtensionsRepo'])]
: (json['novelExtensionsRepo'] as List)
.map((e) => Repo.fromJson(json))
.toList();
}
}

View file

@ -3,10 +3,12 @@ import 'package:json_view/json_view.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/eval/lib.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/manga/home/widget/filter_widget.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/app_font_family.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/services/get_detail.dart';
import 'package:mangayomi/services/get_filter_list.dart';
@ -142,7 +144,14 @@ class _CodeEditorPageState extends ConsumerState<CodeEditorPage> {
return Scaffold(
appBar: AppBar(
leading: BackButton(onPressed: () {
isar.writeTxnSync(() => isar.sources.putSync(source!));
isar.writeTxnSync(() {
isar.sources.putSync(source!);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateExtension,
source!.id,
source!.toJson(),
false);
});
Navigator.pop(context, source);
}),
),
@ -168,8 +177,13 @@ class _CodeEditorPageState extends ConsumerState<CodeEditorPage> {
onChanged: (_) {
source?.sourceCode = _controller.text;
if (source != null && context.mounted) {
isar.writeTxnSync(
() => isar.sources.putSync(source!));
isar.writeTxnSync(() {
isar.sources.putSync(source!);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateExtension,
source!.id, source!.toJson(), false);
});
}
},
wordWrap: false,
@ -255,8 +269,17 @@ class _CodeEditorPageState extends ConsumerState<CodeEditorPage> {
onPressed: () async {
source?.sourceCode = _controller.text;
if (source != null && context.mounted) {
isar.writeTxnSync(
() => isar.sources.putSync(source!));
isar.writeTxnSync(() {
isar.sources.putSync(source!);
ref
.read(synchingProvider(syncId: 1)
.notifier)
.addChangedPart(
ActionType.updateExtension,
source!.id,
source!.toJson(),
false);
});
}
setState(() {
result = null;

View file

@ -5,9 +5,11 @@ import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/eval/model/source_preference.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/browse/extension/providers/extension_preferences_providers.dart';
import 'package:mangayomi/modules/browse/extension/widgets/source_preference_widget.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/services/get_source_preference.dart';
import 'package:mangayomi/services/http/m_client.dart';
@ -258,11 +260,31 @@ class _ExtensionDetailState extends ConsumerState<ExtensionDetail> {
if (source.isObsolete ?? false) {
isar.sources.deleteSync(
widget.source.id!);
ref
.read(synchingProvider(
syncId: 1)
.notifier)
.addChangedPart(
ActionType
.removeExtension,
source.id,
"{}",
false);
} else {
isar.sources.putSync(widget.source
..sourceCode = ""
..isAdded = false
..isPinned = false);
ref
.read(synchingProvider(
syncId: 1)
.notifier)
.addChangedPart(
ActionType
.updateExtension,
source.id,
source.toJson(),
false);
}
isar.sourcePreferences
.deleteAllSync(sourcePrefsIds);

View file

@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/modules/browse/extension/widgets/extension_lang_list_tile_widget.dart';
import 'package:mangayomi/utils/global_style.dart';
@ -48,6 +50,10 @@ class ExtensionsLang extends ConsumerWidget {
.findAllSync();
for (var source in sources) {
isar.sources.putSync(source..isActive = enable);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateExtension, source.id,
source.toJson(), false);
}
});
}),
@ -76,6 +82,10 @@ class ExtensionsLang extends ConsumerWidget {
for (var source in entries) {
if (source.lang!.toLowerCase() == lang.toLowerCase()) {
isar.sources.putSync(source..isActive = val);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateExtension,
source.id, source.toJson(), false);
}
}
});

View file

@ -1,8 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
@ -163,53 +166,61 @@ class _CreateExtensionState extends State<CreateExtension> {
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
if (_name.isNotEmpty &&
_lang.isNotEmpty &&
_baseUrl.isNotEmpty &&
_iconUrl.isNotEmpty) {
try {
final id =
_sourceCodeLanguage == SourceCodeLanguage.dart
? 'mangayomi-$_lang.$_name'.hashCode
: 'mangayomi-js-$_lang.$_name'.hashCode;
final checkIfExist = isar.sources.getSync(id);
if (checkIfExist == null) {
Source source = Source(
id: id,
name: _name,
lang: _lang,
baseUrl: _baseUrl,
apiUrl: _apiUrl,
iconUrl: _iconUrl,
typeSource: _sourceTypes[_sourceTypeIndex],
itemType:
ItemType.values.elementAt(_itemTypeIndex),
isAdded: true,
isActive: true,
version: "0.0.1",
isNsfw: false)
..sourceCodeLanguage = _sourceCodeLanguage;
source = source
..isLocal = true
..sourceCode = _sourceCodeLanguage ==
SourceCodeLanguage.dart
? _dartTemplate
: _jsSample(source);
isar.writeTxnSync(
() => isar.sources.putSync(source));
Navigator.pop(context);
botToast("Source created successfully");
} else {
botToast("Source already exists");
child: Consumer(
builder: (context, ref, child) => ElevatedButton(
onPressed: () {
if (_name.isNotEmpty &&
_lang.isNotEmpty &&
_baseUrl.isNotEmpty &&
_iconUrl.isNotEmpty) {
try {
final id =
_sourceCodeLanguage == SourceCodeLanguage.dart
? 'mangayomi-$_lang.$_name'.hashCode
: 'mangayomi-js-$_lang.$_name'.hashCode;
final checkIfExist = isar.sources.getSync(id);
if (checkIfExist == null) {
Source source = Source(
id: id,
name: _name,
lang: _lang,
baseUrl: _baseUrl,
apiUrl: _apiUrl,
iconUrl: _iconUrl,
typeSource: _sourceTypes[_sourceTypeIndex],
itemType: ItemType.values
.elementAt(_itemTypeIndex),
isAdded: true,
isActive: true,
version: "0.0.1",
isNsfw: false)
..sourceCodeLanguage = _sourceCodeLanguage;
source = source
..isLocal = true
..sourceCode = _sourceCodeLanguage ==
SourceCodeLanguage.dart
? _dartTemplate
: _jsSample(source);
isar.writeTxnSync(() {
isar.sources.putSync(source);
ref
.read(
synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.addExtension,
source.id, source.toJson(), false);
});
Navigator.pop(context);
botToast("Source created successfully");
} else {
botToast("Source already exists");
}
} catch (e) {
botToast("Error when creating source");
}
} catch (e) {
botToast("Error when creating source");
}
}
},
child: Text(context.l10n.save)),
},
child: Text(context.l10n.save)),
),
)
],
),

View file

@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/services/fetch_anime_sources.dart';
import 'package:mangayomi/services/fetch_manga_sources.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
@ -43,7 +45,14 @@ class _ExtensionListTileWidgetState
onTap: () async {
if (sourceNotEmpty || widget.isTestSource) {
if (widget.isTestSource) {
isar.writeTxnSync(() => isar.sources.putSync(widget.source));
isar.writeTxnSync(() {
isar.sources.putSync(widget.source);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateExtension,
widget.source.id,
widget.source.toJson(),
false);
});
}
context.push('/extension_detail', extra: widget.source);
} else {

View file

@ -3,8 +3,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:grouped_list/sliver_grouped_list.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/language.dart';
@ -53,6 +55,10 @@ class SourcesFilterScreen extends ConsumerWidget {
if (source.lang!.toLowerCase() == groupByValue) {
isar.sources
.putSync(source..isActive = val == true);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateExtension,
source.id, source.toJson(), false);
}
}
});
@ -100,6 +106,10 @@ class SourcesFilterScreen extends ConsumerWidget {
onChanged: (bool? value) {
isar.writeTxnSync(() {
isar.sources.putSync(element..isAdded = value);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateExtension,
element.id, element.toJson(), false);
});
},
value: element.isAdded!,

View file

@ -3,8 +3,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
@ -18,87 +20,94 @@ class SourceListTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
final sources = isar.sources
.filter()
.idIsNotNull()
.and()
.itemTypeEqualTo(itemType)
.findAllSync();
isar.writeTxnSync(() {
for (var src in sources) {
isar.sources
.putSync(src..lastUsed = src.id == source.id ? true : false);
}
});
context.push('/mangaHome', extra: (source, false));
},
leading: Container(
height: 37,
width: 37,
decoration: BoxDecoration(
color:
Theme.of(context).secondaryHeaderColor.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(5)),
child: source.iconUrl!.isEmpty
? const Icon(Icons.extension_rounded)
: cachedNetworkImage(
imageUrl: source.iconUrl!,
fit: BoxFit.contain,
width: 37,
height: 37,
errorWidget: const SizedBox(
return Consumer(
builder: (context, ref, child) => ListTile(
onTap: () {
final sources = isar.sources
.filter()
.idIsNotNull()
.and()
.itemTypeEqualTo(itemType)
.findAllSync();
isar.writeTxnSync(() {
for (var src in sources) {
isar.sources
.putSync(src..lastUsed = src.id == source.id ? true : false);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateExtension, src.id, src.toJson(), false);
}
});
context.push('/mangaHome', extra: (source, false));
},
leading: Container(
height: 37,
width: 37,
decoration: BoxDecoration(
color:
Theme.of(context).secondaryHeaderColor.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(5)),
child: source.iconUrl!.isEmpty
? const Icon(Icons.extension_rounded)
: cachedNetworkImage(
imageUrl: source.iconUrl!,
fit: BoxFit.contain,
width: 37,
height: 37,
child: Center(
child: Icon(Icons.extension_rounded),
errorWidget: const SizedBox(
width: 37,
height: 37,
child: Center(
child: Icon(Icons.extension_rounded),
),
),
),
useCustomNetworkImage: false),
),
subtitle: Row(
children: [
Text(
completeLanguageName(source.lang!.toLowerCase()),
style: const TextStyle(fontWeight: FontWeight.w300, fontSize: 12),
),
],
),
title: Text(source.name!),
trailing: SizedBox(
width: 150,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
useCustomNetworkImage: false),
),
subtitle: Row(
children: [
Consumer(
builder: (context, ref, child) {
// final supportsLatest = ref.watch(supportsLatestProvider(source: source));
// if (supportsLatest) {
return TextButton(
style: const ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.all(10))),
onPressed: () =>
context.push('/mangaHome', extra: (source, true)),
child: Text(context.l10n.latest));
// }
// return const SizedBox.shrink();
},
Text(
completeLanguageName(source.lang!.toLowerCase()),
style: const TextStyle(fontWeight: FontWeight.w300, fontSize: 12),
),
const SizedBox(width: 10),
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {
isar.writeTxnSync(() => isar.sources
.putSync(source..isPinned = !source.isPinned!));
},
icon: Icon(
Icons.push_pin_outlined,
color: source.isPinned! ? context.primaryColor : null,
)),
],
),
title: Text(source.name!),
trailing: SizedBox(
width: 150,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Consumer(
builder: (context, ref, child) {
// final supportsLatest = ref.watch(supportsLatestProvider(source: source));
// if (supportsLatest) {
return TextButton(
style: const ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.all(10))),
onPressed: () =>
context.push('/mangaHome', extra: (source, true)),
child: Text(context.l10n.latest));
// }
// return const SizedBox.shrink();
},
),
const SizedBox(width: 10),
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {
isar.writeTxnSync(() => isar.sources
.putSync(source..isPinned = !source.isPinned!));
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateExtension, source.id,
source.toJson(), false);
},
icon: Icon(
Icons.push_pin_outlined,
color: source.isPinned! ? context.primaryColor : null,
)),
],
),
),
),
);
}

View file

@ -6,7 +6,7 @@ part of 'migration.dart';
// RiverpodGenerator
// **************************************************************************
String _$migrationHash() => r'd4ebb16320d44b90ca9532f10e8128b9ee8ab52d';
String _$migrationHash() => r'2a82120544e693a3162da887a3ca1b3066f3799f';
/// See also [migration].
@ProviderFor(migration)

View file

@ -22,6 +22,7 @@ import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -185,6 +186,7 @@ void restoreBackup(Ref ref, Map<String, dynamic> backup, {bool full = true}) {
}
}
if (full) {
ref.read(synchingProvider(syncId: 1).notifier).clearAllChangedParts();
ref.invalidate(themeModeStateProvider);
ref.invalidate(blendLevelStateProvider);
ref.invalidate(flexSchemeColorStateProvider);

View file

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

View file

@ -3,8 +3,10 @@ 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/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
@ -173,16 +175,22 @@ void _showClearAllSourcesDialog(BuildContext context, dynamic l10n) {
const SizedBox(
width: 15,
),
TextButton(
onPressed: () {
isar.writeTxnSync(() {
isar.sources.clearSync();
});
Consumer(
builder: (context, ref, child) => TextButton(
onPressed: () {
isar.writeTxnSync(() {
isar.sources.clearSync();
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(
ActionType.clearHistory, null, "{}", false);
});
Navigator.pop(ctx);
botToast(l10n.sources_cleared);
},
child: Text(l10n.ok)),
Navigator.pop(ctx);
botToast(l10n.sources_cleared);
},
child: Text(l10n.ok)),
),
],
)
],

View file

@ -4,6 +4,7 @@ import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/sync_preference.dart';
import 'package:mangayomi/services/sync_server.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'sync_providers.g.dart';
@ -18,12 +19,16 @@ class Synching extends _$Synching {
isar.writeTxnSync(() {
isar.syncPreferences.putSync(syncPreference);
});
ref.invalidate(synchingProvider(syncId: syncId));
ref.invalidate(syncServerProvider(syncId: syncId!));
}
void logout() {
isar.writeTxnSync(() {
isar.syncPreferences.putSync(state..authToken = null);
});
ref.invalidate(synchingProvider(syncId: syncId));
ref.invalidate(syncServerProvider(syncId: syncId!));
}
void setLastUpload(int timestamp) {

View file

@ -6,7 +6,7 @@ part of 'sync_providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$synchingHash() => r'45ac5bd29f880dfc7ac1fcdf12f49d751325279b';
String _$synchingHash() => r'abef3be5c474e8bf6f9d2b5af7587caf061d1fbd';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -117,6 +117,7 @@ class SyncScreen extends ConsumerWidget {
),
),
),
const SizedBox(height: 20),
Row(
children: [
const SizedBox(width: 20),
@ -141,94 +142,6 @@ class SyncScreen extends ConsumerWidget {
],
),
const SizedBox(width: 20),
Column(
children: [
IconButton(
onPressed: !isLogged
? null
: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(
l10n.sync_confirm_upload),
actions: [
Row(
mainAxisAlignment:
MainAxisAlignment.end,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
Colors
.transparent,
shadowColor: Colors
.transparent,
surfaceTintColor:
Colors
.transparent,
shape: RoundedRectangleBorder(
side: BorderSide(
color: context
.secondaryColor),
borderRadius:
BorderRadius
.circular(
20))),
onPressed: () {
Navigator.pop(
context);
},
child: Text(
l10n.cancel,
style: TextStyle(
color: context
.secondaryColor),
)),
const SizedBox(width: 15),
ElevatedButton(
style: ElevatedButton
.styleFrom(
backgroundColor: Colors
.red
.withValues(
alpha:
0.7)),
onPressed: () {
ref
.read(
syncServerProvider(
syncId:
1)
.notifier)
.uploadToServer(
l10n);
Navigator.pop(
context);
},
child: Text(
l10n.dialog_confirm,
style: TextStyle(
color: context
.secondaryColor),
)),
],
)
],
);
});
},
icon: Icon(
Icons.cloud_upload_outlined,
color: !isLogged
? context.secondaryColor
: context.primaryColor,
)),
Text(l10n.sync_button_upload),
],
),
const SizedBox(width: 20),
Column(
children: [
IconButton(
@ -317,6 +230,94 @@ class SyncScreen extends ConsumerWidget {
],
),
const SizedBox(width: 20),
Column(
children: [
IconButton(
onPressed: !isLogged
? null
: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(
l10n.sync_confirm_upload),
actions: [
Row(
mainAxisAlignment:
MainAxisAlignment.end,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
Colors
.transparent,
shadowColor: Colors
.transparent,
surfaceTintColor:
Colors
.transparent,
shape: RoundedRectangleBorder(
side: BorderSide(
color: context
.secondaryColor),
borderRadius:
BorderRadius
.circular(
20))),
onPressed: () {
Navigator.pop(
context);
},
child: Text(
l10n.cancel,
style: TextStyle(
color: context
.secondaryColor),
)),
const SizedBox(width: 15),
ElevatedButton(
style: ElevatedButton
.styleFrom(
backgroundColor: Colors
.red
.withValues(
alpha:
0.7)),
onPressed: () {
ref
.read(
syncServerProvider(
syncId:
1)
.notifier)
.uploadToServer(
l10n);
Navigator.pop(
context);
},
child: Text(
l10n.dialog_confirm,
style: TextStyle(
color: context
.secondaryColor),
)),
],
)
],
);
});
},
icon: Icon(
Icons.cloud_upload_outlined,
color: !isLogged
? context.secondaryColor
: context.primaryColor,
)),
Text(l10n.sync_button_upload),
],
),
const SizedBox(width: 20),
Column(
children: [
IconButton(
@ -408,7 +409,7 @@ class SyncScreen extends ConsumerWidget {
),
],
),
const SizedBox(height: 30),
const SizedBox(height: 40),
buildChangedItemWidget(
l10n.sync_pending_manga,
changedParts.getChangedParts([
@ -416,7 +417,7 @@ class SyncScreen extends ConsumerWidget {
ActionType.removeItem,
ActionType.updateItem
])),
const SizedBox(height: 10),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_chapter,
changedParts.getChangedParts([
@ -424,7 +425,7 @@ class SyncScreen extends ConsumerWidget {
ActionType.removeChapter,
ActionType.updateChapter
])),
const SizedBox(height: 10),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_category,
changedParts.getChangedParts([
@ -432,7 +433,7 @@ class SyncScreen extends ConsumerWidget {
ActionType.removeCategory,
ActionType.renameCategory
])),
const SizedBox(height: 10),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_history,
changedParts.getChangedParts([
@ -440,12 +441,12 @@ class SyncScreen extends ConsumerWidget {
ActionType.clearHistory,
ActionType.removeHistory
])),
const SizedBox(height: 10),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_update,
changedParts.getChangedParts(
[ActionType.addUpdate, ActionType.clearUpdates])),
const SizedBox(height: 10),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_extension,
changedParts.getChangedParts([
@ -453,7 +454,7 @@ class SyncScreen extends ConsumerWidget {
ActionType.removeExtension,
ActionType.updateExtension
])),
const SizedBox(height: 10),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_track,
changedParts.getChangedParts([
@ -468,15 +469,19 @@ class SyncScreen extends ConsumerWidget {
child: Row(
children: [
ElevatedButton(
onPressed: () {
ref
.read(syncServerProvider(syncId: 1).notifier)
.getSnapshots(l10n);
},
onPressed: !isLogged
? null
: () {
ref
.read(syncServerProvider(syncId: 1)
.notifier)
.getSnapshots(l10n);
},
child: Text("Browse / Download older backups")),
],
),
),
const SizedBox(height: 20),
],
);
}),

View file

@ -3,10 +3,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/lib.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:package_info_plus/package_info_plus.dart';
@ -66,6 +68,11 @@ Future<void> fetchSourcesList(
..isObsolete = false
..repo = repo,
);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateExtension,
sourc.id,
sourc.toJson(),
false);
});
// log("successfully installed or updated");
}
@ -107,6 +114,10 @@ Future<void> fetchSourcesList(
..additionalParams = source.additionalParams ?? ""
..isObsolete = false
..repo = repo);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateExtension, sourc.id,
sourc.toJson(), false);
});
} else {
// log("update aivalable");
@ -115,7 +126,7 @@ Future<void> fetchSourcesList(
}
}
} else {
isar.sources.putSync(Source()
final newSource = Source()
..sourceCodeUrl = source.sourceCodeUrl
..id = source.id
..sourceCode = source.sourceCode
@ -136,7 +147,10 @@ Future<void> fetchSourcesList(
..isFullData = source.isFullData ?? false
..appMinVerReq = source.appMinVerReq
..isObsolete = false
..repo = repo);
..repo = repo;
isar.sources.putSync(newSource);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.addExtension, null, newSource.toJson(), false);
// log("new source");
}
}
@ -144,10 +158,11 @@ Future<void> fetchSourcesList(
}
}
});
checkIfSourceIsObsolete(sourceList, itemType);
checkIfSourceIsObsolete(sourceList, itemType, ref);
}
void checkIfSourceIsObsolete(List<Source> sourceList, ItemType itemType) {
void checkIfSourceIsObsolete(
List<Source> sourceList, ItemType itemType, Ref ref) {
for (var source in isar.sources
.filter()
.idIsNotNull()
@ -157,8 +172,11 @@ void checkIfSourceIsObsolete(List<Source> sourceList, ItemType itemType) {
final ids =
sourceList.where((e) => e.id != null).map((e) => e.id).toList();
if (ids.isNotEmpty) {
isar.writeTxnSync(() => isar.sources
.putSync(source..isObsolete = !ids.contains(source.id)));
isar.writeTxnSync(() {
isar.sources.putSync(source..isObsolete = !ids.contains(source.id));
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateExtension, source.id, source.toJson(), false);
});
}
}
}

View file

@ -86,7 +86,7 @@ class SyncServer extends _$SyncServer {
return;
}
var jsonData = jsonDecode(response.body) as Map<String, dynamic>;
final localHash = _getDataHash(_getData());
final localHash = _getDataHash(_getData(true));
final remoteHash = jsonData["hash"];
if (localHash != remoteHash) {
await downloadFromServer(l10n, true, false);
@ -94,12 +94,11 @@ class SyncServer extends _$SyncServer {
} else {
await forceCheck(l10n, true);
}
ref
.read(synchingProvider(syncId: syncId).notifier)
.setLastSync(DateTime.now().millisecondsSinceEpoch);
ref
.read(synchingProvider(syncId: syncId).notifier)
.clearAllChangedParts();
final syncNotifier = ref.read(synchingProvider(syncId: syncId).notifier);
syncNotifier.setLastSync(DateTime.now().millisecondsSinceEpoch);
syncNotifier.clearAllChangedParts();
ref.invalidate(synchingProvider(syncId: syncId));
botToast(l10n.sync_download_finished, second: 2);
} catch (error) {
@ -113,7 +112,7 @@ class SyncServer extends _$SyncServer {
}
try {
final accessToken = _getAccessToken();
final localHash = _getDataHash(_getData());
final localHash = _getDataHash(_getData(true));
var response = await http.get(
Uri.parse('${_getServer()}$_checkUrl'),
headers: {
@ -190,9 +189,6 @@ class SyncServer extends _$SyncServer {
ref
.read(synchingProvider(syncId: syncId).notifier)
.setLastDownload(DateTime.now().millisecondsSinceEpoch);
ref
.read(synchingProvider(syncId: syncId).notifier)
.clearAllChangedParts();
ref.invalidate(synchingProvider(syncId: syncId));
botToast(l10n.sync_download_finished, second: 2);
} catch (error) {
@ -228,7 +224,7 @@ class SyncServer extends _$SyncServer {
Future<void> uploadToServer(AppLocalizations l10n) async {
botToast(l10n.sync_uploading, second: 2);
try {
final datas = _getData();
final datas = _getData(false);
final accessToken = _getAccessToken();
var response = await http.post(
@ -284,9 +280,6 @@ class SyncServer extends _$SyncServer {
ref
.read(synchingProvider(syncId: syncId).notifier)
.setLastDownload(DateTime.now().millisecondsSinceEpoch);
ref
.read(synchingProvider(syncId: syncId).notifier)
.clearAllChangedParts();
ref.invalidate(synchingProvider(syncId: syncId));
if (!silent) {
botToast(l10n.sync_download_finished, second: 2);
@ -305,11 +298,12 @@ class SyncServer extends _$SyncServer {
datas["tracks"] = data["tracks"];
datas["history"] = data["history"];
datas["updates"] = data["updates"];
datas["extensions"] = data["extensions"];
var encodedJson = jsonEncode(datas);
return sha256.convert(utf8.encode(encodedJson)).toString();
}
Map<String, dynamic> _getData() {
Map<String, dynamic> _getData(bool hashCheck) {
Map<String, dynamic> datas = {};
datas.addAll({"version": "2"});
final mangas = isar.mangas
@ -318,21 +312,21 @@ class SyncServer extends _$SyncServer {
.favoriteEqualTo(true)
.isLocalArchiveEqualTo(false)
.findAllSync()
.map((e) => e.toJson())
.map((e) => hashCheck ? (e..id = 0).toJson() : e.toJson())
.toList();
datas.addAll({"manga": mangas});
final categorys = isar.categorys
.filter()
.idIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.map((e) => hashCheck ? (e..id = 0).toJson() : e.toJson())
.toList();
datas.addAll({"categories": categorys});
final chapters = isar.chapters
.filter()
.idIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.map((e) => hashCheck ? (e..id = 0).toJson() : e.toJson())
.toList();
datas.addAll({"chapters": chapters});
datas.addAll({"downloads": []});
@ -340,7 +334,7 @@ class SyncServer extends _$SyncServer {
.filter()
.idIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.map((e) => hashCheck ? (e..id = 0).toJson() : e.toJson())
.toList();
datas.addAll({"tracks": tracks});
datas.addAll({"trackPreferences": []});
@ -348,21 +342,21 @@ class SyncServer extends _$SyncServer {
.filter()
.idIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.map((e) => hashCheck ? (e..id = 0).toJson() : e.toJson())
.toList();
datas.addAll({"history": historys});
final settings = isar.settings
.filter()
.idIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.map((e) => hashCheck ? (e..id = 0).toJson() : e.toJson())
.toList();
datas.addAll({"settings": settings});
final sources = isar.sources
.filter()
.idIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.map((e) => hashCheck ? (e..id = 0).toJson() : e.toJson())
.toList();
datas.addAll({"extensions": sources});
final sourcePreferences = isar.sourcePreferences
@ -370,14 +364,14 @@ class SyncServer extends _$SyncServer {
.idIsNotNull()
.keyIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.map((e) => hashCheck ? (e..id = 0).toJson() : e.toJson())
.toList();
datas.addAll({"extensions_preferences": sourcePreferences});
final updates = isar.updates
.filter()
.idIsNotNull()
.findAllSync()
.map((e) => e.toJson())
.map((e) => hashCheck ? (e..id = 0).toJson() : e.toJson())
.toList();
datas.addAll({"updates": updates});
return datas;

View file

@ -6,7 +6,7 @@ part of 'sync_server.dart';
// RiverpodGenerator
// **************************************************************************
String _$syncServerHash() => r'f9e68540eb702f3e3498fc642454897921a17f42';
String _$syncServerHash() => r'b1d19a01737badaa79b42a7a39f2f7782463ee63';
/// Copied from Dart SDK
class _SystemHash {