Improve history with grouped chapters.

This commit is contained in:
2025-02-20 19:16:26 +08:00
parent 011619340f
commit a63d458707
7 changed files with 79 additions and 38 deletions

View File

@@ -376,7 +376,7 @@ class ComicChapters {
return _groupedChapters![group] ?? {}; return _groupedChapters![group] ?? {};
} }
/// Get a group of chapters by index /// Get a group of chapters by index(0-based)
Map<String, String> getGroupByIndex(int index) { Map<String, String> getGroupByIndex(int index) {
return _groupedChapters!.values.elementAt(index); return _groupedChapters!.values.elementAt(index);
} }

View File

@@ -50,17 +50,24 @@ class History implements Comic {
@override @override
String cover; String cover;
/// index of chapters. 1-based.
int ep; int ep;
/// index of pages. 1-based.
int page; int page;
/// index of chapter groups. 1-based.
/// If [group] is not null, [ep] is the index of chapter in the group.
int? group;
@override @override
String id; String id;
/// readEpisode is a set of episode numbers that have been read. /// readEpisode is a set of episode numbers that have been read.
/// /// For normal chapters, it is a set of chapter numbers.
/// The number of episodes is 1-based. /// For grouped chapters, it is a set of strings in the format of "group_number-chapter_number".
Set<int> readEpisode; /// 1-based.
Set<String> readEpisode;
@override @override
int? maxPage; int? maxPage;
@@ -69,29 +76,17 @@ class History implements Comic {
{required HistoryMixin model, {required HistoryMixin model,
required this.ep, required this.ep,
required this.page, required this.page,
Set<int>? readChapters, this.group,
Set<String>? readChapters,
DateTime? time}) DateTime? time})
: type = model.historyType, : type = model.historyType,
title = model.title, title = model.title,
subtitle = model.subTitle ?? '', subtitle = model.subTitle ?? '',
cover = model.cover, cover = model.cover,
id = model.id, id = model.id,
readEpisode = readChapters ?? <int>{}, readEpisode = readChapters ?? <String>{},
time = time ?? DateTime.now(); time = time ?? DateTime.now();
Map<String, dynamic> toMap() => {
"type": type.value,
"time": time.millisecondsSinceEpoch,
"title": title,
"subtitle": subtitle,
"cover": cover,
"ep": ep,
"page": page,
"id": id,
"readEpisode": readEpisode.toList(),
"max_page": maxPage
};
History.fromMap(Map<String, dynamic> map) History.fromMap(Map<String, dynamic> map)
: type = HistoryType(map["type"]), : type = HistoryType(map["type"]),
time = DateTime.fromMillisecondsSinceEpoch(map["time"]), time = DateTime.fromMillisecondsSinceEpoch(map["time"]),
@@ -101,8 +96,9 @@ class History implements Comic {
ep = map["ep"], ep = map["ep"],
page = map["page"], page = map["page"],
id = map["id"], id = map["id"],
readEpisode = Set<int>.from( readEpisode = Set<String>.from(
(map["readEpisode"] as List<dynamic>?)?.toSet() ?? const <int>{}), (map["readEpisode"] as List<dynamic>?)?.toSet() ??
const <String>{}),
maxPage = map["max_page"]; maxPage = map["max_page"];
@override @override
@@ -119,11 +115,11 @@ class History implements Comic {
ep = row["ep"], ep = row["ep"],
page = row["page"], page = row["page"],
id = row["id"], id = row["id"],
readEpisode = Set<int>.from((row["readEpisode"] as String) readEpisode = Set<String>.from((row["readEpisode"] as String)
.split(',') .split(',')
.where((element) => element != "") .where((element) => element != "")),
.map((e) => int.parse(e))), maxPage = row["max_page"],
maxPage = row["max_page"]; group = row["chapter_group"];
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
@@ -212,18 +208,24 @@ class HistoryManager with ChangeNotifier {
ep int, ep int,
page int, page int,
readEpisode text, readEpisode text,
max_page int max_page int,
chapter_group int
); );
"""); """);
var columns = _db.select("PRAGMA table_info(history);");
if (!columns.any((element) => element["name"] == "chapter_group")) {
_db.execute("alter table history add column chapter_group int;");
}
notifyListeners(); notifyListeners();
ImageFavoriteManager().init(); ImageFavoriteManager().init();
isInitialized = true; isInitialized = true;
} }
static const _insertHistorySql = """ static const _insertHistorySql = """
insert or replace into history (id, title, subtitle, cover, time, type, ep, page, readEpisode, max_page) insert or replace into history (id, title, subtitle, cover, time, type, ep, page, readEpisode, max_page, chapter_group)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
"""; """;
static Future<void> _addHistoryAsync(int dbAddr, History newItem) { static Future<void> _addHistoryAsync(int dbAddr, History newItem) {
@@ -239,7 +241,8 @@ class HistoryManager with ChangeNotifier {
newItem.ep, newItem.ep,
newItem.page, newItem.page,
newItem.readEpisode.join(','), newItem.readEpisode.join(','),
newItem.maxPage newItem.maxPage,
newItem.group
]); ]);
}); });
} }
@@ -281,7 +284,8 @@ class HistoryManager with ChangeNotifier {
newItem.ep, newItem.ep,
newItem.page, newItem.page,
newItem.readEpisode.join(','), newItem.readEpisode.join(','),
newItem.maxPage newItem.maxPage,
newItem.group
]); ]);
if (_cachedHistoryIds == null) { if (_cachedHistoryIds == null) {
updateCache(); updateCache();

View File

@@ -115,6 +115,7 @@ class LocalComic with HistoryMixin implements Comic {
chapters: chapters, chapters: chapters,
initialChapter: history?.ep, initialChapter: history?.ep,
initialPage: history?.page, initialPage: history?.page,
initialChapterGroup: history?.group,
history: history ?? history: history ??
History.fromModel( History.fromModel(
model: this, model: this,

View File

@@ -286,8 +286,13 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
} }
chapterIndex += chapters.getGroupByIndex(j).length; chapterIndex += chapters.getGroupByIndex(j).length;
} }
bool visited = String rawIndex = (chapterIndex + 1).toString();
(history?.readEpisode ?? {}).contains(chapterIndex + 1); String groupedIndex = "${index + 1}-${i + 1}";
bool visited = false;
if (history != null) {
visited = history!.readEpisode.contains(groupedIndex) ||
history!.readEpisode.contains(rawIndex);
}
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(6, 4, 6, 4), padding: const EdgeInsets.fromLTRB(6, 4, 6, 4),
child: Material( child: Material(

View File

@@ -167,6 +167,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
chapters: localComic.chapters, chapters: localComic.chapters,
initialPage: history?.page, initialPage: history?.page,
initialChapter: history?.ep, initialChapter: history?.ep,
initialChapterGroup: history?.group,
history: history ?? history: history ??
History.fromModel( History.fromModel(
model: localComic, model: localComic,
@@ -383,11 +384,19 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
bool haveChapter = comic.chapters != null; bool haveChapter = comic.chapters != null;
var page = history!.page; var page = history!.page;
var ep = history!.ep; var ep = history!.ep;
var group = history!.group;
String text; String text;
if (haveChapter) { if (haveChapter) {
var epName = group == null
? comic.chapters!.titles.elementAt(
math.min(ep - 1, comic.chapters!.length - 1),
)
: comic.chapters!
.getGroupByIndex(group - 1)
.values
.elementAt(ep - 1);
text = "Last Reading: @epName Page @page".tlParams({ text = "Last Reading: @epName Page @page".tlParams({
'epName': comic.chapters!.titles.elementAt( 'epName': epName,
math.min(ep - 1, comic.chapters!.length - 1)),
'page': page, 'page': page,
}); });
} else { } else {

View File

@@ -33,6 +33,7 @@ class _ReaderWithLoadingState
history: data.history, history: data.history,
initialChapter: widget.initialEp ?? data.history.ep, initialChapter: widget.initialEp ?? data.history.ep,
initialPage: widget.initialPage ?? data.history.page, initialPage: widget.initialPage ?? data.history.page,
initialChapterGroup: data.history.group,
author: data.author, author: data.author,
tags: data.tags, tags: data.tags,
); );

View File

@@ -62,6 +62,7 @@ class Reader extends StatefulWidget {
required this.history, required this.history,
this.initialPage, this.initialPage,
this.initialChapter, this.initialChapter,
this.initialChapterGroup,
required this.author, required this.author,
required this.tags, required this.tags,
}); });
@@ -84,6 +85,9 @@ class Reader extends StatefulWidget {
/// Starts from 1, invalid values equal to 1 /// Starts from 1, invalid values equal to 1
final int? initialChapter; final int? initialChapter;
/// Starts from 1, invalid values equal to 1
final int? initialChapterGroup;
final History history; final History history;
@override @override
@@ -147,13 +151,18 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
@override @override
void initState() { void initState() {
page = widget.initialPage ?? 1; page = widget.initialPage ?? 1;
chapter = widget.initialChapter ?? 1;
if (page < 1) { if (page < 1) {
page = 1; page = 1;
} }
chapter = widget.initialChapter ?? 1;
if (chapter < 1) { if (chapter < 1) {
chapter = 1; chapter = 1;
} }
if (widget.initialChapterGroup != null) {
for (int i=0; i<(widget.initialChapterGroup!-1); i++) {
chapter += widget.chapters!.getGroupByIndex(i).length;
}
}
if (widget.initialPage != null) { if (widget.initialPage != null) {
page = (widget.initialPage! / imagesPerPage).ceil(); page = (widget.initialPage! / imagesPerPage).ceil();
} }
@@ -238,11 +247,23 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
void updateHistory() { void updateHistory() {
if (history != null) { if (history != null) {
history!.page = page; history!.page = page;
history!.ep = chapter;
if (maxPage > 1) { if (maxPage > 1) {
history!.maxPage = maxPage; history!.maxPage = maxPage;
} }
history!.readEpisode.add(chapter); if (widget.chapters?.isGrouped ?? false) {
int g = 0;
int c = chapter;
while (c > widget.chapters!.getGroupByIndex(g).length) {
c -= widget.chapters!.getGroupByIndex(g).length;
g++;
}
history!.readEpisode.add('${g + 1}-$c');
history!.ep = c;
history!.group = g+1;
} else {
history!.readEpisode.add(chapter.toString());
history!.ep = chapter;
}
history!.time = DateTime.now(); history!.time = DateTime.now();
_updateHistoryTimer?.cancel(); _updateHistoryTimer?.cancel();
_updateHistoryTimer = Timer(const Duration(seconds: 1), () { _updateHistoryTimer = Timer(const Duration(seconds: 1), () {