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,19 @@
import 'package:flutter_dmzj/app/controller/base_controller.dart';
import 'package:flutter_dmzj/models/comment/user_comment_item.dart';
import 'package:flutter_dmzj/requests/comment_request.dart';
class UserCommentController extends BasePageController<UserCommentItem> {
final int type;
final int userId;
UserCommentController({required this.type, required this.userId});
final CommentRequest request = CommentRequest();
@override
Future<List<UserCommentItem>> getData(int page, int pageSize) async {
return await request.getUserComment(
type: type,
uid: userId,
page: page - 1,
);
}
}

View File

@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/modules/user/comment/user_comment_view.dart';
import 'package:get/get.dart';
class UserCommentPage extends StatelessWidget {
final int userId;
const UserCommentPage(this.userId, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(right: 56),
child: TabBar(
isScrollable: true,
tabAlignment: TabAlignment.start,
labelPadding: AppStyle.edgeInsetsH24,
indicatorColor: Theme.of(context).colorScheme.primary,
labelColor: Theme.of(context).colorScheme.primary,
unselectedLabelColor:
Get.isDarkMode ? Colors.white70 : Colors.black87,
tabs: const [
Tab(text: "漫画"),
Tab(text: "小说"),
Tab(text: "新闻"),
],
),
),
),
body: TabBarView(
children: [
UserCommentView(type: 0, userId: userId),
UserCommentView(type: 1, userId: userId),
UserCommentView(type: 2, userId: userId),
],
),
),
);
}
}

View File

@@ -0,0 +1,162 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/app/utils.dart';
import 'package:flutter_dmzj/models/comment/user_comment_item.dart';
import 'package:flutter_dmzj/modules/user/comment/user_comment_controller.dart';
import 'package:flutter_dmzj/routes/app_navigator.dart';
import 'package:flutter_dmzj/widgets/keep_alive_wrapper.dart';
import 'package:flutter_dmzj/widgets/net_image.dart';
import 'package:flutter_dmzj/widgets/page_list_view.dart';
import 'package:get/get.dart';
import 'package:remixicon/remixicon.dart';
class UserCommentView extends StatelessWidget {
final int type;
final int userId;
final UserCommentController controller;
UserCommentView({
required this.type,
required this.userId,
Key? key,
}) : controller = Get.put(
UserCommentController(
type: type,
userId: userId,
),
tag: "${userId}_$type",
),
super(key: key);
@override
Widget build(BuildContext context) {
return KeepAliveWrapper(
child: PageListView(
pageController: controller,
firstRefresh: true,
separatorBuilder: (context, i) => Divider(
endIndent: 12,
indent: 12,
color: Colors.grey.withOpacity(.2),
height: 1,
),
itemBuilder: (context, i) {
var item = controller.list[i];
//TODO 跳转评论详情
return Container(
padding: AppStyle.edgeInsetsA12,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: () {
toDetail(item);
},
child: NetImage(
item.objCover,
width: 60,
borderRadius: 4,
),
),
AppStyle.hGap12,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item.objName,
),
AppStyle.vGap8,
Container(
padding: AppStyle.edgeInsetsA8,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(.1),
borderRadius: AppStyle.radius4,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(item.content),
Visibility(
visible: item.mastercomment != null,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.withOpacity(.1),
borderRadius: AppStyle.radius4,
),
padding: AppStyle.edgeInsetsA4,
margin: AppStyle.edgeInsetsV4,
child: Text(
"${item.mastercomment?.nickname}${item.mastercomment?.content}",
style: const TextStyle(
fontSize: 14,
),
),
),
),
AppStyle.vGap4,
Row(
children: [
const Icon(
Remix.thumb_up_line,
color: Colors.grey,
size: 14,
),
AppStyle.hGap4,
Text(
"${item.likeAmount}",
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
AppStyle.hGap12,
const Icon(
Remix.message_2_line,
color: Colors.grey,
size: 14,
),
AppStyle.hGap4,
Text(
"${item.likeAmount}",
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
const Expanded(child: SizedBox()),
Text(
Utils.formatTimestamp(item.createTime),
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
],
),
),
],
),
),
],
),
);
},
),
);
}
void toDetail(UserCommentItem item) {
//漫画
if (type == 0) {
AppNavigator.toComicDetail(item.objId);
} else if (type == 1) {
AppNavigator.toNovelDetail(item.objId);
} else if (type == 2) {
AppNavigator.toNewsDetail(url: item.pageUrl ?? "");
}
}
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter_dmzj/app/controller/base_controller.dart';
import 'package:flutter_dmzj/models/user/comic_history_model.dart';
import 'package:flutter_dmzj/requests/user_request.dart';
class ComicHistoryController extends BasePageController<UserComicHistoryModel> {
final UserRequest request = UserRequest();
@override
Future<List<UserComicHistoryModel>> getData(int page, int pageSize) async {
if (page > 1) {
return [];
}
return await request.comicHistory();
}
}

View File

