Merge pull request #380 from Schnitzel5/feature/sync

enhanced sync feature
This commit is contained in:
Moustapha Kodjo Amadou 2025-02-14 11:06:14 +01:00 committed by GitHub
commit 400fd90c6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 2730 additions and 299 deletions

View file

@ -225,8 +225,26 @@
"sync_pending_track": "Ausstehende Änderungen für Trackings",
"sync_snapshot_creating": "Erstelle Snapshot...",
"sync_snapshot_created": "Snapshot wurde erstellt!",
"sync_snapshot_deleting": "Lösche Snapshot...",
"sync_snapshot_deleted": "Snapshot wurde gelöscht!",
"sync_snapshot_no_data": "Keine Daten zum Sichern! Lade erstmal alles hoch!",
"server_error": "Server error!",
"sync_browse_snapshots": "Durchsuche ältere Backups",
"sync_snapshots": "Snapshots",
"sync_load_snapshot": "Snapshot laden",
"sync_delete_snapshot": "Snapshot löschen",
"sync_auto": "Auto Sync",
"sync_auto_warning": "Auto Sync ist derzeit ein experimentelles Feature!",
"sync_auto_off": "Aus",
"sync_auto_30_seconds": "Alle 30 Sekunden",
"sync_auto_1_minute": "Jede Minute",
"sync_auto_5_minutes": "Alle 5 Minuten",
"sync_auto_10_minutes": "Alle 10 Minuten",
"sync_auto_30_minutes": "Alle 30 Minuten",
"sync_auto_1_hour": "Jede Stunde",
"sync_auto_3_hours": "Alle 3 Stunden",
"sync_auto_6_hours": "Alle 6 Stunden",
"sync_auto_12_hours": "Alle 12 Stunden",
"server_error": "Server Fehler!",
"dialog_confirm": "Fortfahren",
"description": "Beschreibung",
"full_screen_player": "Vollbildmodus aktivieren",

View file

@ -225,7 +225,25 @@
"sync_pending_track": "Track changes pending",
"sync_snapshot_creating": "Creating snapshot...",
"sync_snapshot_created": "Snapshot created!",
"sync_snapshot_deleting": "Deleting snapshot...",
"sync_snapshot_deleted": "Snapshot deleted!",
"sync_snapshot_no_data": "No data to create a snapshot! Do a full upload first!",
"sync_browse_snapshots": "Browse older backups",
"sync_snapshots": "Snapshots",
"sync_load_snapshot": "Load snapshot",
"sync_delete_snapshot": "Delete snapshot",
"sync_auto": "Auto Sync",
"sync_auto_warning": "Auto Sync is currently an experimental feature!",
"sync_auto_off": "Off",
"sync_auto_30_seconds": "Every 30 seconds",
"sync_auto_1_minute": "Every 1 minute",
"sync_auto_5_minutes": "Every 5 minutes",
"sync_auto_10_minutes": "Every 10 minutes",
"sync_auto_30_minutes": "Every 30 minutes",
"sync_auto_1_hour": "Every 1 hour",
"sync_auto_3_hours": "Every 3 hours",
"sync_auto_6_hours": "Every 6 hours",
"sync_auto_12_hours": "Every 12 hours",
"server_error": "Server error!",
"dialog_confirm": "Confirm",
"description": "Description",

56
lib/models/changed.dart Normal file
View file

@ -0,0 +1,56 @@
import 'package:isar/isar.dart';
part 'changed.g.dart';
@collection
@Name("ChangedPart")
class ChangedPart {
Id? id;
@enumerated
late ActionType actionType;
int? isarId;
String data;
int clientDate;
ChangedPart(
{this.id = Isar.autoIncrement,
required this.actionType,
this.isarId,
required this.data,
required this.clientDate});
Map<String, dynamic> toJson() => {
'action': actionType.name,
'isarId': isarId,
'data': data,
'clientDate': clientDate
};
}
enum ActionType {
addItem(name: "ADD_ITEM"),
removeItem(name: "REMOVE_ITEM"),
updateItem(name: "UPDATE_ITEM"),
addCategory(name: "ADD_CATEGORY"),
removeCategory(name: "REMOVE_CATEGORY"),
renameCategory(name: "RENAME_CATEGORY"),
addChapter(name: "ADD_CHAPTER"),
removeChapter(name: "REMOVE_CHAPTER"),
updateChapter(name: "UPDATE_CHAPTER"),
clearHistory(name: "CLEAR_HISTORY"),
addHistory(name: "ADD_HISTORY"),
removeHistory(name: "REMOVE_HISTORY"),
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"),
addTrack(name: "ADD_TRACK"),
removeTrack(name: "REMOVE_TRACK"),
updateTrack(name: "UPDATE_TRACK");
final String name;
const ActionType({required this.name});
}

823
lib/models/changed.g.dart Normal file
View file

@ -0,0 +1,823 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'changed.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
extension GetChangedPartCollection on Isar {
IsarCollection<ChangedPart> get changedParts => this.collection();
}
const ChangedPartSchema = CollectionSchema(
name: r'ChangedPart',
id: 984304309479278230,
properties: {
r'actionType': PropertySchema(
id: 0,
name: r'actionType',
type: IsarType.byte,
enumMap: _ChangedPartactionTypeEnumValueMap,
),
r'clientDate': PropertySchema(
id: 1,
name: r'clientDate',
type: IsarType.long,
),
r'data': PropertySchema(
id: 2,
name: r'data',
type: IsarType.string,
),
r'isarId': PropertySchema(
id: 3,
name: r'isarId',
type: IsarType.long,
)
},
estimateSize: _changedPartEstimateSize,
serialize: _changedPartSerialize,
deserialize: _changedPartDeserialize,
deserializeProp: _changedPartDeserializeProp,
idName: r'id',
indexes: {},
links: {},
embeddedSchemas: {},
getId: _changedPartGetId,
getLinks: _changedPartGetLinks,
attach: _changedPartAttach,
version: '3.1.0+1',
);
int _changedPartEstimateSize(
ChangedPart object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
bytesCount += 3 + object.data.length * 3;
return bytesCount;
}
void _changedPartSerialize(
ChangedPart object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeByte(offsets[0], object.actionType.index);
writer.writeLong(offsets[1], object.clientDate);
writer.writeString(offsets[2], object.data);
writer.writeLong(offsets[3], object.isarId);
}
ChangedPart _changedPartDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = ChangedPart(
actionType:
_ChangedPartactionTypeValueEnumMap[reader.readByteOrNull(offsets[0])] ??
ActionType.addItem,
clientDate: reader.readLong(offsets[1]),
data: reader.readString(offsets[2]),
id: id,
isarId: reader.readLongOrNull(offsets[3]),
);
return object;
}
P _changedPartDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (_ChangedPartactionTypeValueEnumMap[
reader.readByteOrNull(offset)] ??
ActionType.addItem) as P;
case 1:
return (reader.readLong(offset)) as P;
case 2:
return (reader.readString(offset)) as P;
case 3:
return (reader.readLongOrNull(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
const _ChangedPartactionTypeEnumValueMap = {
'addItem': 0,
'removeItem': 1,
'updateItem': 2,
'addCategory': 3,
'removeCategory': 4,
'renameCategory': 5,
'addChapter': 6,
'removeChapter': 7,
'updateChapter': 8,
'clearHistory': 9,
'addHistory': 10,
'removeHistory': 11,
'updateHistory': 12,
'clearUpdates': 13,
'addUpdate': 14,
'clearExtension': 15,
'addExtension': 16,
'removeExtension': 17,
'updateExtension': 18,
'addTrack': 19,
'removeTrack': 20,
'updateTrack': 21,
};
const _ChangedPartactionTypeValueEnumMap = {
0: ActionType.addItem,
1: ActionType.removeItem,
2: ActionType.updateItem,
3: ActionType.addCategory,
4: ActionType.removeCategory,
5: ActionType.renameCategory,
6: ActionType.addChapter,
7: ActionType.removeChapter,
8: ActionType.updateChapter,
9: ActionType.clearHistory,
10: ActionType.addHistory,
11: ActionType.removeHistory,
12: ActionType.updateHistory,
13: ActionType.clearUpdates,
14: ActionType.addUpdate,
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) {
return object.id ?? Isar.autoIncrement;
}
List<IsarLinkBase<dynamic>> _changedPartGetLinks(ChangedPart object) {
return [];
}
void _changedPartAttach(
IsarCollection<dynamic> col, Id id, ChangedPart object) {
object.id = id;
}
extension ChangedPartQueryWhereSort
on QueryBuilder<ChangedPart, ChangedPart, QWhere> {
QueryBuilder<ChangedPart, ChangedPart, QAfterWhere> anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension ChangedPartQueryWhere
on QueryBuilder<ChangedPart, ChangedPart, QWhereClause> {
QueryBuilder<ChangedPart, ChangedPart, QAfterWhereClause> idEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: id,
upper: id,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterWhereClause> idNotEqualTo(
Id id) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
);
}
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterWhereClause> idGreaterThan(Id id,
{bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterWhereClause> idLessThan(Id id,
{bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterWhereClause> idBetween(
Id lowerId,
Id upperId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: lowerId,
includeLower: includeLower,
upper: upperId,
includeUpper: includeUpper,
));
});
}
}
extension ChangedPartQueryFilter
on QueryBuilder<ChangedPart, ChangedPart, QFilterCondition> {
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition>
actionTypeEqualTo(ActionType value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'actionType',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition>
actionTypeGreaterThan(
ActionType value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'actionType',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition>
actionTypeLessThan(
ActionType value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'actionType',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition>
actionTypeBetween(
ActionType lower,
ActionType upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'actionType',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition>
clientDateEqualTo(int value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'clientDate',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition>
clientDateGreaterThan(
int value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'clientDate',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition>
clientDateLessThan(
int value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'clientDate',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition>
clientDateBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'clientDate',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> dataEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'data',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> dataGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'data',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> dataLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'data',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> dataBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'data',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> dataStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'data',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> dataEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'data',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> dataContains(
String value,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'data',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> dataMatches(
String pattern,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'data',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> dataIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'data',
value: '',
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition>
dataIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'data',
value: '',
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> idIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'id',
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> idIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'id',
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> idEqualTo(
Id? value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'id',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> idGreaterThan(
Id? value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> idLessThan(
Id? value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> idBetween(
Id? lower,
Id? upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> isarIdIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'isarId',
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition>
isarIdIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'isarId',
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> isarIdEqualTo(
int? value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'isarId',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition>
isarIdGreaterThan(
int? value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'isarId',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> isarIdLessThan(
int? value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'isarId',
value: value,
));
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterFilterCondition> isarIdBetween(
int? lower,
int? upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'isarId',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
}
extension ChangedPartQueryObject
on QueryBuilder<ChangedPart, ChangedPart, QFilterCondition> {}
extension ChangedPartQueryLinks
on QueryBuilder<ChangedPart, ChangedPart, QFilterCondition> {}
extension ChangedPartQuerySortBy
on QueryBuilder<ChangedPart, ChangedPart, QSortBy> {
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> sortByActionType() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'actionType', Sort.asc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> sortByActionTypeDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'actionType', Sort.desc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> sortByClientDate() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'clientDate', Sort.asc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> sortByClientDateDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'clientDate', Sort.desc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> sortByData() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'data', Sort.asc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> sortByDataDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'data', Sort.desc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> sortByIsarId() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isarId', Sort.asc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> sortByIsarIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isarId', Sort.desc);
});
}
}
extension ChangedPartQuerySortThenBy
on QueryBuilder<ChangedPart, ChangedPart, QSortThenBy> {
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> thenByActionType() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'actionType', Sort.asc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> thenByActionTypeDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'actionType', Sort.desc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> thenByClientDate() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'clientDate', Sort.asc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> thenByClientDateDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'clientDate', Sort.desc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> thenByData() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'data', Sort.asc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> thenByDataDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'data', Sort.desc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> thenByIsarId() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isarId', Sort.asc);
});
}
QueryBuilder<ChangedPart, ChangedPart, QAfterSortBy> thenByIsarIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'isarId', Sort.desc);
});
}
}
extension ChangedPartQueryWhereDistinct
on QueryBuilder<ChangedPart, ChangedPart, QDistinct> {
QueryBuilder<ChangedPart, ChangedPart, QDistinct> distinctByActionType() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'actionType');
});
}
QueryBuilder<ChangedPart, ChangedPart, QDistinct> distinctByClientDate() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'clientDate');
});
}
QueryBuilder<ChangedPart, ChangedPart, QDistinct> distinctByData(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'data', caseSensitive: caseSensitive);
});
}
QueryBuilder<ChangedPart, ChangedPart, QDistinct> distinctByIsarId() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'isarId');
});
}
}
extension ChangedPartQueryProperty
on QueryBuilder<ChangedPart, ChangedPart, QQueryProperty> {
QueryBuilder<ChangedPart, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<ChangedPart, ActionType, QQueryOperations> actionTypeProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'actionType');
});
}
QueryBuilder<ChangedPart, int, QQueryOperations> clientDateProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'clientDate');
});
}
QueryBuilder<ChangedPart, String, QQueryOperations> dataProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'data');
});
}
QueryBuilder<ChangedPart, int?, QQueryOperations> isarIdProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'isarId');
});
}
}

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

