v1.0.1
This commit is contained in:
318
lib/modules/comic/detail/comic_detail_controller.dart
Normal file
318
lib/modules/comic/detail/comic_detail_controller.dart
Normal file
@@ -0,0 +1,318 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_dmzj/app/app_constant.dart';
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/app/event_bus.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
import 'package:flutter_dmzj/app/utils.dart';
|
||||
import 'package:flutter_dmzj/models/comic/detail_info.dart';
|
||||
import 'package:flutter_dmzj/models/db/comic_history.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/detail/comic_detail_related_page.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_request.dart';
|
||||
import 'package:flutter_dmzj/requests/user_request.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/services/app_settings_service.dart';
|
||||
import 'package:flutter_dmzj/services/db_service.dart';
|
||||
import 'package:flutter_dmzj/services/user_service.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicDetailControler extends BaseController {
|
||||
final int comicId;
|
||||
ComicDetailControler(this.comicId);
|
||||
|
||||
final ComicRequest request = ComicRequest();
|
||||
final UserRequest userRequest = UserRequest();
|
||||
|
||||
Rx<ComicDetailInfo> detail = Rx<ComicDetailInfo>(ComicDetailInfo.empty());
|
||||
|
||||
var expandDescription = false.obs;
|
||||
|
||||
/// 是否已订阅
|
||||
var subscribeStatus = false.obs;
|
||||
|
||||
/// 是否已收藏
|
||||
/// 收藏是收藏到本地的,订阅是同步到动漫之家服务器的
|
||||
var favorited = false.obs;
|
||||
|
||||
/// 阅读记录
|
||||
Rx<ComicHistory?> history = Rx<ComicHistory?>(null);
|
||||
|
||||
/// 更新漫画记录
|
||||
StreamSubscription<dynamic>? updateComicSubscription;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
updateComicSubscription = EventBus.instance.listen(
|
||||
EventBus.kUpdatedComicHistory,
|
||||
(id) {
|
||||
if (id == comicId) {
|
||||
getHistory();
|
||||
}
|
||||
},
|
||||
);
|
||||
favorited.value = DBService.instance.hasComicFavorited(comicId: comicId);
|
||||
// 从本地读取订阅状态
|
||||
subscribeStatus.value =
|
||||
UserService.instance.subscribedComicIds.contains(comicId);
|
||||
getHistory();
|
||||
loadDetail();
|
||||
loadSubscribeStatus();
|
||||
//updateSubscribeRead();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
void refreshDetail() {
|
||||
getHistory();
|
||||
loadDetail();
|
||||
loadSubscribeStatus();
|
||||
}
|
||||
|
||||
/// 更新订阅的阅读状态
|
||||
void updateSubscribeRead() {
|
||||
try {
|
||||
userRequest.subscribeRead(id: comicId, type: AppConstant.kTypeComic);
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
updateComicSubscription?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void getHistory() {
|
||||
var comicHistory = DBService.instance.getComicHistory(comicId);
|
||||
if (comicHistory != null) {
|
||||
history.value = comicHistory;
|
||||
history.update((val) {});
|
||||
}
|
||||
}
|
||||
|
||||
void refreshV1() async {
|
||||
try {
|
||||
var result =
|
||||
await request.comicDetail(comicId: comicId, priorityV1: true);
|
||||
if (result.volumes.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (result.isHide && AppSettingsService.instance.collectHideComic.value) {
|
||||
favorite();
|
||||
}
|
||||
detail.update((val) {
|
||||
val!.volumes = result.volumes;
|
||||
});
|
||||
} catch (e) {
|
||||
SmartDialog.showToast("无法获取章节");
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载信息
|
||||
void loadDetail() async {
|
||||
try {
|
||||
pageLoadding.value = true;
|
||||
pageError.value = false;
|
||||
var result = await request.comicDetail(comicId: comicId);
|
||||
detail.value = result;
|
||||
if (result.volumes.isEmpty && !result.isHide) {
|
||||
refreshV1();
|
||||
}
|
||||
if (result.isHide && AppSettingsService.instance.collectHideComic.value) {
|
||||
favorite();
|
||||
}
|
||||
} catch (e) {
|
||||
pageError.value = true;
|
||||
errorMsg.value = e.toString();
|
||||
} finally {
|
||||
pageLoadding.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查订阅状态
|
||||
void loadSubscribeStatus() async {
|
||||
try {
|
||||
var result = await userRequest.checkSubscribeStatus(
|
||||
objId: comicId,
|
||||
type: AppConstant.kTypeComic,
|
||||
);
|
||||
subscribeStatus.value = result;
|
||||
if (subscribeStatus.value) {
|
||||
UserService.instance.subscribedComicIds.add(comicId);
|
||||
} else {
|
||||
UserService.instance.subscribedComicIds.remove(comicId);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// 查看评论
|
||||
void comment() {
|
||||
AppNavigator.toComment(objId: comicId, type: AppConstant.kTypeComic);
|
||||
}
|
||||
|
||||
/// 分享
|
||||
void share() {
|
||||
if (detail.value.id == 0) {
|
||||
return;
|
||||
}
|
||||
Utils.share(
|
||||
"http://m.idmzj.com/info/${detail.value.comicPy}.html",
|
||||
content: detail.value.title,
|
||||
);
|
||||
}
|
||||
|
||||
/// 订阅
|
||||
void subscribe() async {
|
||||
var result = await (subscribeStatus.value
|
||||
? UserService.instance
|
||||
.cancelSubscribe([comicId], AppConstant.kTypeComic)
|
||||
: UserService.instance.addSubscribe([comicId], AppConstant.kTypeComic));
|
||||
if (result) {
|
||||
subscribeStatus.value = !subscribeStatus.value;
|
||||
}
|
||||
}
|
||||
|
||||
/// 下载
|
||||
void download() {
|
||||
AppNavigator.toComicDownloadSelect(comicId);
|
||||
}
|
||||
|
||||
/// 开始/继续阅读
|
||||
void read() {
|
||||
if (detail.value.volumes.isEmpty) {
|
||||
SmartDialog.showToast("没有可阅读的章节");
|
||||
return;
|
||||
}
|
||||
if (detail.value.volumes.first.chapters.isEmpty) {
|
||||
SmartDialog.showToast("没有可阅读的章节");
|
||||
return;
|
||||
}
|
||||
//查找记录
|
||||
if (history.value != null && history.value!.chapterId != 0) {
|
||||
ComicDetailVolume? volume;
|
||||
ComicDetailChapterItem? chapter;
|
||||
for (var volumeItem in detail.value.volumes) {
|
||||
var chapterItem = volumeItem.chapters.firstWhereOrNull(
|
||||
(x) => x.chapterId == history.value!.chapterId,
|
||||
);
|
||||
if (chapterItem != null) {
|
||||
volume = volumeItem;
|
||||
chapter = chapterItem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (volume != null && chapter != null) {
|
||||
var chapters = List<ComicDetailChapterItem>.from(volume.chapters);
|
||||
//正序
|
||||
chapters.sort((a, b) => a.chapterOrder.compareTo(b.chapterOrder));
|
||||
AppNavigator.toComicReader(
|
||||
comicId: comicId,
|
||||
comicTitle: detail.value.title,
|
||||
comicCover: detail.value.cover,
|
||||
chapters: chapters,
|
||||
chapter: chapter,
|
||||
isLongComic: detail.value.isLong,
|
||||
);
|
||||
} else {
|
||||
SmartDialog.showToast("未找到历史记录对应章节,将从头开始阅读");
|
||||
readStart();
|
||||
}
|
||||
} else {
|
||||
readStart();
|
||||
}
|
||||
}
|
||||
|
||||
void readStart() {
|
||||
//从头开始
|
||||
var volume = detail.value.volumes.first;
|
||||
var chapters = List<ComicDetailChapterItem>.from(volume.chapters);
|
||||
//正序
|
||||
chapters.sort((a, b) => a.chapterOrder.compareTo(b.chapterOrder));
|
||||
var chapter = chapters.first;
|
||||
AppNavigator.toComicReader(
|
||||
comicId: comicId,
|
||||
comicCover: detail.value.cover,
|
||||
comicTitle: detail.value.title,
|
||||
chapters: chapters,
|
||||
chapter: chapter,
|
||||
isLongComic: detail.value.isLong,
|
||||
);
|
||||
}
|
||||
|
||||
void readChapter(ComicDetailVolume volume, ComicDetailChapterItem item) {
|
||||
//禁止观看VIP章节
|
||||
if (item.isVip) {
|
||||
SmartDialog.showToast("请使用动漫之家官方APP观看VIP章节");
|
||||
return;
|
||||
}
|
||||
var chapters = List<ComicDetailChapterItem>.from(volume.chapters);
|
||||
//正序
|
||||
chapters.sort((a, b) => a.chapterOrder.compareTo(b.chapterOrder));
|
||||
AppNavigator.toComicReader(
|
||||
comicId: comicId,
|
||||
comicCover: detail.value.cover,
|
||||
comicTitle: detail.value.title,
|
||||
chapters: chapters,
|
||||
chapter: item,
|
||||
isLongComic: detail.value.isLong,
|
||||
);
|
||||
}
|
||||
|
||||
void related() async {
|
||||
try {
|
||||
SmartDialog.showLoading();
|
||||
var data = await request.related(id: comicId);
|
||||
SmartDialog.dismiss(status: SmartStatus.loading);
|
||||
AppNavigator.showBottomSheet(
|
||||
ComicDetailRelatedPage(data),
|
||||
isScrollControlled: true,
|
||||
);
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
} finally {
|
||||
SmartDialog.dismiss(status: SmartStatus.loading);
|
||||
}
|
||||
}
|
||||
|
||||
void toAuthorDetail(ComicDetailTag e) {
|
||||
if (e.tagId == 0) {
|
||||
//神隐漫画没有ID,直接跳转搜索
|
||||
AppNavigator.toComicSearch(keyword: e.tagName);
|
||||
} else {
|
||||
AppNavigator.toComicAuthorDetail(e.tagId);
|
||||
}
|
||||
}
|
||||
|
||||
void toCategoryDetail(ComicDetailTag e) {
|
||||
if (e.tagId == 0) {
|
||||
//神隐漫画没有ID,直接跳转搜索
|
||||
AppNavigator.toComicSearch(keyword: e.tagName);
|
||||
} else {
|
||||
AppNavigator.toComicCategoryDetail(e.tagId);
|
||||
}
|
||||
}
|
||||
|
||||
void favorite() {
|
||||
if (detail.value.id == 0) {
|
||||
return;
|
||||
}
|
||||
if (!DBService.instance.hasComicFavorited(comicId: comicId)) {
|
||||
DBService.instance.putComicFavorite(
|
||||
comicId: comicId,
|
||||
title: detail.value.title,
|
||||
cover: detail.value.cover,
|
||||
);
|
||||
favorited.value = true;
|
||||
SmartDialog.showToast("已将漫画添加至本地收藏");
|
||||
}
|
||||
}
|
||||
|
||||
void cancelFavorite() {
|
||||
DBService.instance.removeComicFavorite(comicId: comicId);
|
||||
favorited.value = false;
|
||||
SmartDialog.showToast("已从本地收藏删除漫画");
|
||||
}
|
||||
}
|
||||
533
lib/modules/comic/detail/comic_detail_page.dart
Normal file
533
lib/modules/comic/detail/comic_detail_page.dart
Normal file
@@ -0,0 +1,533 @@
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_color.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/app/utils.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/detail/comic_detail_controller.dart';
|
||||
import 'package:flutter_dmzj/widgets/net_image.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 ComicDetailPage extends StatelessWidget {
|
||||
final int id;
|
||||
final ComicDetailControler controller;
|
||||
ComicDetailPage(this.id, {super.key})
|
||||
: controller = Get.put(
|
||||
ComicDetailControler(id),
|
||||
tag: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Obx(
|
||||
() => Text(
|
||||
controller.detail.value.title.isEmpty
|
||||
? "漫画详情"
|
||||
: controller.detail.value.title,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Obx(
|
||||
() => IconButton(
|
||||
onPressed: controller.favorited.value
|
||||
? controller.cancelFavorite
|
||||
: controller.favorite,
|
||||
icon: Icon(controller.favorited.value
|
||||
? Remix.star_fill
|
||||
: Remix.star_line),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: controller.share,
|
||||
icon: const Icon(Icons.share),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: controller.detail.value.id == 0,
|
||||
child: EasyRefresh(
|
||||
header: const MaterialHeader(),
|
||||
onRefresh: controller.refreshDetail,
|
||||
child: ListView(
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
children: [
|
||||
_buildHeader(),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: controller.history.value == null,
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
"上次看到:${controller.history.value?.chapterName ?? ""} 第${controller.history.value?.page}页",
|
||||
style: Get.textTheme.titleSmall,
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
controller.read();
|
||||
},
|
||||
),
|
||||
Divider(
|
||||
color: Colors.grey.withOpacity(.2),
|
||||
height: 1.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildChapter(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageLoadding.value,
|
||||
child: const AppLoaddingWidget(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageError.value,
|
||||
child: AppErrorWidget(
|
||||
errorMsg: controller.errorMsg.value,
|
||||
onRefresh: () => controller.loadDetail(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
elevation: 2,
|
||||
onPressed: controller.read,
|
||||
child: const Icon(Icons.play_circle_outline_rounded),
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
),
|
||||
onPressed: controller.subscribe,
|
||||
icon: Icon(
|
||||
controller.subscribeStatus.value
|
||||
? Remix.heart_fill
|
||||
: Remix.heart_line,
|
||||
size: 20,
|
||||
),
|
||||
label: Text(controller.subscribeStatus.value ? "取消" : "订阅"),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
),
|
||||
onPressed: controller.comment,
|
||||
icon: const Icon(
|
||||
Remix.chat_2_line,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text("评论"),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
),
|
||||
onPressed: controller.download,
|
||||
icon: const Icon(
|
||||
Remix.download_line,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text("下载"),
|
||||
),
|
||||
),
|
||||
// Expanded(
|
||||
// child: TextButton.icon(
|
||||
// style: TextButton.styleFrom(
|
||||
// textStyle: const TextStyle(fontSize: 14),
|
||||
// ),
|
||||
// onPressed: controller.related,
|
||||
// icon: const Icon(
|
||||
// Remix.links_line,
|
||||
// size: 20,
|
||||
// ),
|
||||
// label: const Text("相关"),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
//信息
|
||||
Stack(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
NetImage(
|
||||
controller.detail.value.cover,
|
||||
width: 120,
|
||||
height: 160,
|
||||
borderRadius: 4,
|
||||
),
|
||||
AppStyle.hGap12,
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
controller.detail.value.title,
|
||||
style: Get.textTheme.titleMedium,
|
||||
),
|
||||
AppStyle.vGap8,
|
||||
_buildInfoItems(
|
||||
iconData: Remix.user_smile_line,
|
||||
children: controller.detail.value.authors
|
||||
.map(
|
||||
(e) => GestureDetector(
|
||||
onTap: () => controller.toAuthorDetail(e),
|
||||
child: Text(
|
||||
e.tagName,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
height: 1.2,
|
||||
decoration: TextDecoration.underline,
|
||||
color: Get.isDarkMode
|
||||
? Colors.white
|
||||
: AppColor.black333,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
|
||||
// _buildInfo(
|
||||
// title: controller.detail.value.types
|
||||
// .map((e) => e.tagName)
|
||||
// .join("/"),
|
||||
// iconData: Remix.hashtag,
|
||||
// ),
|
||||
_buildInfoItems(
|
||||
iconData: Remix.hashtag,
|
||||
children: controller.detail.value.types
|
||||
.map(
|
||||
(e) => GestureDetector(
|
||||
onTap: () => controller.toCategoryDetail(e),
|
||||
child: Text(
|
||||
e.tagName,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
height: 1.2,
|
||||
decoration: TextDecoration.underline,
|
||||
color: Get.isDarkMode
|
||||
? Colors.white
|
||||
: AppColor.black333,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
// _buildInfo(
|
||||
// title: "人气 ${controller.detail.value.hitNum}",
|
||||
// iconData: Remix.fire_line,
|
||||
// ),
|
||||
// _buildInfo(
|
||||
// title: "订阅 ${controller.detail.value.subscribeNum}",
|
||||
// iconData: Remix.heart_line,
|
||||
// ),
|
||||
_buildInfo(
|
||||
title:
|
||||
"${Utils.formatTimestampToDate(controller.detail.value.lastUpdatetime)} ${controller.detail.value.status.map((e) => e.tagName).join("/")}",
|
||||
iconData: Icons.schedule,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Obx(
|
||||
() => Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: Offstage(
|
||||
offstage: !controller.detail.value.isVip,
|
||||
child: Image.asset(
|
||||
"assets/images/vip_comic.png",
|
||||
width: 36,
|
||||
height: 36,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
controller.expandDescription.value =
|
||||
!controller.expandDescription.value;
|
||||
},
|
||||
child: Text(
|
||||
controller.detail.value.description,
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLines: controller.expandDescription.value ? 999 : 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
Divider(
|
||||
color: Colors.grey.withOpacity(.2),
|
||||
height: 1.0,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChapter() {
|
||||
return Column(
|
||||
children: controller.detail.value.volumes.isEmpty
|
||||
? [
|
||||
const Padding(
|
||||
padding: AppStyle.edgeInsetsA24,
|
||||
child: Text(
|
||||
"(~ ̄▽ ̄)~\n没有可阅读的章节\n漫画可能已下架或您没有阅读的权限",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey, fontSize: 14),
|
||||
),
|
||||
)
|
||||
]
|
||||
: controller.detail.value.volumes
|
||||
.map(
|
||||
(item) => 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Tooltip(
|
||||
message: item.chapters[i].chapterTitle,
|
||||
child: Obx(
|
||||
() => Stack(
|
||||
children: [
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: item
|
||||
.chapters[i].chapterId ==
|
||||
controller
|
||||
.history.value?.chapterId
|
||||
? Theme.of(ctx).colorScheme.primary
|
||||
: Get.textTheme.bodyMedium!.color,
|
||||
textStyle:
|
||||
const TextStyle(fontSize: 14),
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize:
|
||||
const Size.fromHeight(40),
|
||||
),
|
||||
onPressed: () {
|
||||
controller.readChapter(
|
||||
item, item.chapters[i]);
|
||||
},
|
||||
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,
|
||||
),
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfo({
|
||||
required String title,
|
||||
IconData iconData = Icons.tag,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
iconData,
|
||||
color: Colors.grey,
|
||||
size: 16,
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Get.isDarkMode ? Colors.white : AppColor.black333,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoItems({
|
||||
required List<Widget> children,
|
||||
IconData iconData = Icons.tag,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
iconData,
|
||||
color: Colors.grey,
|
||||
size: 16,
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
164
lib/modules/comic/detail/comic_detail_related_page.dart
Normal file
164
lib/modules/comic/detail/comic_detail_related_page.dart
Normal file
@@ -0,0 +1,164 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/models/comic/comic_related_model.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/widgets/net_image.dart';
|
||||
import 'package:flutter_dmzj/widgets/shadow_card.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicDetailRelatedPage extends StatelessWidget {
|
||||
final ComicRelatedModel related;
|
||||
const ComicDetailRelatedPage(this.related, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text("作品相关"),
|
||||
trailing: IconButton(
|
||||
onPressed: () {
|
||||
AppNavigator.closePage();
|
||||
},
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
contentPadding: AppStyle.edgeInsetsL12,
|
||||
),
|
||||
const Divider(
|
||||
height: 1,
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: AppStyle.edgeInsetsA12.copyWith(top: 0),
|
||||
children: [
|
||||
...related.authorComics
|
||||
.map(
|
||||
(e) =>
|
||||
buildCard("${e.authorName}的其他作品", e.data, onTap: () {
|
||||
AppNavigator.toComicAuthorDetail(e.authorId);
|
||||
}),
|
||||
)
|
||||
.toList(),
|
||||
buildCard("同类题材作品", related.themeComics),
|
||||
buildCard("轻小说", related.novels, isComic: false),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildCard(String title, List<ComicRelatedItemModel> list,
|
||||
{Function()? onTap, bool isComic = true}) {
|
||||
return Visibility(
|
||||
visible: list.isNotEmpty,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsV8,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: Get.textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: onTap != null,
|
||||
child: IconButton(
|
||||
onPressed: onTap,
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
LayoutBuilder(builder: (ctx, constraints) {
|
||||
var count = constraints.maxWidth ~/ 160;
|
||||
if (count < 3) count = 3;
|
||||
|
||||
return MasonryGridView.count(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: list.length,
|
||||
crossAxisCount: count,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
itemBuilder: (_, i) {
|
||||
var item = list[i];
|
||||
return ShadowCard(
|
||||
onTap: () {
|
||||
if (isComic) {
|
||||
AppNavigator.toComicDetail(item.id);
|
||||
} else {
|
||||
AppNavigator.toNovelDetail(item.id);
|
||||
}
|
||||
},
|
||||
radius: 4,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 27 / 36,
|
||||
child: NetImage(
|
||||
item.cover,
|
||||
borderRadius: 4,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: item.status == "连载中"
|
||||
? Get.theme.colorScheme.primary
|
||||
: Colors.orange,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(4),
|
||||
bottomLeft: Radius.circular(4),
|
||||
),
|
||||
),
|
||||
padding: AppStyle.edgeInsetsH8
|
||||
.copyWith(top: 2, bottom: 2),
|
||||
child: Text(
|
||||
item.status,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
AppStyle.vGap4,
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsA4,
|
||||
child: Text(
|
||||
item.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user