@@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/app/utils.dart';
import 'package:flutter_dmzj/models/user/comic_history_model.dart';
import 'package:flutter_dmzj/modules/user/history/comic/comic_history_controller.dart';
import 'package:flutter_dmzj/routes/app_navigator.dart';
import 'package:flutter_dmzj/widgets/keep_alive_wrapper.dart';
import 'package:flutter_dmzj/widgets/net_image.dart';
import 'package:flutter_dmzj/widgets/page_list_view.dart';
import 'package:get/get.dart';
class ComicHistoryView extends StatelessWidget {
final ComicHistoryController controller;
ComicHistoryView({super.key})
: controller = Get.put(ComicHistoryController());
@override
Widget build(BuildContext context) {
return KeepAliveWrapper(
child: PageListView(
pageController: controller,
firstRefresh: true,
loadMore: false,
separatorBuilder: (context, i) => Divider(
endIndent: 12,
indent: 12,
color: Colors.grey.withOpacity(.2),
height: 1,
),
itemBuilder: (context, i) {
var item = controller.list[i];
return buildItem(item);
},
),
);
}
Widget buildItem(UserComicHistoryModel item) {
return InkWell(
onTap: () {
AppNavigator.toComicDetail(item.comicId);
},
child: Container(
padding: AppStyle.edgeInsetsA12,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
NetImage(
item.cover,
width: 80,
height: 110,
borderRadius: 4,
),
AppStyle.hGap12,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
item.comicName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
AppStyle.vGap4,
Text("看到${item.chapterName} ${item.record}",
style: const TextStyle(color: Colors.grey, fontSize: 14)),
AppStyle.vGap4,
Text("观看于${Utils.formatTimestamp(item.viewingTime ?? 0)}",
style: const TextStyle(color: Colors.grey, fontSize: 14)),
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter_dmzj/app/controller/base_controller.dart';
import 'package:flutter_dmzj/models/user/novel_history_model.dart';
import 'package:flutter_dmzj/requests/user_request.dart';
class NovelHistoryController extends BasePageController<UserNovelHistoryModel> {
final UserRequest request = UserRequest();
@override
Future<List<UserNovelHistoryModel>> getData(int page, int pageSize) async {
if (page > 1) {
return [];
}
return await request.novelHistory();
}
}

View File

@@ -0,0 +1,79 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/app/utils.dart';
import 'package:flutter_dmzj/models/user/novel_history_model.dart';
import 'package:flutter_dmzj/modules/user/history/novel/novel_history_controller.dart';
import 'package:flutter_dmzj/routes/app_navigator.dart';
import 'package:flutter_dmzj/widgets/keep_alive_wrapper.dart';
import 'package:flutter_dmzj/widgets/net_image.dart';
import 'package:flutter_dmzj/widgets/page_list_view.dart';
import 'package:get/get.dart';
class NovelHistoryView extends StatelessWidget {
final NovelHistoryController controller;
NovelHistoryView({super.key})
: controller = Get.put(NovelHistoryController());
@override
Widget build(BuildContext context) {
return KeepAliveWrapper(
child: PageListView(
pageController: controller,
firstRefresh: true,
loadMore: false,
separatorBuilder: (context, i) => Divider(
endIndent: 12,
indent: 12,
color: Colors.grey.withOpacity(.2),
height: 1,
),
itemBuilder: (context, i) {
var item = controller.list[i];
return buildItem(item);
},
),
);
}
Widget buildItem(UserNovelHistoryModel item) {
return InkWell(
onTap: () {
AppNavigator.toNovelDetail(item.lnovelId);
},
child: Container(
padding: AppStyle.edgeInsetsA12,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
NetImage(
item.cover,
width: 80,
height: 110,
borderRadius: 4,
),
AppStyle.hGap12,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
item.novelName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
AppStyle.vGap4,
Text(
"看到${item.volumeName} ${item.chapterName} ${item.record}",
style: const TextStyle(color: Colors.grey, fontSize: 14)),
AppStyle.vGap4,
Text("观看于${Utils.formatTimestamp(item.viewingTime ?? 0)}",
style: const TextStyle(color: Colors.grey, fontSize: 14)),
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class UserHistoryController extends GetxController
with GetSingleTickerProviderStateMixin {
final int type;
UserHistoryController(this.type);
late TabController tabController;
@override
void onInit() {
tabController = TabController(length: 2, vsync: this, initialIndex: type);
super.onInit();
}
}

View File

@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/modules/user/history/comic/comic_history_view.dart';
import 'package:flutter_dmzj/modules/user/history/novel/novel_history_view.dart';
import 'package:flutter_dmzj/modules/user/history/user_history_controller.dart';
import 'package:get/get.dart';
class UserHistoryPage extends StatelessWidget {
final UserHistoryController controller;
final int type;
UserHistoryPage({this.type = 0, super.key})
: controller = Get.put(
UserHistoryController(type),
tag: DateTime.now().millisecondsSinceEpoch.toString(),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(right: 56),
child: TabBar(
controller: controller.tabController,
isScrollable: true,
tabAlignment: TabAlignment.start,
labelPadding: AppStyle.edgeInsetsH24,
indicatorColor: Theme.of(context).colorScheme.primary,
labelColor: Theme.of(context).colorScheme.primary,
unselectedLabelColor:
Get.isDarkMode ? Colors.white70 : Colors.black87,
tabs: const [
Tab(text: "漫画记录"),
Tab(text: "小说记录"),
],
),
),
),
body: TabBarView(
controller: controller.tabController,
children: [
ComicHistoryView(),
NovelHistoryView(),
],
),
);
}
}

View File

@@ -0,0 +1,39 @@
import 'package:flutter_dmzj/app/app_constant.dart';
import 'package:flutter_dmzj/app/controller/base_controller.dart';
import 'package:flutter_dmzj/models/db/local_favorite.dart';
import 'package:flutter_dmzj/services/db_service.dart';
import 'package:get/get.dart';
class LocalFavoriteController extends BasePageController<LocalFavorite> {
var editMode = false.obs;
@override
Future<List<LocalFavorite>> getData(int page, int pageSize) async {
if (page > 1) {
return [];
}
return DBService.instance.localFavoriteBox.values
.where((x) => x.type == AppConstant.kTypeComic)
.toList();
}
void cancelEdit() {
for (var item in list) {
item.isChecked.value = false;
}
editMode.value = false;
}
void cancelFavorite() async {
var items = list.where((x) => x.isChecked.value).toList();
if (items.isEmpty) {
cancelEdit();
return;
}
cancelEdit();
for (var item in items) {
DBService.instance.removeComicFavorite(comicId: item.objId);
}
refreshData();
}
}

View File

@@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/models/db/local_favorite.dart';
import 'package:flutter_dmzj/modules/user/local_favorite/local_favorite_controller.dart';
import 'package:flutter_dmzj/routes/app_navigator.dart';
import 'package:flutter_dmzj/widgets/net_image.dart';
import 'package:flutter_dmzj/widgets/page_grid_view.dart';
import 'package:flutter_dmzj/widgets/shadow_card.dart';
import 'package:get/get.dart';
class LocalFavoritePage extends StatelessWidget {
final LocalFavoriteController controller;
LocalFavoritePage({super.key})
: controller = Get.put(LocalFavoriteController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("本机收藏"),
),
body: LayoutBuilder(builder: (context, constraints) {
var count = constraints.maxWidth ~/ 160;
if (count < 3) count = 3;
return PageGridView(
pageController: controller,
firstRefresh: true,
crossAxisCount: count,
padding: AppStyle.edgeInsetsA12,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
itemBuilder: (context, i) {
var item = controller.list[i];
return buildItem(item);
},
);
}),
bottomNavigationBar: Obx(
() => Offstage(
offstage: !controller.editMode.value,
child: SizedBox(
height: 48,
child: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton.icon(
onPressed: controller.cancelFavorite,
icon: const Icon(Icons.favorite_border),
label: const Text("取消收藏"),
),
AppStyle.hGap8,
TextButton.icon(
onPressed: controller.cancelEdit,
icon: const Icon(Icons.cancel_outlined),
label: const Text("取消"),
),
],
),
),
),
),
),
);
}
Widget buildItem(LocalFavorite item) {
return ShadowCard(
onTap: () {
if (controller.editMode.value) {
item.isChecked.value = !item.isChecked.value;
return;
}
AppNavigator.toComicDetail(item.objId);
},
onLongPress: () {
if (controller.editMode.value) {
return;
}
item.isChecked.value = true;
controller.editMode.value = true;
},
radius: 4,
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 27 / 36,
child: NetImage(
item.cover,
borderRadius: 4,
),
),
Padding(
padding: AppStyle.edgeInsetsA8,
child: Text(
item.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
height: 1.2,
),
),
),
],
),
Obx(
() => Positioned(
right: 0,
top: 0,
child: Offstage(
offstage: !controller.editMode.value,
child: Checkbox(
value: item.isChecked.value,
onChanged: (e) {
item.isChecked.value = e!;
},
),
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,17 @@
import 'package:flutter_dmzj/app/controller/base_controller.dart';
import 'package:flutter_dmzj/models/db/comic_history.dart';
import 'package:flutter_dmzj/requests/user_request.dart';
import 'package:flutter_dmzj/services/db_service.dart';
class LocalComicHistoryController extends BasePageController<ComicHistory> {
final UserRequest request = UserRequest();
@override
Future<List<ComicHistory>> getData(int page, int pageSize) async {
if (page > 1) {
return [];
}
return DBService.instance.getComicHistoryList();
}
}

View File

@@ -0,0 +1,79 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/app/utils.dart';
import 'package:flutter_dmzj/models/db/comic_history.dart';
import 'package:flutter_dmzj/modules/user/local_history/comic/comic_history_controller.dart';
import 'package:flutter_dmzj/routes/app_navigator.dart';
import 'package:flutter_dmzj/widgets/keep_alive_wrapper.dart';
import 'package:flutter_dmzj/widgets/net_image.dart';
import 'package:flutter_dmzj/widgets/page_list_view.dart';
import 'package:get/get.dart';
class LocalComicHistoryView extends StatelessWidget {
final LocalComicHistoryController controller;
LocalComicHistoryView({super.key})
: controller = Get.put(LocalComicHistoryController());
@override
Widget build(BuildContext context) {
return KeepAliveWrapper(
child: PageListView(
pageController: controller,
firstRefresh: true,
loadMore: false,
separatorBuilder: (context, i) => Divider(
endIndent: 12,
indent: 12,
color: Colors.grey.withOpacity(.2),
height: 1,
),
itemBuilder: (context, i) {
var item = controller.list[i];
return buildItem(item);
},
),
);
}
Widget buildItem(ComicHistory item) {
return InkWell(
onTap: () {
AppNavigator.toComicDetail(item.comicId);
},
child: Container(
padding: AppStyle.edgeInsetsA12,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
NetImage(
item.comicCover,
width: 80,
height: 110,
borderRadius: 4,
),
AppStyle.hGap12,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
item.comicName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
AppStyle.vGap4,
Text("看到${item.chapterName} ${item.page}",
style: const TextStyle(color: Colors.grey, fontSize: 14)),
AppStyle.vGap4,
Text(
"观看于${Utils.formatTimestampMS(item.updateTime.millisecondsSinceEpoch)}",
style: const TextStyle(color: Colors.grey, fontSize: 14)),
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class LocalHistoryController extends GetxController
with GetSingleTickerProviderStateMixin {
final int type;
LocalHistoryController(this.type);
late TabController tabController;
@override
void onInit() {
tabController = TabController(length: 2, vsync: this, initialIndex: type);
super.onInit();
}
}

View File

@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/modules/user/local_history/comic/comic_history_view.dart';
import 'package:flutter_dmzj/modules/user/local_history/local_history_controller.dart';
import 'package:flutter_dmzj/modules/user/local_history/novel/novel_history_view.dart';
import 'package:get/get.dart';
class LocalHistoryPage extends StatelessWidget {
final LocalHistoryController controller;
final int type;
LocalHistoryPage({this.type = 0, super.key})
: controller = Get.put(
LocalHistoryController(type),
tag: DateTime.now().millisecondsSinceEpoch.toString(),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(right: 56),
child: TabBar(
controller: controller.tabController,
isScrollable: true,
tabAlignment: TabAlignment.start,
labelPadding: AppStyle.edgeInsetsH24,
indicatorColor: Theme.of(context).colorScheme.primary,
indicatorSize: TabBarIndicatorSize.label,
labelColor: Theme.of(context).colorScheme.primary,
unselectedLabelColor:
Get.isDarkMode ? Colors.white70 : Colors.black87,
tabs: const [
Tab(text: "漫画记录"),
Tab(text: "小说记录"),
],
),
),
),
body: TabBarView(
controller: controller.tabController,
children: [
LocalComicHistoryView(),
LocalNovelHistoryView(),
],
),
);
}
}

View File

@@ -0,0 +1,17 @@
import 'package:flutter_dmzj/app/controller/base_controller.dart';
import 'package:flutter_dmzj/models/db/novel_history.dart';
import 'package:flutter_dmzj/requests/user_request.dart';
import 'package:flutter_dmzj/services/db_service.dart';
class LocalNovelHistoryController extends BasePageController<NovelHistory> {
final UserRequest request = UserRequest();
@override
Future<List<NovelHistory>> getData(int page, int pageSize) async {
if (page > 1) {
return [];
}
return DBService.instance.getNovelHistoryList();
}
}

View File

@@ -0,0 +1,79 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/app/utils.dart';
import 'package:flutter_dmzj/models/db/novel_history.dart';
import 'package:flutter_dmzj/modules/user/local_history/novel/novel_history_controller.dart';
import 'package:flutter_dmzj/routes/app_navigator.dart';
import 'package:flutter_dmzj/widgets/keep_alive_wrapper.dart';
import 'package:flutter_dmzj/widgets/net_image.dart';
import 'package:flutter_dmzj/widgets/page_list_view.dart';
import 'package:get/get.dart';
class LocalNovelHistoryView extends StatelessWidget {
final LocalNovelHistoryController controller;
LocalNovelHistoryView({super.key})
: controller = Get.put(LocalNovelHistoryController());
@override
Widget build(BuildContext context) {
return KeepAliveWrapper(
child: PageListView(
pageController: controller,
firstRefresh: true,
loadMore: false,
separatorBuilder: (context, i) => Divider(
endIndent: 12,
indent: 12,
color: Colors.grey.withOpacity(.2),
height: 1,
),
itemBuilder: (context, i) {
var item = controller.list[i];
return buildItem(item);
},
),
);
}
Widget buildItem(NovelHistory item) {
return InkWell(
onTap: () {
AppNavigator.toNovelDetail(item.novelId);
},
child: Container(
padding: AppStyle.edgeInsetsA12,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
NetImage(
item.novelCover,
width: 80,
height: 110,
borderRadius: 4,
),
AppStyle.hGap12,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
item.novelName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
AppStyle.vGap4,
Text("看到${item.volumeName} ${item.chapterName}",
style: const TextStyle(color: Colors.grey, fontSize: 14)),
AppStyle.vGap4,
Text(
"观看于${Utils.formatTimestampMS(item.updateTime.millisecondsSinceEpoch)}",
style: const TextStyle(color: Colors.grey, fontSize: 14)),
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/log.dart';
import 'package:flutter_dmzj/requests/user_request.dart';
import 'package:flutter_dmzj/services/user_service.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class UserLoginController extends GetxController {
final TextEditingController userNameController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final UserRequest userRequest = UserRequest();
var loadding = false.obs;
void login() async {
if (userNameController.text.isEmpty) {
SmartDialog.showToast("请输入用户名");
return;
}
if (passwordController.text.isEmpty) {
SmartDialog.showToast("请输入密码");
return;
}
try {
loadding.value = true;
var data = await userRequest.login(
nickname: userNameController.text,
password: passwordController.text,
);
UserService.instance.setAuthInfo(data);
loadding.value = false;
Get.back(result: true);
} catch (e) {
SmartDialog.showToast(e.toString());
Log.logPrint(e);
} finally {
loadding.value = false;
}
}
}

View File

@@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/modules/user/login/user_login_controller.dart';
import 'package:get/get.dart';
class UserLoginDialog extends StatelessWidget {
final UserLoginController controller = Get.put(UserLoginController());
UserLoginDialog({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: AppStyle.radius12,
),
child: Container(
constraints: const BoxConstraints(
maxWidth: 400,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
contentPadding: AppStyle.edgeInsetsL12,
title: const Text("登录"),
trailing: IconButton(
onPressed: Get.back,
icon: const Icon(Icons.close),
),
),
AppStyle.vGap12,
Padding(
padding: AppStyle.edgeInsetsH24,
child: TextField(
controller: controller.userNameController,
autofocus: true,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
hintText: "请输入用户名/手机号",
labelText: "用户名/手机号",
floatingLabelBehavior: FloatingLabelBehavior.always,
contentPadding: AppStyle.edgeInsetsH8,
border: OutlineInputBorder(),
),
),
),
AppStyle.vGap24,
Padding(
padding: AppStyle.edgeInsetsH24,
child: TextField(
controller: controller.passwordController,
obscureText: true,
textInputAction: TextInputAction.done,
decoration: const InputDecoration(
hintText: "请输入密码",
labelText: "密码",
floatingLabelBehavior: FloatingLabelBehavior.always,
contentPadding: AppStyle.edgeInsetsH8,
border: OutlineInputBorder(),
),
onSubmitted: (e) {
controller.login();
},
),
),
AppStyle.vGap12,
Container(
width: double.infinity,
padding: AppStyle.edgeInsetsA12.copyWith(left: 24, right: 24),
child: SizedBox(
height: 40,
child: Obx(
() => ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: AppStyle.radius24,
),
),
onPressed:
controller.loadding.value ? null : controller.login,
child: controller.loadding.value
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
color: Colors.white,
),
)
: const Text("登录"),
),
),
),
),
AppStyle.vGap12,
],
),
),
);
}
}