@ -18,6 +18,10 @@ class SyncPreference {
String? server;
bool syncOn = false;
int autoSyncFrequency = 0;
SyncPreference({
this.syncId,
this.email,
@ -26,6 +30,8 @@ class SyncPreference {
this.lastUpload,
this.lastDownload,
this.server,
this.syncOn = false,
this.autoSyncFrequency = 0,
});
SyncPreference.fromJson(Map<String, dynamic> json) {
@ -36,6 +42,8 @@ class SyncPreference {
lastUpload = json['lastUpload'];
lastDownload = json['lastDownload'];
server = json['server'];
syncOn = json['syncOn'] ?? false;
syncOn = json['autoSyncFrequency'] ?? 0;
}
Map<String, dynamic> toJson() => {
@ -45,6 +53,7 @@ class SyncPreference {
'lastSync': lastSync,
'lastUpload': lastUpload,
'lastDownload': lastDownload,
'server': server
'syncOn': syncOn,
'autoSyncFrequency': autoSyncFrequency,
};
}

View file

@ -22,30 +22,40 @@ const SyncPreferenceSchema = CollectionSchema(
name: r'authToken',
type: IsarType.string,
),
r'email': PropertySchema(
r'autoSyncFrequency': PropertySchema(
id: 1,
name: r'autoSyncFrequency',
type: IsarType.long,
),
r'email': PropertySchema(
id: 2,
name: r'email',
type: IsarType.string,
),
r'lastDownload': PropertySchema(
id: 2,
id: 3,
name: r'lastDownload',
type: IsarType.long,
),
r'lastSync': PropertySchema(
id: 3,
id: 4,
name: r'lastSync',
type: IsarType.long,
),
r'lastUpload': PropertySchema(
id: 4,
id: 5,
name: r'lastUpload',
type: IsarType.long,
),
r'server': PropertySchema(
id: 5,
id: 6,
name: r'server',
type: IsarType.string,
),
r'syncOn': PropertySchema(
id: 7,
name: r'syncOn',
type: IsarType.bool,
)
},
estimateSize: _syncPreferenceEstimateSize,
@ -96,11 +106,13 @@ void _syncPreferenceSerialize(
Map<Type, List<int>> allOffsets,
) {
writer.writeString(offsets[0], object.authToken);
writer.writeString(offsets[1], object.email);
writer.writeLong(offsets[2], object.lastDownload);
writer.writeLong(offsets[3], object.lastSync);
writer.writeLong(offsets[4], object.lastUpload);
writer.writeString(offsets[5], object.server);
writer.writeLong(offsets[1], object.autoSyncFrequency);
writer.writeString(offsets[2], object.email);
writer.writeLong(offsets[3], object.lastDownload);
writer.writeLong(offsets[4], object.lastSync);
writer.writeLong(offsets[5], object.lastUpload);
writer.writeString(offsets[6], object.server);
writer.writeBool(offsets[7], object.syncOn);
}
SyncPreference _syncPreferenceDeserialize(
@ -111,12 +123,14 @@ SyncPreference _syncPreferenceDeserialize(
) {
final object = SyncPreference(
authToken: reader.readStringOrNull(offsets[0]),
email: reader.readStringOrNull(offsets[1]),
lastDownload: reader.readLongOrNull(offsets[2]),
lastSync: reader.readLongOrNull(offsets[3]),
lastUpload: reader.readLongOrNull(offsets[4]),
server: reader.readStringOrNull(offsets[5]),
autoSyncFrequency: reader.readLongOrNull(offsets[1]) ?? 0,
email: reader.readStringOrNull(offsets[2]),
lastDownload: reader.readLongOrNull(offsets[3]),
lastSync: reader.readLongOrNull(offsets[4]),
lastUpload: reader.readLongOrNull(offsets[5]),
server: reader.readStringOrNull(offsets[6]),
syncId: id,
syncOn: reader.readBoolOrNull(offsets[7]) ?? false,
);
return object;
}
@ -131,15 +145,19 @@ P _syncPreferenceDeserializeProp<P>(
case 0:
return (reader.readStringOrNull(offset)) as P;
case 1:
return (reader.readStringOrNull(offset)) as P;
return (reader.readLongOrNull(offset) ?? 0) as P;
case 2:
return (reader.readLongOrNull(offset)) as P;
return (reader.readStringOrNull(offset)) as P;
case 3:
return (reader.readLongOrNull(offset)) as P;
case 4:
return (reader.readLongOrNull(offset)) as P;
case 5:
return (reader.readLongOrNull(offset)) as P;
case 6:
return (reader.readStringOrNull(offset)) as P;
case 7:
return (reader.readBoolOrNull(offset) ?? false) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
@ -393,6 +411,62 @@ extension SyncPreferenceQueryFilter
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterFilterCondition>
autoSyncFrequencyEqualTo(int value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'autoSyncFrequency',
value: value,
));
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterFilterCondition>
autoSyncFrequencyGreaterThan(
int value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'autoSyncFrequency',
value: value,
));
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterFilterCondition>
autoSyncFrequencyLessThan(
int value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'autoSyncFrequency',
value: value,
));
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterFilterCondition>
autoSyncFrequencyBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'autoSyncFrequency',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterFilterCondition>
emailIsNull() {
return QueryBuilder.apply(this, (query) {
@ -996,6 +1070,16 @@ extension SyncPreferenceQueryFilter
));
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterFilterCondition>
syncOnEqualTo(bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'syncOn',
value: value,
));
});
}
}
extension SyncPreferenceQueryObject
@ -1019,6 +1103,20 @@ extension SyncPreferenceQuerySortBy
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterSortBy>
sortByAutoSyncFrequency() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'autoSyncFrequency', Sort.asc);
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterSortBy>
sortByAutoSyncFrequencyDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'autoSyncFrequency', Sort.desc);
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterSortBy> sortByEmail() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'email', Sort.asc);
@ -1084,6 +1182,19 @@ extension SyncPreferenceQuerySortBy
return query.addSortBy(r'server', Sort.desc);
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterSortBy> sortBySyncOn() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'syncOn', Sort.asc);
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterSortBy>
sortBySyncOnDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'syncOn', Sort.desc);
});
}
}
extension SyncPreferenceQuerySortThenBy
@ -1101,6 +1212,20 @@ extension SyncPreferenceQuerySortThenBy
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterSortBy>
thenByAutoSyncFrequency() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'autoSyncFrequency', Sort.asc);
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterSortBy>
thenByAutoSyncFrequencyDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'autoSyncFrequency', Sort.desc);
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterSortBy> thenByEmail() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'email', Sort.asc);
@ -1179,6 +1304,19 @@ extension SyncPreferenceQuerySortThenBy
return query.addSortBy(r'syncId', Sort.desc);
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterSortBy> thenBySyncOn() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'syncOn', Sort.asc);
});
}
QueryBuilder<SyncPreference, SyncPreference, QAfterSortBy>
thenBySyncOnDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'syncOn', Sort.desc);
});
}
}
extension SyncPreferenceQueryWhereDistinct
@ -1190,6 +1328,13 @@ extension SyncPreferenceQueryWhereDistinct
});
}
QueryBuilder<SyncPreference, SyncPreference, QDistinct>
distinctByAutoSyncFrequency() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'autoSyncFrequency');
});
}
QueryBuilder<SyncPreference, SyncPreference, QDistinct> distinctByEmail(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
@ -1223,6 +1368,12 @@ extension SyncPreferenceQueryWhereDistinct
return query.addDistinctBy(r'server', caseSensitive: caseSensitive);
});
}
QueryBuilder<SyncPreference, SyncPreference, QDistinct> distinctBySyncOn() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'syncOn');
});
}
}
extension SyncPreferenceQueryProperty
@ -1239,6 +1390,13 @@ extension SyncPreferenceQueryProperty
});
}
QueryBuilder<SyncPreference, int, QQueryOperations>
autoSyncFrequencyProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'autoSyncFrequency');
});
}
QueryBuilder<SyncPreference, String?, QQueryOperations> emailProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'email');
@ -1268,4 +1426,10 @@ extension SyncPreferenceQueryProperty
return query.addPropertyName(r'server');
});
}
QueryBuilder<SyncPreference, bool, QQueryOperations> syncOnProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'syncOn');
});
}
}

