v1.0.1
This commit is contained in:
19
lib/modules/user/comment/user_comment_controller.dart
Normal file
19
lib/modules/user/comment/user_comment_controller.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
45
lib/modules/user/comment/user_comment_page.dart
Normal file
45
lib/modules/user/comment/user_comment_page.dart
Normal 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),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
162
lib/modules/user/comment/user_comment_view.dart
Normal file
162
lib/modules/user/comment/user_comment_view.dart
Normal 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 ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
15
lib/modules/user/history/comic/comic_history_controller.dart
Normal file
15
lib/modules/user/history/comic/comic_history_controller.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
78
lib/modules/user/history/comic/comic_history_view.dart
Normal file
78
lib/modules/user/history/comic/comic_history_view.dart
Normal 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)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
15
lib/modules/user/history/novel/novel_history_controller.dart
Normal file
15
lib/modules/user/history/novel/novel_history_controller.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
79
lib/modules/user/history/novel/novel_history_view.dart
Normal file
79
lib/modules/user/history/novel/novel_history_view.dart
Normal 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)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
17
lib/modules/user/history/user_history_controller.dart
Normal file
17
lib/modules/user/history/user_history_controller.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
49
lib/modules/user/history/user_history_page.dart
Normal file
49
lib/modules/user/history/user_history_page.dart
Normal 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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
129
lib/modules/user/local_favorite/local_favorite_page.dart
Normal file
129
lib/modules/user/local_favorite/local_favorite_page.dart
Normal 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!;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
79
lib/modules/user/local_history/comic/comic_history_view.dart
Normal file
79
lib/modules/user/local_history/comic/comic_history_view.dart
Normal 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)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
17
lib/modules/user/local_history/local_history_controller.dart
Normal file
17
lib/modules/user/local_history/local_history_controller.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
51
lib/modules/user/local_history/local_history_page.dart
Normal file
51
lib/modules/user/local_history/local_history_page.dart
Normal 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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
79
lib/modules/user/local_history/novel/novel_history_view.dart
Normal file
79
lib/modules/user/local_history/novel/novel_history_view.dart
Normal 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)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
39
lib/modules/user/login/user_login_controller.dart
Normal file
39
lib/modules/user/login/user_login_controller.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
101
lib/modules/user/login/user_login_dialog.dart
Normal file
101
lib/modules/user/login/user_login_dialog.dart
Normal 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,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
97
lib/modules/user/settings/settings_controller.dart
Normal file
97
lib/modules/user/settings/settings_controller.dart
Normal 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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
529
lib/modules/user/settings/settings_page.dart
Normal file
529
lib/modules/user/settings/settings_page.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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("已添加至本机收藏");
|
||||
}
|
||||
}
|
||||
273
lib/modules/user/subscribe/comic/comic_subscribe_view.dart
Normal file
273
lib/modules/user/subscribe/comic/comic_subscribe_view.dart
Normal 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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
118
lib/modules/user/subscribe/news/news_subscribe_view.dart
Normal file
118
lib/modules/user/subscribe/news/news_subscribe_view.dart
Normal 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,
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
267
lib/modules/user/subscribe/novel/novel_subscribe_view.dart
Normal file
267
lib/modules/user/subscribe/novel/novel_subscribe_view.dart
Normal 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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
16
lib/modules/user/subscribe/user_subscribe_controller.dart
Normal file
16
lib/modules/user/subscribe/user_subscribe_controller.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
52
lib/modules/user/subscribe/user_subscribe_pgae.dart
Normal file
52
lib/modules/user/subscribe/user_subscribe_pgae.dart
Normal 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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
111
lib/modules/user/user_home_controller.dart
Normal file
111
lib/modules/user/user_home_controller.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
371
lib/modules/user/user_home_page.dart
Normal file
371
lib/modules/user/user_home_page.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user