This commit is contained in:
2026-03-07 17:24:59 +08:00
parent 4418ebecac
commit b0ec8ab4bd
417 changed files with 42546 additions and 2 deletions

View File

@@ -0,0 +1,147 @@
import 'package:flutter_dmzj/app/controller/base_controller.dart';
import 'package:flutter_dmzj/models/comic/detail_info.dart';
import 'package:flutter_dmzj/requests/comic_request.dart';
import 'package:flutter_dmzj/routes/app_navigator.dart';
import 'package:flutter_dmzj/services/comic_download_service.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class ComicSelectChapterController extends BaseController {
final int comicId;
ComicSelectChapterController(this.comicId);
final ComicRequest request = ComicRequest();
RxList<ComicDetailVolume> volumes = RxList<ComicDetailVolume>();
RxSet<int> chapterIds = RxSet<int>();
String comicTitle = "";
String comicCover = "";
bool islong = false;
@override
void onInit() {
loadDetail();
super.onInit();
}
void refreshV1() async {
try {
var result =
await request.comicDetail(comicId: comicId, priorityV1: true);
if (result.volumes.isEmpty) {
SmartDialog.showToast("没有找到任何章节");
return;
}
comicTitle = result.title;
comicCover = result.cover;
islong = result.isLong;
for (var volume in result.volumes) {
volume.sortType.value = 1;
volume.sort();
}
volumes.value = result.volumes;
} catch (e) {
SmartDialog.showToast("无法获取章节");
}
}
/// 加载信息
void loadDetail() async {
try {
pageLoadding.value = true;
pageError.value = false;
var result = await request.comicDetail(comicId: comicId);
comicTitle = result.title;
comicCover = result.cover;
islong = result.isLong;
if (result.volumes.isEmpty && !result.isHide) {
refreshV1();
} else {
for (var volume in result.volumes) {
volume.sortType.value = 1;
volume.sort();
}
volumes.value = result.volumes;
}
} catch (e) {
pageError.value = true;
errorMsg.value = e.toString();
} finally {
pageLoadding.value = false;
}
}
void selectItem(ComicDetailChapterItem item) {
//禁止下载VIP章节
if (item.isVip) {
SmartDialog.showToast("请使用动漫之家官方APP下载VIP章节");
return;
}
if (chapterIds.contains(item.chapterId)) {
chapterIds.remove(item.chapterId);
} else {
chapterIds.add(item.chapterId);
}
}
void selectAll() {
for (var volume in volumes) {
for (var chapter in volume.chapters) {
if (chapter.isVip) {
continue;
}
var id = "${comicId}_${chapter.chapterId}";
if (!ComicDownloadService.instance.downloadIds.contains(id)) {
chapterIds.add(chapter.chapterId);
}
}
}
}
void cleanAll() {
chapterIds.clear();
}
void toDownloadManage() {
AppNavigator.toComicDownloadManage(1);
}
void startDownload() {
if (chapterIds.isEmpty) {
SmartDialog.showToast("请选择需要下载的章节");
return;
}
for (var id in chapterIds) {
//搜索章节
ComicDetailVolume? volume;
ComicDetailChapterItem? chapter;
for (var item in volumes) {
var chapterItem =
item.chapters.firstWhereOrNull((y) => y.chapterId == id);
if (chapterItem != null) {
volume = item;
chapter = chapterItem;
break;
}
}
if (volume == null || chapter == null) {
continue;
}
ComicDownloadService.instance.addTask(
comicId: comicId,
chapterId: chapter.chapterId,
chapterSort: chapter.chapterOrder,
volumeName: volume.title,
comicTitle: comicTitle,
comicCover: comicCover,
chapterName: chapter.chapterTitle,
isVip: chapter.isVip,
isLongComic: islong,
);
}
chapterIds.clear();
SmartDialog.showToast("已添加到下载列表下载过程中请保持APP在前台运行");
}
}

View File