View file

@ -1,5 +1,6 @@
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/history.dart';
import 'package:mangayomi/models/manga.dart';
@ -7,6 +8,7 @@ import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/track.dart';
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart';
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/services/aniskip.dart';
import 'package:mangayomi/utils/chapter_recognition.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -121,6 +123,8 @@ class AnimeStreamController extends _$AnimeStreamController {
Manga? anime = episode.manga.value;
anime!.lastRead = DateTime.now().millisecondsSinceEpoch;
isar.mangas.putSync(anime);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateItem, anime.id, anime.toJson(), false);
});
History? history;
@ -146,6 +150,13 @@ class AnimeStreamController extends _$AnimeStreamController {
isar.writeTxnSync(() {
isar.historys.putSync(history!);
history.chapter.saveSync();
if (empty) {
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.addHistory, null, history.toJson(), false);
} else {
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateHistory, history.id, history.toJson(), false);
}
});
}
@ -166,6 +177,8 @@ class AnimeStreamController extends _$AnimeStreamController {
ep.isRead = isWatch;
ep.lastPageRead = (duration.inMilliseconds).toString();
isar.chapters.putSync(ep);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateChapter, ep.id, ep.toJson(), false);
});
if (isWatch) {
episode.updateTrackChapterRead(ref);

View file

@ -7,7 +7,7 @@ part of 'anime_player_controller_provider.dart';
// **************************************************************************
String _$animeStreamControllerHash() =>
r'57ebd35f033d51fd213763173c26cd887f5c42d7';
r'e0217071ae7b908a12bbba2dcdc4a6da8828e1c5';
/// Copied from Dart SDK
class _SystemHash {

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

@ -233,6 +233,7 @@ class _MangaGlobalImageCardState extends ConsumerState<MangaGlobalImageCard>
return GestureDetector(
onTap: () async {
pushToMangaReaderDetail(
ref: ref,
context: context,
getManga: getMangaDetail,
lang: widget.source.lang!,

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,11 +6,13 @@ 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/chapter.dart';
import 'package:mangayomi/models/history.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/history/providers/isar_providers.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:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/constant.dart';
@ -424,6 +426,17 @@ class _HistoryTabState extends ConsumerState<HistoryTab> {
.delete(manga
.id!);
});
await ref
.read(synchingProvider(
syncId:
1)
.notifier)
.addChangedPartAsync(
ActionType
.removeItem,
manga.id,
"{}",
true);
if (context
.mounted) {
Navigator.pop(

View file

@ -11,6 +11,7 @@ import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/category.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/download.dart';
import 'package:mangayomi/models/history.dart';
@ -21,6 +22,7 @@ import 'package:mangayomi/modules/library/providers/add_torrent.dart';
import 'package:mangayomi/modules/library/providers/local_archive.dart';
import 'package:mangayomi/modules/manga/detail/providers/update_manga_detail_providers.dart';
import 'package:mangayomi/modules/more/categories/providers/isar_providers.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
@ -1030,6 +1032,15 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
manga!.categories =
categoryIds;
isar.mangas.putSync(manga);
ref
.read(synchingProvider(
syncId: 1)
.notifier)
.addChangedPart(
ActionType.updateItem,
manga.id,
manga.toJson(),
false);
}
});
ref
@ -1041,6 +1052,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
isLongPressedMangaStateProvider
.notifier)
.update(false);
if (mounted) {
Navigator.pop(context);
}
@ -1164,9 +1176,19 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
isar.chapters.deleteSync(chapter.id!);
}
isar.mangas.deleteSync(manga.id!);
ref
.read(synchingProvider(syncId: 1)
.notifier)
.addChangedPart(ActionType.removeItem,
manga.id, "{}", false);
} else {
manga.favorite = false;
isar.mangas.putSync(manga);
ref
.read(synchingProvider(syncId: 1)
.notifier)
.addChangedPart(ActionType.updateItem,
manga.id, manga.toJson(), false);
}
}
});
@ -1804,6 +1826,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
manga.whenData((value) {
var randomManga = (value..shuffle()).first;
pushToMangaReaderDetail(
ref: ref,
archiveId: randomManga.isLocalArchive ?? false
? randomManga.id
: null,

View file

@ -1,7 +1,9 @@
import 'package:file_picker/file_picker.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/services/torrent_server.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -45,6 +47,11 @@ Future addTorrentFromUrlOrFromFile(Ref ref, Manga? mManga,
isLocalArchive: true,
artist: '',
);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.addItem, null, manga.toJson(), true);
if (url != null) {
manga.customCoverImage = null;
isar.writeTxnSync(() {
@ -53,6 +60,8 @@ Future addTorrentFromUrlOrFromFile(Ref ref, Manga? mManga,
..manga.value = manga;
isar.chapters.putSync(chapters);
chapters.manga.saveSync();
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.addChapter, null, chapters.toJson(), false);
});
} else {
for (var file in result!.files.reversed.toList()) {
@ -69,6 +78,8 @@ Future addTorrentFromUrlOrFromFile(Ref ref, Manga? mManga,
..manga.value = manga;
isar.chapters.putSync(chapters);
chapters.manga.saveSync();
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.addChapter, null, chapters.toJson(), false);
});
}
}

View file

@ -7,7 +7,7 @@ part of 'add_torrent.dart';
// **************************************************************************
String _$addTorrentFromUrlOrFromFileHash() =>
r'11cc239bb8b517326f9a005b0c89dd5eb1127099';
r'd12f901b675ecbf4a29c496cf99da17f219745f7';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_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';
part 'library_state_provider.g.dart';
@ -788,6 +790,8 @@ class MangasSetIsReadState extends _$MangasSetIsReadState {
chapter.lastPageRead = "1";
isar.chapters.putSync(chapter..manga.value = manga);
chapter.manga.saveSync();
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateChapter, chapter.id, chapter.toJson(), false);
}
});
}
@ -812,6 +816,8 @@ class MangasSetUnReadState extends _$MangasSetUnReadState {
chapter.isRead = false;
isar.chapters.putSync(chapter..manga.value = manga);
chapter.manga.saveSync();
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateChapter, chapter.id, chapter.toJson(), false);
}
});
}

View file

@ -2551,7 +2551,7 @@ final isLongPressedMangaStateProvider =
typedef _$IsLongPressedMangaState = AutoDisposeNotifier<bool>;
String _$mangasSetIsReadStateHash() =>
r'8f86296f588a48747de625e0471048978ee9bdeb';
r'b599664aed8cc00d35a683fa6660bf79b66c555d';
abstract class _$MangasSetIsReadState
extends BuildlessAutoDisposeNotifier<void> {
@ -2698,7 +2698,7 @@ class _MangasSetIsReadStateProviderElement
}
String _$mangasSetUnReadStateHash() =>
r'3413e731b2fd8476a4032d3e47b943ca12f25090';
r'03906113f5e5878909a5a6399ead997eaa2c1204';
abstract class _$MangasSetUnReadState
extends BuildlessAutoDisposeNotifier<void> {

View file

@ -1,10 +1,12 @@
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/manga/archive_reader/models/models.dart';
import 'package:mangayomi/modules/manga/archive_reader/providers/archive_reader_providers.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
part 'local_archive.g.dart';
@ -38,6 +40,11 @@ Future importArchivesFromFile(Ref ref, Manga? mManga,
isLocalArchive: true,
artist: '',
);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.addItem, null, manga.toJson(), true);
for (var file in result.files.reversed.toList()) {
(String, LocalExtensionType, Uint8List, String)? data = itemType ==
ItemType.manga
@ -58,6 +65,8 @@ Future importArchivesFromFile(Ref ref, Manga? mManga,
..manga.value = manga;
isar.chapters.putSync(chapters);
chapters.manga.saveSync();
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.addChapter, null, chapters.toJson(), false);
});
}
}

View file

@ -7,7 +7,7 @@ part of 'local_archive.dart';
// **************************************************************************
String _$importArchivesFromFileHash() =>
r'8e6e592c927ad080e93d54dac1144ef8637a7e52';
r'49cd5455a5ff601e4b7b3fccd2fd5f6463c35fb3';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -91,6 +91,7 @@ class _LibraryGridViewWidgetState extends State<LibraryGridViewWidget> {
ref.read(mangasListStateProvider.notifier).update(entry);
} else {
await pushToMangaReaderDetail(
ref: ref,
archiveId: isLocalArchive ? entry.id : null,
context: context,
lang: entry.lang!,

View file

@ -53,6 +53,7 @@ class LibraryListViewWidget extends StatelessWidget {
ref.read(mangasListStateProvider.notifier).update(entry);
} else {
await pushToMangaReaderDetail(
ref: ref,
archiveId: isLocalArchive ? entry.id : null,
context: context,
lang: entry.lang!,

View file

@ -5,12 +5,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/update.dart';
import 'package:mangayomi/models/source.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/modules/widgets/loading_icon.dart';
import 'package:mangayomi/services/fetch_anime_sources.dart';
import 'package:mangayomi/services/fetch_manga_sources.dart';
@ -21,6 +23,7 @@ import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/router/router.dart';
import 'package:mangayomi/services/fetch_novel_sources.dart';
import 'package:mangayomi/services/fetch_sources_list.dart';
import 'package:mangayomi/services/sync_server.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/modules/library/providers/library_state_provider.dart';
import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart';
@ -52,6 +55,8 @@ class _MainScreenState extends ConsumerState<MainScreen> {
}
late final navigationOrder = ref.watch(navigationOrderStateProvider);
late final autoSyncFrequency =
ref.watch(synchingProvider(syncId: 1)).autoSyncFrequency;
late String? location =
ref.watch(routerCurrentLocationStateProvider(context));
late String defaultLocation = navigationOrder.first;
@ -63,6 +68,19 @@ class _MainScreenState extends ConsumerState<MainScreen> {
Timer.periodic(Duration(minutes: 5), (timer) {
ref.read(checkAndBackupProvider);
});
if (autoSyncFrequency != 0) {
final l10n = l10nLocalizations(context)!;
Timer.periodic(Duration(seconds: autoSyncFrequency), (timer) {
try {
ref.read(syncServerProvider(syncId: 1).notifier).startSync(l10n);
} catch (e) {
botToast(
"Failed to sync! Maybe the sync server is down. Restart the app to resume auto sync.");
timer.cancel();
}
});
}
ref.watch(checkForUpdateProvider(context: context));
ref.watch(fetchMangaSourcesListProvider(id: null, reFresh: false));
ref.watch(fetchAnimeSourcesListProvider(id: null, reFresh: false));

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

@ -9,6 +9,7 @@ import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/download.dart';
import 'package:mangayomi/models/manga.dart';
@ -22,6 +23,7 @@ import 'package:mangayomi/modules/manga/detail/widgets/tracker_search_widget.dar
import 'package:mangayomi/modules/manga/detail/widgets/tracker_widget.dart';
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_dark_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/modules/more/settings/track/widgets/track_listile.dart';
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
@ -729,6 +731,11 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
isar.chapters.putSync(
chapter..manga.value = widget.manga);
chapter.manga.saveSync();
ref
.read(
synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateChapter,
chapter.id, chapter.toJson(), false);
}
});
ref
@ -772,6 +779,11 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
if (chapter.isRead!) {
chapter.updateTrackChapterRead(ref);
}
ref
.read(
synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateChapter,
chapter.id, chapter.toJson(), false);
}
});
ref
@ -814,6 +826,14 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
isar.chapters.putSync(chapters[i]
..manga.value = widget.manga);
chapters[i].manga.saveSync();
ref
.read(synchingProvider(syncId: 1)
.notifier)
.addChangedPart(
ActionType.updateChapter,
chapters[i].id,
chapters[i].toJson(),
false);
}
}
ref
@ -1666,6 +1686,15 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
..customCoverImage = null
..customCoverFromTracker =
trackSearch.coverUrl);
ref
.read(synchingProvider(
syncId: 1)
.notifier)
.addChangedPart(
ActionType.updateItem,
widget.manga!.id,
widget.manga!.toJson(),
false);
});
if (context.mounted) {
Navigator.pop(context);
@ -1791,6 +1820,15 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
isar.mangas.putSync(manga
..customCoverImage = null
..customCoverFromTracker = null);
ref
.read(
synchingProvider(syncId: 1)
.notifier)
.addChangedPart(
ActionType.updateItem,
manga.id,
manga.toJson(),
false);
});
Navigator.pop(context);
} else if (value == 1) {
@ -1814,6 +1852,15 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
isar.mangas.putSync(manga
..customCoverImage =
customCoverImage);
ref
.read(synchingProvider(
syncId: 1)
.notifier)
.addChangedPart(
ActionType.updateItem,
manga.id,
manga.toJson(),
false);
});
botToast(
context.l10n.cover_updated,
@ -1920,6 +1967,10 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
manga.description = description.text;
manga.name = name.text;
isar.mangas.putSync(manga);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateItem, manga.id,
manga.toJson(), false);
});
Navigator.pop(context);
},

