enhanced sync feature

This commit is contained in:
Schnitzel5 2025-02-12 19:13:16 +01:00
parent 011f62e157
commit aa946c9d51
44 changed files with 1923 additions and 118 deletions

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

@ -0,0 +1,55 @@
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"),
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});
}

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

@ -0,0 +1,821 @@
// 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,
'addExtension': 15,
'removeExtension': 16,
'updateExtension': 17,
'addTrack': 18,
'removeTrack': 19,
'updateTrack': 20,
};
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.addExtension,
16: ActionType.removeExtension,
17: ActionType.updateExtension,
18: ActionType.addTrack,
19: ActionType.removeTrack,
20: 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

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

View file

@ -46,6 +46,11 @@ const SyncPreferenceSchema = CollectionSchema(
id: 5,
name: r'server',
type: IsarType.string,
),
r'syncOn': PropertySchema(
id: 6,
name: r'syncOn',
type: IsarType.bool,
)
},
estimateSize: _syncPreferenceEstimateSize,
@ -101,6 +106,7 @@ void _syncPreferenceSerialize(
writer.writeLong(offsets[3], object.lastSync);
writer.writeLong(offsets[4], object.lastUpload);
writer.writeString(offsets[5], object.server);
writer.writeBool(offsets[6], object.syncOn);
}
SyncPreference _syncPreferenceDeserialize(
@ -117,6 +123,7 @@ SyncPreference _syncPreferenceDeserialize(
lastUpload: reader.readLongOrNull(offsets[4]),
server: reader.readStringOrNull(offsets[5]),
syncId: id,
syncOn: reader.readBoolOrNull(offsets[6]) ?? false,
);
return object;
}
@ -140,6 +147,8 @@ P _syncPreferenceDeserializeProp<P>(
return (reader.readLongOrNull(offset)) as P;
case 5:
return (reader.readStringOrNull(offset)) as P;
case 6:
return (reader.readBoolOrNull(offset) ?? false) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
@ -996,6 +1005,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
@ -1084,6 +1103,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
@ -1179,6 +1211,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
@ -1223,6 +1268,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
@ -1268,4 +1319,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

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

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

@ -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,6 +20,7 @@ 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/providers/l10n_providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -54,7 +55,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 +107,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 +160,42 @@ 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.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;
@ -203,7 +217,7 @@ ItemType _convertToItemType(Map<String, dynamic> backup) {
ItemType _convertToItemTypeCategory(Map<String, dynamic> backup) {
final forManga = backup['forManga'];
return forManga == null
? ItemType.values[backup['itemType'] ?? 0]
? ItemType.values[backup['forItemType'] ?? 0]
: forManga
? ItemType.manga
: ItemType.anime;

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'2645a4e3f29e1e5b65acff8d66a6f634a8773acf';
/// 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

@ -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,4 +1,8 @@
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:riverpod_annotation/riverpod_annotation.dart';
part 'sync_providers.g.dart';
@ -6,8 +10,8 @@ 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) {
@ -18,28 +22,152 @@ class Synching extends _$Synching {
void logout() {
isar.writeTxnSync(() {
isar.syncPreferences.deleteSync(syncId!);
isar.syncPreferences.putSync(state..authToken = null);
});
}
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);
});
}
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() {
isar.writeTxnSync(() {
isar.changedParts.clearSync();
});
}
}

View file