@@ -0,0 +1,259 @@
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/models/comic/detail_info.dart';
import 'package:flutter_dmzj/modules/comic/select_chapter/comic_select_chapter_controller.dart';
import 'package:flutter_dmzj/services/comic_download_service.dart';
import 'package:flutter_dmzj/widgets/status/app_error_widget.dart';
import 'package:flutter_dmzj/widgets/status/app_loadding_widget.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:get/get.dart';
import 'package:remixicon/remixicon.dart';
class ComicSelectChapterPage extends StatelessWidget {
final int comicId;
final ComicSelectChapterController controller;
ComicSelectChapterPage(this.comicId, {super.key})
: controller = Get.put(
ComicSelectChapterController(comicId),
tag: DateTime.now().millisecondsSinceEpoch.toString(),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("选择下载章节"),
actions: [
TextButton(
onPressed: controller.toDownloadManage,
child: const Text("下载管理"),
),
],
),
body: Stack(
children: [
EasyRefresh(
header: const MaterialHeader(),
onRefresh: controller.loadDetail,
child: _buildVolumes(),
),
Obx(
() => Offstage(
offstage: !controller.pageLoadding.value,
child: const AppLoaddingWidget(),
),
),
Obx(
() => Offstage(
offstage: !controller.pageError.value,
child: AppErrorWidget(
errorMsg: controller.errorMsg.value,
onRefresh: () => controller.loadDetail(),
),
),
),
],
),
bottomNavigationBar: BottomAppBar(
child: SizedBox(
height: 48,
child: Row(
children: [
Expanded(
child: TextButton.icon(
style: TextButton.styleFrom(
textStyle: const TextStyle(fontSize: 14),
),
onPressed: controller.selectAll,
icon: const Icon(
Remix.checkbox_line,
size: 20,
),
label: const Text("全选"),
),
),
Expanded(
child: TextButton.icon(
style: TextButton.styleFrom(
textStyle: const TextStyle(fontSize: 14),
),
onPressed: controller.cleanAll,
icon: const Icon(
Remix.checkbox_blank_line,
size: 20,
),
label: const Text("取消选中"),
),
),
Expanded(
child: TextButton.icon(
style: TextButton.styleFrom(
textStyle: const TextStyle(fontSize: 14),
),
onPressed: controller.startDownload,
icon: const Icon(
Remix.download_line,
size: 20,
),
label:
Obx(() => Text("下载选中(${controller.chapterIds.length})")),
),
),
],
),
),
),
);
}
Widget _buildVolumes() {
return Obx(
() => ListView.builder(
padding: AppStyle.edgeInsetsA12,
itemCount: controller.volumes.length,
itemBuilder: (_, i) {
var item = controller.volumes[i];
return _buildChapters(item);
},
),
);
}
Widget _buildChapters(ComicDetailVolume item) {
return Obx(
() => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: AppStyle.edgeInsetsV8,
child: Row(
children: [
Expanded(
child: Text(
"${item.title}(共${item.chapters.length}话)",
style: Get.textTheme.titleSmall,
),
),
item.sortType.value == 1
? TextButton.icon(
style: TextButton.styleFrom(
textStyle: const TextStyle(fontSize: 14),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
item.sortType.value = 0;
item.sort();
},
icon: const Icon(
Remix.sort_asc,
size: 20,
),
label: const Text("升序"),
)
: TextButton.icon(
style: TextButton.styleFrom(
textStyle: const TextStyle(fontSize: 14),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
item.sortType.value = 1;
item.sort();
},
icon: const Icon(
Remix.sort_desc,
size: 20,
),
label: const Text("倒序"),
),
],
),
),
LayoutBuilder(builder: (ctx, constraints) {
var count = constraints.maxWidth ~/ 160;
if (count < 3) count = 3;
return Obx(
() => MasonryGridView.count(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
itemCount: (item.showMoreButton && !item.showAll.value)
? 15
: item.chapters.length,
itemBuilder: (_, i) {
if (item.showMoreButton && !item.showAll.value && i == 14) {
return Tooltip(
message: "展开全部章节",
child: OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: Colors.grey,
textStyle: const TextStyle(fontSize: 14),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
minimumSize: const Size.fromHeight(40),
),
onPressed: () {
item.showAll.value = true;
},
child: const Icon(Icons.arrow_drop_down),
),
);
}
var chapter = item.chapters[i];
return Tooltip(
message: chapter.chapterTitle,
child: Obx(
() => Stack(
children: [
OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: controller.chapterIds
.contains(chapter.chapterId)
? Get.theme.colorScheme.primary
: Get.textTheme.bodyMedium!.color,
side: controller.chapterIds
.contains(chapter.chapterId)
? BorderSide(color: Get.theme.colorScheme.primary)
: null,
textStyle: const TextStyle(fontSize: 14),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
minimumSize: const Size.fromHeight(40),
),
onPressed: ComicDownloadService.instance.downloadIds
.contains("${comicId}_${chapter.chapterId}")
? null
: () => controller.selectItem(chapter),
child: Text(
item.chapters[i].chapterTitle,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
),
),
Positioned(
left: -2,
top: 0,
child: Offstage(
offstage: !item.chapters[i].isVip,
child: Image.asset(
"assets/images/vip_chapter.png",
height: 16,
),
),
),
],
),
),
);
},
crossAxisCount: count,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
);
})
],
),
);
}
}