View file

@ -4,10 +4,12 @@ import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/category.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/history.dart';
import 'package:mangayomi/modules/manga/detail/widgets/custom_floating_action_btn.dart';
import 'package:mangayomi/models/manga.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/utils/constant.dart';
@ -197,6 +199,10 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
model.favorite = false;
model.dateAdded = 0;
isar.mangas.putSync(model);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateItem, model.id,
model.toJson(), false);
});
},
child: Column(
@ -236,6 +242,14 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
model.favorite = true;
model.dateAdded = DateTime.now().millisecondsSinceEpoch;
isar.mangas.putSync(model);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(
ActionType.addItem, null, model.toJson(), false);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateItem, model.id,
model.toJson(), false);
});
}
},
@ -355,6 +369,16 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
model.dateAdded =
DateTime.now().millisecondsSinceEpoch;
isar.mangas.putSync(model);
ref
.read(
synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.addItem,
model.id, model.toJson(), false);
ref
.read(
synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateItem,
model.id, model.toJson(), false);
});
if (mounted) {
Navigator.pop(context);

View file

@ -1,10 +1,12 @@
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/download.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/manga/download/providers/download_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'state_providers.g.dart';
@ -311,6 +313,8 @@ class ChapterSetIsBookmarkState extends _$ChapterSetIsBookmarkState {
chapter.isBookmarked = !chapter.isBookmarked!;
isar.chapters.putSync(chapter..manga.value = manga);
chapter.manga.saveSync();
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateChapter, chapter.id, chapter.toJson(), false);
}
});
ref.read(isLongPressedStateProvider.notifier).update(false);
@ -330,6 +334,8 @@ class ChapterSetIsReadState extends _$ChapterSetIsReadState {
chapter.isRead = !chapter.isRead!;
isar.chapters.putSync(chapter..manga.value = manga);
chapter.manga.saveSync();
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateChapter, chapter.id, chapter.toJson(), false);
}
});
ref.read(isLongPressedStateProvider.notifier).update(false);

View file

@ -816,7 +816,7 @@ class _ChapterFilterResultStateProviderElement
}
String _$chapterSetIsBookmarkStateHash() =>
r'113131bb13e50566390ee3e34aa2f08820a8870c';
r'9b4359e87f6083323cc49d20bedde0ce0f61d9b3';
abstract class _$ChapterSetIsBookmarkState
extends BuildlessAutoDisposeNotifier<void> {
@ -963,7 +963,7 @@ class _ChapterSetIsBookmarkStateProviderElement
}
String _$chapterSetIsReadStateHash() =>
r'c319f81ec30565ad81a28cb0a8ce7fddcb47cd77';
r'9cfd45df3f359a43140c023a584b52f8c81cbace';
abstract class _$ChapterSetIsReadState
extends BuildlessAutoDisposeNotifier<void> {

View file

@ -1,9 +1,11 @@
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/update.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/services/get_detail.dart';
import 'package:mangayomi/utils/utils.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -56,6 +58,9 @@ Future<dynamic> updateMangaDetail(Ref ref,
}
isar.writeTxnSync(() {
isar.mangas.putSync(manga);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateItem, manga.id, manga.toJson(), false);
manga.lastUpdate = DateTime.now().millisecondsSinceEpoch;
List<Chapter> chapters = [];
@ -81,6 +86,8 @@ Future<dynamic> updateMangaDetail(Ref ref,
for (var chap in chapters.reversed.toList()) {
isar.chapters.putSync(chap);
chap.manga.saveSync();
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.addChapter, chap.id, chap.toJson(), false);
if (manga.chapters.isNotEmpty) {
final update = Update(
mangaId: mangaId,
@ -89,6 +96,8 @@ Future<dynamic> updateMangaDetail(Ref ref,
..chapter.value = chap;
isar.updates.putSync(update);
update.chapter.saveSync();
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.addUpdate, update.id, update.toJson(), false);
}
}
}
@ -103,6 +112,10 @@ Future<dynamic> updateMangaDetail(Ref ref,
oldChap.scanlator = newChap.scanlator;
isar.chapters.putSync(oldChap);
oldChap.manga.saveSync();
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateItem, manga.id, manga.toJson(), false);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateChapter, oldChap.id, oldChap.toJson(), false);
}
}
});

View file

@ -6,7 +6,7 @@ part of 'update_manga_detail_providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$updateMangaDetailHash() => r'a86fe8fea46e411203182287c970cd80cc9a1a0c';
String _$updateMangaDetailHash() => r'ebd820d3e9d1900c464aebfbf711f43f6619e586';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -2,6 +2,7 @@ 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/chapter.dart';
import 'package:mangayomi/models/download.dart';
import 'package:mangayomi/models/history.dart';
@ -11,6 +12,7 @@ import 'package:mangayomi/models/track.dart';
import 'package:mangayomi/models/track_preference.dart';
import 'package:mangayomi/modules/manga/detail/providers/track_state_providers.dart';
import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/modules/more/settings/track/providers/track_providers.dart';
import 'package:mangayomi/utils/chapter_recognition.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -159,6 +161,8 @@ class ReaderController extends _$ReaderController {
Manga? manga = chapter.manga.value;
manga!.lastRead = DateTime.now().millisecondsSinceEpoch;
isar.mangas.putSync(manga);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateItem, manga.id, manga.toJson(), false);
});
History? history;
@ -184,6 +188,13 @@ class ReaderController extends _$ReaderController {
isar.writeTxnSync(() {
isar.historys.putSync(history!);
history.chapter.saveSync();
if (empty) {
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.addHistory, null, history.toJson(), false);
} else {
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateHistory, history.id, history.toJson(), false);
}
});
}
@ -194,6 +205,8 @@ class ReaderController extends _$ReaderController {
isar.writeTxnSync(() {
chap.isBookmarked = !isBookmarked;
isar.chapters.putSync(chap);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateChapter, chapter.id, chapter.toJson(), false);
});
}
@ -331,6 +344,8 @@ class ReaderController extends _$ReaderController {
chap.isRead = isRead;
chap.lastPageRead = isRead ? '1' : (newIndex + 1).toString();
isar.chapters.putSync(chap);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateChapter, chapter.id, chapter.toJson(), false);
});
if (isRead) {
chapter.updateTrackChapterRead(ref);

View file

@ -171,7 +171,7 @@ class _CurrentIndexProviderElement
Chapter get chapter => (origin as CurrentIndexProvider).chapter;
}
String _$readerControllerHash() => r'471617bf6fa730d837c8e0d9504bde17cfb635a8';
String _$readerControllerHash() => r'58256638f87a8c24ee8081260685692b6e819fc3';
abstract class _$ReaderController extends BuildlessAutoDisposeNotifier<void> {
late final Chapter chapter;

View file

@ -11,6 +11,7 @@ import 'package:go_router/go_router.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/changed.dart' as changed;
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/page.dart';
import 'package:mangayomi/models/settings.dart';
@ -22,6 +23,7 @@ import 'package:mangayomi/modules/manga/reader/double_columm_view_center.dart';
import 'package:mangayomi/modules/manga/reader/providers/color_filter_provider.dart';
import 'package:mangayomi/modules/manga/reader/widgets/color_filter_widget.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/modules/widgets/custom_draggable_tabbar.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
@ -335,6 +337,16 @@ class _MangaChapterPageGalleryState
isar.mangas.putSync(manga
..customCoverImage =
imageBytes);
ref
.read(synchingProvider(
syncId: 1)
.notifier)
.addChangedPart(
changed.ActionType
.updateItem,
manga.id,
manga.toJson(),
false);
});
if (mounted) {
Navigator.pop(context, "ok");

View file

@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/utils/date.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
@ -153,17 +155,23 @@ class _ChapterListTileState extends State<ChapterListTile> {
)
],
),
trailing: IconButton(
onPressed: () {
setState(() {
isBookmarked = !isBookmarked;
});
isar.writeTxnSync(() => {
isar.chapters.putSync(chapter..isBookmarked = isBookmarked),
});
},
icon: Icon(isBookmarked ? Icons.bookmark : Icons.bookmark_outline,
color: context.primaryColor),
trailing: Consumer(
builder: (context, ref, child) => IconButton(
onPressed: () {
setState(() {
isBookmarked = !isBookmarked;
});
isar.writeTxnSync(() => {
isar.chapters.putSync(chapter..isBookmarked = isBookmarked),
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.updateChapter, chapter.id,
chapter.toJson(), false),
});
},
icon: Icon(isBookmarked ? Icons.bookmark : Icons.bookmark_outline,
color: context.primaryColor),
),
),
),
);