View File

@@ -0,0 +1,97 @@
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/services/app_settings_service.dart';
import 'package:flutter_dmzj/services/local_storage_service.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class SettingsController extends GetxController {
final settings = AppSettingsService.instance;
var imageCacheSize = "正在计算缓存...".obs;
var novelCacheSize = "正在计算缓存...".obs;
@override
void onInit() {
super.onInit();
getImageCachedSize();
getNovelCachedSize();
}
void getImageCachedSize() async {
try {
imageCacheSize.value = "正在计算缓存...";
var bytes = await getCachedSizeBytes();
imageCacheSize.value = "${(bytes / 1024 / 1024).toStringAsFixed(1)}MB";
} catch (e) {
imageCacheSize.value = "缓存计算失败";
}
}
void getNovelCachedSize() async {
try {
novelCacheSize.value = "正在计算缓存...";
var bytes = await LocalStorageService.instance.getNovelCacheSize();
novelCacheSize.value = "${(bytes / 1024 / 1024).toStringAsFixed(1)}MB";
} catch (e) {
novelCacheSize.value = "缓存计算失败";
}
}
void cleanImageCache() async {
var result = await clearDiskCachedImages();
if (!result) {
SmartDialog.showToast("清除失败");
}
getImageCachedSize();
}
void cleanNovelCache() async {
var result = await LocalStorageService.instance.cleanNovelCacheSize();
if (!result) {
SmartDialog.showToast("清除失败");
}
getNovelCachedSize();
}
void setDownloadComicTask() {
Get.dialog(
SimpleDialog(
title: const Text("漫画最大任务数"),
children: [0, 1, 2, 3, 4, 5]
.map(
(e) => RadioListTile<int>(
title: Text(e == 0 ? "无限制" : "$e个"),
value: e,
groupValue: settings.downloadComicTaskCount.value,
onChanged: (e) {
Get.back();
settings.setDownloadComicTaskCount(e ?? 0);
},
),
)
.toList(),
),
);
}
void setDownloadNovelTask() {
Get.dialog(
SimpleDialog(
title: const Text("小说最大任务数"),
children: [0, 1, 2, 3, 4, 5]
.map(
(e) => RadioListTile<int>(
title: Text(e == 0 ? "无限制" : "$e个"),
value: e,
groupValue: settings.downloadNovelTaskCount.value,
onChanged: (e) {
Get.back();
settings.setDownloadNovelTaskCount(e ?? 0);
},
),
)
.toList(),
),
);
}
}