@ -6,7 +6,7 @@ part of 'sync_providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$synchingHash() => r'3ab44d9e753f2d4b51fd10af6c98ffac78cbf201';
String _$synchingHash() => r'45ac5bd29f880dfc7ac1fcdf12f49d751325279b';
/// 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,6 +16,8 @@ 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 l10n = l10nLocalizations(context)!;
return Scaffold(
appBar: AppBar(
@ -33,6 +37,16 @@ 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);
}),
Padding(
padding: const EdgeInsets.only(
left: 15, right: 15, bottom: 10, top: 5),
@ -62,6 +76,7 @@ class SyncScreen extends ConsumerWidget {
),
const SizedBox(width: 10),
Text(l10n.syncing_subtitle,
softWrap: true,
style: TextStyle(
fontSize: 11, color: context.secondaryColor))
],
@ -80,6 +95,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(
@ -98,6 +119,27 @@ class SyncScreen extends ConsumerWidget {
),
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: [
@ -187,6 +229,94 @@ class SyncScreen extends ConsumerWidget {
],
),
const SizedBox(width: 20),
Column(
children: [
IconButton(
onPressed: !isLogged
? null
: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(
l10n.sync_confirm_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: [
IconButton(
@ -249,7 +379,9 @@ class SyncScreen extends ConsumerWidget {
1)
.notifier)
.downloadFromServer(
l10n);
l10n,
false,
true);
Navigator.pop(
context);
},
@ -275,7 +407,76 @@ class SyncScreen extends ConsumerWidget {
],
),
],
)
),
const SizedBox(height: 30),
buildChangedItemWidget(
l10n.sync_pending_manga,
changedParts.getChangedParts([
ActionType.addItem,
ActionType.removeItem,
ActionType.updateItem
])),
const SizedBox(height: 10),
buildChangedItemWidget(
l10n.sync_pending_chapter,
changedParts.getChangedParts([
ActionType.addChapter,
ActionType.removeChapter,
ActionType.updateChapter
])),
const SizedBox(height: 10),
buildChangedItemWidget(
l10n.sync_pending_category,
changedParts.getChangedParts([
ActionType.addCategory,
ActionType.removeCategory,
ActionType.renameCategory
])),
const SizedBox(height: 10),
buildChangedItemWidget(
l10n.sync_pending_history,
changedParts.getChangedParts([
ActionType.addHistory,
ActionType.clearHistory,
ActionType.removeHistory
])),
const SizedBox(height: 10),
buildChangedItemWidget(
l10n.sync_pending_update,
changedParts.getChangedParts(
[ActionType.addUpdate, ActionType.clearUpdates])),
const SizedBox(height: 10),
buildChangedItemWidget(
l10n.sync_pending_extension,
changedParts.getChangedParts([
ActionType.addExtension,
ActionType.removeExtension,
ActionType.updateExtension
])),
const SizedBox(height: 10),
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: () {
ref
.read(syncServerProvider(syncId: 1).notifier)
.getSnapshots(l10n);
},
child: Text("Browse / Download older backups")),
],
),
),
],
);
}),
@ -284,6 +485,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

@ -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,6 +63,168 @@ 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());
final remoteHash = jsonData["hash"];
if (localHash != remoteHash) {
await downloadFromServer(l10n, true, false);
}
} else {
await forceCheck(l10n, true);
}
ref
.read(synchingProvider(syncId: syncId).notifier)
.setLastSync(DateTime.now().millisecondsSinceEpoch);
ref
.read(synchingProvider(syncId: syncId).notifier)
.clearAllChangedParts();
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());
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> 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 snapshots = jsonDecode(response.body) as List;
for (final snapshot in snapshots) {
print(
"${snapshot["id"]} - ${DateTime.parse(snapshot["dbCreatedAt"]).millisecondsSinceEpoch}");
}
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
.read(synchingProvider(syncId: syncId).notifier)
.clearAllChangedParts();
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> uploadToServer(AppLocalizations l10n) async {
botToast(l10n.sync_uploading, second: 2);
try {
@ -80,14 +246,21 @@ class SyncServer extends _$SyncServer {
ref
.read(synchingProvider(syncId: syncId).notifier)
.setLastUpload(DateTime.now().millisecondsSinceEpoch);
ref
.read(synchingProvider(syncId: syncId).notifier)
.clearAllChangedParts();
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,18 +276,39 @@ 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
.read(synchingProvider(syncId: syncId).notifier)
.clearAllChangedParts();
ref.invalidate(synchingProvider(syncId: syncId));
if (!silent) {
botToast(l10n.sync_download_finished, second: 2);
}
} catch (error) {
botToast(error.toString(), second: 5);
}
}
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"];
var encodedJson = jsonEncode(datas);
return sha256.convert(utf8.encode(encodedJson)).toString();
}
Map<String, dynamic> _getData() {
Map<String, dynamic> datas = {};
datas.addAll({"version": "2"});
@ -189,13 +383,13 @@ class SyncServer extends _$SyncServer {
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 +410,6 @@ class SyncServer extends _$SyncServer {
String _getServer() {
final syncPrefs = ref.watch(synchingProvider(syncId: syncId));
return syncPrefs?.server ?? "";
return syncPrefs.server ?? "";
}
}

View file

@ -6,7 +6,7 @@ part of 'sync_server.dart';
// RiverpodGenerator
// **************************************************************************
String _$syncServerHash() => r'd752de5e7238487e6176bb3ec69d2bd98c0509ad';
String _$syncServerHash() => r'f9e68540eb702f3e3498fc642454897921a17f42';
/// 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 {