View file

@ -2,10 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/category.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/more/categories/providers/isar_providers.dart';
import 'package:mangayomi/modules/more/categories/widgets/custom_textfield.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/modules/widgets/progress_center.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
@ -206,6 +208,19 @@ class _CategoriesTabState extends ConsumerState<CategoriesTab> {
index]
.id!);
});
await ref
.read(synchingProvider(
syncId:
1)
.notifier)
.addChangedPartAsync(
ActionType
.removeCategory,
_entries[
index]
.id,
"{}",
true);
if (context
.mounted) {
Navigator.pop(
@ -291,12 +306,23 @@ class _CategoriesTabState extends ConsumerState<CategoriesTab> {
isExist
? null
: () async {
final category = Category(
forItemType: widget.itemType,
name: controller.text,
);
await isar.writeTxn(() async {
await isar.categorys.put(Category(
forItemType: widget.itemType,
name: controller.text,
));
await isar.categorys
.put(category);
});
await ref
.read(
synchingProvider(syncId: 1)
.notifier)
.addChangedPartAsync(
ActionType.addCategory,
category.id,
category.toJson(),
true);
if (context.mounted) {
Navigator.pop(context);
}
@ -375,18 +401,27 @@ class _CategoriesTabState extends ConsumerState<CategoriesTab> {
width: 15,
),
TextButton(
onPressed:
controller.text.isEmpty || isExist || isSameName
? null
: () async {
await isar.writeTxn(() async {
category.name = controller.text;
await isar.categorys.put(category);
});
if (context.mounted) {
Navigator.pop(context);
}
},
onPressed: controller.text.isEmpty ||
isExist ||
isSameName
? null
: () async {
await isar.writeTxn(() async {
category.name = controller.text;
await isar.categorys.put(category);
});
await ref
.read(
synchingProvider(syncId: 1).notifier)
.addChangedPartAsync(
ActionType.renameCategory,
category.id,
category.toJson(),
true);
if (context.mounted) {
Navigator.pop(context);
}
},
child: Text(
l10n.ok,
style: TextStyle(

View file

@ -20,7 +20,9 @@ import 'package:mangayomi/modules/more/settings/appearance/providers/blend_level
import 'package:mangayomi/modules/more/settings/appearance/providers/flex_scheme_color_state_provider.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_dark_mode_state_provider.dart';
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';
@ -54,7 +56,7 @@ void doRestore(Ref ref, {required String path, required BuildContext context}) {
}
@riverpod
void restoreBackup(Ref ref, Map<String, dynamic> backup) {
void restoreBackup(Ref ref, Map<String, dynamic> backup, {bool full = true}) {
final version = backup['version'];
if (["1", "2"].any((e) => e == version)) {
try {
@ -106,13 +108,15 @@ void restoreBackup(Ref ref, Map<String, dynamic> backup) {
}
}
isar.downloads.clearSync();
if (downloads != null) {
for (var download in downloads) {
final chapter = isar.chapters.getSync(download.id!);
if (chapter != null) {
isar.downloads.putSync(download..chapter.value = chapter);
download.chapter.saveSync();
if (full) {
isar.downloads.clearSync();
if (downloads != null) {
for (var download in downloads) {
final chapter = isar.chapters.getSync(download.id!);
if (chapter != null) {
isar.downloads.putSync(download..chapter.value = chapter);
download.chapter.saveSync();
}
}
}
}
@ -157,31 +161,43 @@ void restoreBackup(Ref ref, Map<String, dynamic> backup) {
isar.tracks.putAllSync(track);
}
isar.trackPreferences.clearSync();
if (trackPreferences != null) {
isar.trackPreferences.putAllSync(trackPreferences);
if (full) {
isar.trackPreferences.clearSync();
if (trackPreferences != null) {
isar.trackPreferences.putAllSync(trackPreferences);
}
}
isar.sources.clearSync();
if (extensions != null) {
isar.sources.putAllSync(extensions);
if (full) {
isar.sources.clearSync();
if (extensions != null) {
isar.sources.putAllSync(extensions);
}
}
isar.sourcePreferences.clearSync();
if (sourcesPrefs != null) {
isar.sourcePreferences.putAllSync(sourcesPrefs);
if (full) {
isar.sourcePreferences.clearSync();
if (sourcesPrefs != null) {
isar.sourcePreferences.putAllSync(sourcesPrefs);
}
isar.settings.clearSync();
if (settings != null) {
isar.settings.putAllSync(settings);
}
}
isar.settings.clearSync();
if (settings != null) {
isar.settings.putAllSync(settings);
if (full) {
ref.read(synchingProvider(syncId: 1).notifier).clearAllChangedParts(false);
ref.invalidate(themeModeStateProvider);
ref.invalidate(blendLevelStateProvider);
ref.invalidate(flexSchemeColorStateProvider);
ref.invalidate(pureBlackDarkModeStateProvider);
ref.invalidate(l10nLocaleStateProvider);
ref.invalidate(navigationOrderStateProvider);
ref.invalidate(hideItemsStateProvider);
ref.invalidate(extensionsRepoStateProvider(ItemType.manga));
ref.invalidate(extensionsRepoStateProvider(ItemType.anime));
ref.invalidate(extensionsRepoStateProvider(ItemType.novel));
}
ref.invalidate(themeModeStateProvider);
ref.invalidate(blendLevelStateProvider);
ref.invalidate(flexSchemeColorStateProvider);
ref.invalidate(pureBlackDarkModeStateProvider);
ref.invalidate(l10nLocaleStateProvider);
ref.invalidate(navigationOrderStateProvider);
ref.invalidate(hideItemsStateProvider);
});
} catch (e) {
rethrow;

View file

@ -173,7 +173,7 @@ class _DoRestoreProviderElement extends AutoDisposeProviderElement<void>
BuildContext get context => (origin as DoRestoreProvider).context;
}
String _$restoreBackupHash() => r'24405b9be28204324e47d6c1db34495d55a491d2';
String _$restoreBackupHash() => r'0b6bdb8eff801da7efa7b3776f80e50bee4d4ad1';
/// See also [restoreBackup].
@ProviderFor(restoreBackup)
@ -186,10 +186,12 @@ class RestoreBackupFamily extends Family<void> {
/// See also [restoreBackup].
RestoreBackupProvider call(
Map<String, dynamic> backup,
) {
Map<String, dynamic> backup, {
bool full = true,
}) {
return RestoreBackupProvider(
backup,
full: full,
);
}
@ -199,6 +201,7 @@ class RestoreBackupFamily extends Family<void> {
) {
return call(
provider.backup,
full: provider.full,
);
}
@ -221,11 +224,13 @@ class RestoreBackupFamily extends Family<void> {
class RestoreBackupProvider extends AutoDisposeProvider<void> {
/// See also [restoreBackup].
RestoreBackupProvider(
Map<String, dynamic> backup,
) : this._internal(
Map<String, dynamic> backup, {
bool full = true,
}) : this._internal(
(ref) => restoreBackup(
ref as RestoreBackupRef,
backup,
full: full,
),
from: restoreBackupProvider,
name: r'restoreBackupProvider',
@ -237,6 +242,7 @@ class RestoreBackupProvider extends AutoDisposeProvider<void> {
allTransitiveDependencies:
RestoreBackupFamily._allTransitiveDependencies,
backup: backup,
full: full,
);
RestoreBackupProvider._internal(
@ -247,9 +253,11 @@ class RestoreBackupProvider extends AutoDisposeProvider<void> {
required super.debugGetCreateSourceHash,
required super.from,
required this.backup,
required this.full,
}) : super.internal();
final Map<String, dynamic> backup;
final bool full;
@override
Override overrideWith(
@ -265,6 +273,7 @@ class RestoreBackupProvider extends AutoDisposeProvider<void> {
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
backup: backup,
full: full,
),
);
}
@ -276,13 +285,16 @@ class RestoreBackupProvider extends AutoDisposeProvider<void> {
@override
bool operator ==(Object other) {
return other is RestoreBackupProvider && other.backup == backup;
return other is RestoreBackupProvider &&
other.backup == backup &&
other.full == full;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, backup.hashCode);
hash = _SystemHash.combine(hash, full.hashCode);
return _SystemHash.finish(hash);
}
@ -293,6 +305,9 @@ class RestoreBackupProvider extends AutoDisposeProvider<void> {
mixin RestoreBackupRef on AutoDisposeProviderRef<void> {
/// The parameter `backup` of this provider.
Map<String, dynamic> get backup;
/// The parameter `full` of this provider.
bool get full;
}
class _RestoreBackupProviderElement extends AutoDisposeProviderElement<void>
@ -301,6 +316,8 @@ class _RestoreBackupProviderElement extends AutoDisposeProviderElement<void>
@override
Map<String, dynamic> get backup => (origin as RestoreBackupProvider).backup;
@override
bool get full => (origin as RestoreBackupProvider).full;
}
// 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

@ -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

@ -6,7 +6,7 @@ part of 'browse_state_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$getRepoInfosHash() => r'd5d5eca9fd23accd515bf51b470edb99a5d58733';
String _$getRepoInfosHash() => r'250bc0082ac2841114d6f1815303955b8798240e';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -1,45 +1,189 @@
import 'dart:convert';
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';
@riverpod
class Synching extends _$Synching {
@override
SyncPreference? build({required int? syncId}) {
return isar.syncPreferences.getSync(syncId!);
SyncPreference build({required int? syncId}) {
return isar.syncPreferences.getSync(syncId!) ?? SyncPreference(syncId: 1);
}
void login(SyncPreference syncPreference) {
isar.writeTxnSync(() {
isar.syncPreferences.putSync(syncPreference);
});
ref.invalidateSelf();
ref.invalidate(syncServerProvider(syncId: syncId!));
}
void logout() {
isar.writeTxnSync(() {
isar.syncPreferences.deleteSync(syncId!);
isar.syncPreferences.putSync(state..authToken = null);
});
ref.invalidateSelf();
ref.invalidate(syncServerProvider(syncId: syncId!));
}
void setLastUpload(int timestamp) {
isar.writeTxnSync(() {
isar.syncPreferences.putSync(
isar.syncPreferences.getSync(syncId!)!..lastUpload = timestamp);
isar.syncPreferences.putSync(state..lastUpload = timestamp);
});
}
void setLastDownload(int timestamp) {
isar.writeTxnSync(() {
isar.syncPreferences.putSync(
isar.syncPreferences.getSync(syncId!)!..lastDownload = timestamp);
isar.syncPreferences.putSync(state..lastDownload = timestamp);
});
}
void setLastSync(int timestamp) {
isar.writeTxnSync(() {
isar.syncPreferences.putSync(state..lastSync = timestamp);
});
}
void setServer(String? server) {
isar.writeTxnSync(() {
isar.syncPreferences
.putSync(isar.syncPreferences.getSync(syncId!)!..server = server);
isar.syncPreferences.putSync(state..server = server);
});
}
void setSyncOn(bool value) {
isar.writeTxnSync(() {
isar.syncPreferences.putSync(state..syncOn = value);
});
}
void setAutoSyncFrequency(int value) {
isar.writeTxnSync(() {
isar.syncPreferences.putSync(state..autoSyncFrequency = value);
});
ref.invalidateSelf();
}
List<ChangedPart> getAllChangedParts() {
return isar.changedParts.filter().idIsNotNull().findAllSync();
}
List<ChangedPart> getChangedParts(List<ActionType> actionTypes) {
var query = isar.changedParts
.filter()
.idIsNotNull()
.and()
.actionTypeEqualTo(actionTypes.first);
for (final at in actionTypes.skip(1)) {
query = query.or().actionTypeEqualTo(at);
}
return query.findAllSync();
}
void addChangedPart(
ActionType action, int? isarId, Object data, bool writeTxn) {
if (!state.syncOn) {
return;
}
final changedPart = isar.changedParts
.filter()
.actionTypeEqualTo(action)
.isarIdEqualTo(isarId)
.findFirstSync();
if (writeTxn) {
isar.writeTxnSync(() {
if (changedPart != null) {
isar.changedParts.putSync(changedPart
..data = jsonEncode(data)
..clientDate = DateTime.now().millisecondsSinceEpoch);
} else {
isar.changedParts.putSync(ChangedPart(
actionType: action,
isarId: isarId,
data: jsonEncode(data),
clientDate: DateTime.now().millisecondsSinceEpoch));
}
});
} else {
if (changedPart != null) {
isar.changedParts.putSync(changedPart
..data = jsonEncode(data)
..clientDate = DateTime.now().millisecondsSinceEpoch);
} else {
isar.changedParts.putSync(ChangedPart(
actionType: action,
isarId: isarId,
data: jsonEncode(data),
clientDate: DateTime.now().millisecondsSinceEpoch));
}
}
}
Future<void> addChangedPartAsync(
ActionType action, int? isarId, Object data, bool writeTxn) async {
if (!state.syncOn) {
return;
}
final changedPart = isar.changedParts
.filter()
.actionTypeEqualTo(action)
.isarIdEqualTo(isarId)
.findFirstSync();
if (writeTxn) {
await isar.writeTxn(() async {
if (changedPart != null) {
await isar.changedParts.put(changedPart
..data = jsonEncode(data)
..clientDate = DateTime.now().millisecondsSinceEpoch);
} else {
await isar.changedParts.put(ChangedPart(
actionType: action,
isarId: isarId,
data: jsonEncode(data),
clientDate: DateTime.now().millisecondsSinceEpoch));
}
});
} else {
if (changedPart != null) {
await isar.changedParts.put(changedPart
..data = jsonEncode(data)
..clientDate = DateTime.now().millisecondsSinceEpoch);
} else {
await isar.changedParts.put(ChangedPart(
actionType: action,
isarId: isarId,
data: jsonEncode(data),
clientDate: DateTime.now().millisecondsSinceEpoch));
}
}
}
void clearChangedParts(List<ActionType> actions) {
var temp = isar.changedParts
.filter()
.idIsNotNull()
.and()
.actionTypeEqualTo(actions.first);
for (ActionType action in actions.skip(1)) {
temp = temp.or().actionTypeEqualTo(action);
}
final changedParts = temp.findAllSync().map((cp) => cp.id as Id).toList();
isar.writeTxnSync(() {
isar.changedParts.deleteAllSync(changedParts);
});
}
void clearAllChangedParts(bool txn) {
if (txn) {
isar.writeTxnSync(() {
isar.changedParts.clearSync();
});
} else {
isar.changedParts.clearSync();
}
}
}

View file

@ -6,7 +6,7 @@ part of 'sync_providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$synchingHash() => r'3ab44d9e753f2d4b51fd10af6c98ffac78cbf201';
String _$synchingHash() => r'8a4f7f408bf0ac26f4a21368620051ecba3adf53';
/// Copied from Dart SDK
class _SystemHash {
@ -29,11 +29,10 @@ class _SystemHash {
}
}
abstract class _$Synching
extends BuildlessAutoDisposeNotifier<SyncPreference?> {
abstract class _$Synching extends BuildlessAutoDisposeNotifier<SyncPreference> {
late final int? syncId;
SyncPreference? build({
SyncPreference build({
required int? syncId,
});
}
@ -43,7 +42,7 @@ abstract class _$Synching
const synchingProvider = SynchingFamily();
/// See also [Synching].
class SynchingFamily extends Family<SyncPreference?> {
class SynchingFamily extends Family<SyncPreference> {
/// See also [Synching].
const SynchingFamily();
@ -82,7 +81,7 @@ class SynchingFamily extends Family<SyncPreference?> {
/// See also [Synching].
class SynchingProvider
extends AutoDisposeNotifierProviderImpl<Synching, SyncPreference?> {
extends AutoDisposeNotifierProviderImpl<Synching, SyncPreference> {
/// See also [Synching].
SynchingProvider({
required int? syncId,
@ -112,7 +111,7 @@ class SynchingProvider
final int? syncId;
@override
SyncPreference? runNotifierBuild(
SyncPreference runNotifierBuild(
covariant Synching notifier,
) {
return notifier.build(
@ -137,8 +136,7 @@ class SynchingProvider
}
@override
AutoDisposeNotifierProviderElement<Synching, SyncPreference?>
createElement() {
AutoDisposeNotifierProviderElement<Synching, SyncPreference> createElement() {
return _SynchingProviderElement(this);
}
@ -158,13 +156,13 @@ class SynchingProvider
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin SynchingRef on AutoDisposeNotifierProviderRef<SyncPreference?> {
mixin SynchingRef on AutoDisposeNotifierProviderRef<SyncPreference> {
/// The parameter `syncId` of this provider.
int? get syncId;
}
class _SynchingProviderElement
extends AutoDisposeNotifierProviderElement<Synching, SyncPreference?>
extends AutoDisposeNotifierProviderElement<Synching, SyncPreference>
with SynchingRef {
_SynchingProviderElement(super.provider);

View file

@ -2,6 +2,8 @@ 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/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/utils/date.dart';
import 'package:mangayomi/models/sync_preference.dart';
import 'package:mangayomi/modules/more/settings/sync/widgets/sync_listile.dart';
@ -14,7 +16,23 @@ class SyncScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final syncProvider = ref.watch(synchingProvider(syncId: 1));
final changedParts = ref.watch(synchingProvider(syncId: 1).notifier);
final autoSyncFrequency =
ref.watch(synchingProvider(syncId: 1)).autoSyncFrequency;
final l10n = l10nLocalizations(context)!;
final autoSyncOptions = {
l10n.sync_auto_off: 0,
l10n.sync_auto_30_seconds: 30,
l10n.sync_auto_1_minute: 60,
l10n.sync_auto_5_minutes: 300,
l10n.sync_auto_10_minutes: 600,
l10n.sync_auto_30_minutes: 1800,
l10n.sync_auto_1_hour: 3600,
l10n.sync_auto_3_hours: 10800,
l10n.sync_auto_6_hours: 21600,
l10n.sync_auto_12_hours: 43200,
};
return Scaffold(
appBar: AppBar(
title: Text(l10nLocalizations(context)!.syncing),
@ -33,6 +51,98 @@ class SyncScreen extends ConsumerWidget {
syncPreference.authToken?.isNotEmpty ?? false;
return Column(
children: [
SwitchListTile(
value: syncProvider.syncOn,
title: Text(context.l10n.sync_on),
onChanged: !isLogged
? null
: (value) {
ref
.read(SynchingProvider(syncId: 1).notifier)
.setSyncOn(value);
}),
ListTile(
onTap: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(
l10n.sync_auto,
),
content: SizedBox(
width: context.width(0.8),
child: ListView.builder(
shrinkWrap: true,
itemCount: autoSyncOptions.length,
itemBuilder: (context, index) {
final optionName =
autoSyncOptions.keys.elementAt(index);
final optionValue = autoSyncOptions.values
.elementAt(index);
return RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: optionValue,
groupValue: autoSyncFrequency,
onChanged: (value) {
ref
.read(synchingProvider(syncId: 1)
.notifier)
.setAutoSyncFrequency(value!);
Navigator.pop(context);
},
title: Text(optionName),
);
},
)),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
l10n.cancel,
style: TextStyle(
color: context.primaryColor),
)),
],
)
],
);
});
},
title: Text(l10n.sync_auto),
subtitle: Text(
autoSyncOptions.entries
.where((o) => o.value == autoSyncFrequency)
.first
.key,
style: TextStyle(
fontSize: 11, color: context.secondaryColor),
),
),
ListTile(
title: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Icon(
Icons.warning_amber_outlined,
color: context.secondaryColor,
),
const SizedBox(width: 10),
Text(l10n.sync_auto_warning,
softWrap: true,
style: TextStyle(
fontSize: 11, color: context.secondaryColor))
],
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15, right: 15, bottom: 10, top: 5),
@ -62,6 +172,7 @@ class SyncScreen extends ConsumerWidget {
),
const SizedBox(width: 10),
Text(l10n.syncing_subtitle,
softWrap: true,
style: TextStyle(
fontSize: 11, color: context.secondaryColor))
],
@ -80,6 +191,12 @@ class SyncScreen extends ConsumerWidget {
const SizedBox(width: 10),
Column(children: [
const SizedBox(width: 20),
Text(
"${l10n.last_sync}: ${dateFormat((syncPreference.lastSync ?? 0).toString(), ref: ref, context: context)} ${dateFormatHour((syncPreference.lastSync ?? 0).toString(), context)}",
style: TextStyle(
fontSize: 11,
color: context.secondaryColor)),
const SizedBox(width: 20),
Text(
"${l10n.last_upload}: ${dateFormat((syncPreference.lastUpload ?? 0).toString(), ref: ref, context: context)} ${dateFormatHour((syncPreference.lastUpload ?? 0).toString(), context)}",
style: TextStyle(
@ -96,8 +213,118 @@ class SyncScreen extends ConsumerWidget {
),
),
),
const SizedBox(height: 20),
Row(
children: [
const SizedBox(width: 20),
Column(
children: [
IconButton(
onPressed: !isLogged
? null
: () {
ref
.read(syncServerProvider(syncId: 1)
.notifier)
.startSync(l10n);
},
icon: Icon(
Icons.sync,
color: !isLogged
? context.secondaryColor
: context.primaryColor,
)),
Text(l10n.sync_button_sync),
],
),
const SizedBox(width: 20),
Column(
children: [
IconButton(
onPressed: !isLogged
? null
: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(
l10n.sync_confirm_snapshot),
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)
.createSnapshot(
l10n);
Navigator.pop(
context);
},
child: Text(
l10n.dialog_confirm,
style: TextStyle(
color: context
.secondaryColor),
)),
],
)
],
);
});
},
icon: Icon(
Icons.save_as,
color: !isLogged
? context.secondaryColor
: context.primaryColor,
)),
Text(l10n.sync_button_snapshot),
],
),
const SizedBox(width: 20),
Column(
children: [
@ -249,7 +476,9 @@ class SyncScreen extends ConsumerWidget {
1)
.notifier)
.downloadFromServer(
l10n);
l10n,
false,
true);
Navigator.pop(
context);
},
@ -275,7 +504,264 @@ class SyncScreen extends ConsumerWidget {
],
),
],
)
),
const SizedBox(height: 40),
buildChangedItemWidget(
l10n.sync_pending_manga,
changedParts.getChangedParts([
ActionType.addItem,
ActionType.removeItem,
ActionType.updateItem
])),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_chapter,
changedParts.getChangedParts([
ActionType.addChapter,
ActionType.removeChapter,
ActionType.updateChapter
])),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_category,
changedParts.getChangedParts([
ActionType.addCategory,
ActionType.removeCategory,
ActionType.renameCategory
])),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_history,
changedParts.getChangedParts([
ActionType.addHistory,
ActionType.clearHistory,
ActionType.removeHistory
])),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_update,
changedParts.getChangedParts(
[ActionType.addUpdate, ActionType.clearUpdates])),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_extension,
changedParts.getChangedParts([
ActionType.addExtension,
ActionType.removeExtension,
ActionType.updateExtension
])),
const SizedBox(height: 15),
buildChangedItemWidget(
l10n.sync_pending_track,
changedParts.getChangedParts([
ActionType.addTrack,
ActionType.removeTrack,
ActionType.updateTrack
])),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.only(
left: 15, right: 15, bottom: 10, top: 5),
child: Row(
children: [
ElevatedButton(
onPressed: !isLogged
? null
: () async {
final snapshots = await ref
.read(syncServerProvider(syncId: 1)
.notifier)
.getSnapshots(l10n);
if (context.mounted) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(
l10n.sync_snapshots,
),
content: SizedBox(
width: context.width(0.8),
child: ListView.builder(
shrinkWrap: true,
itemCount: snapshots.length,
itemBuilder:
(context, index) {
return Padding(
padding:
const EdgeInsets
.symmetric(
horizontal: 5),
child: Card(
child: Column(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
Colors
.transparent,
elevation:
0,
shadowColor:
Colors
.transparent,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(0),
bottomRight: Radius.circular(0),
topRight: Radius.circular(10),
topLeft: Radius.circular(10)))),
onPressed: () {},
child: Row(
crossAxisAlignment:
CrossAxisAlignment
.end,
children: [
const Icon(
Icons
.save),
const SizedBox(
width:
10,
),
Expanded(
child:
Text("${dateFormat((snapshots[index].createdAt!).toString(), ref: ref, context: context)} ${dateFormatHour((snapshots[index].createdAt!).toString(), context)}"))
],
)),
Row(
mainAxisAlignment:
MainAxisAlignment
.end,
children: [
Row(
children: [
IconButton(
onPressed:
() {
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text(
l10n.sync_load_snapshot,
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(l10n.cancel)),
const SizedBox(
width: 15,
),
TextButton(
onPressed: () async {
await ref.read(SyncServerProvider(syncId: 1).notifier).downloadSnapshot(l10n, snapshots[index].uuid!);
if (context.mounted) {
Navigator.pop(context);
}
},
child: Text(
l10n.ok,
)),
],
)
],
);
},
);
});
},
icon:
const Icon(Icons.cloud_download_outlined)),
IconButton(
onPressed:
() {
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text(
l10n.sync_delete_snapshot,
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(l10n.cancel)),
const SizedBox(
width: 15,
),
TextButton(
onPressed: () async {
await ref.read(syncServerProvider(syncId: 1).notifier).deleteSnapshot(l10n, snapshots[index].uuid!);
if (context.mounted) {
Navigator.pop(context);
}
},
child: Text(
l10n.ok,
)),
],
)
],
);
},
);
});
},
icon:
const Icon(Icons.delete_outlined))
],
),
],
)
],
),
),
);
},
)),
actions: [
Row(
mainAxisAlignment:
MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(
context);
},
child: Text(
l10n.cancel,
style: TextStyle(
color: context
.primaryColor),
)),
],
)
],
);
});
}
},
child: Text(l10n.sync_browse_snapshots)),
],
),
),
const SizedBox(height: 20),
],
);
}),
@ -284,6 +770,32 @@ class SyncScreen extends ConsumerWidget {
}
}
Widget buildChangedItemWidget(String text, List<ChangedPart> changedParts) {
return Padding(
padding: const EdgeInsets.only(left: 25, right: 25, bottom: 10, top: 5),
child: Row(
children: [
Text(
"$text: ${changedParts.length}",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
background: Paint()
..color = changedParts.isEmpty
? Color.fromARGB(125, 78, 182, 92)
: Color.fromARGB(123, 245, 233, 132)
..strokeWidth = 20
..strokeJoin = StrokeJoin.round
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke,
color: Colors.white,
),
),
],
),
);
}
void _showDialogLogin(BuildContext context, WidgetRef ref) {
final serverController = TextEditingController();
final emailController = TextEditingController();

View file

@ -1,9 +1,11 @@
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/settings.dart';
import 'package:mangayomi/models/track.dart';
import 'package:mangayomi/models/track_preference.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'track_providers.g.dart';
@ -38,13 +40,28 @@ class Tracks extends _$Tracks {
}
}
isar.writeTxnSync(() => isar.tracks.putSync(track
..syncId = syncId
..itemType = itemType));
isar.writeTxnSync(() {
isar.tracks.putSync(track
..syncId = syncId
..itemType = itemType);
if (tra.isEmpty) {
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.addTrack, null, track.toJson(), false);
} else {
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateTrack, track.id, track.toJson(), false);
}
});
}
void deleteTrackManga(Track track) {
isar.writeTxnSync(() => isar.tracks.deleteSync(track.id!));
isar.writeTxnSync(() {
isar.tracks.deleteSync(track.id!);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.removeTrack, track.id, "{}", false);
});
}
}