View File

@@ -0,0 +1,529 @@
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart' as fluent;
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/platform_utils.dart';
import 'package:flutter_dmzj/modules/user/settings/settings_controller.dart';
import 'package:get/get.dart';
import 'package:remixicon/remixicon.dart';
class SettingsPage extends StatelessWidget {
final int index;
SettingsPage({required this.index, super.key});
final controller = Get.put<SettingsController>(SettingsController());
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 4,
initialIndex: index,
child: Scaffold(
appBar: AppBar(
title: Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(right: 56),
child: TabBar(
isScrollable: true,
tabAlignment: TabAlignment.start,
indicatorSize: TabBarIndicatorSize.label,
indicatorColor: Theme.of(context).colorScheme.primary,
labelColor: Theme.of(context).colorScheme.primary,
unselectedLabelColor:
Get.isDarkMode ? Colors.white70 : Colors.black87,
tabs: const [
Tab(text: "常规"),
Tab(text: "漫画"),
Tab(text: "小说"),
Tab(text: "下载"),
],
),
),
),
body: TabBarView(
children: [
buildGeneralSettings(),
buildComicSettings(),
buildNovelSettings(),
buildDownloadSettings(),
],
),
),
);
}
Widget buildGeneralSettings() {
return Obx(
() => ListView(
padding: AppStyle.edgeInsetsA12,
children: [
buildToggle(
value: controller.settings.useDynamicColor.value,
onChanged: (e) {
controller.settings.setUseDynamicColor(e);
},
title: "使用MD动态取色",
subtitle: "关闭后使用固定主题色 #4196f9",
),
ListTile(
title: const Text("清除图片缓存"),
subtitle: Text(controller.imageCacheSize.value),
trailing: OutlinedButton(
onPressed: () {
controller.cleanImageCache();
},
child: const Text("清除"),
),
),
ListTile(
title: const Text("清除小说缓存"),
subtitle: Text(controller.novelCacheSize.value),
trailing: OutlinedButton(
onPressed: () {},
child: const Text("清除"),
),
),
// SwitchListTile(
// value: controller.settings.comicSearchUseWebApi.value,
// onChanged: (e) {
// controller.settings.setComicSearchUseWebApi(e);
// },
// title: const Text("使用Web接口搜索漫画"),
// subtitle: const Text("开启后可以搜索到更多漫画"),
// ),
buildToggle(
value: controller.settings.useSystemFontSize.value,
onChanged: (e) {
controller.settings.setUseSystemFontSize(e);
},
title: "字体大小跟随系统",
subtitle: "开启可能会有布局错乱",
),
buildToggle(
value: controller.settings.collectHideComic.value,
onChanged: (e) {
controller.settings.setCollectHideComic(e);
},
title: "自动收藏神隐漫画",
subtitle: "浏览神隐漫画时自动添加到本机收藏",
),
ListTile(
title: const Text("代理地址"),
subtitle: TextField(
controller: TextEditingController(text: controller.settings.proxyAddress.value),
decoration: const InputDecoration(
hintText: "仅支持http协议,重启生效 eg:127.0.0.1:7890",
),
onSubmitted: (e){
controller.settings.setProxyAddress(e);
},
),
)
],
),
);
}
Widget buildComicSettings() {
return Obx(
() => ListView(
padding: AppStyle.edgeInsetsA12,
children: [
buildToggle(
value: controller.settings.comicReaderHD.value,
onChanged: (e) {
controller.settings.setComicReaderHD(e);
},
title: "优先加载高清图",
subtitle: "部分单行本可能未分页",
),
ListTile(
title: const Text("阅读方向"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
buildSelectedButton(
onTap: () {
controller.settings.setComicReaderDirection(0);
},
selected: controller.settings.comicReaderDirection.value == 0,
child: const Icon(Remix.arrow_right_line),
),
AppStyle.hGap8,
buildSelectedButton(
onTap: () {
controller.settings.setComicReaderDirection(2);
},
selected: controller.settings.comicReaderDirection.value == 2,
child: const Icon(Remix.arrow_left_line),
),
AppStyle.hGap8,
buildSelectedButton(
onTap: () {
controller.settings.setComicReaderDirection(1);
},
selected: controller.settings.comicReaderDirection.value == 1,
child: const Icon(Remix.arrow_down_line),
)
],
),
),
buildToggle(
value: controller.settings.comicReaderLeftHandMode.value,
onChanged: (e) {
controller.settings.setComicReaderLeftHandMode(e);
},
title: "操作反转",
subtitle: "点击左侧下一页,右侧上一页",
),
buildToggle(
value: controller.settings.comicReaderFullScreen.value,
onChanged: (e) {
controller.settings.setComicReaderFullScreen(e);
},
title: "全屏阅读",
),
buildToggle(
value: controller.settings.comicReaderShowStatus.value,
onChanged: (e) {
controller.settings.setComicReaderShowStatus(e);
},
title: "显示状态信息",
),
buildToggle(
value: controller.settings.comicReaderShowViewPoint.value,
onChanged: (e) {
controller.settings.setComicReaderShowViewPoint(e);
},
title: "显示吐槽",
),
buildToggle(
value: controller.settings.comicReaderOldViewPoint.value,
onChanged: (e) {
controller.settings.setComicReaderOldViewPoint(e);
},
title: "旧版吐槽",
),
buildToggle(
value: controller.settings.comicReaderPageAnimation.value,
onChanged: (e) {
controller.settings.setComicReaderPageAnimation(e);
},
title: "翻页动画",
),
],
),
);
}
Widget buildNovelSettings() {
return Obx(
() => ListView(
padding: AppStyle.edgeInsetsA12,
children: [
ListTile(
title: const Text("阅读方向"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
buildSelectedButton(
onTap: () {
controller.settings.setNovelReaderDirection(0);
},
selected: controller.settings.novelReaderDirection.value == 0,
child: const Icon(Remix.arrow_right_line),
),
AppStyle.hGap8,
buildSelectedButton(
onTap: () {
controller.settings.setNovelReaderDirection(2);
},
selected: controller.settings.novelReaderDirection.value == 2,
child: const Icon(Remix.arrow_left_line),
),
AppStyle.hGap8,
buildSelectedButton(
onTap: () {
controller.settings.setNovelReaderDirection(1);
},
selected: controller.settings.novelReaderDirection.value == 1,
child: const Icon(Remix.arrow_down_line),
)
],
),
),
buildToggle(
value: controller.settings.novelReaderLeftHandMode.value,
onChanged: (e) {
controller.settings.setNovelReaderLeftHandMode(e);
},
title: "操作反转",
subtitle: "点击左侧下一页,右侧上一页",
),
// SwitchListTile(
// value: settings.novelReaderFullScreen.value,
// onChanged: (e) {
// settings.setNovelReaderFullScreen(e);
// },
// title: const Text("全屏阅读"),
// ),
buildToggle(
value: controller.settings.novelReaderShowStatus.value,
onChanged: (e) {
controller.settings.setNovelReaderShowStatus(e);
},
title: "显示状态信息",
),
buildToggle(
value: controller.settings.novelReaderPageAnimation.value,
onChanged: (e) {
controller.settings.setNovelReaderPageAnimation(e);
},
title: "翻页动画",
),
ListTile(
title: const Text("字体大小"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
OutlinedButton(
onPressed: () {
controller.settings.setNovelReaderFontSize(
controller.settings.novelReaderFontSize.value + 1,
);
},
child: const Icon(
Icons.add,
),
),
AppStyle.hGap12,
Text("${controller.settings.novelReaderFontSize.value}"),
AppStyle.hGap12,
OutlinedButton(
onPressed: () {
controller.settings.setNovelReaderFontSize(
controller.settings.novelReaderFontSize.value - 1,
);
},
child: const Icon(
Icons.remove,
),
),
],
),
),
ListTile(
title: const Text("行距"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
OutlinedButton(
onPressed: () {
controller.settings.setNovelReaderLineSpacing(
controller.settings.novelReaderLineSpacing.value + 0.1,
);
},
child: const Icon(
Icons.add,
),
),
AppStyle.hGap12,
Text((controller.settings.novelReaderLineSpacing.value)
.toStringAsFixed(1)),
AppStyle.hGap12,
OutlinedButton(
onPressed: () {
controller.settings.setNovelReaderLineSpacing(
controller.settings.novelReaderLineSpacing.value - 0.1,
);
},
child: const Icon(
Icons.remove,
),
),
],
),
),
ListTile(
title: const Text("阅读主题"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: AppColor.novelThemes.keys
.map(
(e) => GestureDetector(
onTap: () {
controller.settings.setNovelReaderTheme(e);
},
child: Container(
margin: AppStyle.edgeInsetsL8,
height: 36,
width: 36,
decoration: BoxDecoration(
color: AppColor.novelThemes[e]!.first,
borderRadius: AppStyle.radius24,
),
child: Visibility(
visible:
AppColor.novelThemes.keys.toList().indexOf(e) ==
controller.settings.novelReaderTheme.value,
child: Icon(
Icons.check,
color: AppColor.novelThemes[e]!.last,
),
),
),
),
)
.toList(),
),
),
Container(
margin: AppStyle.edgeInsetsV12,
padding: AppStyle.edgeInsetsA8,
decoration: BoxDecoration(
borderRadius: AppStyle.radius4,
color: AppColor
.novelThemes[controller.settings.novelReaderTheme]!.first,
),
child: Text(
"""这是一段测试文字,可以预览上面的设置效果。
  晋太元中,武陵人捕鱼为业。缘溪行,忘路之远近。忽逢桃花林,夹岸数百步,中无杂树,芳草鲜美,落英缤纷。渔人甚异之,复前行,欲穷其林。
  林尽水源,便得一山,山有小口,仿佛若有光。便舍船,从口入。初极狭,才通人。复行数十步,豁然开朗。土地平旷,屋舍俨然,有良田、美池、桑竹之属。阡陌交通,鸡犬相闻。其中往来种作,男女衣着,悉如外人。黄发垂髫,并怡然自乐……""",
//不需要跟随系统
textScaler: const TextScaler.linear(1.0),
style: TextStyle(
fontSize:
controller.settings.novelReaderFontSize.value.toDouble(),
height: controller.settings.novelReaderLineSpacing.value,
color: AppColor
.novelThemes[controller.settings.novelReaderTheme]!.last,
),
),
),
],
),
);
}
Widget buildDownloadSettings() {
return Obx(
() => ListView(
padding: AppStyle.edgeInsetsA12,
children: [
buildToggle(
value: controller.settings.downloadAllowCellular.value,
onChanged: (e) {
controller.settings.setDownloadAllowCellular(e);
},
title: "允许使用流量下载",
),
ListTile(
title: const Text("漫画最大任务数"),
onTap: () {
controller.setDownloadComicTask();
},
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
controller.settings.downloadComicTaskCount.value == 0
? "无限制"
: controller.settings.downloadComicTaskCount.toString(),
),
AppStyle.hGap4,
const Icon(
Icons.chevron_right,
color: Colors.grey,
),
],
),
),
ListTile(
title: const Text("小说最大任务数"),
onTap: () {
controller.setDownloadNovelTask();
},
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
controller.settings.downloadNovelTaskCount.value == 0
? "无限制"
: controller.settings.downloadNovelTaskCount.toString(),
),
AppStyle.hGap4,
const Icon(
Icons.chevron_right,
color: Colors.grey,
),
],
),
),
],
),
);
}
Widget buildSelectedButton(
{required Widget child, bool selected = false, Function()? onTap}) {
final primary = Get.theme.colorScheme.primary;
return OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: selected ? primary : Colors.grey,
side: BorderSide(
color: selected ? primary : Colors.grey,
),
),
onPressed: onTap,
child: child,
);
}
/// 平台自适应开关控件
/// Windows使用Fluent ToggleSwitch其他平台使用Material SwitchListTile
Widget buildToggle({
required String title,
required bool value,
required ValueChanged<bool> onChanged,
String? subtitle,
}) {
if (PlatformUtils.isWindows) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: Get.textTheme.bodyMedium),
if (subtitle != null)
Text(subtitle,
style: Get.textTheme.bodySmall
?.copyWith(color: Colors.grey)),
],
),
),
fluent.FluentTheme(
data: PlatformUtils.getFluentTheme(Get.context!),
child: fluent.ToggleSwitch(
checked: value,
onChanged: onChanged,
),
),
],
),
);
}
return SwitchListTile(
value: value,
onChanged: onChanged,
title: Text(title),
subtitle: subtitle != null ? Text(subtitle) : null,
);
}
}

