Merge pull request #253 from Schnitzel5/feature/sync-server
testing first version of sync feature
This commit is contained in:
commit
1a03a4ec79
43 changed files with 7072 additions and 37 deletions
|
|
@ -3,6 +3,7 @@
|
|||
"library": "Library",
|
||||
"updates": "Updates",
|
||||
"history": "History",
|
||||
"feed": "Feed",
|
||||
"browse": "Browse",
|
||||
"more": "More",
|
||||
"open_random_entry": "Open random entry",
|
||||
|
|
@ -39,6 +40,7 @@
|
|||
"no_recent_updates": "No recent updates",
|
||||
"remove_everything": "Remove everything",
|
||||
"remove_everything_msg": "Are you sure? All history will be lost",
|
||||
"remove_all_feed_msg": "Are you sure? The whole feed will be cleared",
|
||||
"ok": "OK",
|
||||
"cancel": "Cancel",
|
||||
"remove": "Remove",
|
||||
|
|
@ -180,6 +182,28 @@
|
|||
"one_tracker": "1 tracker",
|
||||
"n_tracker": "{n} trackers",
|
||||
"tracking": "Tracking",
|
||||
"syncing": "Sync",
|
||||
"sync_logged": "Login successful",
|
||||
"syncing_subtitle": "Sync your progress across multiple devices via a self-hosted \nserver. Make sure to upload first if this is your first time \nsyncing or download before using (auto) sync on this device!",
|
||||
"last_sync": "Last sync at: ",
|
||||
"last_upload": "Last upload at: ",
|
||||
"last_download": "Last download at: ",
|
||||
"sync_server": "Sync Server Address",
|
||||
"sync_login_invalid_creds": "Invalid email or password",
|
||||
"sync_checking": "Checking for sync...",
|
||||
"sync_uploading": "Upload started...",
|
||||
"sync_downloading": "Download started...",
|
||||
"sync_upload_finished": "Upload finished",
|
||||
"sync_download_finished": "Download finished",
|
||||
"sync_up_to_date": "Sync up to date",
|
||||
"sync_upload_failed": "Upload failed",
|
||||
"sync_download_failed": "Download failed",
|
||||
"sync_button_sync": "Sync progress",
|
||||
"sync_button_upload": "Full upload",
|
||||
"sync_button_download": "Full download",
|
||||
"sync_confirm_upload": "A full upload will completely replace the remote data with your current one!",
|
||||
"sync_confirm_download": "A full download will completely replace your current data with the remote one!",
|
||||
"dialog_confirm": "Confirm",
|
||||
"description": "Description",
|
||||
"episode_progress": "Progress: {n}",
|
||||
"n_episodes": "{n} episodes",
|
||||
|
|
@ -274,6 +298,8 @@
|
|||
"default_skip_intro_length": "Default Skip intro length",
|
||||
"default_playback_speed_length": "Default Playback speed length",
|
||||
"updateProgressAfterReading": "Update progress after reading",
|
||||
"syncAfterReading": "Sync after reading or watching",
|
||||
"syncOnAppLaunch": "Sync when opening the app",
|
||||
"no_sources_installed": "No sources installed!",
|
||||
"show_extensions": "Show extensions",
|
||||
"default_skip_forward_skip_length": "Default skip forward skip length",
|
||||
|
|
@ -334,5 +360,12 @@
|
|||
"or": "OR",
|
||||
"advanced": "Advanced",
|
||||
"use_native_http_client": "Use native http client",
|
||||
"use_native_http_client_info": "it automatically supports platform features such VPNs, support more HTTP features such as HTTP/3 and custom redirect handling"
|
||||
"use_native_http_client_info": "it automatically supports platform features such VPNs, support more HTTP features such as HTTP/3 and custom redirect handling",
|
||||
"n_hour_ago": "{hour} hour ago",
|
||||
"n_hours_ago": "{hours} hours ago",
|
||||
"n_minute_ago": "{minute} minute ago",
|
||||
"n_minutes_ago": "{minutes} minutes ago",
|
||||
"n_day_ago": "{day} day ago",
|
||||
"now": "now",
|
||||
"library_last_updated": "Library last updated: {lastUpdated}"
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@ 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/sync/providers/sync_providers.dart';
|
||||
import 'package:mangayomi/services/sync_server.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:mangayomi/src/rust/frb_generated.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
|
|
@ -68,6 +70,10 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final syncOnAppLaunch = ref.watch(syncOnAppLaunchStateProvider);
|
||||
if (syncOnAppLaunch) {
|
||||
ref.read(syncServerProvider(syncId: 1).notifier).checkForSync(true);
|
||||
}
|
||||
final isDarkTheme = ref.watch(themeModeStateProvider);
|
||||
final blendLevel = ref.watch(blendLevelStateProvider);
|
||||
final appFontFamily = ref.watch(appFontFamilyProvider);
|
||||
|
|
|
|||
86
lib/models/changed_items.dart
Normal file
86
lib/models/changed_items.dart
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import 'package:isar/isar.dart';
|
||||
part 'changed_items.g.dart';
|
||||
|
||||
@collection
|
||||
@Name("Changed Items")
|
||||
class ChangedItems {
|
||||
Id? id;
|
||||
List<DeletedManga>? deletedMangas;
|
||||
List<UpdatedChapter>? updatedChapters;
|
||||
List<DeletedCategory>? deletedCategories;
|
||||
ChangedItems(
|
||||
{this.id = Isar.autoIncrement,
|
||||
this.deletedMangas = const [],
|
||||
this.updatedChapters = const [],
|
||||
this.deletedCategories = const []});
|
||||
|
||||
ChangedItems.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
deletedMangas = json['deletedMangas'];
|
||||
updatedChapters = json['updatedChapters'];
|
||||
deletedCategories = json['deletedCategories'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'deletedMangas': deletedMangas,
|
||||
'updatedChapters': updatedChapters,
|
||||
'deletedCategories': deletedCategories
|
||||
};
|
||||
}
|
||||
|
||||
@embedded
|
||||
class DeletedManga {
|
||||
int? mangaId;
|
||||
DeletedManga({this.mangaId});
|
||||
DeletedManga.fromJson(Map<String, dynamic> json) {
|
||||
mangaId = json['mangaId'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {'mangaId': mangaId};
|
||||
}
|
||||
|
||||
@embedded
|
||||
class UpdatedChapter {
|
||||
int? chapterId;
|
||||
int? mangaId;
|
||||
bool? isBookmarked;
|
||||
bool? isRead;
|
||||
String? lastPageRead;
|
||||
bool? deleted;
|
||||
UpdatedChapter(
|
||||
{this.chapterId,
|
||||
this.mangaId,
|
||||
this.isBookmarked,
|
||||
this.isRead,
|
||||
this.lastPageRead,
|
||||
this.deleted});
|
||||
UpdatedChapter.fromJson(Map<String, dynamic> json) {
|
||||
chapterId = json['chapterId'];
|
||||
mangaId = json['mangaId'];
|
||||
isBookmarked = json['isBookmarked'];
|
||||
isRead = json['isRead'];
|
||||
lastPageRead = json['lastPageRead'];
|
||||
deleted = json['deleted'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'chapterId': chapterId,
|
||||
'mangaId': mangaId,
|
||||
'isBookmarked': isBookmarked,
|
||||
'isRead': isRead,
|
||||
'lastPageRead': lastPageRead,
|
||||
'deleted': deleted
|
||||
};
|
||||
}
|
||||
|
||||
@embedded
|
||||
class DeletedCategory {
|
||||
int? categoryId;
|
||||
DeletedCategory({this.categoryId});
|
||||
DeletedCategory.fromJson(Map<String, dynamic> json) {
|
||||
categoryId = json['categoryId'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {'categoryId': categoryId};
|
||||
}
|
||||
1555
lib/models/changed_items.g.dart
Normal file
1555
lib/models/changed_items.g.dart
Normal file
File diff suppressed because it is too large
Load diff
38
lib/models/feed.dart
Normal file
38
lib/models/feed.dart
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
part 'feed.g.dart';
|
||||
|
||||
@collection
|
||||
@Name("Feed")
|
||||
class Feed {
|
||||
Id? id;
|
||||
|
||||
int? mangaId;
|
||||
|
||||
String? chapterName;
|
||||
|
||||
final chapter = IsarLink<Chapter>();
|
||||
|
||||
String? date;
|
||||
|
||||
Feed({
|
||||
this.id = Isar.autoIncrement,
|
||||
required this.mangaId,
|
||||
required this.chapterName,
|
||||
required this.date,
|
||||
});
|
||||
|
||||
Feed.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
mangaId = json['mangaId'];
|
||||
mangaId = json['chapterName'];
|
||||
date = json['date'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'mangaId': mangaId,
|
||||
'chapterName': chapterName,
|
||||
'date': date,
|
||||
};
|
||||
}
|
||||
789
lib/models/feed.g.dart
Normal file
789
lib/models/feed.g.dart
Normal file
|
|
@ -0,0 +1,789 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'feed.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 GetFeedCollection on Isar {
|
||||
IsarCollection<Feed> get feeds => this.collection();
|
||||
}
|
||||
|
||||
const FeedSchema = CollectionSchema(
|
||||
name: r'Feed',
|
||||
id: 8879644747771893978,
|
||||
properties: {
|
||||
r'chapterName': PropertySchema(
|
||||
id: 0,
|
||||
name: r'chapterName',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'date': PropertySchema(
|
||||
id: 1,
|
||||
name: r'date',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'mangaId': PropertySchema(
|
||||
id: 2,
|
||||
name: r'mangaId',
|
||||
type: IsarType.long,
|
||||
)
|
||||
},
|
||||
estimateSize: _feedEstimateSize,
|
||||
serialize: _feedSerialize,
|
||||
deserialize: _feedDeserialize,
|
||||
deserializeProp: _feedDeserializeProp,
|
||||
idName: r'id',
|
||||
indexes: {},
|
||||
links: {
|
||||
r'chapter': LinkSchema(
|
||||
id: 8037684855892205613,
|
||||
name: r'chapter',
|
||||
target: r'Chapter',
|
||||
single: true,
|
||||
)
|
||||
},
|
||||
embeddedSchemas: {},
|
||||
getId: _feedGetId,
|
||||
getLinks: _feedGetLinks,
|
||||
attach: _feedAttach,
|
||||
version: '3.1.0+1',
|
||||
);
|
||||
|
||||
int _feedEstimateSize(
|
||||
Feed object,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
var bytesCount = offsets.last;
|
||||
{
|
||||
final value = object.chapterName;
|
||||
if (value != null) {
|
||||
bytesCount += 3 + value.length * 3;
|
||||
}
|
||||
}
|
||||
{
|
||||
final value = object.date;
|
||||
if (value != null) {
|
||||
bytesCount += 3 + value.length * 3;
|
||||
}
|
||||
}
|
||||
return bytesCount;
|
||||
}
|
||||
|
||||
void _feedSerialize(
|
||||
Feed object,
|
||||
IsarWriter writer,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
writer.writeString(offsets[0], object.chapterName);
|
||||
writer.writeString(offsets[1], object.date);
|
||||
writer.writeLong(offsets[2], object.mangaId);
|
||||
}
|
||||
|
||||
Feed _feedDeserialize(
|
||||
Id id,
|
||||
IsarReader reader,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
final object = Feed(
|
||||
chapterName: reader.readStringOrNull(offsets[0]),
|
||||
date: reader.readStringOrNull(offsets[1]),
|
||||
id: id,
|
||||
mangaId: reader.readLongOrNull(offsets[2]),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
||||
P _feedDeserializeProp<P>(
|
||||
IsarReader reader,
|
||||
int propertyId,
|
||||
int offset,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
switch (propertyId) {
|
||||
case 0:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 1:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 2:
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
}
|
||||
}
|
||||
|
||||
Id _feedGetId(Feed object) {
|
||||
return object.id ?? Isar.autoIncrement;
|
||||
}
|
||||
|
||||
List<IsarLinkBase<dynamic>> _feedGetLinks(Feed object) {
|
||||
return [object.chapter];
|
||||
}
|
||||
|
||||
void _feedAttach(IsarCollection<dynamic> col, Id id, Feed object) {
|
||||
object.id = id;
|
||||
object.chapter.attach(col, col.isar.collection<Chapter>(), r'chapter', id);
|
||||
}
|
||||
|
||||
extension FeedQueryWhereSort on QueryBuilder<Feed, Feed, QWhere> {
|
||||
QueryBuilder<Feed, Feed, QAfterWhere> anyId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(const IdWhereClause.any());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedQueryWhere on QueryBuilder<Feed, Feed, QWhereClause> {
|
||||
QueryBuilder<Feed, Feed, QAfterWhereClause> idEqualTo(Id id) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IdWhereClause.between(
|
||||
lower: id,
|
||||
upper: id,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, 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<Feed, Feed, QAfterWhereClause> idGreaterThan(Id id,
|
||||
{bool include = false}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(
|
||||
IdWhereClause.greaterThan(lower: id, includeLower: include),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterWhereClause> idLessThan(Id id,
|
||||
{bool include = false}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(
|
||||
IdWhereClause.lessThan(upper: id, includeUpper: include),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, 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 FeedQueryFilter on QueryBuilder<Feed, Feed, QFilterCondition> {
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'chapterName',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'chapterName',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameEqualTo(
|
||||
String? value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'chapterName',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameGreaterThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'chapterName',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameLessThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'chapterName',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameBetween(
|
||||
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'chapterName',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'chapterName',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'chapterName',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameContains(
|
||||
String value,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'chapterName',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameMatches(
|
||||
String pattern,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'chapterName',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'chapterName',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterNameIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'chapterName',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'date',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'date',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateEqualTo(
|
||||
String? value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'date',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateGreaterThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'date',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateLessThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'date',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateBetween(
|
||||
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'date',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'date',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'date',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateContains(String value,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'date',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateMatches(String pattern,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'date',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'date',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> dateIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'date',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> idIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'id',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> idIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'id',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> idEqualTo(Id? value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'id',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, 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<Feed, Feed, 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<Feed, Feed, 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<Feed, Feed, QAfterFilterCondition> mangaIdIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'mangaId',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> mangaIdIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'mangaId',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> mangaIdEqualTo(int? value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'mangaId',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> mangaIdGreaterThan(
|
||||
int? value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'mangaId',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> mangaIdLessThan(
|
||||
int? value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'mangaId',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> mangaIdBetween(
|
||||
int? lower,
|
||||
int? upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'mangaId',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedQueryObject on QueryBuilder<Feed, Feed, QFilterCondition> {}
|
||||
|
||||
extension FeedQueryLinks on QueryBuilder<Feed, Feed, QFilterCondition> {
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapter(
|
||||
FilterQuery<Chapter> q) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.link(q, r'chapter');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterFilterCondition> chapterIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.linkLength(r'chapter', 0, true, 0, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedQuerySortBy on QueryBuilder<Feed, Feed, QSortBy> {
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> sortByChapterName() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'chapterName', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> sortByChapterNameDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'chapterName', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> sortByDate() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'date', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> sortByDateDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'date', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> sortByMangaId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'mangaId', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> sortByMangaIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'mangaId', Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedQuerySortThenBy on QueryBuilder<Feed, Feed, QSortThenBy> {
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> thenByChapterName() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'chapterName', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> thenByChapterNameDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'chapterName', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> thenByDate() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'date', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> thenByDateDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'date', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> thenById() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'id', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> thenByIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'id', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> thenByMangaId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'mangaId', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QAfterSortBy> thenByMangaIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'mangaId', Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedQueryWhereDistinct on QueryBuilder<Feed, Feed, QDistinct> {
|
||||
QueryBuilder<Feed, Feed, QDistinct> distinctByChapterName(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'chapterName', caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QDistinct> distinctByDate(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'date', caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, Feed, QDistinct> distinctByMangaId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'mangaId');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension FeedQueryProperty on QueryBuilder<Feed, Feed, QQueryProperty> {
|
||||
QueryBuilder<Feed, int, QQueryOperations> idProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'id');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, String?, QQueryOperations> chapterNameProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'chapterName');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, String?, QQueryOperations> dateProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'date');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Feed, int?, QQueryOperations> mangaIdProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'mangaId');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -137,6 +137,10 @@ class Settings {
|
|||
|
||||
List<int>? backupFrequencyOptions;
|
||||
|
||||
bool? syncOnAppLaunch;
|
||||
|
||||
bool? syncAfterReading;
|
||||
|
||||
String? autoBackupLocation;
|
||||
|
||||
bool? usePageTapZones;
|
||||
|
|
@ -246,6 +250,8 @@ class Settings {
|
|||
this.personalPageModeList,
|
||||
this.backupFrequency,
|
||||
this.backupFrequencyOptions,
|
||||
this.syncOnAppLaunch,
|
||||
this.syncAfterReading,
|
||||
this.autoBackupLocation,
|
||||
this.startDatebackup,
|
||||
this.usePageTapZones = true,
|
||||
|
|
@ -390,13 +396,15 @@ class Settings {
|
|||
userAgent = json['userAgent'];
|
||||
backupFrequency = json['backupFrequency'];
|
||||
backupFrequencyOptions = json['backupFrequencyOptions']?.cast<int>();
|
||||
syncOnAppLaunch = json['syncOnAppLaunch'];
|
||||
syncAfterReading = json['syncAfterReading'];
|
||||
autoBackupLocation = json['autoBackupLocation'];
|
||||
startDatebackup = json['startDatebackup'];
|
||||
usePageTapZones = json['usePageTapZones'];
|
||||
markEpisodeAsSeenType = json['markEpisodeAsSeenType'];
|
||||
defaultSkipIntroLength = json['defaultSkipIntroLength'];
|
||||
defaultDoubleTapToSkipLength = json['defaultDoubleTapToSkipLength'];
|
||||
defaultPlayBackSpeed = json['defaultPlayBackSpeed'];
|
||||
defaultPlayBackSpeed = json['defaultPlayBackSpeed'] is double ? json['defaultPlayBackSpeed'] : (json['defaultPlayBackSpeed'] as int).toDouble();
|
||||
updateProgressAfterReading = json['updateProgressAfterReading'];
|
||||
enableAniSkip = json['enableAniSkip'];
|
||||
enableAutoSkip = json['enableAutoSkip'];
|
||||
|
|
@ -410,7 +418,7 @@ class Settings {
|
|||
colorFilterBlendMode = ColorFilterBlendMode
|
||||
.values[json['colorFilterBlendMode'] ?? ColorFilterBlendMode.none];
|
||||
playerSubtitleSettings = json['playerSubtitleSettings'] != null
|
||||
? PlayerSubtitleSettings.fromJson(json['customColorFilter'])
|
||||
? PlayerSubtitleSettings.fromJson(json['playerSubtitleSettings'])
|
||||
: null;
|
||||
mangaHomeDisplayType = DisplayType.values[
|
||||
json['mangaHomeDisplayType'] ?? DisplayType.comfortableGrid.index];
|
||||
|
|
@ -503,6 +511,8 @@ class Settings {
|
|||
'userAgent': userAgent,
|
||||
'backupFrequency': backupFrequency,
|
||||
'backupFrequencyOptions': backupFrequencyOptions,
|
||||
'syncOnAppLaunch': syncOnAppLaunch,
|
||||
'syncAfterReading': syncAfterReading,
|
||||
'autoBackupLocation': autoBackupLocation,
|
||||
'startDatebackup': startDatebackup,
|
||||
'usePageTapZones': usePageTapZones,
|
||||
|
|
|
|||
|
|
@ -441,28 +441,38 @@ const SettingsSchema = CollectionSchema(
|
|||
name: r'startDatebackup',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'themeIsDark': PropertySchema(
|
||||
r'syncAfterReading': PropertySchema(
|
||||
id: 80,
|
||||
name: r'syncAfterReading',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'syncOnAppLaunch': PropertySchema(
|
||||
id: 81,
|
||||
name: r'syncOnAppLaunch',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'themeIsDark': PropertySchema(
|
||||
id: 82,
|
||||
name: r'themeIsDark',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'updateProgressAfterReading': PropertySchema(
|
||||
id: 81,
|
||||
id: 83,
|
||||
name: r'updateProgressAfterReading',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'useLibass': PropertySchema(
|
||||
id: 82,
|
||||
id: 84,
|
||||
name: r'useLibass',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'usePageTapZones': PropertySchema(
|
||||
id: 83,
|
||||
id: 85,
|
||||
name: r'usePageTapZones',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'userAgent': PropertySchema(
|
||||
id: 84,
|
||||
id: 86,
|
||||
name: r'userAgent',
|
||||
type: IsarType.string,
|
||||
)
|
||||
|
|
@ -914,11 +924,13 @@ void _settingsSerialize(
|
|||
object.sortLibraryManga,
|
||||
);
|
||||
writer.writeLong(offsets[79], object.startDatebackup);
|
||||
writer.writeBool(offsets[80], object.themeIsDark);
|
||||
writer.writeBool(offsets[81], object.updateProgressAfterReading);
|
||||
writer.writeBool(offsets[82], object.useLibass);
|
||||
writer.writeBool(offsets[83], object.usePageTapZones);
|
||||
writer.writeString(offsets[84], object.userAgent);
|
||||
writer.writeBool(offsets[80], object.syncAfterReading);
|
||||
writer.writeBool(offsets[81], object.syncOnAppLaunch);
|
||||
writer.writeBool(offsets[82], object.themeIsDark);
|
||||
writer.writeBool(offsets[83], object.updateProgressAfterReading);
|
||||
writer.writeBool(offsets[84], object.useLibass);
|
||||
writer.writeBool(offsets[85], object.usePageTapZones);
|
||||
writer.writeString(offsets[86], object.userAgent);
|
||||
}
|
||||
|
||||
Settings _settingsDeserialize(
|
||||
|
|
@ -1077,11 +1089,13 @@ Settings _settingsDeserialize(
|
|||
allOffsets,
|
||||
),
|
||||
startDatebackup: reader.readLongOrNull(offsets[79]),
|
||||
themeIsDark: reader.readBoolOrNull(offsets[80]),
|
||||
updateProgressAfterReading: reader.readBoolOrNull(offsets[81]),
|
||||
useLibass: reader.readBoolOrNull(offsets[82]),
|
||||
usePageTapZones: reader.readBoolOrNull(offsets[83]),
|
||||
userAgent: reader.readStringOrNull(offsets[84]),
|
||||
syncAfterReading: reader.readBoolOrNull(offsets[80]),
|
||||
syncOnAppLaunch: reader.readBoolOrNull(offsets[81]),
|
||||
themeIsDark: reader.readBoolOrNull(offsets[82]),
|
||||
updateProgressAfterReading: reader.readBoolOrNull(offsets[83]),
|
||||
useLibass: reader.readBoolOrNull(offsets[84]),
|
||||
usePageTapZones: reader.readBoolOrNull(offsets[85]),
|
||||
userAgent: reader.readStringOrNull(offsets[86]),
|
||||
);
|
||||
object.chapterFilterBookmarkedList =
|
||||
reader.readObjectList<ChapterFilterBookmarked>(
|
||||
|
|
@ -1375,6 +1389,10 @@ P _settingsDeserializeProp<P>(
|
|||
case 83:
|
||||
return (reader.readBoolOrNull(offset)) as P;
|
||||
case 84:
|
||||
return (reader.readBoolOrNull(offset)) as P;
|
||||
case 85:
|
||||
return (reader.readBoolOrNull(offset)) as P;
|
||||
case 86:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
|
|
@ -6759,6 +6777,62 @@ extension SettingsQueryFilter
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterFilterCondition>
|
||||
syncAfterReadingIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'syncAfterReading',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterFilterCondition>
|
||||
syncAfterReadingIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'syncAfterReading',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterFilterCondition>
|
||||
syncAfterReadingEqualTo(bool? value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'syncAfterReading',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterFilterCondition>
|
||||
syncOnAppLaunchIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'syncOnAppLaunch',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterFilterCondition>
|
||||
syncOnAppLaunchIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'syncOnAppLaunch',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterFilterCondition>
|
||||
syncOnAppLaunchEqualTo(bool? value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'syncOnAppLaunch',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterFilterCondition> themeIsDarkIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
|
|
@ -8017,6 +8091,30 @@ extension SettingsQuerySortBy on QueryBuilder<Settings, Settings, QSortBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> sortBySyncAfterReading() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'syncAfterReading', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> sortBySyncAfterReadingDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'syncAfterReading', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> sortBySyncOnAppLaunch() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'syncOnAppLaunch', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> sortBySyncOnAppLaunchDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'syncOnAppLaunch', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> sortByThemeIsDark() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'themeIsDark', Sort.asc);
|
||||
|
|
@ -8917,6 +9015,30 @@ extension SettingsQuerySortThenBy
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> thenBySyncAfterReading() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'syncAfterReading', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> thenBySyncAfterReadingDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'syncAfterReading', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> thenBySyncOnAppLaunch() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'syncOnAppLaunch', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> thenBySyncOnAppLaunchDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'syncOnAppLaunch', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QAfterSortBy> thenByThemeIsDark() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'themeIsDark', Sort.asc);
|
||||
|
|
@ -9404,6 +9526,18 @@ extension SettingsQueryWhereDistinct
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QDistinct> distinctBySyncAfterReading() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'syncAfterReading');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QDistinct> distinctBySyncOnAppLaunch() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'syncOnAppLaunch');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, Settings, QDistinct> distinctByThemeIsDark() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'themeIsDark');
|
||||
|
|
@ -9980,6 +10114,18 @@ extension SettingsQueryProperty
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, bool?, QQueryOperations> syncAfterReadingProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'syncAfterReading');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, bool?, QQueryOperations> syncOnAppLaunchProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'syncOnAppLaunch');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Settings, bool?, QQueryOperations> themeIsDarkProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'themeIsDark');
|
||||
|
|
|
|||
50
lib/models/sync_preference.dart
Normal file
50
lib/models/sync_preference.dart
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import 'package:isar/isar.dart';
|
||||
part 'sync_preference.g.dart';
|
||||
|
||||
@collection
|
||||
@Name("Sync Preference")
|
||||
class SyncPreference {
|
||||
Id? syncId;
|
||||
|
||||
String? email;
|
||||
|
||||
String? authToken;
|
||||
|
||||
int? lastSync;
|
||||
|
||||
int? lastUpload;
|
||||
|
||||
int? lastDownload;
|
||||
|
||||
String? server;
|
||||
|
||||
SyncPreference({
|
||||
this.syncId,
|
||||
this.email,
|
||||
this.authToken,
|
||||
this.lastSync,
|
||||
this.lastUpload,
|
||||
this.lastDownload,
|
||||
this.server,
|
||||
});
|
||||
|
||||
SyncPreference.fromJson(Map<String, dynamic> json) {
|
||||
syncId = json['syncId'];
|
||||
email = json['email'];
|
||||
authToken = json['authToken'];
|
||||
lastSync = json['lastSync'];
|
||||
lastUpload = json['lastUpload'];
|
||||
lastDownload = json['lastDownload'];
|
||||
server = json['server'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'syncId': syncId,
|
||||
'email': email,
|
||||
'authToken': authToken,
|
||||
'lastSync': lastSync,
|
||||
'lastUpload': lastUpload,
|
||||
'lastDownload': lastDownload,
|
||||
'server': server
|
||||
};
|
||||
}
|
||||
1271
lib/models/sync_preference.g.dart
Normal file
1271
lib/models/sync_preference.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -7,6 +7,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';
|
||||
|
|
@ -164,10 +165,14 @@ class AnimeStreamController extends _$AnimeStreamController {
|
|||
isar.writeTxnSync(() {
|
||||
ep.isRead = isWatch;
|
||||
ep.lastPageRead = (duration.inMilliseconds).toString();
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.addUpdatedChapter(ep, false, false);
|
||||
isar.chapters.putSync(ep);
|
||||
});
|
||||
if (isWatch) {
|
||||
episode.updateTrackChapterRead(ref);
|
||||
episode.syncProgressAfterChapterRead(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ part of 'anime_player_controller_provider.dart';
|
|||
// **************************************************************************
|
||||
|
||||
String _$animeStreamControllerHash() =>
|
||||
r'24639a8644ea9820458658807035e4cffb1b1644';
|
||||
r'afa475dbb6f73d33b2495dd6d4502f3df1ab931d';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
262
lib/modules/feed/feed_screen.dart
Normal file
262
lib/modules/feed/feed_screen.dart
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:grouped_list/sliver_grouped_list.dart';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/feed.dart';
|
||||
import 'package:mangayomi/models/history.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/modules/feed/widgets/feed_chapter_list_tile_widget.dart';
|
||||
import 'package:mangayomi/modules/history/providers/isar_providers.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/utils/date.dart';
|
||||
import 'package:mangayomi/modules/library/widgets/search_text_form_field.dart';
|
||||
import 'package:mangayomi/modules/widgets/error_text.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
|
||||
class FeedScreen extends ConsumerStatefulWidget {
|
||||
const FeedScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<FeedScreen> createState() => _FeedScreenState();
|
||||
}
|
||||
|
||||
class _FeedScreenState extends ConsumerState<FeedScreen>
|
||||
with TickerProviderStateMixin {
|
||||
late TabController _tabBarController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_tabBarController = TabController(length: 2, vsync: this);
|
||||
_tabBarController.animateTo(0);
|
||||
_tabBarController.addListener(() {
|
||||
setState(() {
|
||||
_textEditingController.clear();
|
||||
_isSearch = false;
|
||||
});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final _textEditingController = TextEditingController();
|
||||
bool _isSearch = false;
|
||||
List<History> entriesData = [];
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return DefaultTabController(
|
||||
animationDuration: Duration.zero,
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
title: _isSearch
|
||||
? null
|
||||
: Text(
|
||||
l10n.feed,
|
||||
style: TextStyle(color: Theme.of(context).hintColor),
|
||||
),
|
||||
actions: [
|
||||
_isSearch
|
||||
? SeachFormTextField(
|
||||
onChanged: (value) {
|
||||
setState(() {});
|
||||
},
|
||||
onSuffixPressed: () {
|
||||
_textEditingController.clear();
|
||||
setState(() {});
|
||||
},
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isSearch = false;
|
||||
});
|
||||
_textEditingController.clear();
|
||||
},
|
||||
controller: _textEditingController,
|
||||
)
|
||||
: IconButton(
|
||||
splashRadius: 20,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isSearch = true;
|
||||
});
|
||||
},
|
||||
icon:
|
||||
Icon(Icons.search, color: Theme.of(context).hintColor)),
|
||||
IconButton(
|
||||
splashRadius: 20,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
l10n.remove_everything,
|
||||
),
|
||||
content: Text(l10n.remove_all_feed_msg),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(l10n.cancel)),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
List<Feed> feeds = isar.feeds
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.chapter((q) => q.manga((q) =>
|
||||
q.isMangaEqualTo(
|
||||
_tabBarController.index ==
|
||||
0)))
|
||||
.findAllSync()
|
||||
.toList();
|
||||
isar.writeTxnSync(() {
|
||||
for (var feed in feeds) {
|
||||
isar.feeds.deleteSync(feed.id!);
|
||||
}
|
||||
});
|
||||
if (mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Text(l10n.ok)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
icon: Icon(Icons.delete_sweep_outlined,
|
||||
color: Theme.of(context).hintColor)),
|
||||
],
|
||||
bottom: TabBar(
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
controller: _tabBarController,
|
||||
tabs: [
|
||||
Tab(text: l10n.manga),
|
||||
Tab(text: l10n.anime),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: TabBarView(controller: _tabBarController, children: [
|
||||
FeedTab(
|
||||
isManga: true,
|
||||
query: _textEditingController.text,
|
||||
),
|
||||
FeedTab(
|
||||
isManga: false,
|
||||
query: _textEditingController.text,
|
||||
)
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FeedTab extends ConsumerStatefulWidget {
|
||||
final String query;
|
||||
final bool isManga;
|
||||
const FeedTab({required this.isManga, required this.query, super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<FeedTab> createState() => _FeedTabState();
|
||||
}
|
||||
|
||||
class _FeedTabState extends ConsumerState<FeedTab> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
final feed = ref.watch(getAllFeedStreamProvider(isManga: widget.isManga));
|
||||
return Scaffold(
|
||||
body: feed.when(
|
||||
data: (data) {
|
||||
final entries = data
|
||||
.where((element) => widget.query.isNotEmpty
|
||||
? element.chapter.value!.manga.value!.name!
|
||||
.toLowerCase()
|
||||
.contains(widget.query.toLowerCase())
|
||||
: true)
|
||||
.toList();
|
||||
final lastUpdatedList =
|
||||
data.map((e) => e.chapter.value!.manga.value!.lastUpdate!).toList();
|
||||
lastUpdatedList.sort((a, b) => a.compareTo(b));
|
||||
final lastUpdated = lastUpdatedList.firstOrNull;
|
||||
if (entries.isNotEmpty) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
if (lastUpdated != null)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 10, right: 10, top: 10, bottom: 20),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate.fixed([
|
||||
Text(
|
||||
l10n.library_last_updated(dateFormat(
|
||||
lastUpdated.toString(),
|
||||
ref: ref,
|
||||
context: context,
|
||||
showHOURorMINUTE: true)),
|
||||
style: TextStyle(
|
||||
fontStyle: FontStyle.italic,
|
||||
color: context.secondaryColor))
|
||||
])),
|
||||
),
|
||||
SliverGroupedListView<Feed, String>(
|
||||
elements: entries,
|
||||
groupBy: (element) => dateFormat(element.date!,
|
||||
context: context,
|
||||
ref: ref,
|
||||
forHistoryValue: true,
|
||||
useRelativeTimesTamps: false),
|
||||
groupSeparatorBuilder: (String groupByValue) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8, left: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(dateFormat(
|
||||
null,
|
||||
context: context,
|
||||
stringDate: groupByValue,
|
||||
ref: ref,
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, Feed element) {
|
||||
final chapter = element.chapter.value!;
|
||||
return FeedChapterListTileWidget(
|
||||
chapter: chapter, sourceExist: true);
|
||||
},
|
||||
itemComparator: (item1, item2) =>
|
||||
item1.date!.compareTo(item2.date!),
|
||||
order: GroupedListOrder.DESC,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Center(
|
||||
child: Text(l10n.no_recent_updates),
|
||||
);
|
||||
},
|
||||
error: (Object error, StackTrace stackTrace) {
|
||||
return ErrorText(error);
|
||||
},
|
||||
loading: () {
|
||||
return const ProgressCenter();
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
112
lib/modules/feed/widgets/feed_chapter_list_tile_widget.dart
Normal file
112
lib/modules/feed/widgets/feed_chapter_list_tile_widget.dart
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
|
||||
import 'package:mangayomi/utils/constant.dart';
|
||||
import 'package:mangayomi/modules/manga/download/download_page_widget.dart';
|
||||
import 'package:mangayomi/utils/extensions/chapter.dart';
|
||||
import 'package:mangayomi/utils/headers.dart';
|
||||
|
||||
class FeedChapterListTileWidget extends ConsumerWidget {
|
||||
final Chapter chapter;
|
||||
final bool sourceExist;
|
||||
const FeedChapterListTileWidget({
|
||||
required this.chapter,
|
||||
required this.sourceExist,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final manga = chapter.manga.value!;
|
||||
return Material(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.transparent,
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
chapter.pushToReaderView(context, ignoreIsRead: true);
|
||||
},
|
||||
onLongPress: () {},
|
||||
onSecondaryTap: () {},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 5),
|
||||
child: Container(
|
||||
height: 45,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: Material(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.push('/manga-reader/detail',
|
||||
extra: manga.id);
|
||||
},
|
||||
child: Ink.image(
|
||||
fit: BoxFit.cover,
|
||||
width: 40,
|
||||
height: 45,
|
||||
image: manga.customCoverImage != null
|
||||
? MemoryImage(
|
||||
manga.customCoverImage as Uint8List)
|
||||
as ImageProvider
|
||||
: CustomExtendedNetworkImageProvider(
|
||||
toImgUrl(manga.customCoverFromTracker ??
|
||||
manga.imageUrl!),
|
||||
headers: ref.watch(headersProvider(
|
||||
source: manga.source!,
|
||||
lang: manga.lang!)),
|
||||
),
|
||||
child: InkWell(child: Container()),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(manga.name!,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color)),
|
||||
Text(chapter.name!,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (sourceExist) ChapterPageDownload(chapter: chapter)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ 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/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';
|
||||
|
|
@ -357,6 +358,10 @@ class _HistoryTabState extends ConsumerState<HistoryTab> {
|
|||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await manga.chapters
|
||||
.load();
|
||||
final chapters =
|
||||
manga.chapters;
|
||||
await isar.writeTxn(
|
||||
() async {
|
||||
await isar
|
||||
|
|
@ -364,6 +369,34 @@ class _HistoryTabState extends ConsumerState<HistoryTab> {
|
|||
.delete(
|
||||
element
|
||||
.id!);
|
||||
for (var chapter
|
||||
in chapters) {
|
||||
await ref
|
||||
.read(changedItemsManagerProvider(
|
||||
managerId:
|
||||
1)
|
||||
.notifier)
|
||||
.addUpdatedChapterAsync(
|
||||
chapter,
|
||||
true,
|
||||
false);
|
||||
await isar
|
||||
.chapters
|
||||
.delete(
|
||||
chapter
|
||||
.id!);
|
||||
}
|
||||
await ref
|
||||
.read(changedItemsManagerProvider(
|
||||
managerId:
|
||||
1)
|
||||
.notifier)
|
||||
.addDeletedMangaAsync(
|
||||
manga,
|
||||
false);
|
||||
await isar.mangas
|
||||
.delete(manga
|
||||
.id!);
|
||||
});
|
||||
if (context
|
||||
.mounted) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/feed.dart';
|
||||
import 'package:mangayomi/models/history.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
|
@ -16,3 +17,14 @@ Stream<List<History>> getAllHistoryStream(GetAllHistoryStreamRef ref,
|
|||
.chapter((q) => q.manga((q) => q.isMangaEqualTo(isManga)))
|
||||
.watch(fireImmediately: true);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Stream<List<Feed>> getAllFeedStream(GetAllFeedStreamRef ref,
|
||||
{required bool isManga}) async* {
|
||||
yield* isar.feeds
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.and()
|
||||
.chapter((q) => q.manga((q) => q.isMangaEqualTo(isManga)))
|
||||
.watch(fireImmediately: true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,5 +157,134 @@ class _GetAllHistoryStreamProviderElement
|
|||
@override
|
||||
bool get isManga => (origin as GetAllHistoryStreamProvider).isManga;
|
||||
}
|
||||
|
||||
String _$getAllFeedStreamHash() => r'3d60bca5377bf6fc2aee36e7bec5b319b2377add';
|
||||
|
||||
/// See also [getAllFeedStream].
|
||||
@ProviderFor(getAllFeedStream)
|
||||
const getAllFeedStreamProvider = GetAllFeedStreamFamily();
|
||||
|
||||
/// See also [getAllFeedStream].
|
||||
class GetAllFeedStreamFamily extends Family<AsyncValue<List<Feed>>> {
|
||||
/// See also [getAllFeedStream].
|
||||
const GetAllFeedStreamFamily();
|
||||
|
||||
/// See also [getAllFeedStream].
|
||||
GetAllFeedStreamProvider call({
|
||||
required bool isManga,
|
||||
}) {
|
||||
return GetAllFeedStreamProvider(
|
||||
isManga: isManga,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
GetAllFeedStreamProvider getProviderOverride(
|
||||
covariant GetAllFeedStreamProvider provider,
|
||||
) {
|
||||
return call(
|
||||
isManga: provider.isManga,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'getAllFeedStreamProvider';
|
||||
}
|
||||
|
||||
/// See also [getAllFeedStream].
|
||||
class GetAllFeedStreamProvider extends AutoDisposeStreamProvider<List<Feed>> {
|
||||
/// See also [getAllFeedStream].
|
||||
GetAllFeedStreamProvider({
|
||||
required bool isManga,
|
||||
}) : this._internal(
|
||||
(ref) => getAllFeedStream(
|
||||
ref as GetAllFeedStreamRef,
|
||||
isManga: isManga,
|
||||
),
|
||||
from: getAllFeedStreamProvider,
|
||||
name: r'getAllFeedStreamProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$getAllFeedStreamHash,
|
||||
dependencies: GetAllFeedStreamFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
GetAllFeedStreamFamily._allTransitiveDependencies,
|
||||
isManga: isManga,
|
||||
);
|
||||
|
||||
GetAllFeedStreamProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.isManga,
|
||||
}) : super.internal();
|
||||
|
||||
final bool isManga;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
Stream<List<Feed>> Function(GetAllFeedStreamRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: GetAllFeedStreamProvider._internal(
|
||||
(ref) => create(ref as GetAllFeedStreamRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
isManga: isManga,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamProviderElement<List<Feed>> createElement() {
|
||||
return _GetAllFeedStreamProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is GetAllFeedStreamProvider && other.isManga == isManga;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, isManga.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin GetAllFeedStreamRef on AutoDisposeStreamProviderRef<List<Feed>> {
|
||||
/// The parameter `isManga` of this provider.
|
||||
bool get isManga;
|
||||
}
|
||||
|
||||
class _GetAllFeedStreamProviderElement
|
||||
extends AutoDisposeStreamProviderElement<List<Feed>>
|
||||
with GetAllFeedStreamRef {
|
||||
_GetAllFeedStreamProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
bool get isManga => (origin as GetAllFeedStreamProvider).isManga;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@ 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/models/feed.dart';
|
||||
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';
|
||||
|
|
@ -1157,8 +1159,24 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
|||
}
|
||||
|
||||
for (var chapter in manga.chapters) {
|
||||
ref
|
||||
.read(changedItemsManagerProvider(
|
||||
managerId: 1)
|
||||
.notifier)
|
||||
.addUpdatedChapter(
|
||||
chapter, true, false);
|
||||
isar.feeds
|
||||
.filter()
|
||||
.mangaIdEqualTo(chapter.mangaId)
|
||||
.chapterNameEqualTo(chapter.name)
|
||||
.deleteAllSync();
|
||||
isar.chapters.deleteSync(chapter.id!);
|
||||
}
|
||||
ref
|
||||
.read(changedItemsManagerProvider(
|
||||
managerId: 1)
|
||||
.notifier)
|
||||
.addDeletedManga(manga, false);
|
||||
isar.mangas.deleteSync(manga.id!);
|
||||
} else {
|
||||
manga.favorite = false;
|
||||
|
|
@ -1792,7 +1810,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
|||
});
|
||||
} else if (value == 2) {
|
||||
_importLocal(context, widget.isManga);
|
||||
} else if (value == 3 && !widget.isManga){
|
||||
} else if (value == 3 && !widget.isManga) {
|
||||
addTorrent(context);
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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';
|
||||
|
|
@ -650,10 +651,14 @@ class MangasSetIsReadState extends _$MangasSetIsReadState {
|
|||
for (var chapter in chapters) {
|
||||
chapter.isRead = true;
|
||||
chapter.lastPageRead = "1";
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.addUpdatedChapter(chapter, false, false);
|
||||
isar.chapters.putSync(chapter..manga.value = manga);
|
||||
chapter.manga.saveSync();
|
||||
}
|
||||
});
|
||||
chapters.last.syncProgressAfterChapterRead(ref);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -674,6 +679,9 @@ class MangasSetUnReadState extends _$MangasSetUnReadState {
|
|||
isar.writeTxnSync(() {
|
||||
for (var chapter in chapters) {
|
||||
chapter.isRead = false;
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.addUpdatedChapter(chapter, false, false);
|
||||
isar.chapters.putSync(chapter..manga.value = manga);
|
||||
chapter.manga.saveSync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2520,7 +2520,7 @@ final isLongPressedMangaStateProvider =
|
|||
|
||||
typedef _$IsLongPressedMangaState = AutoDisposeNotifier<bool>;
|
||||
String _$mangasSetIsReadStateHash() =>
|
||||
r'8f86296f588a48747de625e0471048978ee9bdeb';
|
||||
r'926659caebb85b15952f66f437f773a4b660bb78';
|
||||
|
||||
abstract class _$MangasSetIsReadState
|
||||
extends BuildlessAutoDisposeNotifier<void> {
|
||||
|
|
@ -2665,7 +2665,7 @@ class _MangasSetIsReadStateProviderElement
|
|||
}
|
||||
|
||||
String _$mangasSetUnReadStateHash() =>
|
||||
r'3413e731b2fd8476a4032d3e47b943ca12f25090';
|
||||
r'7b2f4c579f9cb392830ed4d70aff9ccc3e7952a0';
|
||||
|
||||
abstract class _$MangasSetUnReadState
|
||||
extends BuildlessAutoDisposeNotifier<void> {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/feed.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/modules/widgets/loading_icon.dart';
|
||||
import 'package:mangayomi/services/fetch_anime_sources.dart';
|
||||
|
|
@ -45,8 +46,9 @@ class MainScreen extends ConsumerWidget {
|
|||
'/MangaLibrary' => 0,
|
||||
'/AnimeLibrary' => 1,
|
||||
'/history' => 2,
|
||||
'/browse' => 3,
|
||||
_ => 4,
|
||||
'/feed' => 3,
|
||||
'/browse' => 4,
|
||||
_ => 5,
|
||||
};
|
||||
|
||||
final incognitoMode = ref.watch(incognitoModeStateProvider);
|
||||
|
|
@ -96,6 +98,7 @@ class MainScreen extends ConsumerWidget {
|
|||
!= '/MangaLibrary' &&
|
||||
!= '/AnimeLibrary' &&
|
||||
!= '/history' &&
|
||||
!= '/feed' &&
|
||||
!= '/browse' &&
|
||||
!= '/more' =>
|
||||
0,
|
||||
|
|
@ -141,6 +144,36 @@ class MainScreen extends ConsumerWidget {
|
|||
padding:
|
||||
const EdgeInsets.only(top: 5),
|
||||
child: Text(l10n.history))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon: Stack(
|
||||
children: [
|
||||
const Icon(Icons.rss_feed),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: _feedTotalNumbers(
|
||||
ref, false))
|
||||
],
|
||||
),
|
||||
icon: Stack(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.rss_feed_outlined),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: _feedTotalNumbers(
|
||||
ref, false))
|
||||
],
|
||||
),
|
||||
label: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5),
|
||||
child: Stack(
|
||||
children: [
|
||||
Text(l10n.feed),
|
||||
],
|
||||
))),
|
||||
NavigationRailDestination(
|
||||
selectedIcon:
|
||||
const Icon(Icons.explore),
|
||||
|
|
@ -169,8 +202,10 @@ class MainScreen extends ConsumerWidget {
|
|||
} else if (newIndex == 2) {
|
||||
route.go('/history');
|
||||
} else if (newIndex == 3) {
|
||||
route.go('/browse');
|
||||
route.go('/feed');
|
||||
} else if (newIndex == 4) {
|
||||
route.go('/browse');
|
||||
} else if (newIndex == 5) {
|
||||
route.go('/more');
|
||||
}
|
||||
},
|
||||
|
|
@ -199,6 +234,7 @@ class MainScreen extends ConsumerWidget {
|
|||
!= '/MangaLibrary' &&
|
||||
!= '/AnimeLibrary' &&
|
||||
!= '/history' &&
|
||||
!= '/feed' &&
|
||||
!= '/browse' &&
|
||||
!= '/more' =>
|
||||
0,
|
||||
|
|
@ -231,6 +267,18 @@ class MainScreen extends ConsumerWidget {
|
|||
selectedIcon: const Icon(Icons.history),
|
||||
icon: const Icon(Icons.history_outlined),
|
||||
label: l10n.history),
|
||||
Stack(
|
||||
children: [
|
||||
NavigationDestination(
|
||||
selectedIcon: const Icon(Icons.rss_feed),
|
||||
icon: const Icon(Icons.rss_feed_outlined),
|
||||
label: l10n.feed),
|
||||
Positioned(
|
||||
right: 14,
|
||||
top: 3,
|
||||
child: _feedTotalNumbers(ref, true)),
|
||||
],
|
||||
),
|
||||
Stack(
|
||||
children: [
|
||||
NavigationDestination(
|
||||
|
|
@ -256,8 +304,10 @@ class MainScreen extends ConsumerWidget {
|
|||
} else if (newIndex == 2) {
|
||||
route.go('/history');
|
||||
} else if (newIndex == 3) {
|
||||
route.go('/browse');
|
||||
route.go('/feed');
|
||||
} else if (newIndex == 4) {
|
||||
route.go('/browse');
|
||||
} else if (newIndex == 5) {
|
||||
route.go('/more');
|
||||
}
|
||||
},
|
||||
|
|
@ -315,3 +365,38 @@ Widget _extensionUpdateTotalNumbers(WidgetRef ref) {
|
|||
return Container();
|
||||
});
|
||||
}
|
||||
|
||||
Widget _feedTotalNumbers(WidgetRef ref, bool mobile) {
|
||||
return StreamBuilder(
|
||||
stream: isar.feeds.filter().idIsNotNull().watch(fireImmediately: true),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
|
||||
final entries = snapshot.data!.where((element) {
|
||||
if (!element.chapter.isLoaded) {
|
||||
element.chapter.loadSync();
|
||||
}
|
||||
return !(element.chapter.value?.isRead ?? false);
|
||||
}).toList();
|
||||
return entries.isEmpty
|
||||
? Container()
|
||||
: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: const Color.fromARGB(255, 176, 46, 37)),
|
||||
child: Padding(
|
||||
padding: mobile
|
||||
? const EdgeInsets.symmetric(horizontal: 5, vertical: 3)
|
||||
: const EdgeInsets.symmetric(
|
||||
horizontal: 3, vertical: 1),
|
||||
child: Text(
|
||||
entries.length.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).textTheme.bodySmall!.color),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,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';
|
||||
|
|
@ -708,6 +709,11 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
isar.writeTxnSync(() {
|
||||
for (var chapter in chapters) {
|
||||
chapter.isBookmarked = !chapter.isBookmarked!;
|
||||
ref
|
||||
.read(changedItemsManagerProvider(
|
||||
managerId: 1)
|
||||
.notifier)
|
||||
.addUpdatedChapter(chapter, false, false);
|
||||
isar.chapters.putSync(
|
||||
chapter..manga.value = widget.manga);
|
||||
chapter.manga.saveSync();
|
||||
|
|
@ -748,11 +754,17 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
if (!chapter.isRead!) {
|
||||
chapter.lastPageRead = "1";
|
||||
}
|
||||
ref
|
||||
.read(changedItemsManagerProvider(
|
||||
managerId: 1)
|
||||
.notifier)
|
||||
.addUpdatedChapter(chapter, false, false);
|
||||
isar.chapters.putSync(
|
||||
chapter..manga.value = widget.manga);
|
||||
chapter.manga.saveSync();
|
||||
if (chapter.isRead!) {
|
||||
chapter.updateTrackChapterRead(ref);
|
||||
chapter.syncProgressAfterChapterRead(ref);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -793,6 +805,12 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
if (!chapters[i].isRead!) {
|
||||
chapters[i].isRead = true;
|
||||
chapters[i].lastPageRead = "1";
|
||||
ref
|
||||
.read(changedItemsManagerProvider(
|
||||
managerId: 1)
|
||||
.notifier)
|
||||
.addUpdatedChapter(
|
||||
chapters[i], false, false);
|
||||
isar.chapters.putSync(chapters[i]
|
||||
..manga.value = widget.manga);
|
||||
chapters[i].manga.saveSync();
|
||||
|
|
@ -805,6 +823,8 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
.read(chaptersListStateProvider.notifier)
|
||||
.clear();
|
||||
});
|
||||
chapters[index + 1]
|
||||
.syncProgressAfterChapterRead(ref);
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ 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';
|
||||
|
||||
|
|
@ -292,6 +293,9 @@ class ChapterSetIsBookmarkState extends _$ChapterSetIsBookmarkState {
|
|||
isar.writeTxnSync(() {
|
||||
for (var chapter in chapters) {
|
||||
chapter.isBookmarked = !chapter.isBookmarked!;
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.addUpdatedChapter(chapter, false, false);
|
||||
isar.chapters.putSync(chapter..manga.value = manga);
|
||||
chapter.manga.saveSync();
|
||||
}
|
||||
|
|
@ -311,6 +315,9 @@ class ChapterSetIsReadState extends _$ChapterSetIsReadState {
|
|||
isar.writeTxnSync(() {
|
||||
for (var chapter in chapters) {
|
||||
chapter.isRead = !chapter.isRead!;
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.addUpdatedChapter(chapter, false, false);
|
||||
isar.chapters.putSync(chapter..manga.value = manga);
|
||||
chapter.manga.saveSync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -806,7 +806,7 @@ class _ChapterFilterResultStateProviderElement
|
|||
}
|
||||
|
||||
String _$chapterSetIsBookmarkStateHash() =>
|
||||
r'113131bb13e50566390ee3e34aa2f08820a8870c';
|
||||
r'48d4f203ba51616e9d1142e0dd482d3ae065a4f4';
|
||||
|
||||
abstract class _$ChapterSetIsBookmarkState
|
||||
extends BuildlessAutoDisposeNotifier<void> {
|
||||
|
|
@ -951,7 +951,7 @@ class _ChapterSetIsBookmarkStateProviderElement
|
|||
}
|
||||
|
||||
String _$chapterSetIsReadStateHash() =>
|
||||
r'c319f81ec30565ad81a28cb0a8ce7fddcb47cd77';
|
||||
r'1e219dd68898fc30b6cb64d294377776516775d4';
|
||||
|
||||
abstract class _$ChapterSetIsReadState
|
||||
extends BuildlessAutoDisposeNotifier<void> {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import 'package:mangayomi/eval/dart/model/m_bridge.dart';
|
|||
import 'package:mangayomi/eval/dart/model/m_manga.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/feed.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';
|
||||
|
|
@ -77,8 +79,20 @@ Future<dynamic> updateMangaDetail(UpdateMangaDetailRef ref,
|
|||
}
|
||||
if (chapters.isNotEmpty) {
|
||||
for (var chap in chapters.reversed.toList()) {
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.addUpdatedChapter(chap, false, false);
|
||||
isar.chapters.putSync(chap);
|
||||
chap.manga.saveSync();
|
||||
if (manga.chapters.isNotEmpty) {
|
||||
final feed = Feed(
|
||||
mangaId: mangaId,
|
||||
chapterName: chap.name,
|
||||
date: DateTime.now().millisecondsSinceEpoch.toString())
|
||||
..chapter.value = chap;
|
||||
isar.feeds.putSync(feed);
|
||||
feed.chapter.saveSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
final oldChapers =
|
||||
|
|
@ -93,6 +107,9 @@ Future<dynamic> updateMangaDetail(UpdateMangaDetailRef ref,
|
|||
newChap.name == oldChap.name) {
|
||||
oldChap.url = newChap.url;
|
||||
oldChap.scanlator = newChap.scanlator;
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.addUpdatedChapter(oldChap, false, false);
|
||||
isar.chapters.putSync(oldChap);
|
||||
oldChap.manga.saveSync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'update_manga_detail_providers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$updateMangaDetailHash() => r'7733551fd578f5d093c4ca11427015a3f3af415a';
|
||||
String _$updateMangaDetailHash() => r'c21ac4f7725b5ac4403902bac07a3b5462488bbd';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,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';
|
||||
|
|
@ -177,6 +178,7 @@ class ReaderController extends _$ReaderController {
|
|||
.filter()
|
||||
.mangaIdEqualTo(getManga().id)
|
||||
.findFirstSync())!
|
||||
..chapterId = chapter.id
|
||||
..chapter.value = chapter
|
||||
..date = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
}
|
||||
|
|
@ -192,6 +194,9 @@ class ReaderController extends _$ReaderController {
|
|||
final chap = chapter;
|
||||
isar.writeTxnSync(() {
|
||||
chap.isBookmarked = !isBookmarked;
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.addUpdatedChapter(chap, false, false);
|
||||
isar.chapters.putSync(chap);
|
||||
});
|
||||
}
|
||||
|
|
@ -329,10 +334,14 @@ class ReaderController extends _$ReaderController {
|
|||
getIsarSetting()..chapterPageIndexList = chapterPageIndexs);
|
||||
chap.isRead = isRead;
|
||||
chap.lastPageRead = isRead ? '1' : (newIndex + 1).toString();
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.addUpdatedChapter(chap, false, false);
|
||||
isar.chapters.putSync(chap);
|
||||
});
|
||||
if (isRead) {
|
||||
chapter.updateTrackChapterRead(ref);
|
||||
chapter.syncProgressAfterChapterRead(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -396,6 +405,14 @@ extension ChapterExtensions on Chapter {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void syncProgressAfterChapterRead(dynamic ref) {
|
||||
if (!(ref is WidgetRef || ref is AutoDisposeNotifierProviderRef)) return;
|
||||
final syncAfterReading = ref.watch(syncAfterReadingStateProvider);
|
||||
if (!syncAfterReading) return;
|
||||
checkForSyncIndependentProvider.call(true);
|
||||
// ref.read(syncServerProvider(syncId: 1).notifier).checkForSync(ref, true);
|
||||
}
|
||||
}
|
||||
|
||||
extension MangaExtensions on Manga {
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ class _CurrentIndexProviderElement
|
|||
Chapter get chapter => (origin as CurrentIndexProvider).chapter;
|
||||
}
|
||||
|
||||
String _$readerControllerHash() => r'b334d7b508df43c66ce57f0d2bf7c59ea4bf3ff7';
|
||||
String _$readerControllerHash() => r'f615f15a622ac5ccdb16318dcdefc097a175fd33';
|
||||
|
||||
abstract class _$ReaderController extends BuildlessAutoDisposeNotifier<void> {
|
||||
late final Chapter chapter;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:mangayomi/main.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';
|
||||
|
|
@ -155,8 +156,11 @@ class _ChapterListTileState extends State<ChapterListTile> {
|
|||
setState(() {
|
||||
isBookmarked = !isBookmarked;
|
||||
});
|
||||
isar.writeTxnSync(() =>
|
||||
isar.chapters.putSync(chapter..isBookmarked = isBookmarked));
|
||||
isar.writeTxnSync(() => {
|
||||
addUpdatedChapterIndependentProvider.call(
|
||||
chapter, false, false),
|
||||
isar.chapters.putSync(chapter..isBookmarked = isBookmarked),
|
||||
});
|
||||
},
|
||||
icon: Icon(isBookmarked ? Icons.bookmark : Icons.bookmark_outline,
|
||||
color: context.primaryColor),
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ import 'dart:convert';
|
|||
import 'package:archive/archive_io.dart';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
|
||||
import 'package:mangayomi/eval/dart/model/source_preference.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/category.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/download.dart';
|
||||
import 'package:mangayomi/models/feed.dart';
|
||||
import 'package:mangayomi/models/history.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
|
|
@ -59,6 +61,8 @@ void doRestore(DoRestoreRef ref,
|
|||
final extensionsPref = (backup["extensions_preferences"] as List?)
|
||||
?.map((e) => SourcePreference.fromJson(e))
|
||||
.toList();
|
||||
final feeds =
|
||||
(backup["feeds"] as List?)?.map((e) => Feed.fromJson(e)).toList();
|
||||
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.clearSync();
|
||||
|
|
@ -95,6 +99,23 @@ void doRestore(DoRestoreRef ref,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
isar.feeds.clearSync();
|
||||
if (feeds != null) {
|
||||
final tempChapters =
|
||||
isar.chapters.filter().idIsNotNull().findAllSync().toList();
|
||||
for (var feed in feeds) {
|
||||
final matchingChapter = tempChapters
|
||||
.where((chapter) =>
|
||||
chapter.mangaId == feed.mangaId &&
|
||||
chapter.name == feed.chapterName)
|
||||
.firstOrNull;
|
||||
if (matchingChapter != null) {
|
||||
isar.feeds.putSync(feed..chapter.value = matchingChapter);
|
||||
feed.chapter.saveSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isar.categorys.clearSync();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'restore.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$doRestoreHash() => r'3c88ad8ba80c245a4b511961111f7ab79c0d330f';
|
||||
String _$doRestoreHash() => r'823b26bade20d89ae7b7b56a7eb7c25020795b45';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:mangayomi/main.dart';
|
|||
import 'package:mangayomi/models/category.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/sync/providers/sync_providers.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
|
||||
|
|
@ -177,13 +178,22 @@ class _CategoriesTabState extends ConsumerState<CategoriesTab> {
|
|||
onPressed: () async {
|
||||
await isar.writeTxn(
|
||||
() async {
|
||||
await ref
|
||||
.read(changedItemsManagerProvider(
|
||||
managerId:
|
||||
1)
|
||||
.notifier)
|
||||
.addDeletedCategoryAsync(
|
||||
_entries[
|
||||
index], false);
|
||||
await isar
|
||||
.categorys
|
||||
.delete(_entries[
|
||||
index]
|
||||
.id!);
|
||||
});
|
||||
if (context.mounted) {
|
||||
if (context
|
||||
.mounted) {
|
||||
Navigator.pop(
|
||||
context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,11 @@ class SettingsScreen extends StatelessWidget {
|
|||
subtitle: "",
|
||||
icon: Icons.sync_outlined,
|
||||
onTap: () => context.push('/track')),
|
||||
ListTileWidget(
|
||||
title: l10n.syncing,
|
||||
subtitle: l10n.syncing_subtitle,
|
||||
icon: Icons.cloud_sync_outlined,
|
||||
onTap: () => context.push('/sync')),
|
||||
ListTileWidget(
|
||||
title: l10n.browse,
|
||||
subtitle: l10n.browse_subtitle,
|
||||
|
|
|
|||
24
lib/modules/more/settings/sync/models/jwt.dart
Normal file
24
lib/modules/more/settings/sync/models/jwt.dart
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
class JWToken {
|
||||
String? uuid;
|
||||
String? email;
|
||||
int? iat;
|
||||
int? exp;
|
||||
|
||||
JWToken({this.uuid, this.email, this.iat, this.exp});
|
||||
|
||||
JWToken.fromJson(Map<String, dynamic> json) {
|
||||
email = json['email'];
|
||||
uuid = json['uuid'];
|
||||
iat = (json['iat'] as int) * 1000;
|
||||
exp = (json['exp'] as int) * 1000;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['uuid'] = uuid;
|
||||
data['email'] = email;
|
||||
data['iat'] = iat;
|
||||
data['exp'] = exp;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
283
lib/modules/more/settings/sync/providers/sync_providers.dart
Normal file
283
lib/modules/more/settings/sync/providers/sync_providers.dart
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/category.dart';
|
||||
import 'package:mangayomi/models/changed_items.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/models/sync_preference.dart';
|
||||
import 'package:mangayomi/services/sync_server.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'sync_providers.g.dart';
|
||||
|
||||
@riverpod
|
||||
void addUpdatedChapterIndependent(AddUpdatedChapterIndependentRef ref,
|
||||
Chapter chapter, bool deleted, bool txn) {
|
||||
final changedItems = isar.changedItems.getSync(1) ?? ChangedItems();
|
||||
bool updated = false;
|
||||
changedItems.updatedChapters = changedItems.updatedChapters?.map((e) {
|
||||
if (e.chapterId == chapter.id) {
|
||||
e.isBookmarked = chapter.isBookmarked;
|
||||
e.isRead = chapter.isRead;
|
||||
e.lastPageRead = chapter.lastPageRead;
|
||||
e.deleted = deleted;
|
||||
updated = true;
|
||||
}
|
||||
return e;
|
||||
}).toList();
|
||||
if (!updated) {
|
||||
final updatedChapter = UpdatedChapter(
|
||||
chapterId: chapter.id,
|
||||
isBookmarked: chapter.isBookmarked,
|
||||
isRead: chapter.isRead,
|
||||
lastPageRead: chapter.lastPageRead,
|
||||
deleted: deleted);
|
||||
changedItems.updatedChapters = changedItems.updatedChapters?.toList()
|
||||
?..add(updatedChapter);
|
||||
}
|
||||
if (!txn) {
|
||||
isar.changedItems.putSync(changedItems);
|
||||
} else {
|
||||
isar.writeTxnSync(() {
|
||||
isar.changedItems.putSync(changedItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
void checkForSyncIndependent(CheckForSyncIndependentRef ref, bool silent) {
|
||||
ref.read(SyncServerProvider(syncId: 1).notifier).checkForSync(silent);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class ChangedItemsManager extends _$ChangedItemsManager {
|
||||
@override
|
||||
ChangedItems? build({required int? managerId}) {
|
||||
return isar.changedItems.getSync(managerId!);
|
||||
}
|
||||
|
||||
void cleanChangedItems(bool txn) {
|
||||
final changedItems =
|
||||
isar.changedItems.getSync(managerId!) ?? ChangedItems(id: managerId);
|
||||
changedItems.deletedMangas = [];
|
||||
changedItems.updatedChapters = [];
|
||||
changedItems.deletedCategories = [];
|
||||
if (!txn) {
|
||||
isar.changedItems.putSync(changedItems);
|
||||
} else {
|
||||
isar.writeTxnSync(() {
|
||||
isar.changedItems.putSync(changedItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void addDeletedManga(Manga manga, bool txn) {
|
||||
final changedItems =
|
||||
isar.changedItems.getSync(managerId!) ?? ChangedItems(id: managerId);
|
||||
log("DEBUG");
|
||||
log(jsonEncode(changedItems));
|
||||
final deletedManga = DeletedManga(mangaId: manga.id);
|
||||
changedItems.deletedMangas = changedItems.deletedMangas?.toList()
|
||||
?..add(deletedManga);
|
||||
if (!txn) {
|
||||
isar.changedItems.putSync(changedItems);
|
||||
} else {
|
||||
isar.writeTxnSync(() {
|
||||
isar.changedItems.putSync(changedItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future addDeletedMangaAsync(Manga manga, bool txn) async {
|
||||
final changedItems =
|
||||
await isar.changedItems.get(managerId!) ?? ChangedItems(id: managerId);
|
||||
final deletedManga = DeletedManga(mangaId: manga.id);
|
||||
changedItems.deletedMangas = changedItems.deletedMangas?.toList()
|
||||
?..add(deletedManga);
|
||||
if (!txn) {
|
||||
await isar.changedItems.put(changedItems);
|
||||
} else {
|
||||
await isar.writeTxn(() async {
|
||||
await isar.changedItems.put(changedItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void addUpdatedChapter(Chapter chapter, bool deleted, bool txn) {
|
||||
final changedItems =
|
||||
isar.changedItems.getSync(managerId!) ?? ChangedItems(id: managerId);
|
||||
bool updated = false;
|
||||
changedItems.updatedChapters = changedItems.updatedChapters?.map((e) {
|
||||
if (e.chapterId == chapter.id && e.mangaId == chapter.mangaId) {
|
||||
e.isBookmarked = chapter.isBookmarked;
|
||||
e.isRead = chapter.isRead;
|
||||
e.lastPageRead = chapter.lastPageRead;
|
||||
e.deleted = deleted;
|
||||
updated = true;
|
||||
}
|
||||
return e;
|
||||
}).toList();
|
||||
if (!updated) {
|
||||
final updatedChapter = UpdatedChapter(
|
||||
chapterId: chapter.id,
|
||||
mangaId: chapter.mangaId,
|
||||
isBookmarked: chapter.isBookmarked,
|
||||
isRead: chapter.isRead,
|
||||
lastPageRead: chapter.lastPageRead,
|
||||
deleted: deleted);
|
||||
changedItems.updatedChapters = changedItems.updatedChapters?.toList()
|
||||
?..add(updatedChapter);
|
||||
}
|
||||
if (!txn) {
|
||||
isar.changedItems.putSync(changedItems);
|
||||
} else {
|
||||
isar.writeTxnSync(() {
|
||||
isar.changedItems.putSync(changedItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future addUpdatedChapterAsync(Chapter chapter, bool deleted, bool txn) async {
|
||||
final changedItems =
|
||||
await isar.changedItems.get(managerId!) ?? ChangedItems(id: managerId);
|
||||
bool updated = false;
|
||||
changedItems.updatedChapters = changedItems.updatedChapters?.map((e) {
|
||||
if (e.chapterId == chapter.id && e.mangaId == chapter.mangaId) {
|
||||
e.isBookmarked = chapter.isBookmarked;
|
||||
e.isRead = chapter.isRead;
|
||||
e.lastPageRead = chapter.lastPageRead;
|
||||
e.deleted = deleted;
|
||||
updated = true;
|
||||
}
|
||||
return e;
|
||||
}).toList();
|
||||
if (!updated) {
|
||||
final updatedChapter = UpdatedChapter(
|
||||
chapterId: chapter.id,
|
||||
mangaId: chapter.mangaId,
|
||||
isBookmarked: chapter.isBookmarked,
|
||||
isRead: chapter.isRead,
|
||||
lastPageRead: chapter.lastPageRead,
|
||||
deleted: deleted);
|
||||
changedItems.updatedChapters = changedItems.updatedChapters?.toList()
|
||||
?..add(updatedChapter);
|
||||
}
|
||||
if (!txn) {
|
||||
await isar.changedItems.put(changedItems);
|
||||
} else {
|
||||
await isar.writeTxn(() async {
|
||||
await isar.changedItems.put(changedItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void addDeletedCategory(Category category, bool txn) {
|
||||
final changedItems =
|
||||
isar.changedItems.getSync(managerId!) ?? ChangedItems(id: managerId);
|
||||
final deletedCategory = DeletedCategory(categoryId: category.id);
|
||||
changedItems.deletedCategories = changedItems.deletedCategories?.toList()
|
||||
?..add(deletedCategory);
|
||||
if (!txn) {
|
||||
isar.changedItems.putSync(changedItems);
|
||||
} else {
|
||||
isar.writeTxnSync(() {
|
||||
isar.changedItems.putSync(changedItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future addDeletedCategoryAsync(Category category, bool txn) async {
|
||||
final changedItems =
|
||||
await isar.changedItems.get(managerId!) ?? ChangedItems(id: managerId);
|
||||
final deletedCategory = DeletedCategory(categoryId: category.id);
|
||||
changedItems.deletedCategories = changedItems.deletedCategories?.toList()
|
||||
?..add(deletedCategory);
|
||||
if (!txn) {
|
||||
await isar.changedItems.put(changedItems);
|
||||
} else {
|
||||
await isar.writeTxn(() async {
|
||||
await isar.changedItems.put(changedItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class Synching extends _$Synching {
|
||||
@override
|
||||
SyncPreference? build({required int? syncId}) {
|
||||
return isar.syncPreferences.getSync(syncId!);
|
||||
}
|
||||
|
||||
void login(SyncPreference syncPreference) {
|
||||
isar.writeTxnSync(() {
|
||||
isar.syncPreferences.putSync(syncPreference);
|
||||
});
|
||||
}
|
||||
|
||||
void logout() {
|
||||
isar.writeTxnSync(() {
|
||||
isar.syncPreferences.deleteSync(syncId!);
|
||||
});
|
||||
}
|
||||
|
||||
void setLastSync(int timestamp) {
|
||||
isar.writeTxnSync(() {
|
||||
isar.syncPreferences.putSync(
|
||||
isar.syncPreferences.getSync(syncId!)!..lastSync = timestamp);
|
||||
});
|
||||
}
|
||||
|
||||
void setLastUpload(int timestamp) {
|
||||
isar.writeTxnSync(() {
|
||||
isar.syncPreferences.putSync(
|
||||
isar.syncPreferences.getSync(syncId!)!..lastUpload = timestamp);
|
||||
});
|
||||
}
|
||||
|
||||
void setLastDownload(int timestamp) {
|
||||
isar.writeTxnSync(() {
|
||||
isar.syncPreferences.putSync(
|
||||
isar.syncPreferences.getSync(syncId!)!..lastDownload = timestamp);
|
||||
});
|
||||
}
|
||||
|
||||
void setServer(String? server) {
|
||||
isar.writeTxnSync(() {
|
||||
isar.syncPreferences
|
||||
.putSync(isar.syncPreferences.getSync(syncId!)!..server = server);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class SyncOnAppLaunchState extends _$SyncOnAppLaunchState {
|
||||
@override
|
||||
bool build() {
|
||||
return isar.settings.getSync(227)!.syncOnAppLaunch ?? false;
|
||||
}
|
||||
|
||||
void set(bool value) {
|
||||
final settings = isar.settings.getSync(227);
|
||||
state = value;
|
||||
isar.writeTxnSync(
|
||||
() => isar.settings.putSync(settings!..syncOnAppLaunch = value));
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class SyncAfterReadingState extends _$SyncAfterReadingState {
|
||||
@override
|
||||
bool build() {
|
||||
return isar.settings.getSync(227)!.syncAfterReading ?? false;
|
||||
}
|
||||
|
||||
void set(bool value) {
|
||||
final settings = isar.settings.getSync(227);
|
||||
state = value;
|
||||
isar.writeTxnSync(
|
||||
() => isar.settings.putSync(settings!..syncAfterReading = value));
|
||||
}
|
||||
}
|
||||
647
lib/modules/more/settings/sync/providers/sync_providers.g.dart
Normal file
647
lib/modules/more/settings/sync/providers/sync_providers.g.dart
Normal file
|
|
@ -0,0 +1,647 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'sync_providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$addUpdatedChapterIndependentHash() =>
|
||||
r'2a609f968ab03f617df4957fdd1ace6f013a3d2a';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [addUpdatedChapterIndependent].
|
||||
@ProviderFor(addUpdatedChapterIndependent)
|
||||
const addUpdatedChapterIndependentProvider =
|
||||
AddUpdatedChapterIndependentFamily();
|
||||
|
||||
/// See also [addUpdatedChapterIndependent].
|
||||
class AddUpdatedChapterIndependentFamily extends Family<void> {
|
||||
/// See also [addUpdatedChapterIndependent].
|
||||
const AddUpdatedChapterIndependentFamily();
|
||||
|
||||
/// See also [addUpdatedChapterIndependent].
|
||||
AddUpdatedChapterIndependentProvider call(
|
||||
Chapter chapter,
|
||||
bool deleted,
|
||||
bool txn,
|
||||
) {
|
||||
return AddUpdatedChapterIndependentProvider(
|
||||
chapter,
|
||||
deleted,
|
||||
txn,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AddUpdatedChapterIndependentProvider getProviderOverride(
|
||||
covariant AddUpdatedChapterIndependentProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.chapter,
|
||||
provider.deleted,
|
||||
provider.txn,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'addUpdatedChapterIndependentProvider';
|
||||
}
|
||||
|
||||
/// See also [addUpdatedChapterIndependent].
|
||||
class AddUpdatedChapterIndependentProvider extends AutoDisposeProvider<void> {
|
||||
/// See also [addUpdatedChapterIndependent].
|
||||
AddUpdatedChapterIndependentProvider(
|
||||
Chapter chapter,
|
||||
bool deleted,
|
||||
bool txn,
|
||||
) : this._internal(
|
||||
(ref) => addUpdatedChapterIndependent(
|
||||
ref as AddUpdatedChapterIndependentRef,
|
||||
chapter,
|
||||
deleted,
|
||||
txn,
|
||||
),
|
||||
from: addUpdatedChapterIndependentProvider,
|
||||
name: r'addUpdatedChapterIndependentProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$addUpdatedChapterIndependentHash,
|
||||
dependencies: AddUpdatedChapterIndependentFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
AddUpdatedChapterIndependentFamily._allTransitiveDependencies,
|
||||
chapter: chapter,
|
||||
deleted: deleted,
|
||||
txn: txn,
|
||||
);
|
||||
|
||||
AddUpdatedChapterIndependentProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.chapter,
|
||||
required this.deleted,
|
||||
required this.txn,
|
||||
}) : super.internal();
|
||||
|
||||
final Chapter chapter;
|
||||
final bool deleted;
|
||||
final bool txn;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
void Function(AddUpdatedChapterIndependentRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: AddUpdatedChapterIndependentProvider._internal(
|
||||
(ref) => create(ref as AddUpdatedChapterIndependentRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
chapter: chapter,
|
||||
deleted: deleted,
|
||||
txn: txn,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<void> createElement() {
|
||||
return _AddUpdatedChapterIndependentProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AddUpdatedChapterIndependentProvider &&
|
||||
other.chapter == chapter &&
|
||||
other.deleted == deleted &&
|
||||
other.txn == txn;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, chapter.hashCode);
|
||||
hash = _SystemHash.combine(hash, deleted.hashCode);
|
||||
hash = _SystemHash.combine(hash, txn.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin AddUpdatedChapterIndependentRef on AutoDisposeProviderRef<void> {
|
||||
/// The parameter `chapter` of this provider.
|
||||
Chapter get chapter;
|
||||
|
||||
/// The parameter `deleted` of this provider.
|
||||
bool get deleted;
|
||||
|
||||
/// The parameter `txn` of this provider.
|
||||
bool get txn;
|
||||
}
|
||||
|
||||
class _AddUpdatedChapterIndependentProviderElement
|
||||
extends AutoDisposeProviderElement<void>
|
||||
with AddUpdatedChapterIndependentRef {
|
||||
_AddUpdatedChapterIndependentProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Chapter get chapter =>
|
||||
(origin as AddUpdatedChapterIndependentProvider).chapter;
|
||||
@override
|
||||
bool get deleted => (origin as AddUpdatedChapterIndependentProvider).deleted;
|
||||
@override
|
||||
bool get txn => (origin as AddUpdatedChapterIndependentProvider).txn;
|
||||
}
|
||||
|
||||
String _$checkForSyncIndependentHash() =>
|
||||
r'7f3820bbb551ad5a98145c0f05106bb104f2db26';
|
||||
|
||||
/// See also [checkForSyncIndependent].
|
||||
@ProviderFor(checkForSyncIndependent)
|
||||
const checkForSyncIndependentProvider = CheckForSyncIndependentFamily();
|
||||
|
||||
/// See also [checkForSyncIndependent].
|
||||
class CheckForSyncIndependentFamily extends Family<void> {
|
||||
/// See also [checkForSyncIndependent].
|
||||
const CheckForSyncIndependentFamily();
|
||||
|
||||
/// See also [checkForSyncIndependent].
|
||||
CheckForSyncIndependentProvider call(
|
||||
bool silent,
|
||||
) {
|
||||
return CheckForSyncIndependentProvider(
|
||||
silent,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
CheckForSyncIndependentProvider getProviderOverride(
|
||||
covariant CheckForSyncIndependentProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.silent,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'checkForSyncIndependentProvider';
|
||||
}
|
||||
|
||||
/// See also [checkForSyncIndependent].
|
||||
class CheckForSyncIndependentProvider extends AutoDisposeProvider<void> {
|
||||
/// See also [checkForSyncIndependent].
|
||||
CheckForSyncIndependentProvider(
|
||||
bool silent,
|
||||
) : this._internal(
|
||||
(ref) => checkForSyncIndependent(
|
||||
ref as CheckForSyncIndependentRef,
|
||||
silent,
|
||||
),
|
||||
from: checkForSyncIndependentProvider,
|
||||
name: r'checkForSyncIndependentProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$checkForSyncIndependentHash,
|
||||
dependencies: CheckForSyncIndependentFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
CheckForSyncIndependentFamily._allTransitiveDependencies,
|
||||
silent: silent,
|
||||
);
|
||||
|
||||
CheckForSyncIndependentProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.silent,
|
||||
}) : super.internal();
|
||||
|
||||
final bool silent;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
void Function(CheckForSyncIndependentRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: CheckForSyncIndependentProvider._internal(
|
||||
(ref) => create(ref as CheckForSyncIndependentRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
silent: silent,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<void> createElement() {
|
||||
return _CheckForSyncIndependentProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is CheckForSyncIndependentProvider && other.silent == silent;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, silent.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin CheckForSyncIndependentRef on AutoDisposeProviderRef<void> {
|
||||
/// The parameter `silent` of this provider.
|
||||
bool get silent;
|
||||
}
|
||||
|
||||
class _CheckForSyncIndependentProviderElement
|
||||
extends AutoDisposeProviderElement<void> with CheckForSyncIndependentRef {
|
||||
_CheckForSyncIndependentProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
bool get silent => (origin as CheckForSyncIndependentProvider).silent;
|
||||
}
|
||||
|
||||
String _$changedItemsManagerHash() =>
|
||||
r'a4f0363ab430ddb6c2a23fde6f5671ba8ec252cf';
|
||||
|
||||
abstract class _$ChangedItemsManager
|
||||
extends BuildlessAutoDisposeNotifier<ChangedItems?> {
|
||||
late final int? managerId;
|
||||
|
||||
ChangedItems? build({
|
||||
required int? managerId,
|
||||
});
|
||||
}
|
||||
|
||||
/// See also [ChangedItemsManager].
|
||||
@ProviderFor(ChangedItemsManager)
|
||||
const changedItemsManagerProvider = ChangedItemsManagerFamily();
|
||||
|
||||
/// See also [ChangedItemsManager].
|
||||
class ChangedItemsManagerFamily extends Family<ChangedItems?> {
|
||||
/// See also [ChangedItemsManager].
|
||||
const ChangedItemsManagerFamily();
|
||||
|
||||
/// See also [ChangedItemsManager].
|
||||
ChangedItemsManagerProvider call({
|
||||
required int? managerId,
|
||||
}) {
|
||||
return ChangedItemsManagerProvider(
|
||||
managerId: managerId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ChangedItemsManagerProvider getProviderOverride(
|
||||
covariant ChangedItemsManagerProvider provider,
|
||||
) {
|
||||
return call(
|
||||
managerId: provider.managerId,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'changedItemsManagerProvider';
|
||||
}
|
||||
|
||||
/// See also [ChangedItemsManager].
|
||||
class ChangedItemsManagerProvider extends AutoDisposeNotifierProviderImpl<
|
||||
ChangedItemsManager, ChangedItems?> {
|
||||
/// See also [ChangedItemsManager].
|
||||
ChangedItemsManagerProvider({
|
||||
required int? managerId,
|
||||
}) : this._internal(
|
||||
() => ChangedItemsManager()..managerId = managerId,
|
||||
from: changedItemsManagerProvider,
|
||||
name: r'changedItemsManagerProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$changedItemsManagerHash,
|
||||
dependencies: ChangedItemsManagerFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ChangedItemsManagerFamily._allTransitiveDependencies,
|
||||
managerId: managerId,
|
||||
);
|
||||
|
||||
ChangedItemsManagerProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.managerId,
|
||||
}) : super.internal();
|
||||
|
||||
final int? managerId;
|
||||
|
||||
@override
|
||||
ChangedItems? runNotifierBuild(
|
||||
covariant ChangedItemsManager notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
managerId: managerId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(ChangedItemsManager Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: ChangedItemsManagerProvider._internal(
|
||||
() => create()..managerId = managerId,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
managerId: managerId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeNotifierProviderElement<ChangedItemsManager, ChangedItems?>
|
||||
createElement() {
|
||||
return _ChangedItemsManagerProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ChangedItemsManagerProvider && other.managerId == managerId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, managerId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin ChangedItemsManagerRef on AutoDisposeNotifierProviderRef<ChangedItems?> {
|
||||
/// The parameter `managerId` of this provider.
|
||||
int? get managerId;
|
||||
}
|
||||
|
||||
class _ChangedItemsManagerProviderElement
|
||||
extends AutoDisposeNotifierProviderElement<ChangedItemsManager,
|
||||
ChangedItems?> with ChangedItemsManagerRef {
|
||||
_ChangedItemsManagerProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
int? get managerId => (origin as ChangedItemsManagerProvider).managerId;
|
||||
}
|
||||
|
||||
String _$synchingHash() => r'2ef7fd99da4292ed236252d2b727cff9a69f43a9';
|
||||
|
||||
abstract class _$Synching
|
||||
extends BuildlessAutoDisposeNotifier<SyncPreference?> {
|
||||
late final int? syncId;
|
||||
|
||||
SyncPreference? build({
|
||||
required int? syncId,
|
||||
});
|
||||
}
|
||||
|
||||
/// See also [Synching].
|
||||
@ProviderFor(Synching)
|
||||
const synchingProvider = SynchingFamily();
|
||||
|
||||
/// See also [Synching].
|
||||
class SynchingFamily extends Family<SyncPreference?> {
|
||||
/// See also [Synching].
|
||||
const SynchingFamily();
|
||||
|
||||
/// See also [Synching].
|
||||
SynchingProvider call({
|
||||
required int? syncId,
|
||||
}) {
|
||||
return SynchingProvider(
|
||||
syncId: syncId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SynchingProvider getProviderOverride(
|
||||
covariant SynchingProvider provider,
|
||||
) {
|
||||
return call(
|
||||
syncId: provider.syncId,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'synchingProvider';
|
||||
}
|
||||
|
||||
/// See also [Synching].
|
||||
class SynchingProvider
|
||||
extends AutoDisposeNotifierProviderImpl<Synching, SyncPreference?> {
|
||||
/// See also [Synching].
|
||||
SynchingProvider({
|
||||
required int? syncId,
|
||||
}) : this._internal(
|
||||
() => Synching()..syncId = syncId,
|
||||
from: synchingProvider,
|
||||
name: r'synchingProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$synchingHash,
|
||||
dependencies: SynchingFamily._dependencies,
|
||||
allTransitiveDependencies: SynchingFamily._allTransitiveDependencies,
|
||||
syncId: syncId,
|
||||
);
|
||||
|
||||
SynchingProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.syncId,
|
||||
}) : super.internal();
|
||||
|
||||
final int? syncId;
|
||||
|
||||
@override
|
||||
SyncPreference? runNotifierBuild(
|
||||
covariant Synching notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
syncId: syncId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(Synching Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: SynchingProvider._internal(
|
||||
() => create()..syncId = syncId,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
syncId: syncId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeNotifierProviderElement<Synching, SyncPreference?>
|
||||
createElement() {
|
||||
return _SynchingProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SynchingProvider && other.syncId == syncId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, syncId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin SynchingRef on AutoDisposeNotifierProviderRef<SyncPreference?> {
|
||||
/// The parameter `syncId` of this provider.
|
||||
int? get syncId;
|
||||
}
|
||||
|
||||
class _SynchingProviderElement
|
||||
extends AutoDisposeNotifierProviderElement<Synching, SyncPreference?>
|
||||
with SynchingRef {
|
||||
_SynchingProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
int? get syncId => (origin as SynchingProvider).syncId;
|
||||
}
|
||||
|
||||
String _$syncOnAppLaunchStateHash() =>
|
||||
r'dc7f3243e38a748462628229066c8fc0653c908b';
|
||||
|
||||
/// See also [SyncOnAppLaunchState].
|
||||
@ProviderFor(SyncOnAppLaunchState)
|
||||
final syncOnAppLaunchStateProvider =
|
||||
AutoDisposeNotifierProvider<SyncOnAppLaunchState, bool>.internal(
|
||||
SyncOnAppLaunchState.new,
|
||||
name: r'syncOnAppLaunchStateProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$syncOnAppLaunchStateHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SyncOnAppLaunchState = AutoDisposeNotifier<bool>;
|
||||
String _$syncAfterReadingStateHash() =>
|
||||
r'e507acd490b5aea7fc1a8fd7a369ec01f4c47192';
|
||||
|
||||
/// See also [SyncAfterReadingState].
|
||||
@ProviderFor(SyncAfterReadingState)
|
||||
final syncAfterReadingStateProvider =
|
||||
AutoDisposeNotifierProvider<SyncAfterReadingState, bool>.internal(
|
||||
SyncAfterReadingState.new,
|
||||
name: r'syncAfterReadingStateProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$syncAfterReadingStateHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SyncAfterReadingState = AutoDisposeNotifier<bool>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
470
lib/modules/more/settings/sync/sync.dart
Normal file
470
lib/modules/more/settings/sync/sync.dart
Normal file
|
|
@ -0,0 +1,470 @@
|
|||
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/utils/date.dart';
|
||||
import 'package:mangayomi/models/sync_preference.dart';
|
||||
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
|
||||
import 'package:mangayomi/modules/more/settings/sync/widgets/sync_listile.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/services/sync_server.dart';
|
||||
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||
|
||||
class SyncScreen extends ConsumerWidget {
|
||||
const SyncScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final syncAfterReading = ref.watch(syncAfterReadingStateProvider);
|
||||
final syncOnAppLaunch = ref.watch(syncOnAppLaunchStateProvider);
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(l10nLocalizations(context)!.syncing),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: StreamBuilder(
|
||||
stream: isar.syncPreferences
|
||||
.filter()
|
||||
.syncIdIsNotNull()
|
||||
.watch(fireImmediately: true),
|
||||
builder: (context, snapshot) {
|
||||
SyncPreference syncPreference = snapshot.data?.isNotEmpty ?? false
|
||||
? snapshot.data?.first ?? SyncPreference()
|
||||
: SyncPreference();
|
||||
final bool isLogged =
|
||||
syncPreference.authToken?.isNotEmpty ?? false;
|
||||
return Column(
|
||||
children: [
|
||||
SwitchListTile(
|
||||
value: syncAfterReading,
|
||||
title: Text(context.l10n.syncAfterReading),
|
||||
onChanged: !isLogged
|
||||
? null
|
||||
: (value) {
|
||||
ref
|
||||
.read(syncAfterReadingStateProvider.notifier)
|
||||
.set(value);
|
||||
}),
|
||||
SwitchListTile(
|
||||
value: syncOnAppLaunch,
|
||||
title: Text(context.l10n.syncOnAppLaunch),
|
||||
onChanged: !isLogged
|
||||
? null
|
||||
: (value) {
|
||||
ref
|
||||
.read(syncOnAppLaunchStateProvider.notifier)
|
||||
.set(value);
|
||||
}),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15, right: 15, bottom: 10, top: 5),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(l10n.services,
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: context.primaryColor)),
|
||||
],
|
||||
),
|
||||
),
|
||||
SyncListile(
|
||||
onTap: () async {
|
||||
_showDialogLogin(context, ref);
|
||||
},
|
||||
id: 1,
|
||||
preference: syncPreference,
|
||||
),
|
||||
ListTile(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline_rounded,
|
||||
color: context.secondaryColor,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(l10n.syncing_subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 11, color: context.secondaryColor))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.sync,
|
||||
color: context.secondaryColor,
|
||||
),
|
||||
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(
|
||||
fontSize: 11,
|
||||
color: context.secondaryColor)),
|
||||
const SizedBox(width: 20),
|
||||
Text(
|
||||
"${l10n.last_download}: ${dateFormat((syncPreference.lastDownload ?? 0).toString(), ref: ref, context: context)} ${dateFormatHour((syncPreference.lastDownload ?? 0).toString(), context)}",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: context.secondaryColor)),
|
||||
]),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 20),
|
||||
Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: !isLogged
|
||||
? null
|
||||
: () {
|
||||
ref
|
||||
.read(syncServerProvider(syncId: 1)
|
||||
.notifier)
|
||||
.checkForSync(false);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.sync,
|
||||
color: !isLogged ? context.secondaryColor : context.primaryColor,
|
||||
)),
|
||||
Text(l10n.sync_button_sync),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: !isLogged
|
||||
? null
|
||||
: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
l10n.sync_confirm_upload),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
Colors
|
||||
.transparent,
|
||||
shadowColor: Colors
|
||||
.transparent,
|
||||
surfaceTintColor:
|
||||
Colors
|
||||
.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: context
|
||||
.secondaryColor),
|
||||
borderRadius:
|
||||
BorderRadius
|
||||
.circular(
|
||||
20))),
|
||||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context);
|
||||
},
|
||||
child: Text(
|
||||
l10n.cancel,
|
||||
style: TextStyle(
|
||||
color: context
|
||||
.secondaryColor),
|
||||
)),
|
||||
const SizedBox(width: 15),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton
|
||||
.styleFrom(
|
||||
backgroundColor:
|
||||
Colors.red
|
||||
.withOpacity(
|
||||
0.7)),
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(
|
||||
syncServerProvider(
|
||||
syncId:
|
||||
1)
|
||||
.notifier)
|
||||
.uploadToServer(
|
||||
l10n);
|
||||
Navigator.pop(
|
||||
context);
|
||||
},
|
||||
child: Text(
|
||||
l10n.dialog_confirm,
|
||||
style: TextStyle(
|
||||
color: context
|
||||
.secondaryColor),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.cloud_upload_outlined,
|
||||
color: !isLogged ? context.secondaryColor : context.primaryColor,
|
||||
)),
|
||||
Text(l10n.sync_button_upload),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: !isLogged
|
||||
? null
|
||||
: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
l10n.sync_confirm_download),
|
||||
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
|
||||
.withOpacity(
|
||||
0.7)),
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(
|
||||
syncServerProvider(
|
||||
syncId:
|
||||
1)
|
||||
.notifier)
|
||||
.downloadFromServer(
|
||||
l10n);
|
||||
Navigator.pop(
|
||||
context);
|
||||
},
|
||||
child: Text(
|
||||
l10n.dialog_confirm,
|
||||
style: TextStyle(
|
||||
color: context
|
||||
.secondaryColor),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.cloud_download_outlined,
|
||||
color: !isLogged ? context.secondaryColor : context.primaryColor,
|
||||
)),
|
||||
Text(l10n.sync_button_download),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showDialogLogin(BuildContext context, WidgetRef ref) {
|
||||
final serverController = TextEditingController();
|
||||
final emailController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
String server = "";
|
||||
String email = "";
|
||||
String password = "";
|
||||
String errorMessage = "";
|
||||
bool isLoading = false;
|
||||
bool obscureText = true;
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
l10n.login_into("SyncServer"),
|
||||
style: const TextStyle(fontSize: 30),
|
||||
),
|
||||
content: SizedBox(
|
||||
height: 400,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: TextFormField(
|
||||
controller: serverController,
|
||||
autofocus: true,
|
||||
onChanged: (value) => setState(() {
|
||||
server = value;
|
||||
}),
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.sync_server,
|
||||
filled: false,
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(width: 0.4),
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(),
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderSide: const BorderSide()))),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: TextFormField(
|
||||
controller: emailController,
|
||||
autofocus: true,
|
||||
onChanged: (value) => setState(() {
|
||||
email = value;
|
||||
}),
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.email_adress,
|
||||
filled: false,
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(width: 0.4),
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(),
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderSide: const BorderSide()))),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: TextFormField(
|
||||
controller: passwordController,
|
||||
obscureText: obscureText,
|
||||
onChanged: (value) => setState(() {
|
||||
password = value;
|
||||
}),
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.password,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () => setState(() {
|
||||
obscureText = !obscureText;
|
||||
}),
|
||||
icon: Icon(obscureText
|
||||
? Icons.visibility_outlined
|
||||
: Icons.visibility_off_outlined)),
|
||||
filled: false,
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(width: 0.4),
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(),
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderSide: const BorderSide()))),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(errorMessage, style: const TextStyle(color: Colors.red)),
|
||||
const SizedBox(height: 30),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: SizedBox(
|
||||
width: context.width(1),
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
onPressed: isLoading
|
||||
? null
|
||||
: () async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
final res = await ref
|
||||
.read(
|
||||
syncServerProvider(syncId: 1).notifier)
|
||||
.login(l10n, server, email, password);
|
||||
if (!res.$1) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
errorMessage = res.$2;
|
||||
});
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: isLoading
|
||||
? const CircularProgressIndicator()
|
||||
: Text(l10n.login))),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
107
lib/modules/more/settings/sync/widgets/sync_listile.dart
Normal file
107
lib/modules/more/settings/sync/widgets/sync_listile.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/models/sync_preference.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';
|
||||
|
||||
class SyncListile extends ConsumerWidget {
|
||||
final VoidCallback onTap;
|
||||
final int id;
|
||||
final SyncPreference preference;
|
||||
final String? text;
|
||||
const SyncListile(
|
||||
{super.key,
|
||||
required this.onTap,
|
||||
required this.id,
|
||||
required this.preference,
|
||||
this.text});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final bool isLogged = preference.authToken?.isNotEmpty ?? false;
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: ListTile(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
leading: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: const Color.fromRGBO(18, 25, 35, 1)),
|
||||
width: 60,
|
||||
height: 70,
|
||||
child: const Icon(
|
||||
Icons.dns_outlined,
|
||||
size: 30,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
trailing: (isLogged
|
||||
? const Icon(
|
||||
Icons.check,
|
||||
size: 30,
|
||||
color: Colors.green,
|
||||
)
|
||||
: null),
|
||||
onTap: isLogged
|
||||
? () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(l10n.log_out_from(l10n.sync_server)),
|
||||
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.withOpacity(0.7)),
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(synchingProvider(syncId: id)
|
||||
.notifier)
|
||||
.logout();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
l10n.log_out,
|
||||
style: TextStyle(
|
||||
color: context.secondaryColor),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
: onTap,
|
||||
title: Text(
|
||||
text ?? l10n.sync_server,
|
||||
style: TextStyle(fontSize: text != null ? 13 : null),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,15 @@ import 'package:isar/isar.dart';
|
|||
import 'package:mangayomi/eval/dart/model/source_preference.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/category.dart';
|
||||
import 'package:mangayomi/models/changed_items.dart';
|
||||
import 'package:mangayomi/models/chapter.dart';
|
||||
import 'package:mangayomi/models/download.dart';
|
||||
import 'package:mangayomi/models/feed.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/track_preference.dart';
|
||||
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
||||
|
|
@ -126,16 +129,19 @@ class StorageProvider {
|
|||
|
||||
final isar = Isar.openSync([
|
||||
MangaSchema,
|
||||
ChangedItemsSchema,
|
||||
ChapterSchema,
|
||||
CategorySchema,
|
||||
FeedSchema,
|
||||
HistorySchema,
|
||||
DownloadSchema,
|
||||
SourceSchema,
|
||||
SettingsSchema,
|
||||
TrackPreferenceSchema,
|
||||
TrackSchema,
|
||||
SyncPreferenceSchema,
|
||||
SourcePreferenceSchema,
|
||||
SourcePreferenceStringValueSchema
|
||||
SourcePreferenceStringValueSchema,
|
||||
], directory: dir!.path, name: "mangayomiDb", inspector: inspector!);
|
||||
|
||||
if (isar.settings.filter().idEqualTo(227).isEmptySync()) {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ import 'package:mangayomi/modules/browse/extension/edit_code.dart';
|
|||
import 'package:mangayomi/modules/browse/extension/extension_detail.dart';
|
||||
import 'package:mangayomi/modules/browse/extension/widgets/create_extension.dart';
|
||||
import 'package:mangayomi/modules/browse/sources/sources_filter_screen.dart';
|
||||
import 'package:mangayomi/modules/feed/feed_screen.dart';
|
||||
import 'package:mangayomi/modules/more/backup_and_restore/backup_and_restore.dart';
|
||||
import 'package:mangayomi/modules/more/categories/categories_screen.dart';
|
||||
import 'package:mangayomi/modules/more/settings/downloads/downloads_screen.dart';
|
||||
import 'package:mangayomi/modules/more/settings/player/player_screen.dart';
|
||||
import 'package:mangayomi/modules/more/settings/sync/sync.dart';
|
||||
import 'package:mangayomi/modules/more/settings/track/track.dart';
|
||||
import 'package:mangayomi/modules/more/settings/track/manage_trackers/manage_trackers.dart';
|
||||
import 'package:mangayomi/modules/more/settings/track/manage_trackers/tracking_detail.dart';
|
||||
|
|
@ -114,6 +116,15 @@ class RouterNotifier extends ChangeNotifier {
|
|||
child: const HistoryScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
name: "feed",
|
||||
path: '/feed',
|
||||
builder: (context, state) => const FeedScreen(),
|
||||
pageBuilder: (context, state) => transitionPage(
|
||||
key: state.pageKey,
|
||||
child: const FeedScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
name: "browse",
|
||||
path: '/browse',
|
||||
|
|
@ -327,6 +338,19 @@ class RouterNotifier extends ChangeNotifier {
|
|||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: "/sync",
|
||||
name: "sync",
|
||||
builder: (context, state) {
|
||||
return const SyncScreen();
|
||||
},
|
||||
pageBuilder: (context, state) {
|
||||
return transitionPage(
|
||||
key: state.pageKey,
|
||||
child: const SyncScreen(),
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: "/sourceFilter",
|
||||
name: "sourceFilter",
|
||||
|
|
|
|||
532
lib/services/sync_server.dart
Normal file
532
lib/services/sync_server.dart
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
import 'package:crypto/crypto.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/eval/dart/model/m_bridge.dart';
|
||||
import 'package:mangayomi/eval/dart/model/source_preference.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/changed_items.dart';
|
||||
import 'package:mangayomi/models/feed.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/settings.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:mangayomi/modules/more/settings/sync/models/jwt.dart';
|
||||
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
|
||||
import 'package:mangayomi/modules/more/settings/appearance/providers/blend_level_state_provider.dart';
|
||||
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/providers/l10n_providers.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:mangayomi/services/http/m_client.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
part 'sync_server.g.dart';
|
||||
|
||||
@riverpod
|
||||
class SyncServer extends _$SyncServer {
|
||||
final http = MClient.init(reqcopyWith: {'useDartHttpClient': true});
|
||||
final String _loginUrl = '/login';
|
||||
final String _checkUrl = '/check';
|
||||
final String _syncUrl = '/sync';
|
||||
final String _uploadUrl = '/upload/full';
|
||||
final String _downloadUrl = '/download';
|
||||
|
||||
@override
|
||||
void build({required int syncId}) {}
|
||||
|
||||
Future<(bool, String)> login(AppLocalizations l10n, String server,
|
||||
String username, String password) async {
|
||||
try {
|
||||
var response = await http.post(
|
||||
Uri.parse('$server$_loginUrl'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: jsonEncode({'email': username, 'password': password}),
|
||||
);
|
||||
var jsonData = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
if (response.statusCode != 200) {
|
||||
return (false, jsonData["error"] as String);
|
||||
}
|
||||
ref.read(synchingProvider(syncId: syncId).notifier).login(SyncPreference(
|
||||
syncId: syncId,
|
||||
email: username,
|
||||
server: server,
|
||||
authToken: jsonData["token"]));
|
||||
botToast(l10n.sync_logged);
|
||||
return (true, "");
|
||||
} catch (e) {
|
||||
return (false, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> checkForSync(bool silent) async {
|
||||
if (!silent) {
|
||||
botToast("Checking for sync...", second: 2);
|
||||
}
|
||||
try {
|
||||
final datas = _getData();
|
||||
final accessToken = _getAccessToken();
|
||||
final localHash = _getDataHash(datas);
|
||||
|
||||
var response = await http.get(
|
||||
Uri.parse('${_getServer()}$_checkUrl'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
botToast("Check failed", second: 5);
|
||||
return;
|
||||
}
|
||||
var jsonData = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final remoteHash = jsonData["hash"];
|
||||
if (localHash != remoteHash) {
|
||||
syncToServer(silent);
|
||||
} else if (!silent) {
|
||||
botToast("Sync up to date", second: 2);
|
||||
}
|
||||
} catch (error) {
|
||||
botToast(error.toString(), second: 5);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> syncToServer(bool silent) async {
|
||||
if (!silent) {
|
||||
botToast("Sync started...", second: 2);
|
||||
}
|
||||
try {
|
||||
final datas = _getData();
|
||||
final accessToken = _getAccessToken();
|
||||
|
||||
var response = await http.post(
|
||||
Uri.parse('${_getServer()}$_syncUrl'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
body: jsonEncode(
|
||||
{'backupData': datas, 'changedItems': _getChangedData()}),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
botToast("Sync failed", second: 5);
|
||||
return;
|
||||
}
|
||||
var jsonData = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final decodedBackupData = jsonData["backupData"] is String
|
||||
? jsonDecode(jsonData["backupData"])
|
||||
: jsonData["backupData"];
|
||||
_restoreMerge(decodedBackupData);
|
||||
ref
|
||||
.read(synchingProvider(syncId: syncId).notifier)
|
||||
.setLastSync(DateTime.now().millisecondsSinceEpoch);
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.cleanChangedItems(true);
|
||||
if (!silent) {
|
||||
botToast("Sync finished", second: 2);
|
||||
}
|
||||
} catch (error) {
|
||||
botToast(error.toString(), second: 5);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> uploadToServer(AppLocalizations l10n) async {
|
||||
botToast(l10n.sync_uploading, second: 2);
|
||||
try {
|
||||
final datas = _getData();
|
||||
final accessToken = _getAccessToken();
|
||||
|
||||
var response = await http.post(
|
||||
Uri.parse('${_getServer()}$_uploadUrl'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
},
|
||||
body: jsonEncode({'backupData': datas}),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
botToast(l10n.sync_upload_failed, second: 5);
|
||||
return;
|
||||
}
|
||||
ref
|
||||
.read(synchingProvider(syncId: syncId).notifier)
|
||||
.setLastUpload(DateTime.now().millisecondsSinceEpoch);
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.cleanChangedItems(true);
|
||||
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);
|
||||
try {
|
||||
final accessToken = _getAccessToken();
|
||||
|
||||
var response = await http.get(
|
||||
Uri.parse('${_getServer()}$_downloadUrl'),
|
||||
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"]);
|
||||
ref
|
||||
.read(synchingProvider(syncId: syncId).notifier)
|
||||
.setLastDownload(DateTime.now().millisecondsSinceEpoch);
|
||||
ref
|
||||
.read(changedItemsManagerProvider(managerId: 1).notifier)
|
||||
.cleanChangedItems(true);
|
||||
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["feeds"] = data["feeds"];
|
||||
var encodedJson = jsonEncode(datas);
|
||||
return sha256.convert(utf8.encode(encodedJson)).toString();
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getChangedData() {
|
||||
Map<String, dynamic> data = {};
|
||||
final changedItems = isar.changedItems.getSync(1);
|
||||
if (changedItems != null) {
|
||||
data.addAll({
|
||||
"deletedMangas":
|
||||
changedItems.deletedMangas?.map((e) => e.toJson()).toList() ?? []
|
||||
});
|
||||
data.addAll({
|
||||
"updatedChapters":
|
||||
changedItems.updatedChapters?.map((e) => e.toJson()).toList() ?? []
|
||||
});
|
||||
data.addAll({
|
||||
"deletedCategories":
|
||||
changedItems.deletedCategories?.map((e) => e.toJson()).toList() ??
|
||||
[]
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getData() {
|
||||
Map<String, dynamic> datas = {};
|
||||
datas.addAll({"version": "1"});
|
||||
final mangas = isar.mangas
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.favoriteEqualTo(true)
|
||||
.isLocalArchiveEqualTo(false)
|
||||
.findAllSync()
|
||||
.map((e) => e.toJson())
|
||||
.toList();
|
||||
datas.addAll({"manga": mangas});
|
||||
final categorys = isar.categorys
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.findAllSync()
|
||||
.map((e) => e.toJson())
|
||||
.toList();
|
||||
datas.addAll({"categories": categorys});
|
||||
final chapters = isar.chapters
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.findAllSync()
|
||||
.map((e) => e.toJson())
|
||||
.toList();
|
||||
datas.addAll({"chapters": chapters});
|
||||
datas.addAll({"downloads": []});
|
||||
final tracks = isar.tracks
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.findAllSync()
|
||||
.map((e) => e.toJson())
|
||||
.toList();
|
||||
datas.addAll({"tracks": tracks});
|
||||
datas.addAll({"trackPreferences": []});
|
||||
final historys = isar.historys
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.findAllSync()
|
||||
.map((e) => e.toJson())
|
||||
.toList();
|
||||
datas.addAll({"history": historys});
|
||||
final settings = isar.settings
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.findAllSync()
|
||||
.map((e) => e.toJson())
|
||||
.toList();
|
||||
datas.addAll({"settings": settings});
|
||||
final sources = isar.sources
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.findAllSync()
|
||||
.map((e) => e.toJson())
|
||||
.toList();
|
||||
datas.addAll({"extensions": sources});
|
||||
final sourcePreferences = isar.sourcePreferences
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.keyIsNotNull()
|
||||
.findAllSync()
|
||||
.map((e) => e.toJson())
|
||||
.toList();
|
||||
datas.addAll({"extensions_preferences": sourcePreferences});
|
||||
final feeds = isar.feeds
|
||||
.filter()
|
||||
.idIsNotNull()
|
||||
.findAllSync()
|
||||
.map((e) => e.toJson())
|
||||
.toList();
|
||||
datas.addAll({"feeds": feeds});
|
||||
return datas;
|
||||
}
|
||||
|
||||
void _restoreMerge(Map<String, dynamic> backup) {
|
||||
if (backup['version'] == "1") {
|
||||
try {
|
||||
final manga =
|
||||
(backup["manga"] as List?)?.map((e) => Manga.fromJson(e)).toList();
|
||||
final chapters = (backup["chapters"] as List?)
|
||||
?.map((e) => Chapter.fromJson(e))
|
||||
.toList();
|
||||
final categories = (backup["categories"] as List?)
|
||||
?.map((e) => Category.fromJson(e))
|
||||
.toList();
|
||||
final track =
|
||||
(backup["tracks"] as List?)?.map((e) => Track.fromJson(e)).toList();
|
||||
final history = (backup["history"] as List?)
|
||||
?.map((e) => History.fromJson(e))
|
||||
.toList();
|
||||
final feeds =
|
||||
(backup["feeds"] as List?)?.map((e) => Feed.fromJson(e)).toList();
|
||||
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.clearSync();
|
||||
if (manga != null) {
|
||||
isar.mangas.putAllSync(manga);
|
||||
if (chapters != null) {
|
||||
isar.chapters.clearSync();
|
||||
for (var chapter in chapters) {
|
||||
final manga = isar.mangas.getSync(chapter.mangaId!);
|
||||
if (manga != null) {
|
||||
isar.chapters.putSync(chapter..manga.value = manga);
|
||||
chapter.manga.saveSync();
|
||||
}
|
||||
}
|
||||
|
||||
isar.historys.clearSync();
|
||||
if (history != null) {
|
||||
for (var element in history) {
|
||||
final chapter = isar.chapters.getSync(element.chapterId!);
|
||||
if (chapter != null) {
|
||||
isar.historys.putSync(element..chapter.value = chapter);
|
||||
element.chapter.saveSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isar.feeds.clearSync();
|
||||
if (feeds != null) {
|
||||
final tempChapters =
|
||||
isar.chapters.filter().idIsNotNull().findAllSync().toList();
|
||||
for (var feed in feeds) {
|
||||
final matchingChapter = tempChapters
|
||||
.where((chapter) =>
|
||||
chapter.mangaId == feed.mangaId &&
|
||||
chapter.name == feed.chapterName)
|
||||
.firstOrNull;
|
||||
if (matchingChapter != null) {
|
||||
isar.feeds.putSync(feed..chapter.value = matchingChapter);
|
||||
feed.chapter.saveSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isar.categorys.clearSync();
|
||||
if (categories != null) {
|
||||
isar.categorys.putAllSync(categories);
|
||||
}
|
||||
}
|
||||
|
||||
isar.tracks.clearSync();
|
||||
if (track != null) {
|
||||
isar.tracks.putAllSync(track);
|
||||
}
|
||||
|
||||
ref.invalidate(themeModeStateProvider);
|
||||
ref.invalidate(blendLevelStateProvider);
|
||||
ref.invalidate(flexSchemeColorStateProvider);
|
||||
ref.invalidate(pureBlackDarkModeStateProvider);
|
||||
ref.invalidate(l10nLocaleStateProvider);
|
||||
});
|
||||
} catch (e) {
|
||||
botToast(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _restore(Map<String, dynamic> backup) {
|
||||
if (backup['version'] == "1") {
|
||||
try {
|
||||
final manga =
|
||||
(backup["manga"] as List?)?.map((e) => Manga.fromJson(e)).toList();
|
||||
final chapters = (backup["chapters"] as List?)
|
||||
?.map((e) => Chapter.fromJson(e))
|
||||
.toList();
|
||||
final categories = (backup["categories"] as List?)
|
||||
?.map((e) => Category.fromJson(e))
|
||||
.toList();
|
||||
final track =
|
||||
(backup["tracks"] as List?)?.map((e) => Track.fromJson(e)).toList();
|
||||
final history = (backup["history"] as List?)
|
||||
?.map((e) => History.fromJson(e))
|
||||
.toList();
|
||||
final settings = (backup["settings"] as List?)
|
||||
?.map((e) => Settings.fromJson(e))
|
||||
.toList();
|
||||
final extensions = (backup["extensions"] as List?)
|
||||
?.map((e) => Source.fromJson(e))
|
||||
.toList();
|
||||
final extensionsPref = (backup["extensions_preferences"] as List?)
|
||||
?.map((e) => SourcePreference.fromJson(e))
|
||||
.toList();
|
||||
final feeds =
|
||||
(backup["feeds"] as List?)?.map((e) => Feed.fromJson(e)).toList();
|
||||
|
||||
isar.writeTxnSync(() {
|
||||
isar.mangas.clearSync();
|
||||
if (manga != null) {
|
||||
isar.mangas.putAllSync(manga);
|
||||
if (chapters != null) {
|
||||
isar.chapters.clearSync();
|
||||
for (var chapter in chapters) {
|
||||
final manga = isar.mangas.getSync(chapter.mangaId!);
|
||||
if (manga != null) {
|
||||
isar.chapters.putSync(chapter..manga.value = manga);
|
||||
chapter.manga.saveSync();
|
||||
}
|
||||
}
|
||||
|
||||
isar.historys.clearSync();
|
||||
if (history != null) {
|
||||
for (var element in history) {
|
||||
final chapter = isar.chapters.getSync(element.chapterId!);
|
||||
if (chapter != null) {
|
||||
isar.historys.putSync(element..chapter.value = chapter);
|
||||
element.chapter.saveSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isar.feeds.clearSync();
|
||||
if (feeds != null) {
|
||||
final tempChapters =
|
||||
isar.chapters.filter().idIsNotNull().findAllSync().toList();
|
||||
for (var feed in feeds) {
|
||||
final matchingChapter = tempChapters
|
||||
.where((chapter) =>
|
||||
chapter.mangaId == feed.mangaId &&
|
||||
chapter.name == feed.chapterName)
|
||||
.firstOrNull;
|
||||
if (matchingChapter != null) {
|
||||
isar.feeds.putSync(feed..chapter.value = matchingChapter);
|
||||
feed.chapter.saveSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isar.categorys.clearSync();
|
||||
if (categories != null) {
|
||||
isar.categorys.putAllSync(categories);
|
||||
}
|
||||
}
|
||||
|
||||
isar.tracks.clearSync();
|
||||
if (track != null) {
|
||||
isar.tracks.putAllSync(track);
|
||||
}
|
||||
|
||||
isar.sources.clearSync();
|
||||
if (extensions != null) {
|
||||
isar.sources.putAllSync(extensions);
|
||||
}
|
||||
|
||||
isar.sourcePreferences.clearSync();
|
||||
if (extensionsPref != null) {
|
||||
isar.sourcePreferences.putAllSync(extensionsPref);
|
||||
}
|
||||
final syncAfterReading = isar.settings.getSync(227)!.syncAfterReading;
|
||||
final syncOnAppLaunch = isar.settings.getSync(227)!.syncOnAppLaunch;
|
||||
isar.settings.clearSync();
|
||||
if (settings != null) {
|
||||
isar.settings.putAllSync(settings);
|
||||
}
|
||||
isar.settings.putSync(
|
||||
isar.settings.getSync(227)!..syncAfterReading = syncAfterReading);
|
||||
isar.settings.putSync(
|
||||
isar.settings.getSync(227)!..syncOnAppLaunch = syncOnAppLaunch);
|
||||
ref.invalidate(themeModeStateProvider);
|
||||
ref.invalidate(blendLevelStateProvider);
|
||||
ref.invalidate(flexSchemeColorStateProvider);
|
||||
ref.invalidate(pureBlackDarkModeStateProvider);
|
||||
ref.invalidate(l10nLocaleStateProvider);
|
||||
});
|
||||
} catch (e) {
|
||||
botToast(e.toString(), second: 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _getAccessToken() {
|
||||
final syncPrefs = ref.watch(synchingProvider(syncId: syncId));
|
||||
if (syncPrefs == null || syncPrefs.authToken == null) {
|
||||
return "";
|
||||
}
|
||||
var paddedPayload = syncPrefs.authToken!.split(".")[1];
|
||||
if (paddedPayload.length % 4 > 0) {
|
||||
paddedPayload += '=' * (4 - paddedPayload.length % 4);
|
||||
}
|
||||
final decodedJwt = jsonDecode(utf8.decode(base64Decode(paddedPayload)))
|
||||
as Map<String, dynamic>;
|
||||
final auth = JWToken.fromJson(decodedJwt);
|
||||
final expiresIn = DateTime.fromMillisecondsSinceEpoch(auth.exp!);
|
||||
if (DateTime.now().isAfter(expiresIn)) {
|
||||
ref.read(synchingProvider(syncId: syncId).notifier).logout();
|
||||
botToast("SyncServer Token expired");
|
||||
throw Exception("Token expired");
|
||||
}
|
||||
return syncPrefs.authToken!;
|
||||
}
|
||||
|
||||
String _getServer() {
|
||||
final syncPrefs = ref.watch(synchingProvider(syncId: syncId));
|
||||
return syncPrefs?.server ?? "";
|
||||
}
|
||||
}
|
||||
172
lib/services/sync_server.g.dart
Normal file
172
lib/services/sync_server.g.dart
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'sync_server.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$syncServerHash() => r'e019e8870184d25f7a2659e35f6c3969bc683b50';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$SyncServer extends BuildlessAutoDisposeNotifier<void> {
|
||||
late final int syncId;
|
||||
|
||||
void build({
|
||||
required int syncId,
|
||||
});
|
||||
}
|
||||
|
||||
/// See also [SyncServer].
|
||||
@ProviderFor(SyncServer)
|
||||
const syncServerProvider = SyncServerFamily();
|
||||
|
||||
/// See also [SyncServer].
|
||||
class SyncServerFamily extends Family<void> {
|
||||
/// See also [SyncServer].
|
||||
const SyncServerFamily();
|
||||
|
||||
/// See also [SyncServer].
|
||||
SyncServerProvider call({
|
||||
required int syncId,
|
||||
}) {
|
||||
return SyncServerProvider(
|
||||
syncId: syncId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SyncServerProvider getProviderOverride(
|
||||
covariant SyncServerProvider provider,
|
||||
) {
|
||||
return call(
|
||||
syncId: provider.syncId,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'syncServerProvider';
|
||||
}
|
||||
|
||||
/// See also [SyncServer].
|
||||
class SyncServerProvider
|
||||
extends AutoDisposeNotifierProviderImpl<SyncServer, void> {
|
||||
/// See also [SyncServer].
|
||||
SyncServerProvider({
|
||||
required int syncId,
|
||||
}) : this._internal(
|
||||
() => SyncServer()..syncId = syncId,
|
||||
from: syncServerProvider,
|
||||
name: r'syncServerProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$syncServerHash,
|
||||
dependencies: SyncServerFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
SyncServerFamily._allTransitiveDependencies,
|
||||
syncId: syncId,
|
||||
);
|
||||
|
||||
SyncServerProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.syncId,
|
||||
}) : super.internal();
|
||||
|
||||
final int syncId;
|
||||
|
||||
@override
|
||||
void runNotifierBuild(
|
||||
covariant SyncServer notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
syncId: syncId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(SyncServer Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: SyncServerProvider._internal(
|
||||
() => create()..syncId = syncId,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
syncId: syncId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeNotifierProviderElement<SyncServer, void> createElement() {
|
||||
return _SyncServerProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SyncServerProvider && other.syncId == syncId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, syncId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin SyncServerRef on AutoDisposeNotifierProviderRef<void> {
|
||||
/// The parameter `syncId` of this provider.
|
||||
int get syncId;
|
||||
}
|
||||
|
||||
class _SyncServerProviderElement
|
||||
extends AutoDisposeNotifierProviderElement<SyncServer, void>
|
||||
with SyncServerRef {
|
||||
_SyncServerProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
int get syncId => (origin as SyncServerProvider).syncId;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
|
@ -10,7 +10,8 @@ String dateFormat(String? timestamp,
|
|||
String? stringDate,
|
||||
bool forHistoryValue = false,
|
||||
bool useRelativeTimesTamps = true,
|
||||
String dateFormat = ""}) {
|
||||
String dateFormat = "",
|
||||
bool showHOURorMINUTE = false}) {
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
final locale = currentLocale(context);
|
||||
final relativeTimestamps = ref.watch(relativeTimesTampsStateProvider);
|
||||
|
|
@ -34,6 +35,22 @@ String dateFormat(String? timestamp,
|
|||
dateFormat.isEmpty ? dateFrmt : dateFormat, locale.toLanguageTag());
|
||||
|
||||
if (date == today && useRelativeTimesTamps && relativeTimestamps != 0) {
|
||||
if (showHOURorMINUTE) {
|
||||
final difference = now.difference(dateTime);
|
||||
if (difference.inMinutes < 60) {
|
||||
return switch (difference.inMinutes) {
|
||||
0 => l10n.now,
|
||||
1 => l10n.n_minute_ago(difference.inMinutes),
|
||||
_ => l10n.n_minutes_ago(difference.inMinutes),
|
||||
};
|
||||
} else if (difference.inHours < 24) {
|
||||
return switch (difference.inHours) {
|
||||
1 => l10n.n_hour_ago(difference.inHours),
|
||||
_ => l10n.n_hours_ago(difference.inHours),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return l10n.today;
|
||||
} else if (date == yesterday &&
|
||||
useRelativeTimesTamps &&
|
||||
|
|
@ -48,7 +65,11 @@ String dateFormat(String? timestamp,
|
|||
date.isAfter(sixDaysAgo) ||
|
||||
date.isAfter(aWeekAgo)) {
|
||||
final difference = today.difference(date).inDays;
|
||||
return difference != 7 ? l10n.n_days_ago(difference) : l10n.a_week_ago;
|
||||
return switch (difference) {
|
||||
1 => l10n.n_day_ago(difference),
|
||||
!= 7 => l10n.n_days_ago(difference),
|
||||
_ => l10n.a_week_ago,
|
||||
};
|
||||
}
|
||||
}
|
||||
return forHistoryValue
|
||||
|
|
|
|||
Loading…
Reference in a new issue