View file

@ -6,7 +6,7 @@ part of 'track_providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$tracksHash() => r'a6e052c4102bbe640a397c37887dd618a4512c49';
String _$tracksHash() => r'de3a19fc6542e0f610d154978fbd0272259142fc';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -1,10 +1,12 @@
import 'package:isar/isar.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/download.dart';
import 'package:mangayomi/models/history.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'novel_reader_controller_provider.g.dart';
@ -33,6 +35,8 @@ class NovelReaderController extends _$NovelReaderController {
Manga? manga = chapter.manga.value;
manga!.lastRead = DateTime.now().millisecondsSinceEpoch;
isar.mangas.putSync(manga);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateItem, manga.id, manga.toJson(), false);
});
History? history;
@ -58,6 +62,13 @@ class NovelReaderController extends _$NovelReaderController {
isar.writeTxnSync(() {
isar.historys.putSync(history!);
history.chapter.saveSync();
if (empty) {
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.addHistory, null, history.toJson(), false);
} else {
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateHistory, history.id, history.toJson(), false);
}
});
}
@ -71,6 +82,8 @@ class NovelReaderController extends _$NovelReaderController {
ch.lastPageRead =
(maxOffset != 0 ? newOffset / maxOffset : 0).toString();
isar.chapters.putSync(ch);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateChapter, chapter.id, chapter.toJson(), false);
});
}
}
@ -82,6 +95,8 @@ class NovelReaderController extends _$NovelReaderController {
isar.writeTxnSync(() {
chap.isBookmarked = !isBookmarked;
isar.chapters.putSync(chap);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateChapter, chapter.id, chapter.toJson(), false);
});
}