View File

@@ -0,0 +1,78 @@
import 'package:flutter_dmzj/app/app_constant.dart';
import 'package:flutter_dmzj/app/controller/base_controller.dart';
import 'package:flutter_dmzj/models/user/subscribe_comic_model.dart';
import 'package:flutter_dmzj/requests/user_request.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 ComicSubscribeController
extends BasePageController<UserSubscribeComicItemModel> {
ComicSubscribeController() {
for (var item in List.generate(
26, (index) => String.fromCharCode(index + 65).toLowerCase())) {
letters.addAll({item: "${item.toUpperCase()}开头"});
}
}
final UserRequest request = UserRequest();
var letter = "".obs;
Map letters = {
"": "全部",
"number": "数字开头",
};
Map<int, String> types = {
1: "全部订阅",
2: "未读",
3: "已读",
4: "完结",
};
var type = 1.obs;
var editMode = false.obs;
@override
Future<List<UserSubscribeComicItemModel>> getData(
int page, int pageSize) async {
var ls = await request.comicSubscribes(
subType: type.value,
letter: letter.value,
page: page,
);
UserService.instance.subscribedComicIds.addAll(ls.map((e) => e.id));
return ls;
}
void cancelEdit() {
for (var item in list) {
item.isChecked.value = false;
}
editMode.value = false;
}
void cancelSub() async {
var ids = list.where((x) => x.isChecked.value).map((e) => e.id).toList();
if (ids.isEmpty) {
cancelEdit();
return;
}
cancelEdit();
await UserService.instance.cancelSubscribe(ids, AppConstant.kTypeComic);
easyRefreshController.callRefresh();
}
void addFavorite() async {
for (var item in list.where((x) => x.isChecked.value)) {
DBService.instance.putComicFavorite(
title: item.title,
cover: item.cover,
comicId: item.id,
);
}
cancelEdit();
SmartDialog.showToast("已添加至本机收藏");
}
}

View File

