v1.0.1
This commit is contained in:
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user