View file

@ -7,7 +7,7 @@ part of 'novel_reader_controller_provider.dart';
// **************************************************************************
String _$novelReaderControllerHash() =>
r'2eec885b858de8195e31a2d0b70feb56c1dc4268';
r'f05612ee0d25a5e5592f4e931b4078d992079f37';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -5,10 +5,12 @@ import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/m_manga.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/manga/detail/manga_detail_main.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/router/router.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
@ -64,6 +66,7 @@ class MangaImageCardWidget extends ConsumerWidget {
cacheMaxAge: const Duration(days: 7)),
onTap: () {
pushToMangaReaderDetail(
ref: ref,
context: context,
getManga: getMangaDetail!,
lang: source.lang!,
@ -72,6 +75,7 @@ class MangaImageCardWidget extends ConsumerWidget {
},
onLongPress: () {
pushToMangaReaderDetail(
ref: ref,
context: context,
getManga: getMangaDetail!,
lang: source.lang!,
@ -81,6 +85,7 @@ class MangaImageCardWidget extends ConsumerWidget {
},
onSecondaryTap: () {
pushToMangaReaderDetail(
ref: ref,
context: context,
getManga: getMangaDetail!,
lang: source.lang!,
@ -162,6 +167,7 @@ class MangaImageCardListTileWidget extends ConsumerWidget {
child: InkWell(
onTap: () {
pushToMangaReaderDetail(
ref: ref,
context: context,
getManga: getMangaDetail!,
lang: source.lang!,
@ -170,6 +176,7 @@ class MangaImageCardListTileWidget extends ConsumerWidget {
},
onLongPress: () {
pushToMangaReaderDetail(
ref: ref,
context: context,
getManga: getMangaDetail!,
lang: source.lang!,
@ -179,6 +186,7 @@ class MangaImageCardListTileWidget extends ConsumerWidget {
},
onSecondaryTap: () {
pushToMangaReaderDetail(
ref: ref,
context: context,
getManga: getMangaDetail!,
lang: source.lang!,
@ -247,6 +255,7 @@ class MangaImageCardListTileWidget extends ConsumerWidget {
Future<void> pushToMangaReaderDetail(
{MManga? getManga,
required WidgetRef ref,
required String lang,
required BuildContext context,
required String source,
@ -280,6 +289,9 @@ Future<void> pushToMangaReaderDetail(
if (empty) {
isar.writeTxnSync(() {
isar.mangas.putSync(manga);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.addItem, null, manga.toJson(), false);
});
}
@ -346,6 +358,8 @@ Future<void> pushToMangaReaderDetail(
final getManga = isar.mangas.filter().idEqualTo(mangaId).findFirstSync()!;
isar.writeTxnSync(() {
isar.mangas.putSync(getManga..favorite = !getManga.favorite!);
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateItem, getManga.id, getManga.toJson(), false);
});
}
}

View file

@ -4,6 +4,7 @@ import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/source_preference.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/category.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/download.dart';
import 'package:mangayomi/models/update.dart';
@ -146,6 +147,7 @@ class StorageProvider {
final isar = Isar.openSync([
MangaSchema,
ChangedPartSchema,
ChapterSchema,
CategorySchema,
UpdateSchema,

View file

@ -6,7 +6,7 @@ part of 'aniskip.dart';
// RiverpodGenerator
// **************************************************************************
String _$aniSkipHash() => r'887869b54e2e151633efd46da83bde845e14f421';
String _$aniSkipHash() => r'2e5d19b025a2207ff64da7bf7908450ea9e5ff8c';
/// See also [AniSkip].
@ProviderFor(AniSkip)

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,13 @@ 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(() {
if (source.isObsolete != !ids.contains(source.id)) {
ref.read(synchingProvider(syncId: 1).notifier).addChangedPart(
ActionType.updateExtension, source.id, source.toJson(), false);
}
isar.sources.putSync(source..isObsolete = !ids.contains(source.id));
});
}
}
}

View file

@ -1,21 +1,22 @@
import 'package:crypto/crypto.dart';
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/update.dart';
import 'package:mangayomi/models/sync_preference.dart';
import 'package:mangayomi/models/track.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/category.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/history.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/models/sync_preference.dart';
import 'package:mangayomi/models/track.dart';
import 'package:mangayomi/models/update.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';
import 'package:mangayomi/services/http/m_client.dart';
import 'dart:convert';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
part 'sync_server.g.dart';
@ -26,6 +27,9 @@ class SyncServer extends _$SyncServer {
final String _loginUrl = '/login';
final String _uploadUrl = '/upload/full';
final String _downloadUrl = '/download';
final String _snapshotUrl = '/snapshot';
final String _checkUrl = '/check';
final String _syncUrl = '/sync';
@override
void build({required int syncId}) {}
@ -59,10 +63,190 @@ class SyncServer extends _$SyncServer {
}
}
Future<void> startSync(AppLocalizations l10n) async {
botToast(l10n.sync_checking, second: 2);
try {
final changedParts = ref
.read(synchingProvider(syncId: syncId).notifier)
.getAllChangedParts();
if (changedParts.isNotEmpty) {
final accessToken = _getAccessToken();
var response = await http.post(
Uri.parse('${_getServer()}$_syncUrl'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $accessToken'
},
body: jsonEncode({'changedParts': changedParts}),
);
if (response.statusCode != 200) {
botToast(l10n.sync_upload_failed, second: 5);
return;
}
var jsonData = jsonDecode(response.body) as Map<String, dynamic>;
final localHash = _getDataHash(_getData(true));
final remoteHash = jsonData["hash"];
if (localHash != remoteHash) {
await downloadFromServer(l10n, true, false);
}
} else {
await forceCheck(l10n, true);
}
final syncNotifier = ref.read(synchingProvider(syncId: syncId).notifier);
syncNotifier.setLastSync(DateTime.now().millisecondsSinceEpoch);
syncNotifier.clearAllChangedParts(true);
ref.invalidate(synchingProvider(syncId: syncId));
botToast(l10n.sync_download_finished, second: 2);
} catch (error) {
botToast(error.toString(), second: 5);
}
}
Future<void> forceCheck(AppLocalizations l10n, bool silent) async {
if (!silent) {
botToast(l10n.sync_checking, second: 2);
}
try {
final accessToken = _getAccessToken();
final localHash = _getDataHash(_getData(true));
var response = await http.get(
Uri.parse('${_getServer()}$_checkUrl'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $accessToken'
},
);
if (response.statusCode != 200) {
botToast(l10n.sync_download_failed, second: 2);
return;
}
var jsonData = jsonDecode(response.body) as Map<String, dynamic>;
final remoteHash = jsonData["hash"];
if (localHash != remoteHash) {
await downloadFromServer(l10n, silent, false);
} else if (!silent) {
botToast("Sync up to date", second: 2);
}
} catch (error) {
botToast(error.toString(), second: 5);
}
}
Future<List<Snapshot>> getSnapshots(AppLocalizations l10n) async {
try {
final accessToken = _getAccessToken();
var response = await http.get(
Uri.parse('${_getServer()}$_snapshotUrl'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $accessToken'
},
);
if (response.statusCode != 200) {
botToast(l10n.server_error, second: 5);
return List.empty();
}
var temp = jsonDecode(response.body) as List;
final snapshots = List<Snapshot>.empty(growable: true);
for (final snapshot in temp) {
snapshots.add(Snapshot.fromJson(snapshot));
}
return snapshots;
} catch (error) {
botToast(error.toString(), second: 5);
}
return List.empty();
}
Future<void> downloadSnapshot(
AppLocalizations l10n, String snapshotId) async {
botToast(l10n.sync_downloading, second: 2);
try {
final accessToken = _getAccessToken();
var response = await http.get(
Uri.parse('${_getServer()}$_snapshotUrl/$snapshotId'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $accessToken'
},
);
if (response.statusCode != 200) {
botToast(l10n.sync_download_failed, second: 5);
return;
}
var jsonData = jsonDecode(response.body) as Map<String, dynamic>;
_restore(
jsonData["backupData"] is String
? jsonDecode(jsonData["backupData"])
: jsonData["backupData"],
true);
ref
.read(synchingProvider(syncId: syncId).notifier)
.setLastDownload(DateTime.now().millisecondsSinceEpoch);
ref.invalidate(synchingProvider(syncId: syncId));
botToast(l10n.sync_download_finished, second: 2);
} catch (error) {
botToast(error.toString(), second: 5);
}
}
Future<void> createSnapshot(AppLocalizations l10n) async {
botToast(l10n.sync_snapshot_creating, second: 2);
try {
final accessToken = _getAccessToken();
var response = await http.post(
Uri.parse('${_getServer()}$_snapshotUrl'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $accessToken'
},
);
if (response.statusCode == 400) {
botToast(l10n.sync_snapshot_no_data, second: 5);
return;
} else if (response.statusCode != 200) {
botToast(l10n.server_error, second: 5);
return;
}
botToast(l10n.sync_snapshot_created, second: 2);
} catch (error) {
botToast(error.toString(), second: 5);
}
}
Future<void> deleteSnapshot(AppLocalizations l10n, String snapshotId) async {
botToast(l10n.sync_snapshot_deleting, second: 2);
try {
final accessToken = _getAccessToken();
var response = await http.delete(
Uri.parse('${_getServer()}$_snapshotUrl/$snapshotId'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $accessToken'
},
);
if (response.statusCode != 200) {
botToast(l10n.server_error, second: 5);
return;
}
botToast(l10n.sync_snapshot_deleted, second: 2);
} catch (error) {
botToast(error.toString(), second: 5);
}
}
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(
@ -77,17 +261,23 @@ class SyncServer extends _$SyncServer {
botToast(l10n.sync_upload_failed, second: 5);
return;
}
ref
.read(synchingProvider(syncId: syncId).notifier)
.setLastUpload(DateTime.now().millisecondsSinceEpoch);
final syncNotifier = ref.read(synchingProvider(syncId: syncId).notifier);
syncNotifier.setLastUpload(DateTime.now().millisecondsSinceEpoch);
syncNotifier.clearAllChangedParts(true);
ref.invalidate(synchingProvider(syncId: syncId));
botToast(l10n.sync_upload_finished, second: 2);
} catch (error) {
botToast(error.toString(), second: 5);
}
}
Future<void> downloadFromServer(AppLocalizations l10n) async {
botToast(l10n.sync_downloading, second: 2);
Future<void> downloadFromServer(
AppLocalizations l10n, bool silent, bool full) async {
if (!silent) {
botToast(l10n.sync_downloading, second: 2);
}
try {
final accessToken = _getAccessToken();
@ -103,19 +293,38 @@ class SyncServer extends _$SyncServer {
return;
}
var jsonData = jsonDecode(response.body) as Map<String, dynamic>;
_restore(jsonData["backupData"] is String
? jsonDecode(jsonData["backupData"])
: jsonData["backupData"]);
_restore(
jsonData["backupData"] is String
? jsonDecode(jsonData["backupData"])
: jsonData["backupData"],
full);
ref
.read(synchingProvider(syncId: syncId).notifier)
.setLastDownload(DateTime.now().millisecondsSinceEpoch);
botToast(l10n.sync_download_finished, second: 2);
ref.invalidate(synchingProvider(syncId: syncId));
if (!silent) {
botToast(l10n.sync_download_finished, second: 2);
}
} catch (error) {
botToast(error.toString(), second: 5);
}
}
Map<String, dynamic> _getData() {
String _getDataHash(Map<String, dynamic> data) {
Map<String, dynamic> datas = {};
datas["version"] = data["version"];
datas["manga"] = data["manga"];
datas["categories"] = data["categories"];
datas["chapters"] = data["chapters"];
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(bool hashCheck) {
Map<String, dynamic> datas = {};
datas.addAll({"version": "2"});
final mangas = isar.mangas
@ -124,21 +333,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": []});
@ -146,7 +355,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": []});
@ -154,21 +363,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
@ -176,26 +385,26 @@ 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;
}
void _restore(Map<String, dynamic> backup) {
ref.read(restoreBackupProvider(backup));
void _restore(Map<String, dynamic> backup, bool full) {
ref.read(restoreBackupProvider(backup, full: full));
}
String _getAccessToken() {
final syncPrefs = ref.watch(synchingProvider(syncId: syncId));
if (syncPrefs == null || syncPrefs.authToken == null) {
if (syncPrefs.authToken == null) {
return "";
}
var paddedPayload = syncPrefs.authToken!.split(".")[1];
@ -216,6 +425,17 @@ class SyncServer extends _$SyncServer {
String _getServer() {
final syncPrefs = ref.watch(synchingProvider(syncId: syncId));
return syncPrefs?.server ?? "";
return syncPrefs.server ?? "";
}
}
class Snapshot {
String? uuid;
int? createdAt;
Snapshot({required this.uuid, required this.createdAt});
Snapshot.fromJson(Map<String, dynamic> json) {
uuid = json['id'];
createdAt = DateTime.parse(json["dbCreatedAt"]).millisecondsSinceEpoch;
}
}

View file

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

View file

@ -6,7 +6,7 @@ part of 'anilist.dart';
// RiverpodGenerator
// **************************************************************************
String _$anilistHash() => r'70e8cd537270a9054a1ef72de117fc7ad5545218';
String _$anilistHash() => r'ddd07acc8d28d2aa95c942566109e9393ca9e5ed';
/// Copied from Dart SDK
class _SystemHash {