@@ -0,0 +1,273 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/models/user/subscribe_comic_model.dart';
import 'package:flutter_dmzj/modules/user/subscribe/comic/comic_subscribe_controller.dart';
import 'package:flutter_dmzj/routes/app_navigator.dart';
import 'package:flutter_dmzj/widgets/keep_alive_wrapper.dart';
import 'package:flutter_dmzj/widgets/net_image.dart';
import 'package:flutter_dmzj/widgets/page_grid_view.dart';
import 'package:flutter_dmzj/widgets/shadow_card.dart';
import 'package:get/get.dart';
class ComicSubscribeView extends StatelessWidget {
final ComicSubscribeController controller;
ComicSubscribeView({super.key})
: controller = Get.put(ComicSubscribeController());
@override
Widget build(BuildContext context) {
return KeepAliveWrapper(
child: Column(
children: [
Obx(
() => Row(
children: [
buildFilter(
// ignore: invalid_use_of_protected_member
types: controller.letters,
value: controller.letter.value,
onSelected: (e) {
controller.letter.value = e;
controller.refreshData();
},
),
buildFilter(
types: controller.types,
value: controller.type.value,
onSelected: (e) {
controller.type.value = e;
controller.refreshData();
},
),
],
),
),
Divider(
color: Colors.grey.withOpacity(.2),
height: 1.0,
),
Expanded(
child: LayoutBuilder(builder: (context, constraints) {
var count = constraints.maxWidth ~/ 160;
if (count < 3) count = 3;
return PageGridView(
pageController: controller,
firstRefresh: true,
crossAxisCount: count,
padding: AppStyle.edgeInsetsA12,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
itemBuilder: (context, i) {
var item = controller.list[i];
return buildItem(item);
},
);
}),
),
Obx(
() => Offstage(
offstage: !controller.editMode.value,
child: SizedBox(
height: 48,
child: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton.icon(
onPressed: controller.addFavorite,
icon: const Icon(Icons.star_border),
label: const Text("添加收藏"),
),
AppStyle.hGap8,
TextButton.icon(
onPressed: controller.cancelSub,
icon: const Icon(Icons.favorite_border),
label: const Text("取消订阅"),
),
AppStyle.hGap8,
TextButton.icon(
onPressed: controller.cancelEdit,
icon: const Icon(Icons.cancel_outlined),
label: const Text("取消"),
),
],
),
),
),
),
),
],
),
);
}
Widget buildItem(UserSubscribeComicItemModel item) {
return ShadowCard(
onTap: () {
if (controller.editMode.value) {
item.isChecked.value = !item.isChecked.value;
return;
}
item.hasNew.value = false;
AppNavigator.toComicDetail(item.id);
},
onLongPress: () {
if (controller.editMode.value) {
return;
}
item.isChecked.value = true;
controller.editMode.value = true;
},
radius: 4,
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
AspectRatio(
aspectRatio: 27 / 36,
child: NetImage(
item.cover,
borderRadius: 4,
),
),
Positioned(
left: 0,
bottom: 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,
),
),
),
),
Positioned(
right: 0,
top: 0,
child: Obx(
() => Visibility(
visible: item.hasNew.value,
child: Container(
decoration: const BoxDecoration(
color: Colors.deepOrange,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(4),
topRight: Radius.circular(4),
),
),
padding:
AppStyle.edgeInsetsH8.copyWith(top: 2, bottom: 2),
child: const Text(
"",
style: TextStyle(
fontSize: 12,
color: Colors.white,
),
),
),
),
),
),
],
),
AppStyle.vGap4,
Padding(
padding: AppStyle.edgeInsetsH4,
child: Text(
item.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
height: 1.2,
),
),
),
AppStyle.vGap4,
Padding(
padding: AppStyle.edgeInsetsH4,
child: Text(
"更新 ${item.lastUpdateChapterName}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.grey,
fontSize: 12.0,
height: 1.2,
),
),
),
AppStyle.vGap4,
],
),
Obx(
() => Positioned(
right: 0,
top: 0,
child: Offstage(
offstage: !controller.editMode.value,
child: Checkbox(
value: item.isChecked.value,
onChanged: (e) {
item.isChecked.value = e!;
},
),
),
),
),
],
),
);
}
Widget buildFilter({
required Map types,
required dynamic value,
required Function(dynamic) onSelected,
}) {
return Expanded(
child: PopupMenuButton(
onSelected: onSelected,
itemBuilder: (c) => types.keys
.map(
(k) => CheckedPopupMenuItem(
value: k,
checked: k == value,
child: Text(types[k] ?? ""),
),
)
.toList(),
child: SizedBox(
height: 40,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
types[value] ?? "",
),
const Icon(
Icons.arrow_drop_down,
color: Colors.grey,
)
],
),
),
),
);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter_dmzj/app/controller/base_controller.dart';
import 'package:flutter_dmzj/models/user/subscribe_news_model.dart';
import 'package:flutter_dmzj/requests/user_request.dart';
class NewsSubscribeController
extends BasePageController<UserSubscribeNewsModel> {
final UserRequest request = UserRequest();
@override
Future<List<UserSubscribeNewsModel>> getData(int page, int pageSize) async {
return await request.newsSubscribes(
page: page,
);
}
}

View File

@@ -0,0 +1,118 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/app/utils.dart';
import 'package:flutter_dmzj/modules/user/subscribe/news/news_subscribe_controller.dart';
import 'package:flutter_dmzj/routes/app_navigator.dart';
import 'package:flutter_dmzj/widgets/keep_alive_wrapper.dart';
import 'package:flutter_dmzj/widgets/net_image.dart';
import 'package:flutter_dmzj/widgets/page_list_view.dart';
import 'package:get/get.dart';
class NewsSubscribeView extends StatelessWidget {
final NewsSubscribeController controller;
NewsSubscribeView({super.key})
: controller = Get.put(NewsSubscribeController());
@override
Widget build(BuildContext context) {
return KeepAliveWrapper(
child: PageListView(
pageController: controller,
firstRefresh: true,
separatorBuilder: (context, i) => Divider(
endIndent: 12,
indent: 12,
color: Colors.grey.withOpacity(.2),
height: 1,
),
itemBuilder: (context, i) {
var item = controller.list[i];
return InkWell(
onTap: () {
AppNavigator.toNewsDetail(
newsId: item.subId.toInt(),
title: item.title,
url: item.pageUrl,
);
},
child: Container(
padding: AppStyle.edgeInsetsA12,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
NetImage(
item.rowPicUrl,
width: 100,
height: 62,
borderRadius: 4,
),
AppStyle.hGap12,
Expanded(
child: SizedBox(
height: 62,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Text(
item.title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"收藏于${Utils.formatTimestamp(item.subTime.toInt())}",
style: const TextStyle(
color: Colors.grey, fontSize: 12),
),
Row(
children: <Widget>[
const Icon(
Icons.thumb_up,
size: 12.0,
color: Colors.grey,
),
AppStyle.hGap4,
Text(
item.moodAmount.toString(),
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
),
),
AppStyle.hGap8,
const Icon(
Icons.chat,
size: 12.0,
color: Colors.grey,
),
AppStyle.hGap4,
Text(
item.commentAmount.toString(),
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
),
)
],
)
],
)
],
),
),
),
],
),
),
);
},
),
);
}
}

View File

@@ -0,0 +1,62 @@
import 'package:flutter_dmzj/app/app_constant.dart';
import 'package:flutter_dmzj/app/controller/base_controller.dart';
import 'package:flutter_dmzj/models/user/subscribe_novel_model.dart';
import 'package:flutter_dmzj/requests/user_request.dart';
import 'package:flutter_dmzj/services/user_service.dart';
import 'package:get/get.dart';
class NovelSubscribeController
extends BasePageController<UserSubscribeNovelModel> {
NovelSubscribeController() {
for (var item in List.generate(
26, (index) => String.fromCharCode(index + 65).toLowerCase())) {
letters.addAll({item: "${item.toUpperCase()}开头"});
}
}
final UserRequest request = UserRequest();
var letter = "".obs;
Map letters = {
"": "全部",
"number": "数字开头",
};
Map<int, String> types = {
1: "全部订阅",
2: "未读",
3: "已读",
4: "完结",
};
var type = 1.obs;
@override
Future<List<UserSubscribeNovelModel>> getData(int page, int pageSize) async {
var ls = await request.novelSubscribes(
subType: type.value,
letter: letter.value,
page: page - 1,
);
UserService.instance.subscribedNovelIds.addAll(ls.map((e) => e.id));
return ls;
}
var editMode = false.obs;
void cancelEdit() {
for (var item in list) {
item.isChecked.value = false;
}
editMode.value = false;
}
void cancelSub() async {
var ids = list.where((x) => x.isChecked.value).map((e) => e.id).toList();
if (ids.isEmpty) {
cancelEdit();
return;
}
cancelEdit();
await UserService.instance.cancelSubscribe(ids, AppConstant.kTypeNovel);
easyRefreshController.callRefresh();
}
}

