mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
Improve reader performance
This commit is contained in:
@@ -138,7 +138,7 @@ class ComicTile extends StatelessWidget {
|
|||||||
: false;
|
: false;
|
||||||
var history = appdata.settings['showHistoryStatusOnTile']
|
var history = appdata.settings['showHistoryStatusOnTile']
|
||||||
? HistoryManager()
|
? HistoryManager()
|
||||||
.findSync(comic.id, ComicType(comic.sourceKey.hashCode))
|
.find(comic.id, ComicType(comic.sourceKey.hashCode))
|
||||||
: null;
|
: null;
|
||||||
if (history?.page == 0) {
|
if (history?.page == 0) {
|
||||||
history!.page = 1;
|
history!.page = 1;
|
||||||
|
@@ -2,10 +2,12 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'dart:ffi' as ffi;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart' show ChangeNotifier;
|
import 'package:flutter/widgets.dart' show ChangeNotifier;
|
||||||
|
import 'package:sqlite3/common.dart';
|
||||||
import 'package:sqlite3/sqlite3.dart';
|
import 'package:sqlite3/sqlite3.dart';
|
||||||
import 'package:venera/foundation/comic_source/comic_source.dart';
|
import 'package:venera/foundation/comic_source/comic_source.dart';
|
||||||
import 'package:venera/foundation/comic_type.dart';
|
import 'package:venera/foundation/comic_type.dart';
|
||||||
@@ -124,30 +126,6 @@ class History implements Comic {
|
|||||||
.map((e) => int.parse(e))),
|
.map((e) => int.parse(e))),
|
||||||
maxPage = row["max_page"];
|
maxPage = row["max_page"];
|
||||||
|
|
||||||
static Future<History> findOrCreate(
|
|
||||||
HistoryMixin model, {
|
|
||||||
int ep = 0,
|
|
||||||
int page = 0,
|
|
||||||
}) async {
|
|
||||||
var history = await HistoryManager().find(model.id, model.historyType);
|
|
||||||
if (history != null) {
|
|
||||||
return history;
|
|
||||||
}
|
|
||||||
history = History.fromModel(model: model, ep: ep, page: page);
|
|
||||||
HistoryManager().addHistory(history);
|
|
||||||
return history;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<History> createIfNull(
|
|
||||||
History? history, HistoryMixin model) async {
|
|
||||||
if (history != null) {
|
|
||||||
return history;
|
|
||||||
}
|
|
||||||
history = History.fromModel(model: model, ep: 0, page: 0);
|
|
||||||
HistoryManager().addHistory(history);
|
|
||||||
return history;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is History && type == other.type && id == other.id;
|
return other is History && type == other.type && id == other.id;
|
||||||
@@ -210,7 +188,11 @@ class HistoryManager with ChangeNotifier {
|
|||||||
|
|
||||||
int get length => _db.select("select count(*) from history;").first[0] as int;
|
int get length => _db.select("select count(*) from history;").first[0] as int;
|
||||||
|
|
||||||
Map<String, bool>? _cachedHistory;
|
/// Cache of history ids. Improve the performance of find operation.
|
||||||
|
Map<String, bool>? _cachedHistoryIds;
|
||||||
|
|
||||||
|
/// Cache records recently modified by the app. Improve the performance of listeners.
|
||||||
|
final cachedHistories = <String, History>{};
|
||||||
|
|
||||||
bool isInitialized = false;
|
bool isInitialized = false;
|
||||||
|
|
||||||
@@ -240,14 +222,38 @@ class HistoryManager with ChangeNotifier {
|
|||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const _insertHistorySql = """
|
||||||
|
insert or replace into history (id, title, subtitle, cover, time, type, ep, page, readEpisode, max_page)
|
||||||
|
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
|
""";
|
||||||
|
|
||||||
|
static Future<void> _addHistoryAsync(int dbAddr, History newItem) {
|
||||||
|
return Isolate.run(() {
|
||||||
|
var db = sqlite3.fromPointer(ffi.Pointer.fromAddress(dbAddr));
|
||||||
|
db.execute(_insertHistorySql, [
|
||||||
|
newItem.id,
|
||||||
|
newItem.title,
|
||||||
|
newItem.subtitle,
|
||||||
|
newItem.cover,
|
||||||
|
newItem.time.millisecondsSinceEpoch,
|
||||||
|
newItem.type.value,
|
||||||
|
newItem.ep,
|
||||||
|
newItem.page,
|
||||||
|
newItem.readEpisode.join(','),
|
||||||
|
newItem.maxPage
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addHistoryAsync(History newItem) async {
|
||||||
|
_addHistoryAsync(_db.handle.address, newItem);
|
||||||
|
}
|
||||||
|
|
||||||
/// add history. if exists, update time.
|
/// add history. if exists, update time.
|
||||||
///
|
///
|
||||||
/// This function would be called when user start reading.
|
/// This function would be called when user start reading.
|
||||||
Future<void> addHistory(History newItem) async {
|
void addHistory(History newItem) {
|
||||||
_db.execute("""
|
_db.execute(_insertHistorySql, [
|
||||||
insert or replace into history (id, title, subtitle, cover, time, type, ep, page, readEpisode, max_page)
|
|
||||||
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
|
||||||
""", [
|
|
||||||
newItem.id,
|
newItem.id,
|
||||||
newItem.title,
|
newItem.title,
|
||||||
newItem.subtitle,
|
newItem.subtitle,
|
||||||
@@ -259,7 +265,15 @@ class HistoryManager with ChangeNotifier {
|
|||||||
newItem.readEpisode.join(','),
|
newItem.readEpisode.join(','),
|
||||||
newItem.maxPage
|
newItem.maxPage
|
||||||
]);
|
]);
|
||||||
updateCache();
|
if (_cachedHistoryIds == null) {
|
||||||
|
updateCache();
|
||||||
|
} else {
|
||||||
|
_cachedHistoryIds![newItem.id] = true;
|
||||||
|
}
|
||||||
|
cachedHistories[newItem.id] = newItem;
|
||||||
|
if (cachedHistories.length > 10) {
|
||||||
|
cachedHistories.remove(cachedHistories.keys.first);
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,27 +292,31 @@ class HistoryManager with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<History?> find(String id, ComicType type) async {
|
|
||||||
return findSync(id, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateCache() {
|
void updateCache() {
|
||||||
_cachedHistory = {};
|
_cachedHistoryIds = {};
|
||||||
var res = _db.select("""
|
var res = _db.select("""
|
||||||
select * from history;
|
select id from history;
|
||||||
""");
|
""");
|
||||||
for (var element in res) {
|
for (var element in res) {
|
||||||
_cachedHistory![element["id"] as String] = true;
|
_cachedHistoryIds![element["id"] as String] = true;
|
||||||
|
}
|
||||||
|
for (var key in cachedHistories.keys) {
|
||||||
|
if (!_cachedHistoryIds!.containsKey(key)) {
|
||||||
|
cachedHistories.remove(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
History? findSync(String id, ComicType type) {
|
History? find(String id, ComicType type) {
|
||||||
if (_cachedHistory == null) {
|
if (_cachedHistoryIds == null) {
|
||||||
updateCache();
|
updateCache();
|
||||||
}
|
}
|
||||||
if (!_cachedHistory!.containsKey(id)) {
|
if (!_cachedHistoryIds!.containsKey(id)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (cachedHistories.containsKey(id)) {
|
||||||
|
return cachedHistories[id];
|
||||||
|
}
|
||||||
|
|
||||||
var res = _db.select("""
|
var res = _db.select("""
|
||||||
select * from history
|
select * from history
|
||||||
|
@@ -105,8 +105,8 @@ class LocalComic with HistoryMixin implements Comic {
|
|||||||
@override
|
@override
|
||||||
int? get maxPage => null;
|
int? get maxPage => null;
|
||||||
|
|
||||||
void read() async {
|
void read() {
|
||||||
var history = await HistoryManager().find(id, comicType);
|
var history = HistoryManager().find(id, comicType);
|
||||||
App.rootContext.to(
|
App.rootContext.to(
|
||||||
() => Reader(
|
() => Reader(
|
||||||
type: comicType,
|
type: comicType,
|
||||||
@@ -511,7 +511,7 @@ class LocalManager with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
// Deleting a local comic means that it's nolonger available, thus both favorite and history should be deleted.
|
// Deleting a local comic means that it's nolonger available, thus both favorite and history should be deleted.
|
||||||
if (c.comicType == ComicType.local) {
|
if (c.comicType == ComicType.local) {
|
||||||
if (HistoryManager().findSync(c.id, c.comicType) != null) {
|
if (HistoryManager().find(c.id, c.comicType) != null) {
|
||||||
HistoryManager().remove(c.id, c.comicType);
|
HistoryManager().remove(c.id, c.comicType);
|
||||||
}
|
}
|
||||||
var folders = LocalFavoritesManager().find(c.id, c.comicType);
|
var folders = LocalFavoritesManager().find(c.id, c.comicType);
|
||||||
|
@@ -61,7 +61,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
@override
|
@override
|
||||||
void onReadEnd() {
|
void onReadEnd() {
|
||||||
history ??= HistoryManager()
|
history ??= HistoryManager()
|
||||||
.findSync(widget.id, ComicType(widget.sourceKey.hashCode));
|
.find(widget.id, ComicType(widget.sourceKey.hashCode));
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
if (localComic == null) {
|
if (localComic == null) {
|
||||||
return const Res.error('Local comic not found');
|
return const Res.error('Local comic not found');
|
||||||
}
|
}
|
||||||
var history = await HistoryManager().find(widget.id, ComicType.local);
|
var history = HistoryManager().find(widget.id, ComicType.local);
|
||||||
if (isFirst) {
|
if (isFirst) {
|
||||||
Future.microtask(() {
|
Future.microtask(() {
|
||||||
App.rootContext.to(() {
|
App.rootContext.to(() {
|
||||||
@@ -172,7 +172,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
widget.id,
|
widget.id,
|
||||||
ComicType(widget.sourceKey.hashCode),
|
ComicType(widget.sourceKey.hashCode),
|
||||||
);
|
);
|
||||||
history = await HistoryManager()
|
history = HistoryManager()
|
||||||
.find(widget.id, ComicType(widget.sourceKey.hashCode));
|
.find(widget.id, ComicType(widget.sourceKey.hashCode));
|
||||||
return comicSource.loadComicInfo!(widget.id);
|
return comicSource.loadComicInfo!(widget.id);
|
||||||
}
|
}
|
||||||
|
@@ -41,7 +41,7 @@ class _ReaderWithLoadingState
|
|||||||
@override
|
@override
|
||||||
Future<Res<ReaderProps>> loadData() async {
|
Future<Res<ReaderProps>> loadData() async {
|
||||||
var comicSource = ComicSource.find(widget.sourceKey);
|
var comicSource = ComicSource.find(widget.sourceKey);
|
||||||
var history = HistoryManager().findSync(
|
var history = HistoryManager().find(
|
||||||
widget.id,
|
widget.id,
|
||||||
ComicType.fromKey(widget.sourceKey),
|
ComicType.fromKey(widget.sourceKey),
|
||||||
);
|
);
|
||||||
|
@@ -238,9 +238,8 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
|
|||||||
history!.maxPage = maxPage;
|
history!.maxPage = maxPage;
|
||||||
}
|
}
|
||||||
history!.readEpisode.add(chapter);
|
history!.readEpisode.add(chapter);
|
||||||
print(history!.readEpisode);
|
|
||||||
history!.time = DateTime.now();
|
history!.time = DateTime.now();
|
||||||
HistoryManager().addHistory(history!);
|
HistoryManager().addHistoryAsync(history!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user