mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
Improve comic chapters.
This commit is contained in:
@@ -632,6 +632,7 @@ class _TabViewBodyState extends State<TabViewBody> {
|
|||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
_controller = widget.controller ?? DefaultTabController.of(context);
|
_controller = widget.controller ?? DefaultTabController.of(context);
|
||||||
|
_currentIndex = _controller.index;
|
||||||
_controller.addListener(updateIndex);
|
_controller.addListener(updateIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -128,12 +128,7 @@ class ComicDetails with HistoryMixin {
|
|||||||
final Map<String, List<String>> tags;
|
final Map<String, List<String>> tags;
|
||||||
|
|
||||||
/// id-name
|
/// id-name
|
||||||
final Map<String, String>? chapters;
|
final ComicChapters? chapters;
|
||||||
|
|
||||||
/// key is group name.
|
|
||||||
/// When this field is not null, [chapters] will be a merged map of all groups.
|
|
||||||
/// Only available in some sources.
|
|
||||||
final Map<String, Map<String, String>>? groupedChapters;
|
|
||||||
|
|
||||||
final List<String>? thumbnails;
|
final List<String>? thumbnails;
|
||||||
|
|
||||||
@@ -176,45 +171,13 @@ class ComicDetails with HistoryMixin {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Map<String, String>? _getChapters(dynamic chapters) {
|
|
||||||
if (chapters == null) return null;
|
|
||||||
var result = <String, String>{};
|
|
||||||
if (chapters is Map) {
|
|
||||||
for (var entry in chapters.entries) {
|
|
||||||
var value = entry.value;
|
|
||||||
if (value is Map) {
|
|
||||||
result.addAll(Map.from(value));
|
|
||||||
} else {
|
|
||||||
result[entry.key.toString()] = value.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, Map<String, String>>? _getGroupedChapters(dynamic chapters) {
|
|
||||||
if (chapters == null) return null;
|
|
||||||
var result = <String, Map<String, String>>{};
|
|
||||||
if (chapters is Map) {
|
|
||||||
for (var entry in chapters.entries) {
|
|
||||||
var value = entry.value;
|
|
||||||
if (value is Map) {
|
|
||||||
result[entry.key.toString()] = Map.from(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result.isEmpty) return null;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ComicDetails.fromJson(Map<String, dynamic> json)
|
ComicDetails.fromJson(Map<String, dynamic> json)
|
||||||
: title = json["title"],
|
: title = json["title"],
|
||||||
subTitle = json["subtitle"],
|
subTitle = json["subtitle"],
|
||||||
cover = json["cover"],
|
cover = json["cover"],
|
||||||
description = json["description"],
|
description = json["description"],
|
||||||
tags = _generateMap(json["tags"]),
|
tags = _generateMap(json["tags"]),
|
||||||
chapters = _getChapters(json["chapters"]),
|
chapters = ComicChapters.fromJsonOrNull(json["chapters"]),
|
||||||
groupedChapters = _getGroupedChapters(json["chapters"]),
|
|
||||||
sourceKey = json["sourceKey"],
|
sourceKey = json["sourceKey"],
|
||||||
comicId = json["comicId"],
|
comicId = json["comicId"],
|
||||||
thumbnails = ListOrNull.from(json["thumbnails"]),
|
thumbnails = ListOrNull.from(json["thumbnails"]),
|
||||||
@@ -342,3 +305,122 @@ class ArchiveInfo {
|
|||||||
description = json["description"],
|
description = json["description"],
|
||||||
id = json["id"];
|
id = json["id"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ComicChapters {
|
||||||
|
final Map<String, String>? _chapters;
|
||||||
|
|
||||||
|
final Map<String, Map<String, String>>? _groupedChapters;
|
||||||
|
|
||||||
|
/// Create a ComicChapters object with a flat map
|
||||||
|
const ComicChapters(Map<String, String> this._chapters)
|
||||||
|
: _groupedChapters = null;
|
||||||
|
|
||||||
|
/// Create a ComicChapters object with a grouped map
|
||||||
|
const ComicChapters.grouped(
|
||||||
|
Map<String, Map<String, String>> this._groupedChapters)
|
||||||
|
: _chapters = null;
|
||||||
|
|
||||||
|
factory ComicChapters.fromJson(dynamic json) {
|
||||||
|
if (json is! Map) throw ArgumentError("Invalid json type");
|
||||||
|
var chapters = <String, String>{};
|
||||||
|
var groupedChapters = <String, Map<String, String>>{};
|
||||||
|
for (var entry in json.entries) {
|
||||||
|
var key = entry.key;
|
||||||
|
var value = entry.value;
|
||||||
|
if (key is! String) throw ArgumentError("Invalid key type");
|
||||||
|
if (value is Map) {
|
||||||
|
groupedChapters[key] = Map.from(value);
|
||||||
|
} else {
|
||||||
|
chapters[key] = value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chapters.isNotEmpty) {
|
||||||
|
return ComicChapters(chapters);
|
||||||
|
} else {
|
||||||
|
return ComicChapters.grouped(groupedChapters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJsonOrNull(dynamic json) {
|
||||||
|
if (json == null) return null;
|
||||||
|
return ComicChapters.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
if (_chapters != null) {
|
||||||
|
return _chapters;
|
||||||
|
} else {
|
||||||
|
return _groupedChapters!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the chapters are grouped
|
||||||
|
bool get isGrouped => _groupedChapters != null;
|
||||||
|
|
||||||
|
/// All group names
|
||||||
|
Iterable<String> get groups => _groupedChapters?.keys ?? [];
|
||||||
|
|
||||||
|
/// All chapters.
|
||||||
|
/// If the chapters are grouped, all groups will be merged.
|
||||||
|
Map<String, String> get allChapters {
|
||||||
|
if (_chapters != null) return _chapters;
|
||||||
|
var res = <String, String>{};
|
||||||
|
for (var entry in _groupedChapters!.values) {
|
||||||
|
res.addAll(entry);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a group of chapters by name
|
||||||
|
Map<String, String> getGroup(String group) {
|
||||||
|
return _groupedChapters![group] ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a group of chapters by index
|
||||||
|
Map<String, String> getGroupByIndex(int index) {
|
||||||
|
return _groupedChapters!.values.elementAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a chapter by index
|
||||||
|
int get length {
|
||||||
|
return isGrouped
|
||||||
|
? _groupedChapters!.values.map((e) => e.length).reduce((a, b) => a + b)
|
||||||
|
: _chapters!.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of groups
|
||||||
|
int get groupCount => _groupedChapters?.length ?? 0;
|
||||||
|
|
||||||
|
/// Iterate all chapter ids
|
||||||
|
Iterable<String> get ids sync* {
|
||||||
|
if (isGrouped) {
|
||||||
|
for (var entry in _groupedChapters!.values) {
|
||||||
|
yield* entry.keys;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yield* _chapters!.keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate all chapter titles
|
||||||
|
Iterable<String> get titles sync* {
|
||||||
|
if (isGrouped) {
|
||||||
|
for (var entry in _groupedChapters!.values) {
|
||||||
|
yield* entry.values;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yield* _chapters!.values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String? operator [](String key) {
|
||||||
|
if (isGrouped) {
|
||||||
|
for (var entry in _groupedChapters!.values) {
|
||||||
|
if (entry.containsKey(key)) return entry[key];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return _chapters![key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -97,7 +97,7 @@ class ImageFavoritesProvider
|
|||||||
if (localComic == null) {
|
if (localComic == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var epIndex = localComic.chapters?.keys.toList().indexOf(eid) ?? -1;
|
var epIndex = localComic.chapters?.ids.toList().indexOf(eid) ?? -1;
|
||||||
if (epIndex == -1 && localComic.hasChapters) {
|
if (epIndex == -1 && localComic.hasChapters) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,6 @@ import 'package:venera/foundation/favorites.dart';
|
|||||||
import 'package:venera/foundation/log.dart';
|
import 'package:venera/foundation/log.dart';
|
||||||
import 'package:venera/network/download.dart';
|
import 'package:venera/network/download.dart';
|
||||||
import 'package:venera/pages/reader/reader.dart';
|
import 'package:venera/pages/reader/reader.dart';
|
||||||
import 'package:venera/utils/ext.dart';
|
|
||||||
import 'package:venera/utils/io.dart';
|
import 'package:venera/utils/io.dart';
|
||||||
|
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
@@ -34,7 +33,7 @@ class LocalComic with HistoryMixin implements Comic {
|
|||||||
/// key: chapter id, value: chapter title
|
/// key: chapter id, value: chapter title
|
||||||
///
|
///
|
||||||
/// chapter id is the name of the directory in `LocalManager.path/$directory`
|
/// chapter id is the name of the directory in `LocalManager.path/$directory`
|
||||||
final Map<String, String>? chapters;
|
final ComicChapters? chapters;
|
||||||
|
|
||||||
bool get hasChapters => chapters != null;
|
bool get hasChapters => chapters != null;
|
||||||
|
|
||||||
@@ -67,7 +66,7 @@ class LocalComic with HistoryMixin implements Comic {
|
|||||||
subtitle = row[2] as String,
|
subtitle = row[2] as String,
|
||||||
tags = List.from(jsonDecode(row[3] as String)),
|
tags = List.from(jsonDecode(row[3] as String)),
|
||||||
directory = row[4] as String,
|
directory = row[4] as String,
|
||||||
chapters = MapOrNull.from(jsonDecode(row[5] as String)),
|
chapters = ComicChapters.fromJsonOrNull(jsonDecode(row[5] as String)),
|
||||||
cover = row[6] as String,
|
cover = row[6] as String,
|
||||||
comicType = ComicType(row[7] as int),
|
comicType = ComicType(row[7] as int),
|
||||||
downloadedChapters = List.from(jsonDecode(row[8] as String)),
|
downloadedChapters = List.from(jsonDecode(row[8] as String)),
|
||||||
@@ -99,6 +98,7 @@ class LocalComic with HistoryMixin implements Comic {
|
|||||||
"tags": tags,
|
"tags": tags,
|
||||||
"description": description,
|
"description": description,
|
||||||
"sourceKey": sourceKey,
|
"sourceKey": sourceKey,
|
||||||
|
"chapters": chapters?.toJson(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +391,7 @@ class LocalManager with ChangeNotifier {
|
|||||||
var directory = Directory(comic.baseDir);
|
var directory = Directory(comic.baseDir);
|
||||||
if (comic.hasChapters) {
|
if (comic.hasChapters) {
|
||||||
var cid =
|
var cid =
|
||||||
ep is int ? comic.chapters!.keys.elementAt(ep - 1) : (ep as String);
|
ep is int ? comic.chapters!.ids.elementAt(ep - 1) : (ep as String);
|
||||||
directory = Directory(FilePath.join(directory.path, cid));
|
directory = Directory(FilePath.join(directory.path, cid));
|
||||||
}
|
}
|
||||||
var files = <File>[];
|
var files = <File>[];
|
||||||
@@ -425,7 +425,7 @@ class LocalManager with ChangeNotifier {
|
|||||||
if (comic == null) return false;
|
if (comic == null) return false;
|
||||||
if (comic.chapters == null || ep == null) return true;
|
if (comic.chapters == null || ep == null) return true;
|
||||||
return comic.downloadedChapters
|
return comic.downloadedChapters
|
||||||
.contains(comic.chapters!.keys.elementAt(ep - 1));
|
.contains(comic.chapters!.ids.elementAt(ep - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DownloadTask> downloadingTasks = [];
|
List<DownloadTask> downloadingTasks = [];
|
||||||
|
@@ -328,8 +328,9 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
_images = {};
|
_images = {};
|
||||||
_totalCount = 0;
|
_totalCount = 0;
|
||||||
int cpCount = 0;
|
int cpCount = 0;
|
||||||
int totalCpCount = chapters?.length ?? comic!.chapters!.length;
|
int totalCpCount =
|
||||||
for (var i in comic!.chapters!.keys) {
|
chapters?.length ?? comic!.chapters!.allChapters.length;
|
||||||
|
for (var i in comic!.chapters!.allChapters.keys) {
|
||||||
if (chapters != null && !chapters!.contains(i)) {
|
if (chapters != null && !chapters!.contains(i)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@@ -262,7 +262,7 @@ abstract mixin class _ComicPageActions {
|
|||||||
if (localComic != null) {
|
if (localComic != null) {
|
||||||
for (int i = 0; i < comic.chapters!.length; i++) {
|
for (int i = 0; i < comic.chapters!.length; i++) {
|
||||||
if (localComic.downloadedChapters
|
if (localComic.downloadedChapters
|
||||||
.contains(comic.chapters!.keys.elementAt(i))) {
|
.contains(comic.chapters!.ids.elementAt(i))) {
|
||||||
downloaded.add(i);
|
downloaded.add(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,7 +270,7 @@ abstract mixin class _ComicPageActions {
|
|||||||
await showSideBar(
|
await showSideBar(
|
||||||
App.rootContext,
|
App.rootContext,
|
||||||
_SelectDownloadChapter(
|
_SelectDownloadChapter(
|
||||||
comic.chapters!.values.toList(),
|
comic.chapters!.titles.toList(),
|
||||||
(v) => selected = v,
|
(v) => selected = v,
|
||||||
downloaded,
|
downloaded,
|
||||||
),
|
),
|
||||||
@@ -281,7 +281,7 @@ abstract mixin class _ComicPageActions {
|
|||||||
comicId: comic.id,
|
comicId: comic.id,
|
||||||
comic: comic,
|
comic: comic,
|
||||||
chapters: selected!.map((i) {
|
chapters: selected!.map((i) {
|
||||||
return comic.chapters!.keys.elementAt(i);
|
return comic.chapters!.ids.elementAt(i);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@@ -33,7 +33,7 @@ class _NormalComicChaptersState extends State<_NormalComicChapters> {
|
|||||||
|
|
||||||
late History? history;
|
late History? history;
|
||||||
|
|
||||||
late Map<String, String> chapters;
|
late ComicChapters chapters;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -101,7 +101,7 @@ class _NormalComicChaptersState extends State<_NormalComicChapters> {
|
|||||||
if (reverse) {
|
if (reverse) {
|
||||||
i = chapters.length - i - 1;
|
i = chapters.length - i - 1;
|
||||||
}
|
}
|
||||||
var key = chapters.keys.elementAt(i);
|
var key = chapters.ids.elementAt(i);
|
||||||
var value = chapters[key]!;
|
var value = chapters[key]!;
|
||||||
bool visited = (history?.readEpisode ?? {}).contains(i + 1);
|
bool visited = (history?.readEpisode ?? {}).contains(i + 1);
|
||||||
return Padding(
|
return Padding(
|
||||||
@@ -182,7 +182,7 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
|
|||||||
|
|
||||||
late History? history;
|
late History? history;
|
||||||
|
|
||||||
late Map<String, Map<String, String>> chapters;
|
late ComicChapters chapters;
|
||||||
|
|
||||||
late TabController tabController;
|
late TabController tabController;
|
||||||
|
|
||||||
@@ -197,9 +197,9 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
|
|||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
state = context.findAncestorStateOfType<_ComicPageState>()!;
|
state = context.findAncestorStateOfType<_ComicPageState>()!;
|
||||||
chapters = state.comic.groupedChapters!;
|
chapters = state.comic.chapters!;
|
||||||
tabController = TabController(
|
tabController = TabController(
|
||||||
length: chapters.keys.length,
|
length: chapters.ids.length,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
tabController.addListener(onTabChange);
|
tabController.addListener(onTabChange);
|
||||||
@@ -226,7 +226,7 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverLayoutBuilder(
|
return SliverLayoutBuilder(
|
||||||
builder: (context, constrains) {
|
builder: (context, constrains) {
|
||||||
var group = chapters.values.elementAt(index);
|
var group = chapters.getGroupByIndex(index);
|
||||||
int length = group.length;
|
int length = group.length;
|
||||||
bool canShowAll = showAll;
|
bool canShowAll = showAll;
|
||||||
if (!showAll) {
|
if (!showAll) {
|
||||||
@@ -265,7 +265,7 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
|
|||||||
child: AppTabBar(
|
child: AppTabBar(
|
||||||
withUnderLine: false,
|
withUnderLine: false,
|
||||||
controller: tabController,
|
controller: tabController,
|
||||||
tabs: chapters.keys.map((e) => Tab(text: e)).toList(),
|
tabs: chapters.groups.map((e) => Tab(text: e)).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverPadding(padding: const EdgeInsets.only(top: 8)),
|
SliverPadding(padding: const EdgeInsets.only(top: 8)),
|
||||||
@@ -279,12 +279,12 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
|
|||||||
var key = group.keys.elementAt(i);
|
var key = group.keys.elementAt(i);
|
||||||
var value = group[key]!;
|
var value = group[key]!;
|
||||||
var chapterIndex = 0;
|
var chapterIndex = 0;
|
||||||
for (var j = 0; j < chapters.length; j++) {
|
for (var j = 0; j < chapters.groupCount; j++) {
|
||||||
if (j == index) {
|
if (j == index) {
|
||||||
chapterIndex += i;
|
chapterIndex += i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
chapterIndex += chapters.values.elementAt(j).length;
|
chapterIndex += chapters.getGroupByIndex(j).length;
|
||||||
}
|
}
|
||||||
bool visited =
|
bool visited =
|
||||||
(history?.readEpisode ?? {}).contains(chapterIndex + 1);
|
(history?.readEpisode ?? {}).contains(chapterIndex + 1);
|
||||||
|
@@ -386,7 +386,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
String text;
|
String text;
|
||||||
if (haveChapter) {
|
if (haveChapter) {
|
||||||
text = "Last Reading: @epName Page @page".tlParams({
|
text = "Last Reading: @epName Page @page".tlParams({
|
||||||
'epName': comic.chapters!.values.elementAt(
|
'epName': comic.chapters!.titles.elementAt(
|
||||||
math.min(ep - 1, comic.chapters!.length - 1)),
|
math.min(ep - 1, comic.chapters!.length - 1)),
|
||||||
'page': page,
|
'page': page,
|
||||||
});
|
});
|
||||||
@@ -610,7 +610,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
}
|
}
|
||||||
return _ComicChapters(
|
return _ComicChapters(
|
||||||
history: history,
|
history: history,
|
||||||
groupedMode: comic.groupedChapters != null,
|
groupedMode: comic.chapters!.isGrouped,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -45,7 +45,7 @@ class _ReaderImagesState extends State<_ReaderImages> {
|
|||||||
} else {
|
} else {
|
||||||
var res = await reader.type.comicSource!.loadComicPages!(
|
var res = await reader.type.comicSource!.loadComicPages!(
|
||||||
reader.widget.cid,
|
reader.widget.cid,
|
||||||
reader.widget.chapters?.keys.elementAt(reader.chapter - 1),
|
reader.widget.chapters?.ids.elementAt(reader.chapter - 1),
|
||||||
);
|
);
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@@ -101,7 +101,7 @@ class ReaderProps {
|
|||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
final Map<String, String>? chapters;
|
final ComicChapters? chapters;
|
||||||
|
|
||||||
final History history;
|
final History history;
|
||||||
|
|
||||||
|
@@ -76,9 +76,7 @@ class Reader extends StatefulWidget {
|
|||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
/// key: Chapter ID, value: Chapter Name
|
final ComicChapters? chapters;
|
||||||
/// null if the comic is a gallery
|
|
||||||
final Map<String, String>? chapters;
|
|
||||||
|
|
||||||
/// Starts from 1, invalid values equal to 1
|
/// Starts from 1, invalid values equal to 1
|
||||||
final int? initialPage;
|
final int? initialPage;
|
||||||
@@ -105,7 +103,7 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
|
|||||||
|
|
||||||
String get cid => widget.cid;
|
String get cid => widget.cid;
|
||||||
|
|
||||||
String get eid => widget.chapters?.keys.elementAt(chapter - 1) ?? '0';
|
String get eid => widget.chapters?.ids.elementAt(chapter - 1) ?? '0';
|
||||||
|
|
||||||
List<String>? images;
|
List<String>? images;
|
||||||
|
|
||||||
|
@@ -279,7 +279,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
List<String> tags = context.reader.widget.tags;
|
List<String> tags = context.reader.widget.tags;
|
||||||
String author = context.reader.widget.author;
|
String author = context.reader.widget.author;
|
||||||
|
|
||||||
var epName = context.reader.widget.chapters?.values
|
var epName = context.reader.widget.chapters?.titles
|
||||||
.elementAtOrNull(context.reader.chapter - 1) ??
|
.elementAtOrNull(context.reader.chapter - 1) ??
|
||||||
"E${context.reader.chapter}";
|
"E${context.reader.chapter}";
|
||||||
var translatedTags = tags.map((e) => e.translateTagsToCN).toList();
|
var translatedTags = tags.map((e) => e.translateTagsToCN).toList();
|
||||||
@@ -561,7 +561,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildPageInfoText() {
|
Widget buildPageInfoText() {
|
||||||
var epName = context.reader.widget.chapters?.values
|
var epName = context.reader.widget.chapters?.titles
|
||||||
.elementAtOrNull(context.reader.chapter - 1) ??
|
.elementAtOrNull(context.reader.chapter - 1) ??
|
||||||
"E${context.reader.chapter}";
|
"E${context.reader.chapter}";
|
||||||
if (epName.length > 8) {
|
if (epName.length > 8) {
|
||||||
@@ -614,7 +614,9 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
void openChapterDrawer() {
|
void openChapterDrawer() {
|
||||||
showSideBar(
|
showSideBar(
|
||||||
context,
|
context,
|
||||||
_ChaptersView(context.reader),
|
context.reader.widget.chapters!.isGrouped
|
||||||
|
? _GroupedChaptersView(context.reader)
|
||||||
|
: _ChaptersView(context.reader),
|
||||||
width: 400,
|
width: 400,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1030,14 +1032,27 @@ class _ChaptersView extends StatefulWidget {
|
|||||||
class _ChaptersViewState extends State<_ChaptersView> {
|
class _ChaptersViewState extends State<_ChaptersView> {
|
||||||
bool desc = false;
|
bool desc = false;
|
||||||
|
|
||||||
|
late final ScrollController _scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
int epIndex = widget.reader.chapter - 2;
|
||||||
|
_scrollController = ScrollController(
|
||||||
|
initialScrollOffset: (epIndex * 48.0 + 52).clamp(0, double.infinity),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var chapters = widget.reader.widget.chapters!;
|
var chapters = widget.reader.widget.chapters!;
|
||||||
var current = widget.reader.chapter - 1;
|
var current = widget.reader.chapter - 1;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SmoothCustomScrollView(
|
body: SmoothCustomScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppbar(
|
SliverAppbar(
|
||||||
|
style: AppbarStyle.shadow,
|
||||||
title: Text("Chapters".tl),
|
title: Text("Chapters".tl),
|
||||||
actions: [
|
actions: [
|
||||||
Tooltip(
|
Tooltip(
|
||||||
@@ -1063,26 +1078,35 @@ class _ChaptersViewState extends State<_ChaptersView> {
|
|||||||
if (desc) {
|
if (desc) {
|
||||||
index = chapters.length - 1 - index;
|
index = chapters.length - 1 - index;
|
||||||
}
|
}
|
||||||
var chapter = chapters.values.elementAt(index);
|
var chapter = chapters.titles.elementAt(index);
|
||||||
return ListTile(
|
return InkWell(
|
||||||
shape: Border(
|
|
||||||
left: BorderSide(
|
|
||||||
color: current == index
|
|
||||||
? context.colorScheme.primary
|
|
||||||
: Colors.transparent,
|
|
||||||
width: 4,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
chapter,
|
|
||||||
style: current == index
|
|
||||||
? ts.withColor(context.colorScheme.primary).bold
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
widget.reader.toChapter(index + 1);
|
widget.reader.toChapter(index + 1);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 48,
|
||||||
|
padding: const EdgeInsets.only(left: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
left: BorderSide(
|
||||||
|
color: current == index
|
||||||
|
? context.colorScheme.primary
|
||||||
|
: Colors.transparent,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
chapter,
|
||||||
|
style: current == index
|
||||||
|
? ts.withColor(context.colorScheme.primary).bold.s16
|
||||||
|
: ts.s16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
childCount: chapters.length,
|
childCount: chapters.length,
|
||||||
@@ -1093,3 +1117,120 @@ class _ChaptersViewState extends State<_ChaptersView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _GroupedChaptersView extends StatefulWidget {
|
||||||
|
const _GroupedChaptersView(this.reader);
|
||||||
|
|
||||||
|
final _ReaderState reader;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_GroupedChaptersView> createState() => _GroupedChaptersViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GroupedChaptersViewState extends State<_GroupedChaptersView>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
ComicChapters get chapters => widget.reader.widget.chapters!;
|
||||||
|
|
||||||
|
late final TabController tabController;
|
||||||
|
|
||||||
|
late final ScrollController _scrollController;
|
||||||
|
|
||||||
|
late final initialGroupName;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
int index = 0;
|
||||||
|
int epIndex = widget.reader.chapter - 1;
|
||||||
|
while (epIndex >= 0) {
|
||||||
|
epIndex -= chapters.getGroupByIndex(index).length;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
tabController = TabController(
|
||||||
|
length: chapters.groups.length,
|
||||||
|
vsync: this,
|
||||||
|
initialIndex: index - 1,
|
||||||
|
);
|
||||||
|
initialGroupName = chapters.groups.elementAt(index - 1);
|
||||||
|
var epIndexAtGroup = widget.reader.chapter - 1;
|
||||||
|
for (var i = 0; i < index-1; i++) {
|
||||||
|
epIndexAtGroup -= chapters.getGroupByIndex(i).length;
|
||||||
|
}
|
||||||
|
_scrollController = ScrollController(
|
||||||
|
initialScrollOffset: (epIndexAtGroup * 48.0).clamp(0, double.infinity),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Appbar(title: Text("Chapters".tl)),
|
||||||
|
AppTabBar(
|
||||||
|
controller: tabController,
|
||||||
|
tabs: chapters.groups.map((e) => Tab(text: e)).toList(),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TabViewBody(
|
||||||
|
controller: tabController,
|
||||||
|
children: chapters.groups.map(buildGroup).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildGroup(String groupName) {
|
||||||
|
var group = chapters.getGroup(groupName);
|
||||||
|
return SmoothCustomScrollView(
|
||||||
|
controller: initialGroupName == groupName ? _scrollController : null,
|
||||||
|
slivers: [
|
||||||
|
SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
var name = group.values.elementAt(index);
|
||||||
|
var i = 0;
|
||||||
|
for (var g in chapters.groups) {
|
||||||
|
if (g == groupName) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += chapters.getGroup(g).length;
|
||||||
|
}
|
||||||
|
i += index + 1;
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
widget.reader.toChapter(i);
|
||||||
|
context.pop();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 48,
|
||||||
|
padding: const EdgeInsets.only(left: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
left: BorderSide(
|
||||||
|
color: widget.reader.chapter == i
|
||||||
|
? context.colorScheme.primary
|
||||||
|
: Colors.transparent,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
name,
|
||||||
|
style: widget.reader.chapter == i
|
||||||
|
? ts.withColor(context.colorScheme.primary).bold.s16
|
||||||
|
: ts.s16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
childCount: group.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter_7zip/flutter_7zip.dart';
|
import 'package:flutter_7zip/flutter_7zip.dart';
|
||||||
import 'package:venera/foundation/app.dart';
|
import 'package:venera/foundation/app.dart';
|
||||||
|
import 'package:venera/foundation/comic_source/comic_source.dart';
|
||||||
import 'package:venera/foundation/comic_type.dart';
|
import 'package:venera/foundation/comic_type.dart';
|
||||||
import 'package:venera/foundation/local.dart';
|
import 'package:venera/foundation/local.dart';
|
||||||
import 'package:venera/utils/ext.dart';
|
import 'package:venera/utils/ext.dart';
|
||||||
@@ -176,7 +177,7 @@ abstract class CBZ {
|
|||||||
tags: metaData.tags,
|
tags: metaData.tags,
|
||||||
comicType: ComicType.local,
|
comicType: ComicType.local,
|
||||||
directory: dest.name,
|
directory: dest.name,
|
||||||
chapters: cpMap,
|
chapters: ComicChapters.fromJson(cpMap),
|
||||||
downloadedChapters: cpMap?.keys.toList() ?? [],
|
downloadedChapters: cpMap?.keys.toList() ?? [],
|
||||||
cover: 'cover.${coverFile.extension}',
|
cover: 'cover.${coverFile.extension}',
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
|
@@ -3,6 +3,7 @@ import 'dart:math';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:venera/components/components.dart';
|
import 'package:venera/components/components.dart';
|
||||||
import 'package:venera/foundation/app.dart';
|
import 'package:venera/foundation/app.dart';
|
||||||
|
import 'package:venera/foundation/comic_source/comic_source.dart';
|
||||||
import 'package:venera/foundation/comic_type.dart';
|
import 'package:venera/foundation/comic_type.dart';
|
||||||
import 'package:venera/foundation/favorites.dart';
|
import 'package:venera/foundation/favorites.dart';
|
||||||
import 'package:venera/foundation/local.dart';
|
import 'package:venera/foundation/local.dart';
|
||||||
@@ -262,7 +263,9 @@ class ImportComic {
|
|||||||
subtitle: subtitle ?? '',
|
subtitle: subtitle ?? '',
|
||||||
tags: tags ?? [],
|
tags: tags ?? [],
|
||||||
directory: directory.path,
|
directory: directory.path,
|
||||||
chapters: hasChapters ? Map.fromIterables(chapters, chapters) : null,
|
chapters: hasChapters
|
||||||
|
? ComicChapters(Map.fromIterables(chapters, chapters))
|
||||||
|
: null,
|
||||||
cover: coverPath,
|
cover: coverPath,
|
||||||
comicType: ComicType.local,
|
comicType: ComicType.local,
|
||||||
downloadedChapters: chapters,
|
downloadedChapters: chapters,
|
||||||
|
Reference in New Issue
Block a user