View File

@@ -0,0 +1,267 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/models/user/subscribe_novel_model.dart';
import 'package:flutter_dmzj/modules/user/subscribe/novel/novel_subscribe_controller.dart';
import 'package:flutter_dmzj/routes/app_navigator.dart';
import 'package:flutter_dmzj/widgets/keep_alive_wrapper.dart';
import 'package:flutter_dmzj/widgets/net_image.dart';
import 'package:flutter_dmzj/widgets/page_grid_view.dart';
import 'package:flutter_dmzj/widgets/shadow_card.dart';
import 'package:get/get.dart';
class NovelSubscribeView extends StatelessWidget {
final NovelSubscribeController controller;
NovelSubscribeView({super.key})
: controller = Get.put(NovelSubscribeController());
@override
Widget build(BuildContext context) {
return KeepAliveWrapper(
child: Column(
children: [
Obx(
() => Row(
children: [
buildFilter(
// ignore: invalid_use_of_protected_member
types: controller.letters,
value: controller.letter.value,
onSelected: (e) {
controller.letter.value = e;
controller.refreshData();
},
),
buildFilter(
types: controller.types,
value: controller.type.value,
onSelected: (e) {
controller.type.value = e;
controller.refreshData();
},
),
],
),
),
Divider(
color: Colors.grey.withOpacity(.2),
height: 1.0,
),
Expanded(
child: LayoutBuilder(builder: (context, constraints) {
var count = constraints.maxWidth ~/ 160;
if (count < 3) count = 3;
return PageGridView(
pageController: controller,
firstRefresh: true,
crossAxisCount: count,
padding: AppStyle.edgeInsetsA12,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
itemBuilder: (context, i) {
var item = controller.list[i];
return buildItem(item);
},
);
}),
),
Obx(
() => Offstage(
offstage: !controller.editMode.value,
child: SizedBox(
height: 48,
child: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton.icon(
onPressed: controller.cancelSub,
icon: const Icon(Icons.favorite_border),
label: const Text("取消订阅"),
),
TextButton.icon(
onPressed: controller.cancelEdit,
icon: const Icon(Icons.cancel_outlined),
label: const Text("取消"),
),
],
),
),
),
),
),
],
),
);
}
Widget buildItem(UserSubscribeNovelModel item) {
return ShadowCard(
onTap: () {
if (controller.editMode.value) {
item.isChecked.value = !item.isChecked.value;
return;
}
item.hasNew.value = false;
AppNavigator.toNovelDetail(item.id);
},
onLongPress: () {
if (controller.editMode.value) {
return;
}
item.isChecked.value = true;
controller.editMode.value = true;
},
radius: 4,
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
AspectRatio(
aspectRatio: 27 / 36,
child: NetImage(
item.cover ?? "",
borderRadius: 4,
),
),
Positioned(
left: 0,
bottom: 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,
),
),
),
),
Positioned(
right: 0,
top: 0,
child: Obx(
() => Visibility(
visible: item.hasNew.value,
child: Container(
decoration: const BoxDecoration(
color: Colors.deepOrange,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(4),
topRight: Radius.circular(4),
),
),
padding:
AppStyle.edgeInsetsH8.copyWith(top: 2, bottom: 2),
child: const Text(
"",
style: TextStyle(
fontSize: 12,
color: Colors.white,
),
),
),
),
),
),
],
),
AppStyle.vGap4,
Padding(
padding: AppStyle.edgeInsetsH4,
child: Text(
item.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
height: 1.2,
),
),
),
AppStyle.vGap4,
Padding(
padding: AppStyle.edgeInsetsH4,
child: Text(
"更新 ${item.lastUpdateChapterName}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.grey,
fontSize: 12.0,
height: 1.2,
),
),
),
AppStyle.vGap4,
],
),
Obx(
() => Positioned(
right: 0,
top: 0,
child: Offstage(
offstage: !controller.editMode.value,
child: Checkbox(
value: item.isChecked.value,
onChanged: (e) {
item.isChecked.value = e!;
},
),
),
),
),
],
),
);
}
Widget buildFilter({
required Map types,
required dynamic value,
required Function(dynamic) onSelected,
}) {
return Expanded(
child: PopupMenuButton(
onSelected: onSelected,
itemBuilder: (c) => types.keys
.map(
(k) => CheckedPopupMenuItem(
value: k,
checked: k == value,
child: Text(types[k] ?? ""),
),
)
.toList(),
child: SizedBox(
height: 40,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
types[value] ?? "",
),
const Icon(
Icons.arrow_drop_down,
color: Colors.grey,
)
],
),
),
),
);
}
}

View File

@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class UserSubscribeController extends GetxController
with GetSingleTickerProviderStateMixin {
final int type;
UserSubscribeController(this.type);
late TabController tabController;
@override
void onInit() {
tabController = TabController(length: 2, vsync: this, initialIndex: type);
super.onInit();
}
}

View File

@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/modules/user/subscribe/comic/comic_subscribe_view.dart';
import 'package:flutter_dmzj/modules/user/subscribe/novel/novel_subscribe_view.dart';
import 'package:flutter_dmzj/modules/user/subscribe/user_subscribe_controller.dart';
import 'package:get/get.dart';
class UserSubscribePage extends StatelessWidget {
final UserSubscribeController controller;
final int type;
UserSubscribePage({this.type = 0, super.key})
: controller = Get.put(
UserSubscribeController(type),
tag: DateTime.now().millisecondsSinceEpoch.toString(),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(right: 56),
child: TabBar(
controller: controller.tabController,
isScrollable: true,
tabAlignment: TabAlignment.start,
labelPadding: AppStyle.edgeInsetsH24,
indicatorSize: TabBarIndicatorSize.label,
indicatorColor: Theme.of(context).colorScheme.primary,
labelColor: Theme.of(context).colorScheme.primary,
unselectedLabelColor:
Get.isDarkMode ? Colors.white70 : Colors.black87,
tabs: const [
Tab(text: "漫画"),
Tab(text: "小说"),
// Tab(text: "新闻"),
],
),
),
),
body: TabBarView(
controller: controller.tabController,
children: [
ComicSubscribeView(),
NovelSubscribeView(),
// NewsSubscribeView(),
],
),
);
}
}

View File

@@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/services/app_settings_service.dart';
import 'package:flutter_dmzj/app/dialog_utils.dart';
import 'package:flutter_dmzj/app/utils.dart';
import 'package:flutter_dmzj/routes/app_navigator.dart';
import 'package:flutter_dmzj/services/user_service.dart';
import 'package:get/get.dart';
class UserHomeController extends GetxController {
final AppSettingsService settings = AppSettingsService.instance;
@override
void onInit() {
UserService.instance.refreshProfile();
super.onInit();
}
/// 登录
void login() {
UserService.instance.login();
}
/// 退出登录
void logout() async {
var result = await DialogUtils.showAlertDialog(
"确定要退出登录吗?",
title: "退出登录",
);
if (result) {
UserService.instance.logout();
}
}
/// 主题设置
void setTheme() {
settings.changeTheme();
}
/// 关于我们
void about() {
Get.dialog(AboutDialog(
applicationIcon: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey.withOpacity(.2),
),
borderRadius: AppStyle.radius12,
),
child: ClipRRect(
borderRadius: AppStyle.radius12,
child: Image.asset(
'assets/images/logo.png',
width: 48,
height: 48,
),
),
),
applicationName: "动漫之家",
applicationVersion: "Ver ${Utils.packageInfo.version}",
applicationLegalese: "由akasei二次修改并分发",
));
}
/// 检查更新
void checkUpdate() {
Utils.checkUpdate(showMsg: true);
}
/// 订阅
void toUserSubscribe() async {
if (!await UserService.instance.login()) {
return;
}
AppNavigator.toUserSubscribe();
}
/// 历史
void toUserHistory() async {
if (!await UserService.instance.login()) {
return;
}
AppNavigator.toUserHistory();
}
/// 本机历史
void toLocalHistory() async {
AppNavigator.toLocalHistory();
}
void toSettings() async {
AppNavigator.toSettings();
}
void comicDownload() {
AppNavigator.toComicDownloadManage(0);
}
void novelDownload() {
AppNavigator.toNovelDownloadManage(0);
}
void userComment() {
AppNavigator.toUserComment(int.tryParse(UserService.instance.userId) ?? 0);
}
void toFavorite() {
AppNavigator.tolocalFavorite();
}
}

View File

@@ -0,0 +1,371 @@
import 'package:easy_refresh/easy_refresh.dart';
import 'package:fluent_ui/fluent_ui.dart' as fluent;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_dmzj/app/app_color.dart';
import 'package:flutter_dmzj/app/app_style.dart';
import 'package:flutter_dmzj/app/dialog_utils.dart';
import 'package:flutter_dmzj/app/platform_utils.dart';
import 'package:flutter_dmzj/modules/user/user_home_controller.dart';
import 'package:flutter_dmzj/services/comic_download_service.dart';
import 'package:flutter_dmzj/services/novel_download_service.dart';
import 'package:flutter_dmzj/services/user_service.dart';
import 'package:flutter_dmzj/widgets/user_photo.dart';
import 'package:get/get.dart';
import 'package:remixicon/remixicon.dart';
import 'package:url_launcher/url_launcher_string.dart';
class UserHomePage extends GetView<UserHomeController> {
const UserHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
if (PlatformUtils.isWindows) {
return _buildWindowsLayout(context);
}
return _buildMobileLayout(context);
}
Widget _buildWindowsLayout(BuildContext context) {
final fluentTheme = fluent.FluentTheme.of(context);
return ColoredBox(
color: fluentTheme.micaBackgroundColor,
child: EasyRefresh(
header: const MaterialHeader(),
onRefresh: UserService.instance.refreshProfile,
child: _buildListContent(context),
),
);
}
Widget _buildMobileLayout(BuildContext context) {
return Scaffold(
body: AnnotatedRegion<SystemUiOverlayStyle>(
value: Get.isDarkMode
? SystemUiOverlayStyle.light.copyWith(
systemNavigationBarColor: Colors.transparent,
)
: SystemUiOverlayStyle.dark.copyWith(
systemNavigationBarColor: Colors.transparent,
),
child: SafeArea(
child: EasyRefresh(
header: const MaterialHeader(),
onRefresh: UserService.instance.refreshProfile,
child: _buildListContent(context),
),
),
),
);
}
Widget _buildListContent(BuildContext context) {
return ListView(
padding: AppStyle.edgeInsetsA4,
children: [
AppStyle.vGap12,
// 用户名、头像
Obx(
() => Visibility(
visible: UserService.instance.logined.value,
child: ListTile(
leading: UserPhoto(
url: UserService.instance.userProfile.value?.cover,
size: 48,
),
title: Text.rich(
TextSpan(
text: UserService
.instance.userProfile.value?.nickname ??
UserService.instance.nickname,
children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Visibility(
visible: (UserService.instance.userProfile.value
?.userfeeinfo?.isVip ??
false),
child: Padding(
padding: AppStyle.edgeInsetsL4,
child: Image.asset(
"assets/images/vip.png",
height: 16,
),
),
),
),
],
),
),
subtitle: Text(
UserService.instance.isVip
? UserService.instance.vipInfo
: UserService.instance.sign,
style: Get.textTheme.bodySmall,
),
trailing: IconButton(
onPressed: controller.logout,
icon: const Icon(Remix.logout_box_r_line),
),
),
),
),
Obx(
() => Visibility(
visible: !UserService.instance.logined.value,
child: ListTile(
leading: const UserPhoto(
url: "",
size: 48,
),
title: const Text(
"未登录",
style: TextStyle(height: 1.0),
),
subtitle: const Text(
"点击前往登录",
),
trailing: Icon(
Icons.chevron_right,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onTap: controller.login,
),
),
),
Obx(
() => _buildCard(
context,
children: [
Visibility(
visible: UserService.instance.logined.value,
child: ListTile(
leading: const Icon(Remix.heart_line),
title: const Text("我的订阅"),
trailing: Icon(
Icons.chevron_right,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onTap: controller.toUserSubscribe,
),
),
// Visibility(
// visible: UserService.instance.logined.value,
// child: ListTile(
// leading: const Icon(Remix.history_line),
// title: const Text("浏览记录"),
// trailing: const Icon(
// Icons.chevron_right,
// color: Colors.grey,
// ),
// onTap: controller.toUserHistory,
// ),
// ),
// Visibility(
// visible: UserService.instance.logined.value,
// child: ListTile(
// leading: const Icon(Remix.chat_smile_2_line),
// title: const Text("我的评论"),
// trailing: const Icon(
// Icons.chevron_right,
// color: Colors.grey,
// ),
// onTap: controller.userComment,
// ),
// ),
],
),
),
_buildCard(
context,
children: [
ListTile(
leading: const Icon(Remix.file_history_line),
title: const Text("本机记录"),
trailing: Icon(
Icons.chevron_right,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onTap: controller.toLocalHistory,
),
ListTile(
leading: const Icon(Remix.star_line),
title: const Text("本机收藏"),
trailing: Icon(
Icons.chevron_right,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onTap: controller.toFavorite,
),
ListTile(
leading: const Icon(Remix.download_line),
title: const Text("漫画下载"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Obx(
() => Visibility(
visible: ComicDownloadService
.instance.taskQueues.isNotEmpty,
child: Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: AppStyle.radius24,
),
width: 20,
height: 20,
child: Center(
child: Text(
"${ComicDownloadService.instance.taskQueues.length}",
style: const TextStyle(
fontSize: 10,
color: Colors.white,
),
),
),
),
),
),
Icon(
Icons.chevron_right,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
],
),
onTap: controller.comicDownload,
),
ListTile(
leading: const Icon(Remix.download_line),
title: const Text("小说下载"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Obx(
() => Visibility(
visible: NovelDownloadService
.instance.taskQueues.isNotEmpty,
child: Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: AppStyle.radius24,
),
width: 20,
height: 20,
child: Center(
child: Text(
"${NovelDownloadService.instance.taskQueues.length}",
style: const TextStyle(
fontSize: 10,
color: Colors.white,
),
),
),
),
),
),
Icon(
Icons.chevron_right,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
],
),
onTap: controller.novelDownload,
),
],
),
_buildCard(
context,
children: [
ListTile(
leading: Icon(
Get.isDarkMode ? Remix.moon_line : Remix.sun_line),
title: const Text("显示主题"),
trailing: Icon(
Icons.chevron_right,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onTap: controller.setTheme,
),
ListTile(
leading: const Icon(Remix.settings_line),
title: const Text("更多设置"),
trailing: Icon(
Icons.chevron_right,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onTap: controller.toSettings,
),
],
),
_buildCard(
context,
children: [
ListTile(
leading: const Icon(Remix.error_warning_line),
title: const Text("免责声明"),
trailing: Icon(
Icons.chevron_right,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onTap: DialogUtils.showStatement,
),
// ListTile(
// leading: const Icon(Remix.github_fill),
// title: const Text("开源主页"),
// trailing: const Icon(
// Icons.chevron_right,
// color: Colors.grey,
// ),
// onTap: () {
// launchUrlString(
// "https://github.com/xiaoyaocz/flutter_dmzj",
// mode: LaunchMode.externalApplication,
// );
// },
// ),
// ListTile(
// leading: const Icon(Remix.upload_2_line),
// title: const Text("检查更新"),
// trailing: const Icon(
// Icons.chevron_right,
// color: Colors.grey,
// ),
// onTap: controller.checkUpdate,
// ),
ListTile(
leading: const Icon(Remix.information_line),
title: const Text("关于APP"),
trailing: Icon(
Icons.chevron_right,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onTap: controller.about,
),
],
),
],
);
}
Widget _buildCard(BuildContext context, {required List<Widget> children}) {
return Container(
margin: AppStyle.edgeInsetsH12.copyWith(top: 12),
child: Material(
color: Theme.of(context).cardColor,
borderRadius: AppStyle.radius8,
child: Theme(
data: Theme.of(context).copyWith(
listTileTheme: ListTileThemeData(
shape: RoundedRectangleBorder(borderRadius: AppStyle.radius8),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
),
),
),
);
}
}