v1.0.1
This commit is contained in:
41
lib/app/app_color.dart
Normal file
41
lib/app/app_color.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppColor {
|
||||
static const Color primaryColor = Color(0xff4196f9);
|
||||
|
||||
static ColorScheme colorSchemeLight = ColorScheme.fromSeed(
|
||||
seedColor: primaryColor,
|
||||
brightness: Brightness.light,
|
||||
);
|
||||
static ColorScheme colorSchemeDark = ColorScheme.fromSeed(
|
||||
seedColor: primaryColor,
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
static const Color backgroundColor = Color(0xfffafafa);
|
||||
static const Color backgroundColorDark = Color(0xff212121);
|
||||
static const Color black333 = Color(0xff333333);
|
||||
static const Color greyf0f0f0 = Color(0xfff0f0f0);
|
||||
|
||||
static Map<int, List<Color>> novelThemes = {
|
||||
0: [
|
||||
const Color.fromRGBO(245, 239, 217, 1),
|
||||
const Color(0xff301e1b),
|
||||
],
|
||||
1: [
|
||||
const Color.fromRGBO(248, 247, 252, 1),
|
||||
black333,
|
||||
],
|
||||
2: [
|
||||
const Color.fromRGBO(192, 237, 198, 1),
|
||||
Colors.black,
|
||||
],
|
||||
3: [
|
||||
const Color(0xff3b3a39),
|
||||
const Color.fromRGBO(230, 230, 230, 1),
|
||||
],
|
||||
4: [
|
||||
Colors.black,
|
||||
const Color.fromRGBO(200, 200, 200, 1),
|
||||
],
|
||||
};
|
||||
}
|
||||
27
lib/app/app_constant.dart
Normal file
27
lib/app/app_constant.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
class AppConstant {
|
||||
/// 定义平板宽度,当大于此宽度时APP进入双栏模式
|
||||
static const double kTabletWidth = 1000;
|
||||
|
||||
/// 类型ID-漫画
|
||||
static const int kTypeComic = 4;
|
||||
|
||||
/// 类型ID-新闻
|
||||
static const int kTypeNews = 6;
|
||||
|
||||
/// 类型ID-专题
|
||||
static const int kTypeSpecial = 2;
|
||||
|
||||
/// 类型ID-轻小说
|
||||
static const int kTypeNovel = 1;
|
||||
}
|
||||
|
||||
class ReaderDirection {
|
||||
/// 左右 0
|
||||
static const int kLeftToRight = 0;
|
||||
|
||||
/// 上下 1
|
||||
static const int kUpToDown = 1;
|
||||
|
||||
/// 右左 2
|
||||
static const int kRightToLeft = 2;
|
||||
}
|
||||
13
lib/app/app_error.dart
Normal file
13
lib/app/app_error.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
class AppError implements Exception {
|
||||
final int code;
|
||||
final String message;
|
||||
AppError(
|
||||
this.message, {
|
||||
this.code = 0,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
167
lib/app/app_style.dart
Normal file
167
lib/app/app_style.dart
Normal file
@@ -0,0 +1,167 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dmzj/app/app_color.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class AppStyle {
|
||||
static ThemeData lightTheme = getLightTheme();
|
||||
static ThemeData getLightTheme({ColorScheme? colorScheme}) {
|
||||
final scheme = colorScheme ?? AppColor.colorSchemeLight;
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: scheme,
|
||||
brightness: Brightness.light,
|
||||
).copyWith(
|
||||
scaffoldBackgroundColor: colorScheme != null ? null : Colors.white,
|
||||
cardColor: colorScheme != null ? null : Colors.white,
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: scheme.onSurface,
|
||||
centerTitle: false,
|
||||
shape: Border(
|
||||
bottom: BorderSide(
|
||||
color: Colors.grey.withOpacity(.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
iconTheme: IconThemeData(
|
||||
color: scheme.onSurface,
|
||||
),
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
color: scheme.onSurface,
|
||||
),
|
||||
systemOverlayStyle: SystemUiOverlayStyle.dark.copyWith(
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeData darkTheme = getDarkTheme();
|
||||
static ThemeData getDarkTheme({ColorScheme? colorScheme}) {
|
||||
final scheme = colorScheme ?? AppColor.colorSchemeDark;
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: scheme,
|
||||
brightness: Brightness.dark,
|
||||
).copyWith(
|
||||
primaryColor: scheme.primary,
|
||||
cardColor: const Color(0xff424242),
|
||||
scaffoldBackgroundColor: Colors.black,
|
||||
tabBarTheme: TabBarThemeData(
|
||||
indicatorColor: scheme.primary,
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
centerTitle: false,
|
||||
shape: Border(
|
||||
bottom: BorderSide(
|
||||
color: Colors.grey.withOpacity(.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
titleTextStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
),
|
||||
iconTheme: const IconThemeData(
|
||||
color: Colors.white,
|
||||
),
|
||||
systemOverlayStyle: SystemUiOverlayStyle.light.copyWith(
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
static const vGap4 = SizedBox(
|
||||
height: 4,
|
||||
);
|
||||
static const vGap8 = SizedBox(
|
||||
height: 8,
|
||||
);
|
||||
static const vGap12 = SizedBox(
|
||||
height: 12,
|
||||
);
|
||||
static const vGap24 = SizedBox(
|
||||
height: 24,
|
||||
);
|
||||
static const vGap32 = SizedBox(
|
||||
height: 32,
|
||||
);
|
||||
|
||||
static const hGap4 = SizedBox(
|
||||
width: 4,
|
||||
);
|
||||
static const hGap8 = SizedBox(
|
||||
width: 8,
|
||||
);
|
||||
static const hGap12 = SizedBox(
|
||||
width: 12,
|
||||
);
|
||||
static const hGap16 = SizedBox(
|
||||
width: 16,
|
||||
);
|
||||
|
||||
static const hGap24 = SizedBox(
|
||||
width: 24,
|
||||
);
|
||||
static const hGap32 = SizedBox(
|
||||
width: 32,
|
||||
);
|
||||
|
||||
static const edgeInsetsH4 = EdgeInsets.symmetric(horizontal: 4);
|
||||
static const edgeInsetsH8 = EdgeInsets.symmetric(horizontal: 8);
|
||||
static const edgeInsetsH12 = EdgeInsets.symmetric(horizontal: 12);
|
||||
static const edgeInsetsH16 = EdgeInsets.symmetric(horizontal: 16);
|
||||
static const edgeInsetsH20 = EdgeInsets.symmetric(horizontal: 20);
|
||||
static const edgeInsetsH24 = EdgeInsets.symmetric(horizontal: 24);
|
||||
|
||||
static const edgeInsetsV4 = EdgeInsets.symmetric(vertical: 4);
|
||||
static const edgeInsetsV8 = EdgeInsets.symmetric(vertical: 8);
|
||||
static const edgeInsetsV12 = EdgeInsets.symmetric(vertical: 12);
|
||||
static const edgeInsetsV24 = EdgeInsets.symmetric(vertical: 24);
|
||||
|
||||
static const edgeInsetsA4 = EdgeInsets.all(4);
|
||||
static const edgeInsetsA8 = EdgeInsets.all(8);
|
||||
static const edgeInsetsA12 = EdgeInsets.all(12);
|
||||
static const edgeInsetsA24 = EdgeInsets.all(24);
|
||||
|
||||
static const edgeInsetsR4 = EdgeInsets.only(right: 4);
|
||||
static const edgeInsetsR8 = EdgeInsets.only(right: 8);
|
||||
static const edgeInsetsR12 = EdgeInsets.only(right: 12);
|
||||
static const edgeInsetsR20 = EdgeInsets.only(right: 20);
|
||||
static const edgeInsetsR24 = EdgeInsets.only(right: 24);
|
||||
|
||||
static const edgeInsetsL4 = EdgeInsets.only(left: 4);
|
||||
static const edgeInsetsL8 = EdgeInsets.only(left: 8);
|
||||
static const edgeInsetsL12 = EdgeInsets.only(left: 12);
|
||||
static const edgeInsetsL24 = EdgeInsets.only(left: 24);
|
||||
|
||||
static const edgeInsetsT4 = EdgeInsets.only(top: 4);
|
||||
static const edgeInsetsT8 = EdgeInsets.only(top: 8);
|
||||
static const edgeInsetsT12 = EdgeInsets.only(top: 12);
|
||||
static const edgeInsetsT24 = EdgeInsets.only(top: 24);
|
||||
|
||||
static const edgeInsetsB4 = EdgeInsets.only(bottom: 4);
|
||||
static const edgeInsetsB8 = EdgeInsets.only(bottom: 8);
|
||||
static const edgeInsetsB12 = EdgeInsets.only(bottom: 12);
|
||||
static const edgeInsetsB24 = EdgeInsets.only(bottom: 24);
|
||||
|
||||
static BorderRadius radius4 = BorderRadius.circular(4);
|
||||
static BorderRadius radius8 = BorderRadius.circular(8);
|
||||
static BorderRadius radius12 = BorderRadius.circular(12);
|
||||
static BorderRadius radius24 = BorderRadius.circular(24);
|
||||
static BorderRadius radius32 = BorderRadius.circular(32);
|
||||
static BorderRadius radius48 = BorderRadius.circular(48);
|
||||
|
||||
/// 顶部状态栏的高度
|
||||
static double get statusBarHeight => MediaQuery.of(Get.context!).padding.top;
|
||||
|
||||
/// 底部导航条的高度
|
||||
static double get bottomBarHeight =>
|
||||
MediaQuery.of(Get.context!).padding.bottom;
|
||||
}
|
||||
148
lib/app/controller/base_controller.dart
Normal file
148
lib/app/controller/base_controller.dart
Normal file
@@ -0,0 +1,148 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class BaseController extends GetxController {
|
||||
/// 加载中,更新页面
|
||||
var pageLoadding = false.obs;
|
||||
|
||||
/// 加载中,不会更新页面
|
||||
var loadding = false;
|
||||
|
||||
/// 空白页面
|
||||
var pageEmpty = false.obs;
|
||||
|
||||
/// 页面错误
|
||||
var pageError = false.obs;
|
||||
|
||||
/// 未登录
|
||||
var notLogin = false.obs;
|
||||
|
||||
/// 错误信息
|
||||
var errorMsg = "".obs;
|
||||
|
||||
Error? error;
|
||||
|
||||
/// 显示错误
|
||||
/// * [msg] 错误信息
|
||||
/// * [showPageError] 显示页面错误
|
||||
/// * 只在第一页加载错误时showPageError=true,后续页加载错误时使用Toast弹出通知
|
||||
void handleError(Object exception, {bool showPageError = false}) {
|
||||
Log.logPrint(exception);
|
||||
var msg = exceptionToString(exception);
|
||||
if (exception is Error) {
|
||||
error = exception;
|
||||
}
|
||||
if (showPageError) {
|
||||
pageError.value = true;
|
||||
errorMsg.value = msg;
|
||||
} else {
|
||||
SmartDialog.showToast(exceptionToString(msg));
|
||||
}
|
||||
}
|
||||
|
||||
String exceptionToString(Object exception) {
|
||||
return exception.toString().replaceAll("Exception:", "");
|
||||
}
|
||||
|
||||
void onLogin() {}
|
||||
void onLogout() {}
|
||||
}
|
||||
|
||||
class BaseDataController<T> extends BaseController {
|
||||
T? data;
|
||||
Future loadData() async {
|
||||
try {
|
||||
if (loadding) return;
|
||||
loadding = true;
|
||||
pageError.value = false;
|
||||
pageLoadding.value = true;
|
||||
error = null;
|
||||
var result = await getData();
|
||||
data = result;
|
||||
} catch (e) {
|
||||
handleError(exceptionToString(e), showPageError: true);
|
||||
} finally {
|
||||
loadding = false;
|
||||
pageLoadding.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<T?> getData() async {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class BasePageController<T> extends BaseController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
final EasyRefreshController easyRefreshController = EasyRefreshController();
|
||||
int currentPage = 1;
|
||||
int count = 0;
|
||||
int maxPage = 0;
|
||||
int pageSize = 24;
|
||||
var canLoadMore = false.obs;
|
||||
var list = <T>[].obs;
|
||||
|
||||
Future refreshData() async {
|
||||
currentPage = 1;
|
||||
list.clear();
|
||||
await loadData();
|
||||
}
|
||||
|
||||
Future loadData() async {
|
||||
try {
|
||||
if (loadding) return;
|
||||
loadding = true;
|
||||
pageError.value = false;
|
||||
pageEmpty.value = false;
|
||||
notLogin.value = false;
|
||||
error = null;
|
||||
pageLoadding.value = currentPage == 1;
|
||||
|
||||
var result = await getData(currentPage, pageSize);
|
||||
//是否可以加载更多
|
||||
if (result.isNotEmpty) {
|
||||
currentPage++;
|
||||
canLoadMore.value = true;
|
||||
pageEmpty.value = false;
|
||||
} else {
|
||||
canLoadMore.value = false;
|
||||
if (currentPage == 1) {
|
||||
pageEmpty.value = true;
|
||||
}
|
||||
}
|
||||
// 赋值数据
|
||||
if (currentPage == 1) {
|
||||
list.value = result;
|
||||
} else {
|
||||
list.addAll(result);
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(e, showPageError: currentPage == 1);
|
||||
} finally {
|
||||
loadding = false;
|
||||
pageLoadding.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<T>> getData(int page, int pageSize) async {
|
||||
return [];
|
||||
}
|
||||
|
||||
void scrollToTopOrRefresh() {
|
||||
if (scrollController.offset > 0) {
|
||||
scrollController.animateTo(
|
||||
0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.linear,
|
||||
);
|
||||
} else {
|
||||
easyRefreshController.callRefresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
269
lib/app/dialog_utils.dart
Normal file
269
lib/app/dialog_utils.dart
Normal file
@@ -0,0 +1,269 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/utils.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
|
||||
class DialogUtils {
|
||||
/// 提示弹窗
|
||||
/// - `content` 内容
|
||||
/// - `title` 弹窗标题
|
||||
/// - `confirm` 确认按钮内容,留空为确定
|
||||
/// - `cancel` 取消按钮内容,留空为取消
|
||||
static Future<bool> showAlertDialog(
|
||||
String content, {
|
||||
String title = '',
|
||||
String confirm = '',
|
||||
String cancel = '',
|
||||
bool selectable = false,
|
||||
bool barrierDismissible = true,
|
||||
List<Widget>? actions,
|
||||
}) async {
|
||||
var result = await Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text(title),
|
||||
content: Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 400,
|
||||
maxWidth: 500,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: AppStyle.edgeInsetsV12,
|
||||
child: selectable ? SelectableText(content) : Text(content),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: (() => Get.back(result: false)),
|
||||
child: Text(cancel.isEmpty ? "取消" : cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: (() => Get.back(result: true)),
|
||||
child: Text(confirm.isEmpty ? "确定" : confirm),
|
||||
),
|
||||
...?actions,
|
||||
],
|
||||
),
|
||||
barrierDismissible: barrierDismissible,
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
/// 提示弹窗
|
||||
/// - `content` 内容
|
||||
/// - `title` 弹窗标题
|
||||
/// - `confirm` 确认按钮内容,留空为确定
|
||||
static Future<bool> showMessageDialog(String content,
|
||||
{String title = '', String confirm = '', bool selectable = false}) async {
|
||||
var result = await Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text(title),
|
||||
content: Padding(
|
||||
padding: AppStyle.edgeInsetsV12,
|
||||
child: selectable ? SelectableText(content) : Text(content),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: (() => Get.back(result: true)),
|
||||
child: Text(confirm.isEmpty ? "确定" : confirm),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
/// 文本编辑的弹窗
|
||||
/// - `content` 编辑框默认的内容
|
||||
/// - `title` 弹窗标题
|
||||
/// - `confirm` 确认按钮内容
|
||||
/// - `cancel` 取消按钮内容
|
||||
static Future<String?> showEditTextDialog(String content,
|
||||
{String title = '',
|
||||
String? hintText,
|
||||
String confirm = '',
|
||||
String cancel = ''}) async {
|
||||
final TextEditingController textEditingController =
|
||||
TextEditingController(text: content);
|
||||
var result = await Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text(title),
|
||||
content: Padding(
|
||||
padding: AppStyle.edgeInsetsT12,
|
||||
child: TextField(
|
||||
controller: textEditingController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
//prefixText: title,
|
||||
contentPadding: AppStyle.edgeInsetsA12,
|
||||
hintText: hintText ?? title,
|
||||
),
|
||||
// style: TextStyle(
|
||||
// height: 1.0,
|
||||
// color: Get.isDarkMode ? Colors.white : Colors.black),
|
||||
autofocus: true,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: const Text("取消"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back(result: textEditingController.text);
|
||||
},
|
||||
child: const Text("确定"),
|
||||
),
|
||||
],
|
||||
),
|
||||
// barrierColor:
|
||||
// Get.isDarkMode ? Colors.grey.withOpacity(.3) : Colors.black38,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
static Future<T?> showOptionDialog<T>(
|
||||
List<T> contents,
|
||||
T value, {
|
||||
String title = '',
|
||||
}) async {
|
||||
var result = await Get.dialog(
|
||||
SimpleDialog(
|
||||
title: Text(title),
|
||||
children: contents
|
||||
.map(
|
||||
(e) => RadioListTile<T>(
|
||||
title: Text(e.toString()),
|
||||
value: e,
|
||||
groupValue: value,
|
||||
onChanged: (e) {
|
||||
Get.back(result: e);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void showStatement() async {
|
||||
var text = await rootBundle.loadString("assets/statement.txt");
|
||||
|
||||
showAlertDialog(
|
||||
text,
|
||||
selectable: true,
|
||||
title: "免责声明",
|
||||
confirm: "已阅读并同意",
|
||||
cancel: "退出",
|
||||
barrierDismissible: false,
|
||||
).then((value) {
|
||||
if (!value) {
|
||||
exit(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Future<T?> showMapOptionDialog<T>(
|
||||
Map<T, String> contents,
|
||||
T value, {
|
||||
String title = '',
|
||||
}) async {
|
||||
var result = await Get.dialog(
|
||||
SimpleDialog(
|
||||
title: Text(title),
|
||||
children: contents.keys
|
||||
.map(
|
||||
(e) => RadioListTile<T>(
|
||||
title: Text((contents[e] ?? '-').tr),
|
||||
value: e,
|
||||
groupValue: value,
|
||||
onChanged: (e) {
|
||||
Get.back(result: e);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void showImageViewer(int initIndex, List<String> images) {
|
||||
var index = initIndex.obs;
|
||||
Get.dialog(
|
||||
Scaffold(
|
||||
backgroundColor: Colors.black87,
|
||||
body: Stack(
|
||||
children: [
|
||||
PhotoViewGallery.builder(
|
||||
itemCount: images.length,
|
||||
builder: (_, i) {
|
||||
if (images[i].startsWith("http")) {
|
||||
return PhotoViewGalleryPageOptions(
|
||||
filterQuality: FilterQuality.high,
|
||||
imageProvider: ExtendedNetworkImageProvider(
|
||||
images[i],
|
||||
cache: true,
|
||||
),
|
||||
onTapUp: ((context, details, controllerValue) =>
|
||||
Get.back()),
|
||||
);
|
||||
} else {
|
||||
return PhotoViewGalleryPageOptions(
|
||||
filterQuality: FilterQuality.high,
|
||||
imageProvider: ExtendedMemoryImageProvider(
|
||||
File(images[i]).readAsBytesSync(),
|
||||
),
|
||||
onTapUp: ((context, details, controllerValue) =>
|
||||
Get.back()),
|
||||
);
|
||||
}
|
||||
},
|
||||
loadingBuilder: (context, event) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
pageController: PageController(
|
||||
initialPage: index.value,
|
||||
),
|
||||
onPageChanged: ((i) {
|
||||
index.value = i;
|
||||
}),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
margin: AppStyle.edgeInsetsA24
|
||||
.copyWith(bottom: 24 + AppStyle.bottomBarHeight),
|
||||
child: Obx(
|
||||
() => Text(
|
||||
"${index.value + 1}/${images.length}",
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 12 + AppStyle.bottomBarHeight,
|
||||
bottom: 12,
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
Utils.saveImage(images[index.value]);
|
||||
},
|
||||
icon: const Icon(Icons.save),
|
||||
label: const Text("保存"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
42
lib/app/event_bus.dart
Normal file
42
lib/app/event_bus.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
|
||||
/// 全局事件
|
||||
class EventBus {
|
||||
/// 点击了底部导航
|
||||
static const String kBottomNavigationBarClicked =
|
||||
"BottomNavigationBarClicked";
|
||||
|
||||
/// 更新了漫画记录
|
||||
static const String kUpdatedComicHistory = "UpdateComicHistory";
|
||||
|
||||
/// 更新了小说记录
|
||||
static const String kUpdatedNovelHistory = "UpdateNovelHistory";
|
||||
static EventBus? _instance;
|
||||
|
||||
static EventBus get instance {
|
||||
_instance ??= EventBus();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
final Map<String, StreamController> _streams = {};
|
||||
|
||||
/// 触发事件
|
||||
void emit<T>(String name, T data) {
|
||||
if (!_streams.containsKey(name)) {
|
||||
_streams.addAll({name: StreamController.broadcast()});
|
||||
}
|
||||
Log.d("Emit Event:$name\r\n$data");
|
||||
|
||||
_streams[name]!.add(data);
|
||||
}
|
||||
|
||||
/// 监听事件
|
||||
StreamSubscription<dynamic> listen(String name, Function(dynamic)? onData) {
|
||||
if (!_streams.containsKey(name)) {
|
||||
_streams.addAll({name: StreamController.broadcast()});
|
||||
}
|
||||
return _streams[name]!.stream.listen(onData);
|
||||
}
|
||||
}
|
||||
12
lib/app/keys.dart
Normal file
12
lib/app/keys.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
class Keys {
|
||||
/// APP相关设置的Hive Box名称
|
||||
static const SETTINGS_BOX_NAME = "DmzjSettings";
|
||||
|
||||
/// 主题模式
|
||||
static const SETTINGS_THEME_MODE = "ThemeMode";
|
||||
|
||||
/// 主题颜色
|
||||
static const SETTINGS_THEME_COLOR = "ThemeColor";
|
||||
}
|
||||
39
lib/app/log.dart
Normal file
39
lib/app/log.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
class Log {
|
||||
static Logger logger = Logger(
|
||||
printer: PrettyPrinter(
|
||||
methodCount: 0,
|
||||
errorMethodCount: 8,
|
||||
lineLength: 120,
|
||||
colors: true,
|
||||
printEmojis: true,
|
||||
printTime: false,
|
||||
),
|
||||
);
|
||||
|
||||
static d(String message) {
|
||||
logger.d("${DateTime.now().toString()}\n$message");
|
||||
}
|
||||
|
||||
static i(String message) {
|
||||
logger.i("${DateTime.now().toString()}\n$message");
|
||||
}
|
||||
|
||||
static e(String message, StackTrace stackTrace) {
|
||||
logger.e("${DateTime.now().toString()}\n$message", stackTrace: stackTrace);
|
||||
}
|
||||
|
||||
static w(String message) {
|
||||
logger.w("${DateTime.now().toString()}\n$message");
|
||||
}
|
||||
|
||||
static void logPrint(dynamic obj) {
|
||||
if (obj is Error) {
|
||||
Log.e(obj.toString(), obj.stackTrace ?? StackTrace.current);
|
||||
} else if (kDebugMode) {
|
||||
print(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
lib/app/platform_utils.dart
Normal file
51
lib/app/platform_utils.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart' as fluent;
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Windows平台检测与Fluent UI主题工具
|
||||
class PlatformUtils {
|
||||
/// Web平台不支持dart:io,需先排除
|
||||
static bool get isWindows => !kIsWeb && Platform.isWindows;
|
||||
|
||||
/// 根据当前Material主题生成对应的FluentThemeData
|
||||
static fluent.FluentThemeData getFluentTheme(BuildContext context) {
|
||||
final materialTheme = Theme.of(context);
|
||||
final brightness = materialTheme.brightness;
|
||||
final primary = materialTheme.colorScheme.primary;
|
||||
|
||||
// 根据primary color构建AccentColor渐变色阶
|
||||
final accent = fluent.AccentColor.swatch({
|
||||
'darkest': _darken(primary, 0.4),
|
||||
'darker': _darken(primary, 0.2),
|
||||
'dark': _darken(primary, 0.1),
|
||||
'normal': primary,
|
||||
'light': _lighten(primary, 0.15),
|
||||
'lighter': _lighten(primary, 0.3),
|
||||
'lightest': _lighten(primary, 0.5),
|
||||
});
|
||||
|
||||
return fluent.FluentThemeData(
|
||||
brightness: brightness,
|
||||
accentColor: accent,
|
||||
scaffoldBackgroundColor: brightness == Brightness.dark
|
||||
? const Color(0xff202020)
|
||||
: const Color(0xfff3f3f3),
|
||||
);
|
||||
}
|
||||
|
||||
static Color _darken(Color color, double amount) {
|
||||
final hsl = HSLColor.fromColor(color);
|
||||
return hsl
|
||||
.withLightness((hsl.lightness - amount).clamp(0.0, 1.0))
|
||||
.toColor();
|
||||
}
|
||||
|
||||
static Color _lighten(Color color, double amount) {
|
||||
final hsl = HSLColor.fromColor(color);
|
||||
return hsl
|
||||
.withLightness((hsl.lightness + amount).clamp(0.0, 1.0))
|
||||
.toColor();
|
||||
}
|
||||
}
|
||||
276
lib/app/utils.dart
Normal file
276
lib/app/utils.dart
Normal file
@@ -0,0 +1,276 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
import 'package:flutter_dmzj/requests/common_request.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_gallery_saver_plus/image_gallery_saver_plus.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
// ignore: depend_on_referenced_packages
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class Utils {
|
||||
static late PackageInfo packageInfo;
|
||||
static DateFormat dateFormat = DateFormat("yyyy-MM-dd");
|
||||
static DateFormat dateTimeFormat = DateFormat("MM-dd HH:mm");
|
||||
static DateFormat dateTimeFormatWithYear = DateFormat("yyyy-MM-dd HH:mm");
|
||||
|
||||
/// 版本号解析
|
||||
static int parseVersion(String version) {
|
||||
var sp = version.split('.');
|
||||
var num = "";
|
||||
for (var item in sp) {
|
||||
num = num + item.padLeft(2, '0');
|
||||
}
|
||||
return int.parse(num);
|
||||
}
|
||||
|
||||
/// 时间戳格式化-秒
|
||||
static String formatTimestamp(int ts) {
|
||||
if (ts == 0) {
|
||||
return "----";
|
||||
}
|
||||
return formatTimestampMS(ts * 1000);
|
||||
}
|
||||
|
||||
static String formatTimestampToDate(int ts) {
|
||||
if (ts == 0) {
|
||||
return "----";
|
||||
}
|
||||
var dt = DateTime.fromMillisecondsSinceEpoch(ts * 1000);
|
||||
return dateFormat.format(dt);
|
||||
}
|
||||
|
||||
/// 时间戳格式化-毫秒
|
||||
static String formatTimestampMS(int ts) {
|
||||
var dt = DateTime.fromMillisecondsSinceEpoch(ts);
|
||||
|
||||
var dtNow = DateTime.now();
|
||||
if (dt.year == dtNow.year &&
|
||||
dt.month == dtNow.month &&
|
||||
dt.day == dtNow.day) {
|
||||
return "今天${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}";
|
||||
}
|
||||
if (dt.year == dtNow.year &&
|
||||
dt.month == dtNow.month &&
|
||||
dt.day == dtNow.day - 1) {
|
||||
return "昨天${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}";
|
||||
}
|
||||
|
||||
if (dt.year == dtNow.year) {
|
||||
return dateTimeFormat.format(dt);
|
||||
}
|
||||
|
||||
return dateTimeFormatWithYear.format(dt);
|
||||
}
|
||||
|
||||
/// 检查相册权限
|
||||
static Future<bool> checkPhotoPermission() async {
|
||||
try {
|
||||
var status = await Permission.photos.status;
|
||||
if (status == PermissionStatus.granted) {
|
||||
return true;
|
||||
}
|
||||
status = await Permission.photos.request();
|
||||
if (status.isGranted) {
|
||||
return true;
|
||||
} else {
|
||||
SmartDialog.showToast("请授予相册权限");
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存图片
|
||||
static void saveImage(String url) async {
|
||||
if (Platform.isIOS && !await Utils.checkPhotoPermission()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Uint8List? data;
|
||||
if (url.startsWith("http")) {
|
||||
var provider = ExtendedNetworkImageProvider(url, cache: true);
|
||||
data = await provider.getNetworkImageData();
|
||||
} else {
|
||||
data = await File(url).readAsBytes();
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
SmartDialog.showToast("图片保存失败");
|
||||
return;
|
||||
}
|
||||
if (!kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux)) {
|
||||
saveImageDetktop(p.basename(url), data);
|
||||
} else {
|
||||
var cacheDir = await getTemporaryDirectory();
|
||||
var file = File(p.join(cacheDir.path, p.basename(url)));
|
||||
await file.writeAsBytes(data);
|
||||
final result = await ImageGallerySaverPlus.saveFile(
|
||||
file.path,
|
||||
name: p.basename(url),
|
||||
isReturnPathOfIOS: true,
|
||||
);
|
||||
Log.d(result.toString());
|
||||
SmartDialog.showToast("保存成功");
|
||||
}
|
||||
} catch (e) {
|
||||
SmartDialog.showToast("保存失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存图片-桌面平台
|
||||
static void saveImageDetktop(String fileName, Uint8List list) async {
|
||||
final FileSaveLocation? location =
|
||||
await getSaveLocation(suggestedName: fileName);
|
||||
if (location == null) {
|
||||
return;
|
||||
}
|
||||
final XFile file = XFile.fromData(list, name: fileName);
|
||||
await file.saveTo(location.path);
|
||||
}
|
||||
|
||||
/// 分享
|
||||
static void share(String url, {String content = ""}) {
|
||||
showModalBottomSheet(
|
||||
context: Get.context!,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500,
|
||||
),
|
||||
useSafeArea: true,
|
||||
backgroundColor: Get.theme.cardColor,
|
||||
builder: (context) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
title: const Text("复制链接"),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
Utils.copyText(url);
|
||||
},
|
||||
),
|
||||
Visibility(
|
||||
visible: content.isNotEmpty,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
title: const Text("复制标题与链接"),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
Utils.copyText("$content\n$url");
|
||||
},
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.public),
|
||||
title: const Text("浏览器打开"),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
launchUrlString(url, mode: LaunchMode.externalApplication);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.share),
|
||||
title: const Text("系统分享"),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
Share.share(content.isEmpty ? url : "$content\n$url");
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 检查更新
|
||||
static void checkUpdate({bool showMsg = false}) async {
|
||||
try {
|
||||
int currentVer = Utils.parseVersion(packageInfo.version);
|
||||
CommonRequest request = CommonRequest();
|
||||
var versionInfo = await request.checkUpdate();
|
||||
if (versionInfo.versionNum > currentVer) {
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text(
|
||||
"发现新版本 ${versionInfo.version}",
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
content: Text(
|
||||
versionInfo.versionDesc,
|
||||
style: const TextStyle(fontSize: 14, height: 1.4),
|
||||
),
|
||||
actionsPadding: AppStyle.edgeInsetsH12,
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
child: const Text("取消"),
|
||||
),
|
||||
),
|
||||
AppStyle.hGap12,
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
),
|
||||
onPressed: () {
|
||||
launchUrlString(
|
||||
versionInfo.downloadUrl,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
},
|
||||
child: const Text("更新"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (showMsg) {
|
||||
SmartDialog.showToast("当前已经是最新版本了");
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
if (showMsg) {
|
||||
SmartDialog.showToast("检查更新失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 复制文本
|
||||
static void copyText(String text) async {
|
||||
try {
|
||||
await Clipboard.setData(ClipboardData(text: text));
|
||||
SmartDialog.showToast("已复制到剪切板");
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
140
lib/main.dart
Normal file
140
lib/main.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart' show FluentLocalizations;
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/app/platform_utils.dart';
|
||||
import 'package:flutter_dmzj/models/db/comic_download_info.dart';
|
||||
import 'package:flutter_dmzj/models/db/download_status.dart';
|
||||
import 'package:flutter_dmzj/models/db/local_favorite.dart';
|
||||
import 'package:flutter_dmzj/models/db/novel_download_info.dart';
|
||||
import 'package:flutter_dmzj/services/app_settings_service.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
import 'package:flutter_dmzj/app/utils.dart';
|
||||
import 'package:flutter_dmzj/models/db/comic_history.dart';
|
||||
import 'package:flutter_dmzj/models/db/novel_history.dart';
|
||||
import 'package:flutter_dmzj/services/comic_download_service.dart';
|
||||
import 'package:flutter_dmzj/services/novel_download_service.dart';
|
||||
import 'package:flutter_dmzj/services/db_service.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:flutter_dmzj/routes/app_pages.dart';
|
||||
import 'package:flutter_dmzj/services/local_storage_service.dart';
|
||||
import 'package:flutter_dmzj/services/user_service.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_loadding_widget.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:windows_single_instance/windows_single_instance.dart';
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
if (!kIsWeb && Platform.isWindows) {
|
||||
await WindowsSingleInstance.ensureSingleInstance(
|
||||
[],
|
||||
"com.xycz.zmhx",
|
||||
onSecondWindow: (args) {
|
||||
Log.logPrint(args);
|
||||
},
|
||||
);
|
||||
}
|
||||
await Hive.initFlutter();
|
||||
//初始化服务
|
||||
await initServices();
|
||||
//设置状态栏为透明
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
);
|
||||
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
|
||||
|
||||
runApp(const DMZJApp());
|
||||
}
|
||||
|
||||
Future initServices() async {
|
||||
//包信息
|
||||
Utils.packageInfo = await PackageInfo.fromPlatform();
|
||||
//本地存储
|
||||
Log.d("Init LocalStorage Service");
|
||||
await Get.put(LocalStorageService()).init();
|
||||
|
||||
//用户信息
|
||||
Log.d("Init User Service");
|
||||
Get.put(UserService()).init();
|
||||
|
||||
//注册Hive适配器
|
||||
Hive.registerAdapter(ComicHistoryAdapter());
|
||||
Hive.registerAdapter(NovelHistoryAdapter());
|
||||
Hive.registerAdapter(DownloadStatusAdapter());
|
||||
Hive.registerAdapter(ComicDownloadInfoAdapter());
|
||||
Hive.registerAdapter(NovelDownloadInfoAdapter());
|
||||
Hive.registerAdapter(LocalFavoriteAdapter());
|
||||
await Get.put(DBService()).init();
|
||||
|
||||
//初始化设置服务
|
||||
Get.put(AppSettingsService());
|
||||
|
||||
//初始化漫画下载服务
|
||||
Get.put(ComicDownloadService()).init();
|
||||
//初始化小说下载服务
|
||||
Get.put(NovelDownloadService()).init();
|
||||
}
|
||||
|
||||
class AppScrollBehavior extends MaterialScrollBehavior {
|
||||
@override
|
||||
Set<PointerDeviceKind> get dragDevices => PointerDeviceKind.values.toSet();
|
||||
}
|
||||
|
||||
class DMZJApp extends StatelessWidget {
|
||||
const DMZJApp({Key? key}) : super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DynamicColorBuilder(
|
||||
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
||||
final settings = Get.find<AppSettingsService>();
|
||||
settings.storeDynamicColorSchemes(lightDynamic, darkDynamic);
|
||||
final useDynamic = settings.useDynamicColor.value;
|
||||
return GetMaterialApp(
|
||||
title: '动漫之家 X',
|
||||
scrollBehavior: AppScrollBehavior(),
|
||||
theme: AppStyle.getLightTheme(colorScheme: useDynamic ? lightDynamic : null),
|
||||
darkTheme: AppStyle.getDarkTheme(colorScheme: useDynamic ? darkDynamic : null),
|
||||
themeMode:
|
||||
ThemeMode.values[Get.find<AppSettingsService>().themeMode.value],
|
||||
initialRoute: AppPages.kIndex,
|
||||
localizationsDelegates: [
|
||||
if (PlatformUtils.isWindows) FluentLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
locale: const Locale("zh", "CN"),
|
||||
supportedLocales: const [Locale("zh", "CN")],
|
||||
getPages: AppPages.routes,
|
||||
debugShowCheckedModeBanner: false,
|
||||
navigatorObservers: [FlutterSmartDialog.observer],
|
||||
builder: FlutterSmartDialog.init(
|
||||
loadingBuilder: ((msg) => const AppLoaddingWidget()),
|
||||
//字体大小不跟随系统变化
|
||||
builder: (context, child) => Obx(
|
||||
() => MediaQuery(
|
||||
data: AppSettingsService.instance.useSystemFontSize.value
|
||||
? MediaQuery.of(context)
|
||||
: MediaQuery.of(context)
|
||||
.copyWith(textScaler: const TextScaler.linear(1.0)),
|
||||
child: child!,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
87
lib/models/comic/author_model.dart
Normal file
87
lib/models/comic/author_model.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicAuthorModel {
|
||||
ComicAuthorModel({
|
||||
required this.nickname,
|
||||
this.description,
|
||||
required this.cover,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
factory ComicAuthorModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<ComicAuthorComicModel>? data =
|
||||
json['data'] is List ? <ComicAuthorComicModel>[] : null;
|
||||
if (data != null) {
|
||||
for (final dynamic item in json['data']!) {
|
||||
if (item != null) {
|
||||
data.add(
|
||||
ComicAuthorComicModel.fromJson(asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComicAuthorModel(
|
||||
nickname: asT<String>(json['nickname'])!,
|
||||
description: asT<String?>(json['description']) ?? "",
|
||||
cover: asT<String>(json['cover'])!,
|
||||
data: data!,
|
||||
);
|
||||
}
|
||||
|
||||
String nickname;
|
||||
String? description;
|
||||
String cover;
|
||||
List<ComicAuthorComicModel> data;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'nickname': nickname,
|
||||
'description': description,
|
||||
'cover': cover,
|
||||
'data': data,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicAuthorComicModel {
|
||||
ComicAuthorComicModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.cover,
|
||||
required this.status,
|
||||
});
|
||||
|
||||
factory ComicAuthorComicModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicAuthorComicModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
name: asT<String>(json['name'])!,
|
||||
cover: asT<String>(json['cover'])!,
|
||||
status: asT<String>(json['status'])!,
|
||||
);
|
||||
|
||||
int id;
|
||||
String name;
|
||||
String cover;
|
||||
String status;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'name': name,
|
||||
'cover': cover,
|
||||
'status': status,
|
||||
};
|
||||
}
|
||||
157
lib/models/comic/category_comic_model.dart
Normal file
157
lib/models/comic/category_comic_model.dart
Normal file
@@ -0,0 +1,157 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicCategoryComicModel {
|
||||
ComicCategoryComicModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.authors,
|
||||
this.types,
|
||||
this.status,
|
||||
this.lastUpdateChapterName,
|
||||
this.lastUpdateChapterId,
|
||||
this.lastUpdatetime,
|
||||
this.cover,
|
||||
this.comicPy,
|
||||
this.isFee,
|
||||
this.hotNum,
|
||||
this.authorTag,
|
||||
this.authorTagList,
|
||||
this.copyright,
|
||||
});
|
||||
|
||||
factory ComicCategoryComicModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<AuthorTagList>? authorTagList =
|
||||
json['authorTagList'] is List ? <AuthorTagList>[] : null;
|
||||
if (authorTagList != null) {
|
||||
for (final dynamic item in json['authorTagList']!) {
|
||||
if (item != null) {
|
||||
authorTagList
|
||||
.add(AuthorTagList.fromJson(asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComicCategoryComicModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
name: asT<String>(json['name'])!,
|
||||
authors: asT<String?>(json['authors']),
|
||||
types: asT<String?>(json['types']),
|
||||
status: asT<String?>(json['status']),
|
||||
lastUpdateChapterName: asT<String?>(json['last_update_chapter_name']),
|
||||
lastUpdateChapterId: asT<int?>(json['last_update_chapter_id']),
|
||||
lastUpdatetime: asT<int?>(json['last_updatetime']),
|
||||
cover: asT<String?>(json['cover']),
|
||||
comicPy: asT<String?>(json['comic_py']),
|
||||
isFee: asT<bool?>(json['isFee']),
|
||||
hotNum: asT<int?>(json['hotNum']),
|
||||
authorTag: json['authorTag'] == null
|
||||
? null
|
||||
: AuthorTag.fromJson(asT<Map<String, dynamic>>(json['authorTag'])!),
|
||||
authorTagList: authorTagList,
|
||||
copyright: asT<int?>(json['copyright']),
|
||||
);
|
||||
}
|
||||
|
||||
int id;
|
||||
String name;
|
||||
String? authors;
|
||||
String? types;
|
||||
String? status;
|
||||
String? lastUpdateChapterName;
|
||||
int? lastUpdateChapterId;
|
||||
int? lastUpdatetime;
|
||||
String? cover;
|
||||
String? comicPy;
|
||||
bool? isFee;
|
||||
int? hotNum;
|
||||
AuthorTag? authorTag;
|
||||
List<AuthorTagList>? authorTagList;
|
||||
int? copyright;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'name': name,
|
||||
'authors': authors,
|
||||
'types': types,
|
||||
'status': status,
|
||||
'last_update_chapter_name': lastUpdateChapterName,
|
||||
'last_update_chapter_id': lastUpdateChapterId,
|
||||
'last_updatetime': lastUpdatetime,
|
||||
'cover': cover,
|
||||
'comic_py': comicPy,
|
||||
'isFee': isFee,
|
||||
'hotNum': hotNum,
|
||||
'authorTag': authorTag,
|
||||
'authorTagList': authorTagList,
|
||||
'copyright': copyright,
|
||||
};
|
||||
}
|
||||
|
||||
class AuthorTag {
|
||||
AuthorTag({
|
||||
this.id,
|
||||
this.tagName,
|
||||
this.tagPy,
|
||||
});
|
||||
|
||||
factory AuthorTag.fromJson(Map<String, dynamic> json) => AuthorTag(
|
||||
id: asT<int?>(json['id']),
|
||||
tagName: asT<String?>(json['tagName']),
|
||||
tagPy: asT<String?>(json['tagPy']),
|
||||
);
|
||||
|
||||
int? id;
|
||||
String? tagName;
|
||||
String? tagPy;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'tagName': tagName,
|
||||
'tagPy': tagPy,
|
||||
};
|
||||
}
|
||||
|
||||
class AuthorTagList {
|
||||
AuthorTagList({
|
||||
this.id,
|
||||
this.tagName,
|
||||
this.tagPy,
|
||||
});
|
||||
|
||||
factory AuthorTagList.fromJson(Map<String, dynamic> json) => AuthorTagList(
|
||||
id: asT<int?>(json['id']),
|
||||
tagName: asT<String?>(json['tagName']),
|
||||
tagPy: asT<String?>(json['tagPy']),
|
||||
);
|
||||
|
||||
int? id;
|
||||
String? tagName;
|
||||
String? tagPy;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'tagName': tagName,
|
||||
'tagPy': tagPy,
|
||||
};
|
||||
}
|
||||
73
lib/models/comic/category_filter_model.dart
Normal file
73
lib/models/comic/category_filter_model.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicCategoryFilterModel {
|
||||
ComicCategoryFilterModel({
|
||||
required this.title,
|
||||
required this.items,
|
||||
});
|
||||
|
||||
factory ComicCategoryFilterModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<ComicCategoryFilterItemModel>? items =
|
||||
json['items'] is List ? <ComicCategoryFilterItemModel>[] : null;
|
||||
if (items != null) {
|
||||
for (final dynamic item in json['items']!) {
|
||||
if (item != null) {
|
||||
items.add(ComicCategoryFilterItemModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComicCategoryFilterModel(
|
||||
title: asT<String>(json['title'])!,
|
||||
items: items!,
|
||||
);
|
||||
}
|
||||
|
||||
String title;
|
||||
List<ComicCategoryFilterItemModel> items;
|
||||
var selectId = 0.obs;
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'title': title,
|
||||
'items': items,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicCategoryFilterItemModel {
|
||||
ComicCategoryFilterItemModel({
|
||||
required this.tagId,
|
||||
required this.tagName,
|
||||
});
|
||||
|
||||
factory ComicCategoryFilterItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicCategoryFilterItemModel(
|
||||
tagId: asT<int>(json['tagId'])!,
|
||||
tagName: asT<String>(json['title'])!,
|
||||
);
|
||||
|
||||
int tagId;
|
||||
String tagName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'tag_id': tagId,
|
||||
'tag_name': tagName,
|
||||
};
|
||||
}
|
||||
38
lib/models/comic/category_item_model.dart
Normal file
38
lib/models/comic/category_item_model.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicCategoryItemModel {
|
||||
ComicCategoryItemModel({
|
||||
required this.tagId,
|
||||
required this.title,
|
||||
required this.cover,
|
||||
});
|
||||
|
||||
factory ComicCategoryItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicCategoryItemModel(
|
||||
tagId: asT<int>(json['tagId'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
cover: asT<String>(json['cover'])!,
|
||||
);
|
||||
|
||||
int tagId;
|
||||
String title;
|
||||
String cover;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'tagId': tagId,
|
||||
'title': title,
|
||||
'cover': cover,
|
||||
};
|
||||
}
|
||||
77
lib/models/comic/chapter_detail_model.dart
Normal file
77
lib/models/comic/chapter_detail_model.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicChapterDetailModel {
|
||||
ComicChapterDetailModel({
|
||||
required this.chapterId,
|
||||
required this.comicId,
|
||||
required this.title,
|
||||
required this.chapterOrder,
|
||||
required this.direction,
|
||||
required this.pageUrl,
|
||||
required this.picnum,
|
||||
required this.pageUrlHd,
|
||||
});
|
||||
|
||||
factory ComicChapterDetailModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<String>? pageUrl = json['page_url'] is List ? <String>[] : null;
|
||||
if (pageUrl != null) {
|
||||
for (final dynamic item in json['page_url']!) {
|
||||
if (item != null) {
|
||||
pageUrl.add(asT<String>(item)!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<String>? pageUrlHd =
|
||||
json['page_url_hd'] is List ? <String>[] : null;
|
||||
if (pageUrlHd != null) {
|
||||
for (final dynamic item in json['page_url_hd']!) {
|
||||
if (item != null) {
|
||||
pageUrlHd.add(asT<String>(item)!);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComicChapterDetailModel(
|
||||
chapterId: asT<int>(json['chapter_id'])!,
|
||||
comicId: asT<int>(json['comic_id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
chapterOrder: asT<int>(json['chapter_order'])!,
|
||||
direction: asT<int>(json['direction'])!,
|
||||
pageUrl: pageUrl!,
|
||||
picnum: asT<int>(json['picnum'])!,
|
||||
pageUrlHd: pageUrlHd!,
|
||||
);
|
||||
}
|
||||
|
||||
int chapterId;
|
||||
int comicId;
|
||||
String title;
|
||||
int chapterOrder;
|
||||
int direction;
|
||||
List<String> pageUrl;
|
||||
int picnum;
|
||||
List<String> pageUrlHd;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'chapter_id': chapterId,
|
||||
'comic_id': comicId,
|
||||
'title': title,
|
||||
'chapter_order': chapterOrder,
|
||||
'direction': direction,
|
||||
'page_url': pageUrl,
|
||||
'picnum': picnum,
|
||||
'page_url_hd': pageUrlHd,
|
||||
};
|
||||
}
|
||||
155
lib/models/comic/chapter_detail_web_model.dart
Normal file
155
lib/models/comic/chapter_detail_web_model.dart
Normal file
@@ -0,0 +1,155 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicChapterDetailWebModel {
|
||||
ComicChapterDetailWebModel({
|
||||
required this.id,
|
||||
required this.comicId,
|
||||
required this.chapterName,
|
||||
required this.chapterOrder,
|
||||
required this.createtime,
|
||||
required this.folder,
|
||||
required this.pageUrl,
|
||||
required this.chapterType,
|
||||
required this.chaptertype,
|
||||
required this.chapterTrueType,
|
||||
required this.chapterNum,
|
||||
required this.updatetime,
|
||||
required this.sumPages,
|
||||
required this.snsTag,
|
||||
required this.uid,
|
||||
required this.username,
|
||||
required this.translatorid,
|
||||
required this.translator,
|
||||
required this.link,
|
||||
required this.message,
|
||||
required this.download,
|
||||
required this.hidden,
|
||||
required this.direction,
|
||||
required this.filesize,
|
||||
required this.highFileSize,
|
||||
required this.picnum,
|
||||
required this.hit,
|
||||
required this.nextChapId,
|
||||
required this.prevChapId,
|
||||
required this.commentCount,
|
||||
});
|
||||
|
||||
factory ComicChapterDetailWebModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<String>? pageUrl = json['page_url'] is List ? <String>[] : null;
|
||||
if (pageUrl != null) {
|
||||
for (final dynamic item in json['page_url']!) {
|
||||
if (item != null) {
|
||||
pageUrl.add(asT<String>(item)!);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComicChapterDetailWebModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
comicId: asT<int>(json['comic_id'])!,
|
||||
chapterName: asT<String>(json['chapter_name'])!,
|
||||
chapterOrder: asT<int>(json['chapter_order'])!,
|
||||
createtime: asT<int>(json['createtime'])!,
|
||||
folder: asT<String>(json['folder'])!,
|
||||
pageUrl: pageUrl!,
|
||||
chapterType: asT<int>(json['chapter_type'])!,
|
||||
chaptertype: asT<int>(json['chaptertype'])!,
|
||||
chapterTrueType: asT<int>(json['chapter_true_type'])!,
|
||||
chapterNum: asT<int>(json['chapter_num']) ?? 0,
|
||||
updatetime: asT<int>(json['updatetime'])!,
|
||||
sumPages: asT<int>(json['sum_pages'])!,
|
||||
snsTag: asT<int>(json['sns_tag'])!,
|
||||
uid: asT<int>(json['uid'])!,
|
||||
username: asT<String>(json['username'])!,
|
||||
translatorid: asT<String>(json['translatorid'])!,
|
||||
translator: asT<String>(json['translator'])!,
|
||||
link: asT<String>(json['link'])!,
|
||||
message: asT<String>(json['message'])!,
|
||||
download: asT<String>(json['download'])!,
|
||||
hidden: asT<int>(json['hidden'])!,
|
||||
direction: asT<int>(json['direction']) ?? 0,
|
||||
filesize: asT<int>(json['filesize']) ?? 0,
|
||||
highFileSize: asT<int>(json['high_file_size']) ?? 0,
|
||||
picnum: asT<int>(json['picnum']) ?? 0,
|
||||
hit: asT<int>(json['hit'])!,
|
||||
nextChapId: asT<int?>(json['next_chap_id']) ?? 0,
|
||||
prevChapId: asT<int?>(json['prev_chap_id']) ?? 0,
|
||||
commentCount: asT<int>(json['comment_count'])!,
|
||||
);
|
||||
}
|
||||
|
||||
int id;
|
||||
int comicId;
|
||||
String chapterName;
|
||||
int chapterOrder;
|
||||
int createtime;
|
||||
String folder;
|
||||
List<String> pageUrl;
|
||||
int chapterType;
|
||||
int chaptertype;
|
||||
int chapterTrueType;
|
||||
int chapterNum;
|
||||
int updatetime;
|
||||
int sumPages;
|
||||
int snsTag;
|
||||
int uid;
|
||||
String username;
|
||||
String translatorid;
|
||||
String translator;
|
||||
String link;
|
||||
String message;
|
||||
String download;
|
||||
int hidden;
|
||||
int direction;
|
||||
int filesize;
|
||||
int highFileSize;
|
||||
int picnum;
|
||||
int hit;
|
||||
int nextChapId;
|
||||
int prevChapId;
|
||||
int commentCount;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'comic_id': comicId,
|
||||
'chapter_name': chapterName,
|
||||
'chapter_order': chapterOrder,
|
||||
'createtime': createtime,
|
||||
'folder': folder,
|
||||
'page_url': pageUrl,
|
||||
'chapter_type': chapterType,
|
||||
'chaptertype': chaptertype,
|
||||
'chapter_true_type': chapterTrueType,
|
||||
'chapter_num': chapterNum,
|
||||
'updatetime': updatetime,
|
||||
'sum_pages': sumPages,
|
||||
'sns_tag': snsTag,
|
||||
'uid': uid,
|
||||
'username': username,
|
||||
'translatorid': translatorid,
|
||||
'translator': translator,
|
||||
'link': link,
|
||||
'message': message,
|
||||
'download': download,
|
||||
'hidden': hidden,
|
||||
'direction': direction,
|
||||
'filesize': filesize,
|
||||
'high_file_size': highFileSize,
|
||||
'picnum': picnum,
|
||||
'hit': hit,
|
||||
'next_chap_id': nextChapId,
|
||||
'prev_chap_id': prevChapId,
|
||||
'comment_count': commentCount,
|
||||
};
|
||||
}
|
||||
83
lib/models/comic/chapter_info.dart
Normal file
83
lib/models/comic/chapter_info.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter_dmzj/models/comic/chapter_detail_model.dart';
|
||||
import 'package:flutter_dmzj/models/comic/chapter_detail_web_model.dart';
|
||||
import 'package:flutter_dmzj/models/db/comic_download_info.dart';
|
||||
import 'package:flutter_dmzj/services/comic_download_service.dart';
|
||||
|
||||
// ignore: depend_on_referenced_packages
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class ComicChapterDetail {
|
||||
ComicChapterDetail({
|
||||
required this.chapterId,
|
||||
required this.comicId,
|
||||
required this.chapterOrder,
|
||||
required this.direction,
|
||||
required this.chapterTitle,
|
||||
required this.pageUrls,
|
||||
required this.picnum,
|
||||
required this.commentCount,
|
||||
this.isLocal = false,
|
||||
});
|
||||
factory ComicChapterDetail.empty() => ComicChapterDetail(
|
||||
chapterId: 0,
|
||||
comicId: 0,
|
||||
chapterOrder: 0,
|
||||
direction: 0,
|
||||
chapterTitle: "",
|
||||
pageUrls: [],
|
||||
picnum: 0,
|
||||
commentCount: 0,
|
||||
);
|
||||
factory ComicChapterDetail.fromWebApi(ComicChapterDetailWebModel item) =>
|
||||
ComicChapterDetail(
|
||||
chapterId: item.id,
|
||||
comicId: item.comicId,
|
||||
chapterOrder: item.chapterOrder,
|
||||
direction: item.direction,
|
||||
chapterTitle: item.chapterName,
|
||||
pageUrls: item.pageUrl,
|
||||
picnum: item.picnum,
|
||||
commentCount: item.commentCount,
|
||||
);
|
||||
|
||||
factory ComicChapterDetail.fromV4(ComicChapterDetailModel item, bool useHD) =>
|
||||
ComicChapterDetail(
|
||||
chapterId: item.chapterId.toInt(),
|
||||
comicId: item.comicId.toInt(),
|
||||
chapterOrder: item.chapterOrder,
|
||||
direction: item.direction,
|
||||
chapterTitle: item.title,
|
||||
pageUrls: useHD
|
||||
? (item.pageUrlHd.isNotEmpty ? item.pageUrlHd : item.pageUrl)
|
||||
: (item.pageUrl.isNotEmpty ? item.pageUrl : item.pageUrlHd),
|
||||
//pageUrls: item.pageUrlHD.isNotEmpty ? item.pageUrlHD : item.pageUrl,
|
||||
picnum: item.picnum,
|
||||
commentCount: 0,
|
||||
);
|
||||
|
||||
factory ComicChapterDetail.fromDownload(ComicDownloadInfo item) =>
|
||||
ComicChapterDetail(
|
||||
chapterId: item.chapterId.toInt(),
|
||||
comicId: item.comicId.toInt(),
|
||||
chapterOrder: item.chapterSort,
|
||||
direction: 1,
|
||||
chapterTitle: item.chapterName,
|
||||
pageUrls: item.files
|
||||
.map((e) =>
|
||||
p.join(ComicDownloadService.instance.savePath, item.taskId, e))
|
||||
.toList(),
|
||||
picnum: item.files.length,
|
||||
commentCount: 0,
|
||||
isLocal: true,
|
||||
);
|
||||
|
||||
int chapterId;
|
||||
int comicId;
|
||||
int chapterOrder;
|
||||
int direction;
|
||||
String chapterTitle;
|
||||
List<String> pageUrls;
|
||||
int picnum;
|
||||
int commentCount;
|
||||
bool isLocal;
|
||||
}
|
||||
146
lib/models/comic/comic_related_model.dart
Normal file
146
lib/models/comic/comic_related_model.dart
Normal file
@@ -0,0 +1,146 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicRelatedModel {
|
||||
ComicRelatedModel({
|
||||
required this.authorComics,
|
||||
required this.themeComics,
|
||||
required this.novels,
|
||||
});
|
||||
|
||||
factory ComicRelatedModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<ComicRelatedAuthorModel>? authorComics =
|
||||
json['author_comics'] is List ? <ComicRelatedAuthorModel>[] : null;
|
||||
if (authorComics != null) {
|
||||
for (final dynamic item in json['author_comics']!) {
|
||||
if (item != null) {
|
||||
authorComics.add(ComicRelatedAuthorModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<ComicRelatedItemModel>? themeComics =
|
||||
json['theme_comics'] is List ? <ComicRelatedItemModel>[] : null;
|
||||
if (themeComics != null) {
|
||||
for (final dynamic item in json['theme_comics']!) {
|
||||
if (item != null) {
|
||||
themeComics.add(
|
||||
ComicRelatedItemModel.fromJson(asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<ComicRelatedItemModel>? novels =
|
||||
json['novels'] is List ? <ComicRelatedItemModel>[] : null;
|
||||
if (novels != null) {
|
||||
for (final dynamic item in json['novels']!) {
|
||||
if (item != null) {
|
||||
novels.add(
|
||||
ComicRelatedItemModel.fromJson(asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComicRelatedModel(
|
||||
authorComics: authorComics!,
|
||||
themeComics: themeComics!,
|
||||
novels: novels!,
|
||||
);
|
||||
}
|
||||
|
||||
List<ComicRelatedAuthorModel> authorComics;
|
||||
List<ComicRelatedItemModel> themeComics;
|
||||
List<ComicRelatedItemModel> novels;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'author_comics': authorComics,
|
||||
'theme_comics': themeComics,
|
||||
'novels': novels,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicRelatedAuthorModel {
|
||||
ComicRelatedAuthorModel({
|
||||
required this.authorName,
|
||||
required this.authorId,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
factory ComicRelatedAuthorModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<ComicRelatedItemModel>? data =
|
||||
json['data'] is List ? <ComicRelatedItemModel>[] : null;
|
||||
if (data != null) {
|
||||
for (final dynamic item in json['data']!) {
|
||||
if (item != null) {
|
||||
data.add(
|
||||
ComicRelatedItemModel.fromJson(asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComicRelatedAuthorModel(
|
||||
authorName: asT<String>(json['author_name'])!,
|
||||
authorId: asT<int>(json['author_id'])!,
|
||||
data: data!,
|
||||
);
|
||||
}
|
||||
|
||||
String authorName;
|
||||
int authorId;
|
||||
List<ComicRelatedItemModel> data;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'author_name': authorName,
|
||||
'author_id': authorId,
|
||||
'data': data,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicRelatedItemModel {
|
||||
ComicRelatedItemModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.cover,
|
||||
required this.status,
|
||||
});
|
||||
|
||||
factory ComicRelatedItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicRelatedItemModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
name: asT<String>(json['name'])!,
|
||||
cover: asT<String>(json['cover'])!,
|
||||
status: asT<String>(json['status'])!,
|
||||
);
|
||||
|
||||
int id;
|
||||
String name;
|
||||
String cover;
|
||||
String status;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'name': name,
|
||||
'cover': cover,
|
||||
'status': status,
|
||||
};
|
||||
}
|
||||
275
lib/models/comic/detail_info.dart
Normal file
275
lib/models/comic/detail_info.dart
Normal file
@@ -0,0 +1,275 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_dmzj/models/comic/detail_model.dart';
|
||||
import 'package:flutter_dmzj/models/comic/detail_v1_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicDetailInfo {
|
||||
ComicDetailInfo({
|
||||
this.id = 0,
|
||||
this.title = "",
|
||||
this.direction = 0,
|
||||
this.isLong = false,
|
||||
this.cover = "",
|
||||
this.description = "",
|
||||
this.lastUpdatetime = 0,
|
||||
this.lastUpdatechapterName = "",
|
||||
this.firstLetter = "",
|
||||
this.comicPy = "",
|
||||
this.hotNum = 0,
|
||||
this.hitNum = 0,
|
||||
this.lastUpdateChapterId = 0,
|
||||
required this.types,
|
||||
required this.status,
|
||||
required this.authors,
|
||||
this.subscribeNum = 0,
|
||||
required this.volumes,
|
||||
this.isHide = false,
|
||||
this.isVip = false,
|
||||
});
|
||||
|
||||
factory ComicDetailInfo.empty() => ComicDetailInfo(
|
||||
types: [],
|
||||
status: [],
|
||||
authors: [],
|
||||
volumes: [],
|
||||
);
|
||||
|
||||
factory ComicDetailInfo.fromV4(ComicDetailModel data) => ComicDetailInfo(
|
||||
id: data.data.id,
|
||||
title: data.data.title,
|
||||
direction: data.data.direction ?? 0,
|
||||
isLong: data.data.islong == 1,
|
||||
cover: data.data.cover ?? "",
|
||||
description: data.data.description ?? "",
|
||||
lastUpdateChapterId: data.data.lastUpdateChapterId ?? 0,
|
||||
lastUpdatechapterName: data.data.lastUpdateChapterName ?? "",
|
||||
lastUpdatetime: data.data.lastUpdatetime ?? 0,
|
||||
hitNum: 0,
|
||||
hotNum: 0,
|
||||
subscribeNum: 0,
|
||||
firstLetter: data.data.firstLetter ?? "",
|
||||
comicPy: data.data.comicPy ?? "",
|
||||
isVip: false,
|
||||
types: (data.data.types ?? [])
|
||||
.map(
|
||||
(e) => ComicDetailTag(
|
||||
tagId: e.tagId.toInt(),
|
||||
tagName: e.tagName,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
status: (data.data.status ?? [])
|
||||
.map(
|
||||
(e) => ComicDetailTag(
|
||||
tagId: e.tagId.toInt(),
|
||||
tagName: e.tagName,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
authors: (data.data.authors ?? [])
|
||||
.map(
|
||||
(e) => ComicDetailTag(
|
||||
tagId: e.tagId,
|
||||
tagName: e.tagName,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
volumes: (data.data.chapters ?? [])
|
||||
.map(
|
||||
(e) => ComicDetailVolume(
|
||||
title: e.title!,
|
||||
chapters: RxList<ComicDetailChapterItem>(
|
||||
(e.data ?? [])
|
||||
.map(
|
||||
(x) => ComicDetailChapterItem(
|
||||
chapterId: x.chapterId.toInt(),
|
||||
chapterTitle: x.chapterTitle,
|
||||
updateTime: x.updatetime ?? 0,
|
||||
fileSize: 0,
|
||||
chapterOrder: x.chapterOrder,
|
||||
isVip: false,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
factory ComicDetailInfo.fromV1(ComicDetailV1Model model,
|
||||
{bool isHide = false}) {
|
||||
var lastChapterId = 0;
|
||||
List<ComicDetailVolume> volumes = [];
|
||||
List<ComicDetailChapterItem> serialItems = [];
|
||||
List<ComicDetailChapterItem> aloneItems = [];
|
||||
for (var item in model.list) {
|
||||
serialItems.add(
|
||||
ComicDetailChapterItem(
|
||||
chapterId: int.tryParse(item.id) ?? 0,
|
||||
chapterTitle: item.chapterName,
|
||||
updateTime: int.tryParse(item.updatetime) ?? 0,
|
||||
fileSize: int.tryParse(item.filesize) ?? 0,
|
||||
chapterOrder: int.tryParse(item.chapterOrder) ?? 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
for (var item in model.alone) {
|
||||
aloneItems.add(
|
||||
ComicDetailChapterItem(
|
||||
chapterId: int.tryParse(item.id) ?? 0,
|
||||
chapterTitle: item.chapterName,
|
||||
updateTime: int.tryParse(item.updatetime) ?? 0,
|
||||
fileSize: int.tryParse(item.filesize) ?? 0,
|
||||
chapterOrder: int.tryParse(item.chapterOrder) ?? 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (serialItems.isNotEmpty) {
|
||||
lastChapterId = serialItems.last.chapterId;
|
||||
}
|
||||
volumes.add(
|
||||
ComicDetailVolume(
|
||||
title: isHide ? "神隐" : "连载",
|
||||
chapters: RxList<ComicDetailChapterItem>(serialItems)),
|
||||
);
|
||||
if (aloneItems.isNotEmpty) {
|
||||
volumes.add(
|
||||
ComicDetailVolume(
|
||||
title: isHide ? "神隐-单行本" : "单行本",
|
||||
chapters: RxList<ComicDetailChapterItem>(aloneItems)),
|
||||
);
|
||||
}
|
||||
return ComicDetailInfo(
|
||||
id: int.tryParse(model.info.id) ?? 0,
|
||||
title: model.info.title,
|
||||
direction: int.tryParse(model.info.direction) ?? 0,
|
||||
isLong: (int.tryParse(model.info.islong) ?? 0) == 1,
|
||||
cover: model.info.cover,
|
||||
description: model.info.description,
|
||||
lastUpdateChapterId: lastChapterId,
|
||||
lastUpdatechapterName: model.info.lastUpdateChapterName,
|
||||
lastUpdatetime: int.tryParse(model.info.lastUpdatetime) ?? 0,
|
||||
hitNum: 0,
|
||||
hotNum: 0,
|
||||
subscribeNum: 0,
|
||||
firstLetter: model.info.firstLetter,
|
||||
comicPy: "",
|
||||
isHide: isHide,
|
||||
types: model.info.types
|
||||
.split("/")
|
||||
.map(
|
||||
(e) => ComicDetailTag(
|
||||
tagId: 0,
|
||||
tagName: e,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
status: [
|
||||
ComicDetailTag(
|
||||
tagId: 0,
|
||||
tagName: model.info.status,
|
||||
)
|
||||
],
|
||||
authors: model.info.authors
|
||||
.split("/")
|
||||
.map(
|
||||
(e) => ComicDetailTag(
|
||||
tagId: 0,
|
||||
tagName: e,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
volumes: volumes,
|
||||
);
|
||||
}
|
||||
|
||||
int id;
|
||||
String title;
|
||||
int direction;
|
||||
bool isLong;
|
||||
String cover;
|
||||
String description;
|
||||
int lastUpdatetime;
|
||||
String lastUpdatechapterName;
|
||||
String firstLetter;
|
||||
String comicPy;
|
||||
int hotNum;
|
||||
int hitNum;
|
||||
int lastUpdateChapterId;
|
||||
List<ComicDetailTag> types = [];
|
||||
List<ComicDetailTag> status = [];
|
||||
List<ComicDetailTag> authors = [];
|
||||
int subscribeNum;
|
||||
List<ComicDetailVolume> volumes = [];
|
||||
|
||||
bool isVip;
|
||||
|
||||
/// 是否神隐
|
||||
bool isHide;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class ComicDetailTag {
|
||||
ComicDetailTag({
|
||||
required this.tagId,
|
||||
required this.tagName,
|
||||
});
|
||||
|
||||
int tagId;
|
||||
String tagName;
|
||||
}
|
||||
|
||||
class ComicDetailVolume {
|
||||
ComicDetailVolume({
|
||||
required this.title,
|
||||
required this.chapters,
|
||||
}) {
|
||||
sort();
|
||||
}
|
||||
|
||||
String title;
|
||||
RxList<ComicDetailChapterItem> chapters;
|
||||
//0倒序,1正序
|
||||
var sortType = 0.obs;
|
||||
var showAll = false.obs;
|
||||
bool get showMoreButton => chapters.length > 15;
|
||||
|
||||
void sort() {
|
||||
if (sortType.value == 0) {
|
||||
chapters.sort((a, b) => b.chapterOrder.compareTo(a.chapterOrder));
|
||||
} else {
|
||||
chapters.sort((a, b) => a.chapterOrder.compareTo(b.chapterOrder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ComicDetailChapterItem {
|
||||
ComicDetailChapterItem({
|
||||
required this.chapterId,
|
||||
required this.chapterTitle,
|
||||
required this.updateTime,
|
||||
required this.fileSize,
|
||||
required this.chapterOrder,
|
||||
this.isVip = false,
|
||||
});
|
||||
|
||||
int chapterId;
|
||||
String chapterTitle;
|
||||
int updateTime;
|
||||
int fileSize;
|
||||
int chapterOrder;
|
||||
|
||||
bool isVip;
|
||||
}
|
||||
352
lib/models/comic/detail_model.dart
Normal file
352
lib/models/comic/detail_model.dart
Normal file
@@ -0,0 +1,352 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicDetailModel {
|
||||
ComicDetailModel({
|
||||
required this.data,
|
||||
required this.readingRecord,
|
||||
});
|
||||
|
||||
factory ComicDetailModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicDetailModel(
|
||||
data: ComicDetailDataModel.fromJson(
|
||||
asT<Map<String, dynamic>>(json['data'])!),
|
||||
readingRecord: ComicDetailReadingRecordModel.fromJson(
|
||||
asT<Map<String, dynamic>>(json['readingRecord'])!),
|
||||
);
|
||||
|
||||
ComicDetailDataModel data;
|
||||
ComicDetailReadingRecordModel readingRecord;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'data': data,
|
||||
'readingRecord': readingRecord,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicDetailDataModel {
|
||||
ComicDetailDataModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.direction,
|
||||
this.islong,
|
||||
this.cover,
|
||||
this.description,
|
||||
this.lastUpdatetime,
|
||||
this.lastUpdateChapterName,
|
||||
this.firstLetter,
|
||||
this.comicPy,
|
||||
this.lastUpdateChapterId,
|
||||
this.types,
|
||||
this.status,
|
||||
this.authors,
|
||||
this.chapters,
|
||||
this.dhUrlLinks,
|
||||
});
|
||||
|
||||
factory ComicDetailDataModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<ComicDetailDataTagModel>? types =
|
||||
json['types'] is List ? <ComicDetailDataTagModel>[] : null;
|
||||
if (types != null) {
|
||||
for (final dynamic item in json['types']!) {
|
||||
if (item != null) {
|
||||
types.add(ComicDetailDataTagModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<ComicDetailDataTagModel>? status =
|
||||
json['status'] is List ? <ComicDetailDataTagModel>[] : null;
|
||||
if (status != null) {
|
||||
for (final dynamic item in json['status']!) {
|
||||
if (item != null) {
|
||||
status.add(ComicDetailDataTagModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<ComicDetailDataTagModel>? authors =
|
||||
json['authors'] is List ? <ComicDetailDataTagModel>[] : null;
|
||||
if (authors != null) {
|
||||
for (final dynamic item in json['authors']!) {
|
||||
if (item != null) {
|
||||
authors.add(ComicDetailDataTagModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<ComicDetailChapterModel>? chapters =
|
||||
json['chapters'] is List ? <ComicDetailChapterModel>[] : null;
|
||||
if (chapters != null) {
|
||||
for (final dynamic item in json['chapters']!) {
|
||||
if (item != null) {
|
||||
chapters.add(ComicDetailChapterModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<DhUrlLinks>? dhUrlLinks =
|
||||
json['dh_url_links'] is List ? <DhUrlLinks>[] : null;
|
||||
if (dhUrlLinks != null) {
|
||||
for (final dynamic item in json['dh_url_links']!) {
|
||||
if (item != null) {
|
||||
dhUrlLinks.add(DhUrlLinks.fromJson(asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComicDetailDataModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
direction: asT<int?>(json['direction']),
|
||||
islong: asT<int?>(json['islong']),
|
||||
cover: asT<String?>(json['cover']),
|
||||
description: asT<String?>(json['description']),
|
||||
lastUpdatetime: asT<int?>(json['last_updatetime']),
|
||||
lastUpdateChapterName: asT<String?>(json['last_update_chapter_name']),
|
||||
firstLetter: asT<String?>(json['first_letter']),
|
||||
comicPy: asT<String?>(json['comic_py']),
|
||||
lastUpdateChapterId: asT<int?>(json['last_update_chapter_id']),
|
||||
types: types,
|
||||
status: status,
|
||||
authors: authors,
|
||||
chapters: chapters,
|
||||
dhUrlLinks: dhUrlLinks,
|
||||
);
|
||||
}
|
||||
|
||||
int id;
|
||||
String title;
|
||||
int? direction;
|
||||
int? islong;
|
||||
String? cover;
|
||||
String? description;
|
||||
int? lastUpdatetime;
|
||||
String? lastUpdateChapterName;
|
||||
String? firstLetter;
|
||||
String? comicPy;
|
||||
int? lastUpdateChapterId;
|
||||
List<ComicDetailDataTagModel>? types;
|
||||
List<ComicDetailDataTagModel>? status;
|
||||
List<ComicDetailDataTagModel>? authors;
|
||||
List<ComicDetailChapterModel>? chapters;
|
||||
List<DhUrlLinks>? dhUrlLinks;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'direction': direction,
|
||||
'islong': islong,
|
||||
'cover': cover,
|
||||
'description': description,
|
||||
'last_updatetime': lastUpdatetime,
|
||||
'last_update_chapter_name': lastUpdateChapterName,
|
||||
'first_letter': firstLetter,
|
||||
'comic_py': comicPy,
|
||||
'last_update_chapter_id': lastUpdateChapterId,
|
||||
'types': types,
|
||||
'status': status,
|
||||
'authors': authors,
|
||||
'chapters': chapters,
|
||||
'dh_url_links': dhUrlLinks,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicDetailDataTagModel {
|
||||
ComicDetailDataTagModel({
|
||||
required this.tagId,
|
||||
required this.tagName,
|
||||
});
|
||||
|
||||
factory ComicDetailDataTagModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicDetailDataTagModel(
|
||||
tagId: asT<int>(json['tag_id'])!,
|
||||
tagName: asT<String>(json['tag_name'])!,
|
||||
);
|
||||
|
||||
int tagId;
|
||||
String tagName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'tag_id': tagId,
|
||||
'tag_name': tagName,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicDetailChapterModel {
|
||||
ComicDetailChapterModel({
|
||||
this.title,
|
||||
this.data,
|
||||
});
|
||||
|
||||
factory ComicDetailChapterModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<ComicDetailChapterDataModel>? data =
|
||||
json['data'] is List ? <ComicDetailChapterDataModel>[] : null;
|
||||
if (data != null) {
|
||||
for (final dynamic item in json['data']!) {
|
||||
if (item != null) {
|
||||
data.add(ComicDetailChapterDataModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComicDetailChapterModel(
|
||||
title: asT<String?>(json['title']),
|
||||
data: data,
|
||||
);
|
||||
}
|
||||
|
||||
String? title;
|
||||
List<ComicDetailChapterDataModel>? data;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'title': title,
|
||||
'data': data,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicDetailChapterDataModel {
|
||||
ComicDetailChapterDataModel({
|
||||
required this.chapterId,
|
||||
required this.chapterTitle,
|
||||
this.updatetime,
|
||||
required this.chapterOrder,
|
||||
});
|
||||
|
||||
factory ComicDetailChapterDataModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicDetailChapterDataModel(
|
||||
chapterId: asT<int>(json['chapter_id'])!,
|
||||
chapterTitle: asT<String>(json['chapter_title'])!,
|
||||
updatetime: asT<int?>(json['updatetime']),
|
||||
chapterOrder: asT<int>(json['chapter_order'])!,
|
||||
);
|
||||
|
||||
int chapterId;
|
||||
String chapterTitle;
|
||||
int? updatetime;
|
||||
int chapterOrder;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'chapter_id': chapterId,
|
||||
'chapter_title': chapterTitle,
|
||||
'updatetime': updatetime,
|
||||
'chapter_order': chapterOrder,
|
||||
};
|
||||
}
|
||||
|
||||
class DhUrlLinks {
|
||||
DhUrlLinks({
|
||||
this.title,
|
||||
});
|
||||
|
||||
factory DhUrlLinks.fromJson(Map<String, dynamic> json) => DhUrlLinks(
|
||||
title: asT<String?>(json['title']),
|
||||
);
|
||||
|
||||
String? title;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'title': title,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicDetailReadingRecordModel {
|
||||
ComicDetailReadingRecordModel({
|
||||
this.typeName,
|
||||
this.uid,
|
||||
this.source,
|
||||
this.bizId,
|
||||
this.chapterId,
|
||||
this.viewingTime,
|
||||
this.record,
|
||||
this.volumeId,
|
||||
this.totalNum,
|
||||
this.chapterName,
|
||||
this.volumeName,
|
||||
});
|
||||
|
||||
factory ComicDetailReadingRecordModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicDetailReadingRecordModel(
|
||||
typeName: asT<String?>(json['type_name']),
|
||||
uid: asT<int?>(json['uid']),
|
||||
source: asT<int?>(json['source']),
|
||||
bizId: asT<int?>(json['biz_id']),
|
||||
chapterId: asT<int?>(json['chapter_id']),
|
||||
viewingTime: asT<int?>(json['viewing_time']),
|
||||
record: asT<int?>(json['record']),
|
||||
volumeId: asT<int?>(json['volume_id']),
|
||||
totalNum: asT<int?>(json['total_num']),
|
||||
chapterName: asT<String?>(json['chapter_name']),
|
||||
volumeName: asT<String?>(json['volume_name']),
|
||||
);
|
||||
|
||||
String? typeName;
|
||||
int? uid;
|
||||
int? source;
|
||||
int? bizId;
|
||||
int? chapterId;
|
||||
int? viewingTime;
|
||||
int? record;
|
||||
int? volumeId;
|
||||
int? totalNum;
|
||||
String? chapterName;
|
||||
String? volumeName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'type_name': typeName,
|
||||
'uid': uid,
|
||||
'source': source,
|
||||
'biz_id': bizId,
|
||||
'chapter_id': chapterId,
|
||||
'viewing_time': viewingTime,
|
||||
'record': record,
|
||||
'volume_id': volumeId,
|
||||
'total_num': totalNum,
|
||||
'chapter_name': chapterName,
|
||||
'volume_name': volumeName,
|
||||
};
|
||||
}
|
||||
186
lib/models/comic/detail_v1_model.dart
Normal file
186
lib/models/comic/detail_v1_model.dart
Normal file
@@ -0,0 +1,186 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicDetailV1Model {
|
||||
ComicDetailV1Model({
|
||||
required this.info,
|
||||
required this.list,
|
||||
required this.alone,
|
||||
});
|
||||
|
||||
factory ComicDetailV1Model.fromJson(Map<String, dynamic> json) {
|
||||
final List<ComicDetailV1ChapterModel>? list =
|
||||
json['list'] is List ? <ComicDetailV1ChapterModel>[] : null;
|
||||
if (list != null) {
|
||||
for (final dynamic item in json['list']!) {
|
||||
if (item != null) {
|
||||
list.add(ComicDetailV1ChapterModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<ComicDetailV1ChapterModel>? alone =
|
||||
json['alone'] is List ? <ComicDetailV1ChapterModel>[] : null;
|
||||
if (alone != null) {
|
||||
for (final dynamic item in json['alone']!) {
|
||||
if (item != null) {
|
||||
alone.add(ComicDetailV1ChapterModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ComicDetailV1Model(
|
||||
info: ComicDetailV1InfoModel.fromJson(
|
||||
asT<Map<String, dynamic>>(json['info'])!),
|
||||
list: list!,
|
||||
alone: alone!,
|
||||
);
|
||||
}
|
||||
|
||||
ComicDetailV1InfoModel info;
|
||||
List<ComicDetailV1ChapterModel> list;
|
||||
List<ComicDetailV1ChapterModel> alone;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'info': info,
|
||||
'list': list,
|
||||
'alone': alone,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicDetailV1InfoModel {
|
||||
ComicDetailV1InfoModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.types,
|
||||
required this.zone,
|
||||
required this.status,
|
||||
required this.lastUpdateChapterName,
|
||||
required this.lastUpdatetime,
|
||||
required this.cover,
|
||||
required this.authors,
|
||||
required this.description,
|
||||
required this.firstLetter,
|
||||
required this.direction,
|
||||
required this.islong,
|
||||
required this.copyright,
|
||||
});
|
||||
|
||||
factory ComicDetailV1InfoModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicDetailV1InfoModel(
|
||||
id: asT<String>(json['id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
subtitle: asT<String>(json['subtitle'])!,
|
||||
types: asT<String>(json['types'])!,
|
||||
zone: asT<String>(json['zone'])!,
|
||||
status: asT<String>(json['status'])!,
|
||||
lastUpdateChapterName: asT<String>(json['last_update_chapter_name'])!,
|
||||
lastUpdatetime: asT<String>(json['last_updatetime'])!,
|
||||
cover: asT<String>(json['cover'])!,
|
||||
authors: asT<String>(json['authors'])!,
|
||||
description: asT<String>(json['description'])!,
|
||||
firstLetter: asT<String>(json['first_letter'])!,
|
||||
direction: asT<String>(json['direction'])!,
|
||||
islong: asT<String>(json['islong'])!,
|
||||
copyright: asT<String>(json['copyright'])!,
|
||||
);
|
||||
|
||||
String id;
|
||||
String title;
|
||||
String subtitle;
|
||||
String types;
|
||||
String zone;
|
||||
String status;
|
||||
String lastUpdateChapterName;
|
||||
String lastUpdatetime;
|
||||
String cover;
|
||||
String authors;
|
||||
String description;
|
||||
String firstLetter;
|
||||
String direction;
|
||||
String islong;
|
||||
String copyright;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'subtitle': subtitle,
|
||||
'types': types,
|
||||
'zone': zone,
|
||||
'status': status,
|
||||
'last_update_chapter_name': lastUpdateChapterName,
|
||||
'last_updatetime': lastUpdatetime,
|
||||
'cover': cover,
|
||||
'authors': authors,
|
||||
'description': description,
|
||||
'first_letter': firstLetter,
|
||||
'direction': direction,
|
||||
'islong': islong,
|
||||
'copyright': copyright,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicDetailV1ChapterModel {
|
||||
ComicDetailV1ChapterModel({
|
||||
required this.id,
|
||||
required this.comicId,
|
||||
required this.chapterName,
|
||||
required this.chapterOrder,
|
||||
required this.filesize,
|
||||
required this.createtime,
|
||||
required this.updatetime,
|
||||
});
|
||||
|
||||
factory ComicDetailV1ChapterModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicDetailV1ChapterModel(
|
||||
id: asT<String>(json['id'])!,
|
||||
comicId: asT<String>(json['comic_id'])!,
|
||||
chapterName: asT<String>(json['chapter_name'])!,
|
||||
chapterOrder: asT<String>(json['chapter_order'])!,
|
||||
filesize: asT<String>(json['filesize'])!,
|
||||
createtime: asT<String>(json['createtime'])!,
|
||||
updatetime: asT<String>(json['updatetime'])!,
|
||||
);
|
||||
|
||||
String id;
|
||||
String comicId;
|
||||
String chapterName;
|
||||
String chapterOrder;
|
||||
String filesize;
|
||||
String createtime;
|
||||
String updatetime;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'comic_id': comicId,
|
||||
'chapter_name': chapterName,
|
||||
'chapter_order': chapterOrder,
|
||||
'filesize': filesize,
|
||||
'createtime': createtime,
|
||||
'updatetime': updatetime,
|
||||
};
|
||||
}
|
||||
70
lib/models/comic/rank_item_model.dart
Normal file
70
lib/models/comic/rank_item_model.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicRankListItemModel {
|
||||
ComicRankListItemModel({
|
||||
required this.comicId,
|
||||
required this.title,
|
||||
this.authors,
|
||||
this.status,
|
||||
this.cover,
|
||||
this.types,
|
||||
this.lastUpdatetime,
|
||||
this.lastUpdateChapterName,
|
||||
this.comicPy,
|
||||
this.num,
|
||||
this.tagId,
|
||||
});
|
||||
|
||||
factory ComicRankListItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicRankListItemModel(
|
||||
comicId: asT<int>(json['comic_id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
authors: asT<String?>(json['authors']),
|
||||
status: asT<String?>(json['status']),
|
||||
cover: asT<String?>(json['cover']),
|
||||
types: asT<String?>(json['types']),
|
||||
lastUpdatetime: asT<int?>(json['last_updatetime']),
|
||||
lastUpdateChapterName: asT<String?>(json['last_update_chapter_name']),
|
||||
comicPy: asT<String?>(json['comic_py']),
|
||||
num: asT<int?>(json['num']),
|
||||
tagId: asT<int?>(json['tag_id']),
|
||||
);
|
||||
|
||||
int comicId;
|
||||
String title;
|
||||
String? authors;
|
||||
String? status;
|
||||
String? cover;
|
||||
String? types;
|
||||
int? lastUpdatetime;
|
||||
String? lastUpdateChapterName;
|
||||
String? comicPy;
|
||||
int? num;
|
||||
int? tagId;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'comic_id': comicId,
|
||||
'title': title,
|
||||
'authors': authors,
|
||||
'status': status,
|
||||
'cover': cover,
|
||||
'types': types,
|
||||
'last_updatetime': lastUpdatetime,
|
||||
'last_update_chapter_name': lastUpdateChapterName,
|
||||
'comic_py': comicPy,
|
||||
'num': num,
|
||||
'tag_id': tagId,
|
||||
};
|
||||
}
|
||||
101
lib/models/comic/recommend_model.dart
Normal file
101
lib/models/comic/recommend_model.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicRecommendModel {
|
||||
ComicRecommendModel({
|
||||
required this.categoryId,
|
||||
required this.title,
|
||||
required this.sort,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
factory ComicRecommendModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<ComicRecommendItemModel>? data =
|
||||
json['data'] is List ? <ComicRecommendItemModel>[] : null;
|
||||
if (data != null) {
|
||||
for (final dynamic item in json['data']!) {
|
||||
if (item != null) {
|
||||
data.add(ComicRecommendItemModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComicRecommendModel(
|
||||
categoryId: asT<int>(json['category_id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
sort: asT<int>(json['sort'])!,
|
||||
data: data ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
int categoryId;
|
||||
String title;
|
||||
int sort;
|
||||
List<ComicRecommendItemModel> data;
|
||||
int page = 1;
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'category_id': categoryId,
|
||||
'title': title,
|
||||
'sort': sort,
|
||||
'data': data,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicRecommendItemModel {
|
||||
ComicRecommendItemModel({
|
||||
required this.cover,
|
||||
required this.title,
|
||||
this.subTitle,
|
||||
this.type,
|
||||
this.url,
|
||||
required this.objId,
|
||||
this.status,
|
||||
this.id,
|
||||
});
|
||||
|
||||
factory ComicRecommendItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicRecommendItemModel(
|
||||
id: asT<int?>(json['id']),
|
||||
cover: asT<String>(json['cover'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
subTitle: asT<String?>(json['sub_title']),
|
||||
type: asT<int?>(json['type']),
|
||||
url: asT<String?>(json['url']),
|
||||
objId: asT<int?>(json['obj_id']),
|
||||
status: asT<String?>(json['status']),
|
||||
);
|
||||
int? id;
|
||||
String cover;
|
||||
String title;
|
||||
String? subTitle;
|
||||
int? type;
|
||||
String? url;
|
||||
int? objId;
|
||||
String? status;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'cover': cover,
|
||||
'title': title,
|
||||
'sub_title': subTitle,
|
||||
'type': type,
|
||||
'url': url,
|
||||
'obj_id': objId,
|
||||
'status': status,
|
||||
};
|
||||
}
|
||||
36
lib/models/comic/search_item.dart
Normal file
36
lib/models/comic/search_item.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter_dmzj/models/comic/search_model.dart';
|
||||
import 'package:flutter_dmzj/models/comic/web_search_model.dart';
|
||||
|
||||
class SearchComicItem {
|
||||
final int comicId;
|
||||
final String title;
|
||||
final String cover;
|
||||
final String author;
|
||||
final String lastChapterName;
|
||||
final String tags;
|
||||
SearchComicItem({
|
||||
required this.author,
|
||||
required this.comicId,
|
||||
required this.cover,
|
||||
required this.lastChapterName,
|
||||
required this.tags,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
factory SearchComicItem.fromApi(ComicSearchModel item) => SearchComicItem(
|
||||
author: item.authors ?? "",
|
||||
comicId: item.id,
|
||||
cover: item.cover ?? "",
|
||||
lastChapterName: item.lastName ?? "",
|
||||
tags: item.types ?? "",
|
||||
title: item.title,
|
||||
);
|
||||
factory SearchComicItem.fromWeb(ComicWebSearchModel item) => SearchComicItem(
|
||||
author: item.comicAuthor,
|
||||
comicId: item.id,
|
||||
cover: item.cover,
|
||||
lastChapterName: item.lastUpdateChapterName,
|
||||
tags: "/",
|
||||
title: item.comicName,
|
||||
);
|
||||
}
|
||||
70
lib/models/comic/search_model.dart
Normal file
70
lib/models/comic/search_model.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicSearchModel {
|
||||
ComicSearchModel({
|
||||
required this.id,
|
||||
this.authors,
|
||||
this.copyright,
|
||||
this.cover,
|
||||
this.hotHits,
|
||||
this.lastName,
|
||||
this.status,
|
||||
required this.title,
|
||||
this.types,
|
||||
this.aliasName,
|
||||
this.comicPy,
|
||||
});
|
||||
|
||||
factory ComicSearchModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicSearchModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
authors: asT<String?>(json['authors']),
|
||||
copyright: asT<int?>(json['copyright']),
|
||||
cover: asT<String?>(json['cover']),
|
||||
hotHits: asT<int?>(json['hot_hits']),
|
||||
lastName: asT<String?>(json['last_name']),
|
||||
status: asT<String?>(json['status']),
|
||||
title: asT<String>(json['title'])!,
|
||||
types: asT<String?>(json['types']),
|
||||
aliasName: asT<String?>(json['alias_name']),
|
||||
comicPy: asT<String?>(json['comic_py']),
|
||||
);
|
||||
|
||||
int id;
|
||||
String? authors;
|
||||
int? copyright;
|
||||
String? cover;
|
||||
int? hotHits;
|
||||
String? lastName;
|
||||
String? status;
|
||||
String title;
|
||||
String? types;
|
||||
String? aliasName;
|
||||
String? comicPy;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'authors': authors,
|
||||
'copyright': copyright,
|
||||
'cover': cover,
|
||||
'hot_hits': hotHits,
|
||||
'last_name': lastName,
|
||||
'status': status,
|
||||
'title': title,
|
||||
'types': types,
|
||||
'alias_name': aliasName,
|
||||
'comic_py': comicPy,
|
||||
};
|
||||
}
|
||||
103
lib/models/comic/special_detail_model.dart
Normal file
103
lib/models/comic/special_detail_model.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicSpecialDetailModel {
|
||||
ComicSpecialDetailModel({
|
||||
required this.mobileHeaderPic,
|
||||
required this.title,
|
||||
required this.pageUrl,
|
||||
required this.description,
|
||||
required this.comics,
|
||||
required this.commentAmount,
|
||||
});
|
||||
|
||||
factory ComicSpecialDetailModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<ComicSpecialComicModel>? comics =
|
||||
json['comics'] is List ? <ComicSpecialComicModel>[] : null;
|
||||
if (comics != null) {
|
||||
for (final dynamic item in json['comics']!) {
|
||||
if (item != null) {
|
||||
comics.add(ComicSpecialComicModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComicSpecialDetailModel(
|
||||
mobileHeaderPic: asT<String>(json['mobile_header_pic'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
pageUrl: asT<String>(json['page_url'])!,
|
||||
description: asT<String>(json['description'])!,
|
||||
comics: comics!,
|
||||
commentAmount: asT<int>(json['comment_amount'])!,
|
||||
);
|
||||
}
|
||||
|
||||
String mobileHeaderPic;
|
||||
String title;
|
||||
String pageUrl;
|
||||
String description;
|
||||
List<ComicSpecialComicModel> comics;
|
||||
int commentAmount;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'mobile_header_pic': mobileHeaderPic,
|
||||
'title': title,
|
||||
'page_url': pageUrl,
|
||||
'description': description,
|
||||
'comics': comics,
|
||||
'comment_amount': commentAmount,
|
||||
};
|
||||
}
|
||||
|
||||
class ComicSpecialComicModel {
|
||||
ComicSpecialComicModel({
|
||||
required this.cover,
|
||||
required this.recommendBrief,
|
||||
required this.recommendReason,
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.aliasName,
|
||||
});
|
||||
|
||||
factory ComicSpecialComicModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicSpecialComicModel(
|
||||
cover: asT<String?>(json['cover']) ?? "",
|
||||
recommendBrief: asT<String?>(json['recommend_brief']) ?? "",
|
||||
recommendReason: asT<String?>(json['recommend_reason']) ?? "",
|
||||
id: asT<int>(json['id'])!,
|
||||
name: asT<String?>(json['name']) ?? "",
|
||||
aliasName: asT<String?>(json['alias_name']) ?? "",
|
||||
);
|
||||
|
||||
String cover;
|
||||
String recommendBrief;
|
||||
String recommendReason;
|
||||
int id;
|
||||
String name;
|
||||
String aliasName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'cover': cover,
|
||||
'recommend_brief': recommendBrief,
|
||||
'recommend_reason': recommendReason,
|
||||
'id': id,
|
||||
'name': name,
|
||||
'alias_name': aliasName,
|
||||
};
|
||||
}
|
||||
58
lib/models/comic/special_model.dart
Normal file
58
lib/models/comic/special_model.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicSpecialModel {
|
||||
ComicSpecialModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.shortTitle,
|
||||
required this.createTime,
|
||||
required this.smallCover,
|
||||
required this.pageType,
|
||||
required this.sort,
|
||||
required this.pageUrl,
|
||||
});
|
||||
|
||||
factory ComicSpecialModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicSpecialModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
shortTitle: asT<String>(json['short_title'])!,
|
||||
createTime: asT<int>(json['create_time'])!,
|
||||
smallCover: asT<String>(json['small_cover'])!,
|
||||
pageType: asT<int>(json['page_type'])!,
|
||||
sort: asT<int>(json['sort'])!,
|
||||
pageUrl: asT<String>(json['page_url'])!,
|
||||
);
|
||||
|
||||
int id;
|
||||
String title;
|
||||
String shortTitle;
|
||||
int createTime;
|
||||
String smallCover;
|
||||
int pageType;
|
||||
int sort;
|
||||
String pageUrl;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'short_title': shortTitle,
|
||||
'create_time': createTime,
|
||||
'small_cover': smallCover,
|
||||
'page_type': pageType,
|
||||
'sort': sort,
|
||||
'page_url': pageUrl,
|
||||
};
|
||||
}
|
||||
66
lib/models/comic/update_item_model.dart
Normal file
66
lib/models/comic/update_item_model.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicUpdateItemModel {
|
||||
ComicUpdateItemModel({
|
||||
required this.comicId,
|
||||
required this.title,
|
||||
this.islong,
|
||||
this.authors,
|
||||
this.types,
|
||||
this.cover,
|
||||
this.status,
|
||||
this.lastUpdateChapterName,
|
||||
this.lastUpdateChapterId,
|
||||
this.lastUpdatetime,
|
||||
});
|
||||
|
||||
factory ComicUpdateItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicUpdateItemModel(
|
||||
comicId: asT<int>(json['comic_id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
islong: asT<int?>(json['islong']),
|
||||
authors: asT<String?>(json['authors']),
|
||||
types: asT<String?>(json['types']),
|
||||
cover: asT<String?>(json['cover']),
|
||||
status: asT<String?>(json['status']),
|
||||
lastUpdateChapterName: asT<String?>(json['last_update_chapter_name']),
|
||||
lastUpdateChapterId: asT<int?>(json['last_update_chapter_id']),
|
||||
lastUpdatetime: asT<int?>(json['last_updatetime']),
|
||||
);
|
||||
|
||||
int comicId;
|
||||
String title;
|
||||
int? islong;
|
||||
String? authors;
|
||||
String? types;
|
||||
String? cover;
|
||||
String? status;
|
||||
String? lastUpdateChapterName;
|
||||
int? lastUpdateChapterId;
|
||||
int? lastUpdatetime;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'comic_id': comicId,
|
||||
'title': title,
|
||||
'islong': islong,
|
||||
'authors': authors,
|
||||
'types': types,
|
||||
'cover': cover,
|
||||
'status': status,
|
||||
'last_update_chapter_name': lastUpdateChapterName,
|
||||
'last_update_chapter_id': lastUpdateChapterId,
|
||||
'last_updatetime': lastUpdatetime,
|
||||
};
|
||||
}
|
||||
48
lib/models/comic/view_point_model.dart
Normal file
48
lib/models/comic/view_point_model.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicViewPointModel {
|
||||
ComicViewPointModel({
|
||||
required this.id,
|
||||
required this.uid,
|
||||
required this.content,
|
||||
required this.num,
|
||||
required this.page,
|
||||
});
|
||||
|
||||
factory ComicViewPointModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicViewPointModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
uid: asT<int>(json['uid'])!,
|
||||
content: asT<String>(json['content'])!,
|
||||
num: (asT<int?>(json['num']) ?? 0).obs,
|
||||
page: asT<int>(json['page'])!,
|
||||
);
|
||||
|
||||
int id;
|
||||
int uid;
|
||||
String content;
|
||||
RxInt num;
|
||||
int page;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'uid': uid,
|
||||
'content': content,
|
||||
'num': num,
|
||||
'page': page,
|
||||
};
|
||||
}
|
||||
71
lib/models/comic/web_search_model.dart
Normal file
71
lib/models/comic/web_search_model.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class ComicWebSearchModel {
|
||||
ComicWebSearchModel({
|
||||
required this.id,
|
||||
required this.comicName,
|
||||
required this.comicAuthor,
|
||||
required this.comicCover,
|
||||
required this.cover,
|
||||
required this.lastUpdateChapterName,
|
||||
required this.comicUrlRaw,
|
||||
required this.comicUrl,
|
||||
required this.status,
|
||||
required this.chapterUrlRaw,
|
||||
required this.chapterUrl,
|
||||
});
|
||||
|
||||
factory ComicWebSearchModel.fromJson(Map<String, dynamic> json) =>
|
||||
ComicWebSearchModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
comicName: asT<String>(json['comic_name'])!,
|
||||
comicAuthor: asT<String?>(json['comic_author']) ?? "",
|
||||
comicCover: asT<String?>(json['comic_cover']) ?? "",
|
||||
cover: asT<String?>(json['cover']) ?? "",
|
||||
lastUpdateChapterName:
|
||||
asT<String?>(json['last_update_chapter_name']) ?? "",
|
||||
comicUrlRaw: asT<String?>(json['comic_url_raw']) ?? "",
|
||||
comicUrl: asT<String?>(json['comic_url']) ?? "",
|
||||
status: asT<String?>(json['status']) ?? "",
|
||||
chapterUrlRaw: asT<String?>(json['chapter_url_raw']) ?? "",
|
||||
chapterUrl: asT<String?>(json['chapter_url']) ?? "",
|
||||
);
|
||||
|
||||
int id;
|
||||
String comicName;
|
||||
String comicAuthor;
|
||||
String comicCover;
|
||||
String cover;
|
||||
String lastUpdateChapterName;
|
||||
String comicUrlRaw;
|
||||
String comicUrl;
|
||||
String status;
|
||||
String chapterUrlRaw;
|
||||
String chapterUrl;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'comic_name': comicName,
|
||||
'comic_author': comicAuthor,
|
||||
'comic_cover': comicCover,
|
||||
'cover': cover,
|
||||
'last_update_chapter_name': lastUpdateChapterName,
|
||||
'comic_url_raw': comicUrlRaw,
|
||||
'comic_url': comicUrl,
|
||||
'status': status,
|
||||
'chapter_url_raw': chapterUrlRaw,
|
||||
'chapter_url': chapterUrl,
|
||||
};
|
||||
}
|
||||
58
lib/models/comment/comment_item.dart
Normal file
58
lib/models/comment/comment_item.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
/// 动漫之家评论接口太TM混乱了
|
||||
/// 使用此类统一Model
|
||||
|
||||
class CommentItem {
|
||||
CommentItem({
|
||||
required this.id,
|
||||
required this.objId,
|
||||
required this.content,
|
||||
required this.photo,
|
||||
required this.createTime,
|
||||
required this.images,
|
||||
required this.likeAmount,
|
||||
required this.nickname,
|
||||
required this.replyAmount,
|
||||
required this.userId,
|
||||
required this.gender,
|
||||
required this.type,
|
||||
required this.originId,
|
||||
this.isEmpty = false,
|
||||
});
|
||||
|
||||
factory CommentItem.createEmpty() {
|
||||
return CommentItem(
|
||||
id: 0,
|
||||
objId: 0,
|
||||
content: "该评论不存在,可能已被删除",
|
||||
photo: "",
|
||||
createTime: 0,
|
||||
images: [],
|
||||
likeAmount: 0.obs,
|
||||
nickname: "-",
|
||||
replyAmount: 0,
|
||||
userId: 0,
|
||||
gender: 0,
|
||||
type: 0,
|
||||
originId: 0,
|
||||
isEmpty: true,
|
||||
);
|
||||
}
|
||||
|
||||
int id;
|
||||
int objId;
|
||||
String content;
|
||||
int createTime;
|
||||
Rx<int> likeAmount;
|
||||
int replyAmount;
|
||||
String nickname;
|
||||
String photo;
|
||||
List<String> images;
|
||||
int userId;
|
||||
List<CommentItem> parents = [];
|
||||
bool isEmpty;
|
||||
int gender;
|
||||
int type;
|
||||
int originId;
|
||||
}
|
||||
123
lib/models/comment/user_comment_item.dart
Normal file
123
lib/models/comment/user_comment_item.dart
Normal file
@@ -0,0 +1,123 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class UserCommentItem {
|
||||
UserCommentItem({
|
||||
required this.commentId,
|
||||
required this.content,
|
||||
required this.replyAmount,
|
||||
required this.likeAmount,
|
||||
this.originCommentId,
|
||||
required this.objId,
|
||||
required this.createTime,
|
||||
this.toCommentId,
|
||||
required this.objCover,
|
||||
required this.objName,
|
||||
this.pageUrl,
|
||||
this.mastercomment,
|
||||
});
|
||||
|
||||
factory UserCommentItem.fromJson(Map<String, dynamic> json) =>
|
||||
UserCommentItem(
|
||||
commentId: asT<int>(json['comment_id']) ?? 0,
|
||||
content: asT<String>(json['content']) ?? '',
|
||||
replyAmount: asT<int>(json['reply_amount']) ?? 0,
|
||||
likeAmount: asT<int>(json['like_amount']) ?? 0,
|
||||
originCommentId: asT<int?>(json['origin_comment_id']),
|
||||
objId: asT<int>(json['obj_id']) ?? 0,
|
||||
createTime: asT<int>(json['create_time']) ?? 0,
|
||||
toCommentId: asT<int?>(json['to_comment_id']),
|
||||
objCover: asT<String>(json['obj_cover']) ?? '',
|
||||
objName: asT<String>(json['obj_name']) ?? '',
|
||||
pageUrl: asT<String?>(json['page_url']),
|
||||
mastercomment: json['masterComment'] == null
|
||||
? null
|
||||
: UserMasterComment.fromJson(
|
||||
asT<Map<String, dynamic>>(json['masterComment'])!),
|
||||
);
|
||||
|
||||
int commentId;
|
||||
String content;
|
||||
int replyAmount;
|
||||
int likeAmount;
|
||||
int? originCommentId;
|
||||
int objId;
|
||||
int createTime;
|
||||
int? toCommentId;
|
||||
String objCover;
|
||||
String objName;
|
||||
String? pageUrl;
|
||||
UserMasterComment? mastercomment;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'comment_id': commentId,
|
||||
'content': content,
|
||||
'reply_amount': replyAmount,
|
||||
'like_amount': likeAmount,
|
||||
'origin_comment_id': originCommentId,
|
||||
'obj_id': objId,
|
||||
'create_time': createTime,
|
||||
'to_comment_id': toCommentId,
|
||||
'obj_cover': objCover,
|
||||
'obj_name': objName,
|
||||
'page_url': pageUrl,
|
||||
'masterComment': mastercomment,
|
||||
};
|
||||
}
|
||||
|
||||
class UserMasterComment {
|
||||
UserMasterComment({
|
||||
required this.id,
|
||||
required this.content,
|
||||
this.senderUid,
|
||||
this.likeAmount,
|
||||
required this.createTime,
|
||||
this.replyAmount,
|
||||
required this.nickname,
|
||||
});
|
||||
|
||||
factory UserMasterComment.fromJson(Map<String, dynamic> json) =>
|
||||
UserMasterComment(
|
||||
id: asT<int>(json['id'])!,
|
||||
content: asT<String>(json['content'])!,
|
||||
senderUid: asT<int?>(json['sender_uid']),
|
||||
likeAmount: asT<int?>(json['like_amount']),
|
||||
createTime: asT<int>(json['create_time'])!,
|
||||
replyAmount: asT<int?>(json['reply_amount']),
|
||||
nickname: asT<String>(json['nickname'])!,
|
||||
);
|
||||
|
||||
int id;
|
||||
String content;
|
||||
int? senderUid;
|
||||
int? likeAmount;
|
||||
int createTime;
|
||||
int? replyAmount;
|
||||
String nickname;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'content': content,
|
||||
'sender_uid': senderUid,
|
||||
'like_amount': likeAmount,
|
||||
'create_time': createTime,
|
||||
'reply_amount': replyAmount,
|
||||
'nickname': nickname,
|
||||
};
|
||||
}
|
||||
94
lib/models/db/comic_download_info.dart
Normal file
94
lib/models/db/comic_download_info.dart
Normal file
@@ -0,0 +1,94 @@
|
||||
import 'package:flutter_dmzj/models/db/download_status.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
part 'comic_download_info.g.dart';
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
class ComicDownloadInfo {
|
||||
ComicDownloadInfo({
|
||||
required this.addTime,
|
||||
required this.chapterId,
|
||||
required this.chapterSort,
|
||||
required this.comicCover,
|
||||
required this.comicId,
|
||||
required this.comicName,
|
||||
required this.files,
|
||||
required this.index,
|
||||
required this.savePath,
|
||||
required this.status,
|
||||
required this.taskId,
|
||||
required this.total,
|
||||
required this.volumeName,
|
||||
required this.urls,
|
||||
required this.chapterName,
|
||||
required this.isVip,
|
||||
required this.isLongComic,
|
||||
});
|
||||
|
||||
///TaskID 任务,由漫画ID_章节ID组成
|
||||
@HiveField(0)
|
||||
String taskId;
|
||||
|
||||
///ComicID 漫画ID
|
||||
@HiveField(1)
|
||||
int comicId;
|
||||
|
||||
///ComicName 漫画名称
|
||||
@HiveField(2)
|
||||
String comicName;
|
||||
|
||||
///ComicCover 漫画封面
|
||||
@HiveField(3)
|
||||
String comicCover;
|
||||
|
||||
///ChapterID 章节ID
|
||||
@HiveField(4)
|
||||
int chapterId;
|
||||
|
||||
@HiveField(5)
|
||||
String chapterName;
|
||||
|
||||
///VoulmeName 分卷名称
|
||||
@HiveField(6)
|
||||
String volumeName;
|
||||
|
||||
///ChapterSort 排序
|
||||
@HiveField(7)
|
||||
int chapterSort;
|
||||
|
||||
///SavePath 存储路径
|
||||
@HiveField(8)
|
||||
String savePath;
|
||||
|
||||
///Files 文件列表
|
||||
@HiveField(9)
|
||||
List<String> files;
|
||||
|
||||
///Index 当前下载页数
|
||||
@HiveField(10)
|
||||
int index;
|
||||
|
||||
///Total 总计页数
|
||||
@HiveField(11)
|
||||
int total;
|
||||
|
||||
///Status 当前状态
|
||||
@HiveField(12)
|
||||
DownloadStatus status;
|
||||
|
||||
///AddTime 任务时间
|
||||
@HiveField(13)
|
||||
DateTime addTime;
|
||||
|
||||
/// 下载图片链接
|
||||
@HiveField(14)
|
||||
List<String> urls;
|
||||
|
||||
/// 是否VIP章节
|
||||
/// * 暂时没啥用,总之先加上
|
||||
@HiveField(15)
|
||||
bool isVip;
|
||||
|
||||
/// 是否为条漫
|
||||
@HiveField(16)
|
||||
bool isLongComic;
|
||||
}
|
||||
89
lib/models/db/comic_download_info.g.dart
Normal file
89
lib/models/db/comic_download_info.g.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'comic_download_info.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class ComicDownloadInfoAdapter extends TypeAdapter<ComicDownloadInfo> {
|
||||
@override
|
||||
final int typeId = 3;
|
||||
|
||||
@override
|
||||
ComicDownloadInfo read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return ComicDownloadInfo(
|
||||
addTime: fields[13] as DateTime,
|
||||
chapterId: fields[4] as int,
|
||||
chapterSort: fields[7] as int,
|
||||
comicCover: fields[3] as String,
|
||||
comicId: fields[1] as int,
|
||||
comicName: fields[2] as String,
|
||||
files: (fields[9] as List).cast<String>(),
|
||||
index: fields[10] as int,
|
||||
savePath: fields[8] as String,
|
||||
status: fields[12] as DownloadStatus,
|
||||
taskId: fields[0] as String,
|
||||
total: fields[11] as int,
|
||||
volumeName: fields[6] as String,
|
||||
urls: (fields[14] as List).cast<String>(),
|
||||
chapterName: fields[5] as String,
|
||||
isVip: (fields[15] ?? false) as bool,
|
||||
isLongComic: (fields[16] ?? false) as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ComicDownloadInfo obj) {
|
||||
writer
|
||||
..writeByte(17)
|
||||
..writeByte(0)
|
||||
..write(obj.taskId)
|
||||
..writeByte(1)
|
||||
..write(obj.comicId)
|
||||
..writeByte(2)
|
||||
..write(obj.comicName)
|
||||
..writeByte(3)
|
||||
..write(obj.comicCover)
|
||||
..writeByte(4)
|
||||
..write(obj.chapterId)
|
||||
..writeByte(5)
|
||||
..write(obj.chapterName)
|
||||
..writeByte(6)
|
||||
..write(obj.volumeName)
|
||||
..writeByte(7)
|
||||
..write(obj.chapterSort)
|
||||
..writeByte(8)
|
||||
..write(obj.savePath)
|
||||
..writeByte(9)
|
||||
..write(obj.files)
|
||||
..writeByte(10)
|
||||
..write(obj.index)
|
||||
..writeByte(11)
|
||||
..write(obj.total)
|
||||
..writeByte(12)
|
||||
..write(obj.status)
|
||||
..writeByte(13)
|
||||
..write(obj.addTime)
|
||||
..writeByte(14)
|
||||
..write(obj.urls)
|
||||
..writeByte(15)
|
||||
..write(obj.isVip)
|
||||
..writeByte(16)
|
||||
..write(obj.isLongComic);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ComicDownloadInfoAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
36
lib/models/db/comic_history.dart
Normal file
36
lib/models/db/comic_history.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:hive/hive.dart';
|
||||
part 'comic_history.g.dart';
|
||||
|
||||
@HiveType(typeId: 1)
|
||||
class ComicHistory {
|
||||
ComicHistory({
|
||||
required this.comicId,
|
||||
required this.chapterId,
|
||||
required this.comicName,
|
||||
required this.comicCover,
|
||||
required this.chapterName,
|
||||
required this.updateTime,
|
||||
required this.page,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
int comicId;
|
||||
|
||||
@HiveField(1)
|
||||
int chapterId;
|
||||
|
||||
@HiveField(2)
|
||||
String comicName;
|
||||
|
||||
@HiveField(3)
|
||||
String comicCover;
|
||||
|
||||
@HiveField(4)
|
||||
String chapterName;
|
||||
|
||||
@HiveField(5)
|
||||
int page;
|
||||
|
||||
@HiveField(6)
|
||||
DateTime updateTime;
|
||||
}
|
||||
59
lib/models/db/comic_history.g.dart
Normal file
59
lib/models/db/comic_history.g.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'comic_history.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class ComicHistoryAdapter extends TypeAdapter<ComicHistory> {
|
||||
@override
|
||||
final int typeId = 1;
|
||||
|
||||
@override
|
||||
ComicHistory read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return ComicHistory(
|
||||
comicId: fields[0] as int,
|
||||
chapterId: fields[1] as int,
|
||||
comicName: fields[2] as String,
|
||||
comicCover: fields[3] as String,
|
||||
chapterName: fields[4] as String,
|
||||
updateTime: fields[6] as DateTime,
|
||||
page: fields[5] as int,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ComicHistory obj) {
|
||||
writer
|
||||
..writeByte(7)
|
||||
..writeByte(0)
|
||||
..write(obj.comicId)
|
||||
..writeByte(1)
|
||||
..write(obj.chapterId)
|
||||
..writeByte(2)
|
||||
..write(obj.comicName)
|
||||
..writeByte(3)
|
||||
..write(obj.comicCover)
|
||||
..writeByte(4)
|
||||
..write(obj.chapterName)
|
||||
..writeByte(5)
|
||||
..write(obj.page)
|
||||
..writeByte(6)
|
||||
..write(obj.updateTime);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ComicHistoryAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
46
lib/models/db/download_status.dart
Normal file
46
lib/models/db/download_status.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:hive/hive.dart';
|
||||
part 'download_status.g.dart';
|
||||
|
||||
/// 下载状态
|
||||
@HiveType(typeId: 4)
|
||||
enum DownloadStatus {
|
||||
/// 等待下载中
|
||||
@HiveField(0)
|
||||
wait,
|
||||
|
||||
/// 正在读取章节信息
|
||||
@HiveField(1)
|
||||
loadding,
|
||||
|
||||
/// 下载中
|
||||
@HiveField(2)
|
||||
downloading,
|
||||
|
||||
/// 使用数据,自动暂停,当网络切换时恢复下载
|
||||
@HiveField(3)
|
||||
pauseCellular,
|
||||
|
||||
/// 暂停
|
||||
@HiveField(4)
|
||||
pause,
|
||||
|
||||
/// 已完成
|
||||
@HiveField(5)
|
||||
complete,
|
||||
|
||||
/// 读取信息时出现错误
|
||||
@HiveField(6)
|
||||
errorLoad,
|
||||
|
||||
/// 下载出错
|
||||
@HiveField(7)
|
||||
error,
|
||||
|
||||
/// 已取消
|
||||
@HiveField(8)
|
||||
cancel,
|
||||
|
||||
/// 等待网络连接
|
||||
@HiveField(9)
|
||||
waitNetwork,
|
||||
}
|
||||
86
lib/models/db/download_status.g.dart
Normal file
86
lib/models/db/download_status.g.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'download_status.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class DownloadStatusAdapter extends TypeAdapter<DownloadStatus> {
|
||||
@override
|
||||
final int typeId = 4;
|
||||
|
||||
@override
|
||||
DownloadStatus read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return DownloadStatus.wait;
|
||||
case 1:
|
||||
return DownloadStatus.loadding;
|
||||
case 2:
|
||||
return DownloadStatus.downloading;
|
||||
case 3:
|
||||
return DownloadStatus.pauseCellular;
|
||||
case 4:
|
||||
return DownloadStatus.pause;
|
||||
case 5:
|
||||
return DownloadStatus.complete;
|
||||
case 6:
|
||||
return DownloadStatus.errorLoad;
|
||||
case 7:
|
||||
return DownloadStatus.error;
|
||||
case 8:
|
||||
return DownloadStatus.cancel;
|
||||
case 9:
|
||||
return DownloadStatus.waitNetwork;
|
||||
default:
|
||||
return DownloadStatus.wait;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, DownloadStatus obj) {
|
||||
switch (obj) {
|
||||
case DownloadStatus.wait:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case DownloadStatus.loadding:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case DownloadStatus.downloading:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
case DownloadStatus.pauseCellular:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
case DownloadStatus.pause:
|
||||
writer.writeByte(4);
|
||||
break;
|
||||
case DownloadStatus.complete:
|
||||
writer.writeByte(5);
|
||||
break;
|
||||
case DownloadStatus.errorLoad:
|
||||
writer.writeByte(6);
|
||||
break;
|
||||
case DownloadStatus.error:
|
||||
writer.writeByte(7);
|
||||
break;
|
||||
case DownloadStatus.cancel:
|
||||
writer.writeByte(8);
|
||||
break;
|
||||
case DownloadStatus.waitNetwork:
|
||||
writer.writeByte(9);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is DownloadStatusAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
39
lib/models/db/local_favorite.dart
Normal file
39
lib/models/db/local_favorite.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
part 'local_favorite.g.dart';
|
||||
|
||||
@HiveType(typeId: 6)
|
||||
class LocalFavorite {
|
||||
LocalFavorite({
|
||||
required this.id,
|
||||
required this.objId,
|
||||
required this.title,
|
||||
required this.cover,
|
||||
required this.type,
|
||||
required this.updateTime,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
String id;
|
||||
|
||||
String get hiveId => "${type}_$objId";
|
||||
|
||||
@HiveField(1)
|
||||
int objId;
|
||||
|
||||
@HiveField(2)
|
||||
String title;
|
||||
|
||||
@HiveField(3)
|
||||
String cover;
|
||||
|
||||
/// 类型,对应app_constant,漫画或小说
|
||||
@HiveField(4)
|
||||
int type;
|
||||
|
||||
@HiveField(5)
|
||||
DateTime updateTime;
|
||||
|
||||
//是否被选中
|
||||
Rx<bool> isChecked = false.obs;
|
||||
}
|
||||
56
lib/models/db/local_favorite.g.dart
Normal file
56
lib/models/db/local_favorite.g.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'local_favorite.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class LocalFavoriteAdapter extends TypeAdapter<LocalFavorite> {
|
||||
@override
|
||||
final int typeId = 6;
|
||||
|
||||
@override
|
||||
LocalFavorite read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return LocalFavorite(
|
||||
id: fields[0] as String,
|
||||
objId: fields[1] as int,
|
||||
title: fields[2] as String,
|
||||
cover: fields[3] as String,
|
||||
type: fields[4] as int,
|
||||
updateTime: fields[5] as DateTime,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, LocalFavorite obj) {
|
||||
writer
|
||||
..writeByte(6)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.objId)
|
||||
..writeByte(2)
|
||||
..write(obj.title)
|
||||
..writeByte(3)
|
||||
..write(obj.cover)
|
||||
..writeByte(4)
|
||||
..write(obj.type)
|
||||
..writeByte(5)
|
||||
..write(obj.updateTime);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is LocalFavoriteAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
105
lib/models/db/novel_download_info.dart
Normal file
105
lib/models/db/novel_download_info.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter_dmzj/models/db/download_status.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
part 'novel_download_info.g.dart';
|
||||
|
||||
@HiveType(typeId: 5)
|
||||
class NovelDownloadInfo {
|
||||
NovelDownloadInfo({
|
||||
required this.addTime,
|
||||
required this.chapterId,
|
||||
required this.chapterSort,
|
||||
required this.novelCover,
|
||||
required this.novelId,
|
||||
required this.novelName,
|
||||
required this.fileName,
|
||||
required this.imageFiles,
|
||||
required this.savePath,
|
||||
required this.status,
|
||||
required this.taskId,
|
||||
required this.isImage,
|
||||
required this.volumeName,
|
||||
required this.progress,
|
||||
required this.chapterName,
|
||||
required this.volumeID,
|
||||
required this.isVip,
|
||||
required this.volumeOrder,
|
||||
required this.imageUrls,
|
||||
});
|
||||
|
||||
///TaskID 任务,由小说ID_章节ID组成
|
||||
@HiveField(0)
|
||||
String taskId;
|
||||
|
||||
///NovelID 小说ID
|
||||
@HiveField(1)
|
||||
int novelId;
|
||||
|
||||
///NovelName 小说名称
|
||||
@HiveField(2)
|
||||
String novelName;
|
||||
|
||||
///NovelCover 小说封面
|
||||
@HiveField(3)
|
||||
String novelCover;
|
||||
|
||||
///ChapterID 章节ID
|
||||
@HiveField(4)
|
||||
int chapterId;
|
||||
|
||||
///chapterName 章节名称
|
||||
@HiveField(5)
|
||||
String chapterName;
|
||||
|
||||
///VoulmeID 分卷ID
|
||||
@HiveField(6)
|
||||
int volumeID;
|
||||
|
||||
///VoulmeName 分卷名称
|
||||
@HiveField(7)
|
||||
String volumeName;
|
||||
|
||||
///volumeOrder 分卷排序
|
||||
@HiveField(8)
|
||||
int volumeOrder;
|
||||
|
||||
///ChapterSort 排序
|
||||
@HiveField(9)
|
||||
int chapterSort;
|
||||
|
||||
///SavePath 存储路径
|
||||
@HiveField(10)
|
||||
String savePath;
|
||||
|
||||
///Files 文件列表
|
||||
@HiveField(11)
|
||||
String fileName;
|
||||
|
||||
///isImage 是否为插图
|
||||
@HiveField(12)
|
||||
bool isImage;
|
||||
|
||||
/// 图片保存路径
|
||||
@HiveField(13)
|
||||
List<String> imageFiles;
|
||||
|
||||
///下载进度 0-100
|
||||
@HiveField(14)
|
||||
int progress;
|
||||
|
||||
///Status 当前状态
|
||||
@HiveField(15)
|
||||
DownloadStatus status;
|
||||
|
||||
///AddTime 任务时间
|
||||
@HiveField(16)
|
||||
DateTime addTime;
|
||||
|
||||
/// 是否VIP章节
|
||||
/// * 暂时没啥用,总之先加上
|
||||
@HiveField(17)
|
||||
bool isVip;
|
||||
|
||||
/// 下载图片链接
|
||||
@HiveField(18)
|
||||
List<String> imageUrls;
|
||||
}
|
||||
95
lib/models/db/novel_download_info.g.dart
Normal file
95
lib/models/db/novel_download_info.g.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'novel_download_info.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class NovelDownloadInfoAdapter extends TypeAdapter<NovelDownloadInfo> {
|
||||
@override
|
||||
final int typeId = 5;
|
||||
|
||||
@override
|
||||
NovelDownloadInfo read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return NovelDownloadInfo(
|
||||
addTime: fields[16] as DateTime,
|
||||
chapterId: fields[4] as int,
|
||||
chapterSort: fields[9] as int,
|
||||
novelCover: fields[3] as String,
|
||||
novelId: fields[1] as int,
|
||||
novelName: fields[2] as String,
|
||||
fileName: fields[11] as String,
|
||||
imageFiles: (fields[13] as List).cast<String>(),
|
||||
savePath: fields[10] as String,
|
||||
status: fields[15] as DownloadStatus,
|
||||
taskId: fields[0] as String,
|
||||
isImage: fields[12] as bool,
|
||||
volumeName: fields[7] as String,
|
||||
progress: fields[14] as int,
|
||||
chapterName: fields[5] as String,
|
||||
volumeID: fields[6] as int,
|
||||
isVip: fields[17] as bool,
|
||||
volumeOrder: fields[8] as int,
|
||||
imageUrls: (fields[18] as List).cast<String>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, NovelDownloadInfo obj) {
|
||||
writer
|
||||
..writeByte(19)
|
||||
..writeByte(0)
|
||||
..write(obj.taskId)
|
||||
..writeByte(1)
|
||||
..write(obj.novelId)
|
||||
..writeByte(2)
|
||||
..write(obj.novelName)
|
||||
..writeByte(3)
|
||||
..write(obj.novelCover)
|
||||
..writeByte(4)
|
||||
..write(obj.chapterId)
|
||||
..writeByte(5)
|
||||
..write(obj.chapterName)
|
||||
..writeByte(6)
|
||||
..write(obj.volumeID)
|
||||
..writeByte(7)
|
||||
..write(obj.volumeName)
|
||||
..writeByte(8)
|
||||
..write(obj.volumeOrder)
|
||||
..writeByte(9)
|
||||
..write(obj.chapterSort)
|
||||
..writeByte(10)
|
||||
..write(obj.savePath)
|
||||
..writeByte(11)
|
||||
..write(obj.fileName)
|
||||
..writeByte(12)
|
||||
..write(obj.isImage)
|
||||
..writeByte(13)
|
||||
..write(obj.imageFiles)
|
||||
..writeByte(14)
|
||||
..write(obj.progress)
|
||||
..writeByte(15)
|
||||
..write(obj.status)
|
||||
..writeByte(16)
|
||||
..write(obj.addTime)
|
||||
..writeByte(17)
|
||||
..write(obj.isVip)
|
||||
..writeByte(18)
|
||||
..write(obj.imageUrls);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is NovelDownloadInfoAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
48
lib/models/db/novel_history.dart
Normal file
48
lib/models/db/novel_history.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:hive/hive.dart';
|
||||
part 'novel_history.g.dart';
|
||||
|
||||
@HiveType(typeId: 2)
|
||||
class NovelHistory {
|
||||
NovelHistory({
|
||||
required this.novelId,
|
||||
required this.chapterId,
|
||||
required this.novelName,
|
||||
required this.novelCover,
|
||||
required this.chapterName,
|
||||
required this.updateTime,
|
||||
required this.index,
|
||||
required this.total,
|
||||
required this.volumeId,
|
||||
required this.volumeName,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
int novelId;
|
||||
|
||||
@HiveField(1)
|
||||
int chapterId;
|
||||
|
||||
@HiveField(2)
|
||||
String novelName;
|
||||
|
||||
@HiveField(3)
|
||||
String novelCover;
|
||||
|
||||
@HiveField(4)
|
||||
String chapterName;
|
||||
|
||||
@HiveField(5)
|
||||
int index;
|
||||
|
||||
@HiveField(6)
|
||||
int total;
|
||||
|
||||
@HiveField(7)
|
||||
int volumeId;
|
||||
|
||||
@HiveField(8)
|
||||
String volumeName;
|
||||
|
||||
@HiveField(9)
|
||||
DateTime updateTime;
|
||||
}
|
||||
68
lib/models/db/novel_history.g.dart
Normal file
68
lib/models/db/novel_history.g.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'novel_history.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class NovelHistoryAdapter extends TypeAdapter<NovelHistory> {
|
||||
@override
|
||||
final int typeId = 2;
|
||||
|
||||
@override
|
||||
NovelHistory read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return NovelHistory(
|
||||
novelId: fields[0] as int,
|
||||
chapterId: fields[1] as int,
|
||||
novelName: fields[2] as String,
|
||||
novelCover: fields[3] as String,
|
||||
chapterName: fields[4] as String,
|
||||
updateTime: fields[9] as DateTime,
|
||||
index: fields[5] as int,
|
||||
total: fields[6] as int,
|
||||
volumeId: fields[7] as int,
|
||||
volumeName: fields[8] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, NovelHistory obj) {
|
||||
writer
|
||||
..writeByte(10)
|
||||
..writeByte(0)
|
||||
..write(obj.novelId)
|
||||
..writeByte(1)
|
||||
..write(obj.chapterId)
|
||||
..writeByte(2)
|
||||
..write(obj.novelName)
|
||||
..writeByte(3)
|
||||
..write(obj.novelCover)
|
||||
..writeByte(4)
|
||||
..write(obj.chapterName)
|
||||
..writeByte(5)
|
||||
..write(obj.index)
|
||||
..writeByte(6)
|
||||
..write(obj.total)
|
||||
..writeByte(7)
|
||||
..write(obj.volumeId)
|
||||
..writeByte(8)
|
||||
..write(obj.volumeName)
|
||||
..writeByte(9)
|
||||
..write(obj.updateTime);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is NovelHistoryAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
46
lib/models/news/news_banner_model.dart
Normal file
46
lib/models/news/news_banner_model.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NewsBannerModel {
|
||||
NewsBannerModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.picUrl,
|
||||
this.objectId,
|
||||
this.objectUrl,
|
||||
});
|
||||
|
||||
factory NewsBannerModel.fromJson(Map<String, dynamic> json) =>
|
||||
NewsBannerModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
picUrl: asT<String>(json['pic_url'])!,
|
||||
objectId: asT<int?>(json['object_id']),
|
||||
objectUrl: asT<String?>(json['object_url']),
|
||||
);
|
||||
|
||||
int id;
|
||||
String title;
|
||||
String picUrl;
|
||||
int? objectId;
|
||||
String? objectUrl;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'pic_url': picUrl,
|
||||
'object_id': objectId,
|
||||
'object_url': objectUrl,
|
||||
};
|
||||
}
|
||||
74
lib/models/news/news_list_item_model.dart
Normal file
74
lib/models/news/news_list_item_model.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NewsListItemModel {
|
||||
NewsListItemModel({
|
||||
required this.articleId,
|
||||
required this.title,
|
||||
this.createTime,
|
||||
this.intro,
|
||||
this.authorId,
|
||||
this.status,
|
||||
this.rowPicUrl,
|
||||
this.colPicUrl,
|
||||
this.pageUrl,
|
||||
this.authorUid,
|
||||
this.cover,
|
||||
this.nickname,
|
||||
});
|
||||
|
||||
factory NewsListItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
NewsListItemModel(
|
||||
articleId: asT<int>(json['article_id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
createTime: asT<int?>(json['create_time']),
|
||||
intro: asT<String?>(json['intro']),
|
||||
authorId: asT<int?>(json['author_id']),
|
||||
status: asT<int?>(json['status']),
|
||||
rowPicUrl: asT<String?>(json['row_pic_url']),
|
||||
colPicUrl: asT<String?>(json['col_pic_url']),
|
||||
pageUrl: asT<String?>(json['page_url']),
|
||||
authorUid: asT<int?>(json['author_uid']),
|
||||
cover: asT<String?>(json['cover']),
|
||||
nickname: asT<String?>(json['nickname']),
|
||||
);
|
||||
|
||||
int articleId;
|
||||
String title;
|
||||
int? createTime;
|
||||
String? intro;
|
||||
int? authorId;
|
||||
int? status;
|
||||
String? rowPicUrl;
|
||||
String? colPicUrl;
|
||||
String? pageUrl;
|
||||
int? authorUid;
|
||||
String? cover;
|
||||
String? nickname;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'article_id': articleId,
|
||||
'title': title,
|
||||
'create_time': createTime,
|
||||
'intro': intro,
|
||||
'author_id': authorId,
|
||||
'status': status,
|
||||
'row_pic_url': rowPicUrl,
|
||||
'col_pic_url': colPicUrl,
|
||||
'page_url': pageUrl,
|
||||
'author_uid': authorUid,
|
||||
'cover': cover,
|
||||
'nickname': nickname,
|
||||
};
|
||||
}
|
||||
42
lib/models/news/news_stat_model.dart
Normal file
42
lib/models/news/news_stat_model.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NewsStatModel {
|
||||
NewsStatModel({
|
||||
required this.commentAmount,
|
||||
required this.moodAmount,
|
||||
required this.rowPicUrl,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
factory NewsStatModel.fromJson(Map<String, dynamic> json) => NewsStatModel(
|
||||
/// DMZJ后端是真混乱... commentAmount是string,mood_amount是int
|
||||
commentAmount: int.tryParse(json['comment_amount'].toString()) ?? 0,
|
||||
moodAmount: int.tryParse(json['mood_amount'].toString()) ?? 0,
|
||||
rowPicUrl: asT<String>(json['row_pic_url'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
);
|
||||
|
||||
int commentAmount;
|
||||
int moodAmount;
|
||||
String rowPicUrl;
|
||||
String title;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'comment_amount': commentAmount,
|
||||
'mood_amount': moodAmount,
|
||||
'row_pic_url': rowPicUrl,
|
||||
'title': title,
|
||||
};
|
||||
}
|
||||
33
lib/models/news/news_tag_model.dart
Normal file
33
lib/models/news/news_tag_model.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NewsTagModel {
|
||||
NewsTagModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
factory NewsTagModel.fromJson(Map<String, dynamic> json) => NewsTagModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
name: asT<String>(json['name'])!,
|
||||
);
|
||||
|
||||
int id;
|
||||
String name;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'name': name,
|
||||
};
|
||||
}
|
||||
73
lib/models/novel/category_filter_model.dart
Normal file
73
lib/models/novel/category_filter_model.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NovelCategoryFilterModel {
|
||||
NovelCategoryFilterModel({
|
||||
required this.title,
|
||||
required this.items,
|
||||
});
|
||||
|
||||
factory NovelCategoryFilterModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<NovelCategoryFilterItemModel>? items =
|
||||
json['items'] is List ? <NovelCategoryFilterItemModel>[] : null;
|
||||
if (items != null) {
|
||||
for (final dynamic item in json['items']!) {
|
||||
if (item != null) {
|
||||
items.add(NovelCategoryFilterItemModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return NovelCategoryFilterModel(
|
||||
title: asT<String>(json['title'])!,
|
||||
items: items!,
|
||||
);
|
||||
}
|
||||
|
||||
String title;
|
||||
List<NovelCategoryFilterItemModel> items;
|
||||
var selectId = 0.obs;
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'title': title,
|
||||
'items': items,
|
||||
};
|
||||
}
|
||||
|
||||
class NovelCategoryFilterItemModel {
|
||||
NovelCategoryFilterItemModel({
|
||||
required this.tagId,
|
||||
required this.tagName,
|
||||
});
|
||||
|
||||
factory NovelCategoryFilterItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
NovelCategoryFilterItemModel(
|
||||
tagId: asT<int>(json['tagId'])!,
|
||||
tagName: asT<String>(json['title'])!,
|
||||
);
|
||||
|
||||
int tagId;
|
||||
String tagName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'tag_id': tagId,
|
||||
'tag_name': tagName,
|
||||
};
|
||||
}
|
||||
38
lib/models/novel/category_model.dart
Normal file
38
lib/models/novel/category_model.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NovelCategoryModel {
|
||||
NovelCategoryModel({
|
||||
required this.tagId,
|
||||
required this.title,
|
||||
required this.cover,
|
||||
});
|
||||
|
||||
factory NovelCategoryModel.fromJson(Map<String, dynamic> json) =>
|
||||
NovelCategoryModel(
|
||||
tagId: asT<int>(json['tagId'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
cover: asT<String>(json['cover'])!,
|
||||
);
|
||||
|
||||
int tagId;
|
||||
String title;
|
||||
String cover;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'tag_id': tagId,
|
||||
'title': title,
|
||||
'cover': cover,
|
||||
};
|
||||
}
|
||||
62
lib/models/novel/category_novel_model.dart
Normal file
62
lib/models/novel/category_novel_model.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NovelCategoryNovelModel {
|
||||
NovelCategoryNovelModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.authors,
|
||||
this.cover,
|
||||
this.hotHits,
|
||||
this.lastName,
|
||||
this.status,
|
||||
this.types,
|
||||
this.subNums,
|
||||
});
|
||||
|
||||
factory NovelCategoryNovelModel.fromJson(Map<String, dynamic> json) =>
|
||||
NovelCategoryNovelModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
authors: asT<String?>(json['authors']),
|
||||
cover: asT<String?>(json['cover']),
|
||||
hotHits: asT<int?>(json['hot_hits']),
|
||||
lastName: asT<String?>(json['last_name']),
|
||||
status: asT<String?>(json['status']),
|
||||
types: asT<String?>(json['types']),
|
||||
subNums: asT<int?>(json['sub_nums']),
|
||||
);
|
||||
|
||||
int id;
|
||||
String title;
|
||||
String? authors;
|
||||
String? cover;
|
||||
int? hotHits;
|
||||
String? lastName;
|
||||
String? status;
|
||||
String? types;
|
||||
int? subNums;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'authors': authors,
|
||||
'cover': cover,
|
||||
'hot_hits': hotHits,
|
||||
'last_name': lastName,
|
||||
'status': status,
|
||||
'types': types,
|
||||
'sub_nums': subNums,
|
||||
};
|
||||
}
|
||||
239
lib/models/novel/detail_model.dart
Normal file
239
lib/models/novel/detail_model.dart
Normal file
@@ -0,0 +1,239 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NovelDetailModel {
|
||||
NovelDetailModel({
|
||||
required this.data,
|
||||
required this.readingRecord,
|
||||
});
|
||||
|
||||
factory NovelDetailModel.fromJson(Map<String, dynamic> json) =>
|
||||
NovelDetailModel(
|
||||
data: NovelDetailDataModel.fromJson(
|
||||
asT<Map<String, dynamic>>(json['data'])!),
|
||||
readingRecord: NovelDetailReadingRecordModel.fromJson(
|
||||
asT<Map<String, dynamic>>(json['readingRecord'])!),
|
||||
);
|
||||
|
||||
NovelDetailDataModel data;
|
||||
NovelDetailReadingRecordModel readingRecord;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'data': data,
|
||||
'readingRecord': readingRecord,
|
||||
};
|
||||
}
|
||||
|
||||
class NovelDetailDataModel {
|
||||
NovelDetailDataModel({
|
||||
required this.novelId,
|
||||
required this.name,
|
||||
required this.zone,
|
||||
required this.status,
|
||||
required this.lastUpdateVolumeName,
|
||||
required this.lastUpdateChapterName,
|
||||
required this.lastUpdateVolumeId,
|
||||
required this.lastUpdateChapterId,
|
||||
required this.lastUpdateTime,
|
||||
required this.cover,
|
||||
required this.hotHits,
|
||||
required this.introduction,
|
||||
required this.types,
|
||||
required this.authors,
|
||||
required this.firstLetter,
|
||||
required this.volume,
|
||||
});
|
||||
|
||||
factory NovelDetailDataModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<String>? types = json['types'] is List ? <String>[] : null;
|
||||
if (types != null) {
|
||||
for (final dynamic item in json['types']!) {
|
||||
if (item != null) {
|
||||
types.add(asT<String>(item)!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<Volume>? volume = json['volume'] is List ? <Volume>[] : null;
|
||||
if (volume != null) {
|
||||
for (final dynamic item in json['volume']!) {
|
||||
if (item != null) {
|
||||
volume.add(Volume.fromJson(asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return NovelDetailDataModel(
|
||||
novelId: asT<int>(json['novel_id'])!,
|
||||
name: asT<String>(json['name'])!,
|
||||
zone: asT<String>(json['zone'])!,
|
||||
status: asT<String>(json['status'])!,
|
||||
lastUpdateVolumeName: asT<String>(json['last_update_volume_name'])!,
|
||||
lastUpdateChapterName: asT<String>(json['last_update_chapter_name'])!,
|
||||
lastUpdateVolumeId: asT<int>(json['last_update_volume_id'])!,
|
||||
lastUpdateChapterId: asT<int>(json['last_update_chapter_id'])!,
|
||||
lastUpdateTime: asT<int>(json['last_update_time'])!,
|
||||
cover: asT<String>(json['cover'])!,
|
||||
hotHits: asT<int?>(json['hot_hits']) ?? 0,
|
||||
introduction: asT<String>(json['introduction'])!,
|
||||
types: types!,
|
||||
authors: asT<String>(json['authors'])!,
|
||||
firstLetter: asT<String>(json['first_letter'])!,
|
||||
volume: volume!,
|
||||
);
|
||||
}
|
||||
|
||||
int novelId;
|
||||
String name;
|
||||
String zone;
|
||||
String status;
|
||||
String lastUpdateVolumeName;
|
||||
String lastUpdateChapterName;
|
||||
int lastUpdateVolumeId;
|
||||
int lastUpdateChapterId;
|
||||
int lastUpdateTime;
|
||||
String cover;
|
||||
int hotHits;
|
||||
String introduction;
|
||||
List<String> types;
|
||||
String authors;
|
||||
String firstLetter;
|
||||
List<Volume> volume;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'novel_id': novelId,
|
||||
'name': name,
|
||||
'zone': zone,
|
||||
'status': status,
|
||||
'last_update_volume_name': lastUpdateVolumeName,
|
||||
'last_update_chapter_name': lastUpdateChapterName,
|
||||
'last_update_volume_id': lastUpdateVolumeId,
|
||||
'last_update_chapter_id': lastUpdateChapterId,
|
||||
'last_update_time': lastUpdateTime,
|
||||
'cover': cover,
|
||||
'hot_hits': hotHits,
|
||||
'introduction': introduction,
|
||||
'types': types,
|
||||
'authors': authors,
|
||||
'first_letter': firstLetter,
|
||||
'volume': volume,
|
||||
};
|
||||
}
|
||||
|
||||
class Volume {
|
||||
Volume({
|
||||
required this.volumeId,
|
||||
required this.lnovelId,
|
||||
required this.volumeName,
|
||||
required this.volumeOrder,
|
||||
required this.addtime,
|
||||
required this.sumChapters,
|
||||
});
|
||||
|
||||
factory Volume.fromJson(Map<String, dynamic> json) => Volume(
|
||||
volumeId: asT<int>(json['volume_id'])!,
|
||||
lnovelId: asT<int>(json['lnovel_id'])!,
|
||||
volumeName: asT<String>(json['volume_name'])!,
|
||||
volumeOrder: asT<int>(json['volume_order'])!,
|
||||
addtime: asT<int>(json['addtime'])!,
|
||||
sumChapters: asT<int>(json['sum_chapters'])!,
|
||||
);
|
||||
|
||||
int volumeId;
|
||||
int lnovelId;
|
||||
String volumeName;
|
||||
int volumeOrder;
|
||||
int addtime;
|
||||
int sumChapters;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'volume_id': volumeId,
|
||||
'lnovel_id': lnovelId,
|
||||
'volume_name': volumeName,
|
||||
'volume_order': volumeOrder,
|
||||
'addtime': addtime,
|
||||
'sum_chapters': sumChapters,
|
||||
};
|
||||
}
|
||||
|
||||
class NovelDetailReadingRecordModel {
|
||||
NovelDetailReadingRecordModel({
|
||||
required this.typeName,
|
||||
required this.uid,
|
||||
required this.source,
|
||||
required this.bizId,
|
||||
required this.chapterId,
|
||||
required this.viewingTime,
|
||||
required this.record,
|
||||
required this.volumeId,
|
||||
required this.totalNum,
|
||||
required this.chapterName,
|
||||
required this.volumeName,
|
||||
});
|
||||
|
||||
factory NovelDetailReadingRecordModel.fromJson(Map<String, dynamic> json) =>
|
||||
NovelDetailReadingRecordModel(
|
||||
typeName: asT<String>(json['type_name'])!,
|
||||
uid: asT<int>(json['uid'])!,
|
||||
source: asT<int>(json['source'])!,
|
||||
bizId: asT<int>(json['biz_id'])!,
|
||||
chapterId: asT<int>(json['chapter_id'])!,
|
||||
viewingTime: asT<int>(json['viewing_time'])!,
|
||||
record: asT<int>(json['record'])!,
|
||||
volumeId: asT<int>(json['volume_id'])!,
|
||||
totalNum: asT<int>(json['total_num'])!,
|
||||
chapterName: asT<String>(json['chapter_name'])!,
|
||||
volumeName: asT<String>(json['volume_name'])!,
|
||||
);
|
||||
|
||||
String typeName;
|
||||
int uid;
|
||||
int source;
|
||||
int bizId;
|
||||
int chapterId;
|
||||
int viewingTime;
|
||||
int record;
|
||||
int volumeId;
|
||||
int totalNum;
|
||||
String chapterName;
|
||||
String volumeName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'type_name': typeName,
|
||||
'uid': uid,
|
||||
'source': source,
|
||||
'biz_id': bizId,
|
||||
'chapter_id': chapterId,
|
||||
'viewing_time': viewingTime,
|
||||
'record': record,
|
||||
'volume_id': volumeId,
|
||||
'total_num': totalNum,
|
||||
'chapter_name': chapterName,
|
||||
'volume_name': volumeName,
|
||||
};
|
||||
}
|
||||
62
lib/models/novel/latest_model.dart
Normal file
62
lib/models/novel/latest_model.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NovelLatestModel {
|
||||
NovelLatestModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.authors,
|
||||
this.cover,
|
||||
this.hotHits,
|
||||
this.lastName,
|
||||
this.status,
|
||||
this.types,
|
||||
this.subNums,
|
||||
});
|
||||
|
||||
factory NovelLatestModel.fromJson(Map<String, dynamic> json) =>
|
||||
NovelLatestModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
authors: asT<String?>(json['authors']),
|
||||
cover: asT<String?>(json['cover']),
|
||||
hotHits: asT<int?>(json['hot_hits']),
|
||||
lastName: asT<String?>(json['last_name']),
|
||||
status: asT<String?>(json['status']),
|
||||
types: asT<String?>(json['types']),
|
||||
subNums: asT<int?>(json['sub_nums']),
|
||||
);
|
||||
|
||||
int id;
|
||||
String title;
|
||||
String? authors;
|
||||
String? cover;
|
||||
int? hotHits;
|
||||
String? lastName;
|
||||
String? status;
|
||||
String? types;
|
||||
int? subNums;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'authors': authors,
|
||||
'cover': cover,
|
||||
'hot_hits': hotHits,
|
||||
'last_name': lastName,
|
||||
'status': status,
|
||||
'types': types,
|
||||
'sub_nums': subNums,
|
||||
};
|
||||
}
|
||||
193
lib/models/novel/novel_detail_model.dart
Normal file
193
lib/models/novel/novel_detail_model.dart
Normal file
@@ -0,0 +1,193 @@
|
||||
import 'package:flutter_dmzj/models/novel/detail_model.dart';
|
||||
import 'package:flutter_dmzj/models/novel/volume_detail_model.dart';
|
||||
import 'package:flutter_dmzj/models/proto/novel.pb.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NovelDetailInfo {
|
||||
NovelDetailInfo({
|
||||
required this.novelId,
|
||||
required this.name,
|
||||
required this.zone,
|
||||
required this.status,
|
||||
required this.lastUpdateVolumeName,
|
||||
required this.lastUpdateChapterName,
|
||||
required this.lastUpdateVolumeId,
|
||||
required this.lastUpdateChapterId,
|
||||
required this.lastUpdateTime,
|
||||
required this.cover,
|
||||
required this.hotHits,
|
||||
required this.introduction,
|
||||
required this.types,
|
||||
required this.authors,
|
||||
required this.firstLetter,
|
||||
required this.subscribeNum,
|
||||
});
|
||||
|
||||
factory NovelDetailInfo.empty() => NovelDetailInfo(
|
||||
novelId: 0,
|
||||
name: "",
|
||||
zone: "",
|
||||
status: "",
|
||||
lastUpdateVolumeName: "",
|
||||
lastUpdateChapterName: "",
|
||||
lastUpdateVolumeId: 0,
|
||||
lastUpdateChapterId: 0,
|
||||
lastUpdateTime: 0,
|
||||
cover: "",
|
||||
hotHits: 0,
|
||||
introduction: "",
|
||||
types: [],
|
||||
authors: "",
|
||||
firstLetter: "",
|
||||
subscribeNum: 0,
|
||||
);
|
||||
factory NovelDetailInfo.fromJson(NovelDetailDataModel item) =>
|
||||
NovelDetailInfo(
|
||||
novelId: item.novelId.toInt(),
|
||||
name: item.name,
|
||||
zone: item.zone,
|
||||
status: item.status,
|
||||
lastUpdateVolumeName: item.lastUpdateVolumeName,
|
||||
lastUpdateChapterName: item.lastUpdateChapterName,
|
||||
lastUpdateVolumeId: item.lastUpdateVolumeId.toInt(),
|
||||
lastUpdateChapterId: item.lastUpdateChapterId.toInt(),
|
||||
lastUpdateTime: item.lastUpdateTime.toInt(),
|
||||
cover: item.cover,
|
||||
hotHits: item.hotHits.toInt(),
|
||||
introduction: item.introduction,
|
||||
types: item.types,
|
||||
authors: item.authors,
|
||||
firstLetter: item.firstLetter,
|
||||
subscribeNum: 0,
|
||||
);
|
||||
|
||||
factory NovelDetailInfo.fromV4(NovelDetailProto item) => NovelDetailInfo(
|
||||
novelId: item.novelId.toInt(),
|
||||
name: item.name,
|
||||
zone: item.zone,
|
||||
status: item.status,
|
||||
lastUpdateVolumeName: item.lastUpdateVolumeName,
|
||||
lastUpdateChapterName: item.lastUpdateChapterName,
|
||||
lastUpdateVolumeId: item.lastUpdateVolumeId.toInt(),
|
||||
lastUpdateChapterId: item.lastUpdateChapterId.toInt(),
|
||||
lastUpdateTime: item.lastUpdateTime.toInt(),
|
||||
cover: item.cover,
|
||||
hotHits: item.hotHits.toInt(),
|
||||
introduction: item.introduction,
|
||||
types: item.types,
|
||||
authors: item.authors,
|
||||
firstLetter: item.firstLetter,
|
||||
subscribeNum: item.subscribeNum.toInt(),
|
||||
);
|
||||
|
||||
int novelId;
|
||||
String name;
|
||||
String zone;
|
||||
String status;
|
||||
String lastUpdateVolumeName;
|
||||
String lastUpdateChapterName;
|
||||
int lastUpdateVolumeId;
|
||||
int lastUpdateChapterId;
|
||||
int lastUpdateTime;
|
||||
String cover;
|
||||
int hotHits;
|
||||
String introduction;
|
||||
List<String> types;
|
||||
String authors;
|
||||
String firstLetter;
|
||||
int subscribeNum;
|
||||
RxList<NovelDetailVolume> volume = RxList<NovelDetailVolume>();
|
||||
}
|
||||
|
||||
class NovelDetailVolume {
|
||||
NovelDetailVolume({
|
||||
required this.volumeId,
|
||||
required this.volumeName,
|
||||
required this.volumeOrder,
|
||||
required this.chapters,
|
||||
});
|
||||
factory NovelDetailVolume.fromJson(NovelVolumeDetailModel item) =>
|
||||
NovelDetailVolume(
|
||||
volumeId: item.volumeId.toInt(),
|
||||
volumeName: item.volumeName,
|
||||
volumeOrder: item.volumeOrder,
|
||||
chapters: item.chapters
|
||||
.map(
|
||||
(e) => NovelDetailChapter.fromJson(
|
||||
e,
|
||||
item.volumeId.toInt(),
|
||||
item.volumeName,
|
||||
item.volumeOrder,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
factory NovelDetailVolume.fromV4(NovelVolumeDetailProto item) =>
|
||||
NovelDetailVolume(
|
||||
volumeId: item.volumeId.toInt(),
|
||||
volumeName: item.volumeName,
|
||||
volumeOrder: item.volumeOrder,
|
||||
chapters: item.chapters
|
||||
.map(
|
||||
(e) => NovelDetailChapter.fromV4(
|
||||
e,
|
||||
item.volumeId.toInt(),
|
||||
item.volumeName,
|
||||
item.volumeOrder,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
int volumeId;
|
||||
String volumeName;
|
||||
int volumeOrder;
|
||||
List<NovelDetailChapter> chapters;
|
||||
}
|
||||
|
||||
class NovelDetailChapter {
|
||||
NovelDetailChapter({
|
||||
required this.chapterId,
|
||||
required this.chapterName,
|
||||
required this.chapterOrder,
|
||||
required this.volumeId,
|
||||
required this.volumeName,
|
||||
required this.volumeOrder,
|
||||
});
|
||||
factory NovelDetailChapter.fromJson(NovelVolumeDetailChapterModel item,
|
||||
int volumeId, String volumeName, int volumeOrder) =>
|
||||
NovelDetailChapter(
|
||||
chapterId: item.chapterId.toInt(),
|
||||
chapterName: item.chapterName,
|
||||
chapterOrder: item.chapterOrder,
|
||||
volumeId: volumeId,
|
||||
volumeName: volumeName,
|
||||
volumeOrder: volumeOrder,
|
||||
);
|
||||
|
||||
factory NovelDetailChapter.fromV4(NovelChapterDetailProto item, int volumeId,
|
||||
String volumeName, int volumeOrder) =>
|
||||
NovelDetailChapter(
|
||||
chapterId: item.chapterId.toInt(),
|
||||
chapterName: item.chapterName,
|
||||
chapterOrder: item.chapterOrder,
|
||||
volumeId: volumeId,
|
||||
volumeName: volumeName,
|
||||
volumeOrder: volumeOrder,
|
||||
);
|
||||
|
||||
int chapterId;
|
||||
String chapterName;
|
||||
int chapterOrder;
|
||||
int volumeId;
|
||||
int volumeOrder;
|
||||
String volumeName;
|
||||
}
|
||||
71
lib/models/novel/rank_model.dart
Normal file
71
lib/models/novel/rank_model.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NovelRankModel {
|
||||
NovelRankModel({
|
||||
required this.id,
|
||||
required this.lastUpdateTime,
|
||||
required this.name,
|
||||
required this.types,
|
||||
required this.cover,
|
||||
required this.authors,
|
||||
required this.lastUpdateChapterName,
|
||||
required this.top,
|
||||
required this.subscribeAmount,
|
||||
});
|
||||
|
||||
factory NovelRankModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<String>? types = json['types'] is List ? <String>[] : null;
|
||||
if (types != null) {
|
||||
for (final dynamic item in json['types']!) {
|
||||
if (item != null) {
|
||||
types.add(asT<String>(item)!);
|
||||
}
|
||||
}
|
||||
}
|
||||
return NovelRankModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
lastUpdateTime: asT<int>(json['last_update_time'])!,
|
||||
name: asT<String>(json['name'])!,
|
||||
types: types!,
|
||||
cover: asT<String>(json['cover'])!,
|
||||
authors: asT<String>(json['authors'])!,
|
||||
lastUpdateChapterName: asT<String>(json['last_update_chapter_name'])!,
|
||||
top: asT<int>(json['top'])!,
|
||||
subscribeAmount: asT<int>(json['subscribe_amount'])!,
|
||||
);
|
||||
}
|
||||
|
||||
int id;
|
||||
int lastUpdateTime;
|
||||
String name;
|
||||
List<String> types;
|
||||
String cover;
|
||||
String authors;
|
||||
String lastUpdateChapterName;
|
||||
int top;
|
||||
int subscribeAmount;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'last_update_time': lastUpdateTime,
|
||||
'name': name,
|
||||
'types': types,
|
||||
'cover': cover,
|
||||
'authors': authors,
|
||||
'last_update_chapter_name': lastUpdateChapterName,
|
||||
'top': top,
|
||||
'subscribe_amount': subscribeAmount,
|
||||
};
|
||||
}
|
||||
101
lib/models/novel/recommend_model.dart
Normal file
101
lib/models/novel/recommend_model.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NovelRecommendModel {
|
||||
NovelRecommendModel({
|
||||
required this.categoryId,
|
||||
required this.title,
|
||||
required this.sort,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
factory NovelRecommendModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<NovelRecommendItemModel>? data =
|
||||
json['data'] is List ? <NovelRecommendItemModel>[] : null;
|
||||
if (data != null) {
|
||||
for (final dynamic item in json['data']!) {
|
||||
if (item != null) {
|
||||
data.add(NovelRecommendItemModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return NovelRecommendModel(
|
||||
categoryId: asT<int>(json['category_id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
sort: asT<int>(json['sort'])!,
|
||||
data: data!,
|
||||
);
|
||||
}
|
||||
|
||||
int categoryId;
|
||||
String title;
|
||||
int sort;
|
||||
List<NovelRecommendItemModel> data;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'category_id': categoryId,
|
||||
'title': title,
|
||||
'sort': sort,
|
||||
'data': data,
|
||||
};
|
||||
}
|
||||
|
||||
class NovelRecommendItemModel {
|
||||
NovelRecommendItemModel({
|
||||
required this.cover,
|
||||
required this.title,
|
||||
this.subTitle,
|
||||
this.type,
|
||||
this.url,
|
||||
required this.objId,
|
||||
this.status,
|
||||
this.id,
|
||||
});
|
||||
|
||||
factory NovelRecommendItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
NovelRecommendItemModel(
|
||||
id: asT<int?>(json['id']),
|
||||
cover: asT<String>(json['cover'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
subTitle: asT<String?>(json['sub_title']),
|
||||
type: asT<int?>(json['type']),
|
||||
url: asT<String?>(json['url']),
|
||||
objId: asT<int?>(json['obj_id']),
|
||||
status: asT<String?>(json['status']),
|
||||
);
|
||||
int? id;
|
||||
String cover;
|
||||
String title;
|
||||
String? subTitle;
|
||||
int? type;
|
||||
String? url;
|
||||
int? objId;
|
||||
String? status;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'cover': cover,
|
||||
'title': title,
|
||||
'sub_title': subTitle,
|
||||
'type': type,
|
||||
'url': url,
|
||||
'obj_id': objId,
|
||||
'status': status,
|
||||
};
|
||||
}
|
||||
62
lib/models/novel/search_model.dart
Normal file
62
lib/models/novel/search_model.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NovelSearchModel {
|
||||
NovelSearchModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.authors,
|
||||
this.cover,
|
||||
this.hotHits,
|
||||
this.lastName,
|
||||
this.status,
|
||||
this.types,
|
||||
this.subNums,
|
||||
});
|
||||
|
||||
factory NovelSearchModel.fromJson(Map<String, dynamic> json) =>
|
||||
NovelSearchModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
authors: asT<String?>(json['authors']),
|
||||
cover: asT<String?>(json['cover']),
|
||||
hotHits: asT<int?>(json['hot_hits']),
|
||||
lastName: asT<String?>(json['last_name']),
|
||||
status: asT<String?>(json['status']),
|
||||
types: asT<String?>(json['types']),
|
||||
subNums: asT<int?>(json['sub_nums']),
|
||||
);
|
||||
|
||||
int id;
|
||||
String title;
|
||||
String? authors;
|
||||
String? cover;
|
||||
int? hotHits;
|
||||
String? lastName;
|
||||
String? status;
|
||||
String? types;
|
||||
int? subNums;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'authors': authors,
|
||||
'cover': cover,
|
||||
'hot_hits': hotHits,
|
||||
'last_name': lastName,
|
||||
'status': status,
|
||||
'types': types,
|
||||
'sub_nums': subNums,
|
||||
};
|
||||
}
|
||||
83
lib/models/novel/volume_detail_model.dart
Normal file
83
lib/models/novel/volume_detail_model.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NovelVolumeDetailModel {
|
||||
NovelVolumeDetailModel({
|
||||
required this.volumeId,
|
||||
required this.volumeName,
|
||||
required this.volumeOrder,
|
||||
required this.chapters,
|
||||
});
|
||||
|
||||
factory NovelVolumeDetailModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<NovelVolumeDetailChapterModel>? chapters =
|
||||
json['chapters'] is List ? <NovelVolumeDetailChapterModel>[] : null;
|
||||
if (chapters != null) {
|
||||
for (final dynamic item in json['chapters']!) {
|
||||
if (item != null) {
|
||||
chapters.add(NovelVolumeDetailChapterModel.fromJson(
|
||||
asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return NovelVolumeDetailModel(
|
||||
volumeId: asT<int>(json['volume_id'])!,
|
||||
volumeName: asT<String>(json['volume_name'])!,
|
||||
volumeOrder: asT<int>(json['volume_order'])!,
|
||||
chapters: chapters!,
|
||||
);
|
||||
}
|
||||
|
||||
int volumeId;
|
||||
String volumeName;
|
||||
int volumeOrder;
|
||||
List<NovelVolumeDetailChapterModel> chapters;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'volume_id': volumeId,
|
||||
'volume_name': volumeName,
|
||||
'volume_order': volumeOrder,
|
||||
'chapters': chapters,
|
||||
};
|
||||
}
|
||||
|
||||
class NovelVolumeDetailChapterModel {
|
||||
NovelVolumeDetailChapterModel({
|
||||
required this.chapterId,
|
||||
required this.chapterName,
|
||||
required this.chapterOrder,
|
||||
});
|
||||
|
||||
factory NovelVolumeDetailChapterModel.fromJson(Map<String, dynamic> json) =>
|
||||
NovelVolumeDetailChapterModel(
|
||||
chapterId: asT<int>(json['chapter_id'])!,
|
||||
chapterName: asT<String>(json['chapter_name'])!,
|
||||
chapterOrder: asT<int>(json['chapter_order'])!,
|
||||
);
|
||||
|
||||
int chapterId;
|
||||
String chapterName;
|
||||
int chapterOrder;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'chapter_id': chapterId,
|
||||
'chapter_name': chapterName,
|
||||
'chapter_order': chapterOrder,
|
||||
};
|
||||
}
|
||||
2593
lib/models/proto/comic.pb.dart
Normal file
2593
lib/models/proto/comic.pb.dart
Normal file
File diff suppressed because it is too large
Load Diff
231
lib/models/proto/comic.pbjson.dart
Normal file
231
lib/models/proto/comic.pbjson.dart
Normal file
@@ -0,0 +1,231 @@
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: comic.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name
|
||||
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
@$core.Deprecated('Use comicChapterDetailProtoDescriptor instead')
|
||||
const ComicChapterDetailProto$json = const {
|
||||
'1': 'ComicChapterDetailProto',
|
||||
'2': const [
|
||||
const {'1': 'chapterId', '3': 1, '4': 1, '5': 3, '10': 'chapterId'},
|
||||
const {'1': 'comicId', '3': 2, '4': 1, '5': 3, '10': 'comicId'},
|
||||
const {'1': 'title', '3': 3, '4': 1, '5': 9, '10': 'title'},
|
||||
const {'1': 'chapterOrder', '3': 4, '4': 1, '5': 5, '10': 'chapterOrder'},
|
||||
const {'1': 'direction', '3': 5, '4': 1, '5': 5, '10': 'direction'},
|
||||
const {'1': 'pageUrl', '3': 6, '4': 3, '5': 9, '10': 'pageUrl'},
|
||||
const {'1': 'picnum', '3': 7, '4': 1, '5': 5, '10': 'picnum'},
|
||||
const {'1': 'pageUrlHD', '3': 8, '4': 3, '5': 9, '10': 'pageUrlHD'},
|
||||
const {'1': 'commentCount', '3': 9, '4': 1, '5': 5, '10': 'commentCount'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicChapterDetailProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicChapterDetailProtoDescriptor = $convert.base64Decode('ChdDb21pY0NoYXB0ZXJEZXRhaWxQcm90bxIcCgljaGFwdGVySWQYASABKANSCWNoYXB0ZXJJZBIYCgdjb21pY0lkGAIgASgDUgdjb21pY0lkEhQKBXRpdGxlGAMgASgJUgV0aXRsZRIiCgxjaGFwdGVyT3JkZXIYBCABKAVSDGNoYXB0ZXJPcmRlchIcCglkaXJlY3Rpb24YBSABKAVSCWRpcmVjdGlvbhIYCgdwYWdlVXJsGAYgAygJUgdwYWdlVXJsEhYKBnBpY251bRgHIAEoBVIGcGljbnVtEhwKCXBhZ2VVcmxIRBgIIAMoCVIJcGFnZVVybEhEEiIKDGNvbW1lbnRDb3VudBgJIAEoBVIMY29tbWVudENvdW50');
|
||||
@$core.Deprecated('Use comicChapterInfoProtoDescriptor instead')
|
||||
const ComicChapterInfoProto$json = const {
|
||||
'1': 'ComicChapterInfoProto',
|
||||
'2': const [
|
||||
const {'1': 'chapterId', '3': 1, '4': 1, '5': 3, '10': 'chapterId'},
|
||||
const {'1': 'chapterTitle', '3': 2, '4': 1, '5': 9, '10': 'chapterTitle'},
|
||||
const {'1': 'updateTime', '3': 3, '4': 1, '5': 3, '10': 'updateTime'},
|
||||
const {'1': 'fileSize', '3': 4, '4': 1, '5': 5, '10': 'fileSize'},
|
||||
const {'1': 'chapterOrder', '3': 5, '4': 1, '5': 5, '10': 'chapterOrder'},
|
||||
const {'1': 'isFee', '3': 6, '4': 1, '5': 5, '10': 'isFee'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicChapterInfoProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicChapterInfoProtoDescriptor = $convert.base64Decode('ChVDb21pY0NoYXB0ZXJJbmZvUHJvdG8SHAoJY2hhcHRlcklkGAEgASgDUgljaGFwdGVySWQSIgoMY2hhcHRlclRpdGxlGAIgASgJUgxjaGFwdGVyVGl0bGUSHgoKdXBkYXRlVGltZRgDIAEoA1IKdXBkYXRlVGltZRIaCghmaWxlU2l6ZRgEIAEoBVIIZmlsZVNpemUSIgoMY2hhcHRlck9yZGVyGAUgASgFUgxjaGFwdGVyT3JkZXISFAoFaXNGZWUYBiABKAVSBWlzRmVl');
|
||||
@$core.Deprecated('Use comicChapterResponseProtoDescriptor instead')
|
||||
const ComicChapterResponseProto$json = const {
|
||||
'1': 'ComicChapterResponseProto',
|
||||
'2': const [
|
||||
const {'1': 'errno', '3': 1, '4': 1, '5': 5, '10': 'errno'},
|
||||
const {'1': 'errmsg', '3': 2, '4': 1, '5': 9, '10': 'errmsg'},
|
||||
const {'1': 'data', '3': 3, '4': 1, '5': 11, '6': '.ComicChapterDetailProto', '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicChapterResponseProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicChapterResponseProtoDescriptor = $convert.base64Decode('ChlDb21pY0NoYXB0ZXJSZXNwb25zZVByb3RvEhQKBWVycm5vGAEgASgFUgVlcnJubxIWCgZlcnJtc2cYAiABKAlSBmVycm1zZxIsCgRkYXRhGAMgASgLMhguQ29taWNDaGFwdGVyRGV0YWlsUHJvdG9SBGRhdGE=');
|
||||
@$core.Deprecated('Use comicChapterListProtoDescriptor instead')
|
||||
const ComicChapterListProto$json = const {
|
||||
'1': 'ComicChapterListProto',
|
||||
'2': const [
|
||||
const {'1': 'title', '3': 1, '4': 1, '5': 9, '10': 'title'},
|
||||
const {'1': 'data', '3': 2, '4': 3, '5': 11, '6': '.ComicChapterInfoProto', '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicChapterListProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicChapterListProtoDescriptor = $convert.base64Decode('ChVDb21pY0NoYXB0ZXJMaXN0UHJvdG8SFAoFdGl0bGUYASABKAlSBXRpdGxlEioKBGRhdGEYAiADKAsyFi5Db21pY0NoYXB0ZXJJbmZvUHJvdG9SBGRhdGE=');
|
||||
@$core.Deprecated('Use comicDetailResponseProtoDescriptor instead')
|
||||
const ComicDetailResponseProto$json = const {
|
||||
'1': 'ComicDetailResponseProto',
|
||||
'2': const [
|
||||
const {'1': 'errno', '3': 1, '4': 1, '5': 5, '10': 'errno'},
|
||||
const {'1': 'errmsg', '3': 2, '4': 1, '5': 9, '10': 'errmsg'},
|
||||
const {'1': 'data', '3': 3, '4': 1, '5': 11, '6': '.ComicDetailProto', '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicDetailResponseProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicDetailResponseProtoDescriptor = $convert.base64Decode('ChhDb21pY0RldGFpbFJlc3BvbnNlUHJvdG8SFAoFZXJybm8YASABKAVSBWVycm5vEhYKBmVycm1zZxgCIAEoCVIGZXJybXNnEiUKBGRhdGEYAyABKAsyES5Db21pY0RldGFpbFByb3RvUgRkYXRh');
|
||||
@$core.Deprecated('Use comicDetailProtoDescriptor instead')
|
||||
const ComicDetailProto$json = const {
|
||||
'1': 'ComicDetailProto',
|
||||
'2': const [
|
||||
const {'1': 'id', '3': 1, '4': 1, '5': 3, '10': 'id'},
|
||||
const {'1': 'title', '3': 2, '4': 1, '5': 9, '10': 'title'},
|
||||
const {'1': 'direction', '3': 3, '4': 1, '5': 5, '10': 'direction'},
|
||||
const {'1': 'islong', '3': 4, '4': 1, '5': 5, '10': 'islong'},
|
||||
const {'1': 'isDmzj', '3': 5, '4': 1, '5': 5, '10': 'isDmzj'},
|
||||
const {'1': 'cover', '3': 6, '4': 1, '5': 9, '10': 'cover'},
|
||||
const {'1': 'description', '3': 7, '4': 1, '5': 9, '10': 'description'},
|
||||
const {'1': 'lastUpdatetime', '3': 8, '4': 1, '5': 3, '10': 'lastUpdatetime'},
|
||||
const {'1': 'lastUpdateChapterName', '3': 9, '4': 1, '5': 9, '10': 'lastUpdateChapterName'},
|
||||
const {'1': 'copyright', '3': 10, '4': 1, '5': 5, '10': 'copyright'},
|
||||
const {'1': 'firstLetter', '3': 11, '4': 1, '5': 9, '10': 'firstLetter'},
|
||||
const {'1': 'comicPy', '3': 12, '4': 1, '5': 9, '10': 'comicPy'},
|
||||
const {'1': 'hidden', '3': 13, '4': 1, '5': 5, '10': 'hidden'},
|
||||
const {'1': 'hotNum', '3': 14, '4': 1, '5': 3, '10': 'hotNum'},
|
||||
const {'1': 'hitNum', '3': 15, '4': 1, '5': 3, '10': 'hitNum'},
|
||||
const {'1': 'uid', '3': 16, '4': 1, '5': 3, '10': 'uid'},
|
||||
const {'1': 'isLock', '3': 17, '4': 1, '5': 5, '10': 'isLock'},
|
||||
const {'1': 'lastUpdateChapterId', '3': 18, '4': 1, '5': 5, '10': 'lastUpdateChapterId'},
|
||||
const {'1': 'types', '3': 19, '4': 3, '5': 11, '6': '.ComicTagProto', '10': 'types'},
|
||||
const {'1': 'status', '3': 20, '4': 3, '5': 11, '6': '.ComicTagProto', '10': 'status'},
|
||||
const {'1': 'authors', '3': 21, '4': 3, '5': 11, '6': '.ComicTagProto', '10': 'authors'},
|
||||
const {'1': 'subscribeNum', '3': 22, '4': 1, '5': 3, '10': 'subscribeNum'},
|
||||
const {'1': 'chapters', '3': 23, '4': 3, '5': 11, '6': '.ComicChapterListProto', '10': 'chapters'},
|
||||
const {'1': 'isNeedLogin', '3': 24, '4': 1, '5': 5, '10': 'isNeedLogin'},
|
||||
const {'1': 'urlLinks', '3': 25, '4': 3, '5': 11, '6': '.ComicDetailUrlLinkProto', '10': 'urlLinks'},
|
||||
const {'1': 'isHideChapter', '3': 26, '4': 1, '5': 5, '10': 'isHideChapter'},
|
||||
const {'1': 'dhUrlLinks', '3': 27, '4': 3, '5': 11, '6': '.ComicDetailUrlLinkProto', '10': 'dhUrlLinks'},
|
||||
const {'1': 'cornerMark', '3': 28, '4': 1, '5': 9, '10': 'cornerMark'},
|
||||
const {'1': 'isFee', '3': 29, '4': 1, '5': 5, '10': 'isFee'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicDetailProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicDetailProtoDescriptor = $convert.base64Decode('ChBDb21pY0RldGFpbFByb3RvEg4KAmlkGAEgASgDUgJpZBIUCgV0aXRsZRgCIAEoCVIFdGl0bGUSHAoJZGlyZWN0aW9uGAMgASgFUglkaXJlY3Rpb24SFgoGaXNsb25nGAQgASgFUgZpc2xvbmcSFgoGaXNEbXpqGAUgASgFUgZpc0RtemoSFAoFY292ZXIYBiABKAlSBWNvdmVyEiAKC2Rlc2NyaXB0aW9uGAcgASgJUgtkZXNjcmlwdGlvbhImCg5sYXN0VXBkYXRldGltZRgIIAEoA1IObGFzdFVwZGF0ZXRpbWUSNAoVbGFzdFVwZGF0ZUNoYXB0ZXJOYW1lGAkgASgJUhVsYXN0VXBkYXRlQ2hhcHRlck5hbWUSHAoJY29weXJpZ2h0GAogASgFUgljb3B5cmlnaHQSIAoLZmlyc3RMZXR0ZXIYCyABKAlSC2ZpcnN0TGV0dGVyEhgKB2NvbWljUHkYDCABKAlSB2NvbWljUHkSFgoGaGlkZGVuGA0gASgFUgZoaWRkZW4SFgoGaG90TnVtGA4gASgDUgZob3ROdW0SFgoGaGl0TnVtGA8gASgDUgZoaXROdW0SEAoDdWlkGBAgASgDUgN1aWQSFgoGaXNMb2NrGBEgASgFUgZpc0xvY2sSMAoTbGFzdFVwZGF0ZUNoYXB0ZXJJZBgSIAEoBVITbGFzdFVwZGF0ZUNoYXB0ZXJJZBIkCgV0eXBlcxgTIAMoCzIOLkNvbWljVGFnUHJvdG9SBXR5cGVzEiYKBnN0YXR1cxgUIAMoCzIOLkNvbWljVGFnUHJvdG9SBnN0YXR1cxIoCgdhdXRob3JzGBUgAygLMg4uQ29taWNUYWdQcm90b1IHYXV0aG9ycxIiCgxzdWJzY3JpYmVOdW0YFiABKANSDHN1YnNjcmliZU51bRIyCghjaGFwdGVycxgXIAMoCzIWLkNvbWljQ2hhcHRlckxpc3RQcm90b1IIY2hhcHRlcnMSIAoLaXNOZWVkTG9naW4YGCABKAVSC2lzTmVlZExvZ2luEjQKCHVybExpbmtzGBkgAygLMhguQ29taWNEZXRhaWxVcmxMaW5rUHJvdG9SCHVybExpbmtzEiQKDWlzSGlkZUNoYXB0ZXIYGiABKAVSDWlzSGlkZUNoYXB0ZXISOAoKZGhVcmxMaW5rcxgbIAMoCzIYLkNvbWljRGV0YWlsVXJsTGlua1Byb3RvUgpkaFVybExpbmtzEh4KCmNvcm5lck1hcmsYHCABKAlSCmNvcm5lck1hcmsSFAoFaXNGZWUYHSABKAVSBWlzRmVl');
|
||||
@$core.Deprecated('Use comicTagProtoDescriptor instead')
|
||||
const ComicTagProto$json = const {
|
||||
'1': 'ComicTagProto',
|
||||
'2': const [
|
||||
const {'1': 'tagId', '3': 1, '4': 1, '5': 3, '10': 'tagId'},
|
||||
const {'1': 'tagName', '3': 2, '4': 1, '5': 9, '10': 'tagName'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicTagProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicTagProtoDescriptor = $convert.base64Decode('Cg1Db21pY1RhZ1Byb3RvEhQKBXRhZ0lkGAEgASgDUgV0YWdJZBIYCgd0YWdOYW1lGAIgASgJUgd0YWdOYW1l');
|
||||
@$core.Deprecated('Use comicDetailUrlLinkProtoDescriptor instead')
|
||||
const ComicDetailUrlLinkProto$json = const {
|
||||
'1': 'ComicDetailUrlLinkProto',
|
||||
'2': const [
|
||||
const {'1': 'title', '3': 1, '4': 1, '5': 9, '10': 'title'},
|
||||
const {'1': 'list', '3': 2, '4': 3, '5': 11, '6': '.ComicDetailUrlProto', '10': 'list'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicDetailUrlLinkProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicDetailUrlLinkProtoDescriptor = $convert.base64Decode('ChdDb21pY0RldGFpbFVybExpbmtQcm90bxIUCgV0aXRsZRgBIAEoCVIFdGl0bGUSKAoEbGlzdBgCIAMoCzIULkNvbWljRGV0YWlsVXJsUHJvdG9SBGxpc3Q=');
|
||||
@$core.Deprecated('Use comicDetailUrlProtoDescriptor instead')
|
||||
const ComicDetailUrlProto$json = const {
|
||||
'1': 'ComicDetailUrlProto',
|
||||
'2': const [
|
||||
const {'1': 'id', '3': 1, '4': 1, '5': 3, '10': 'id'},
|
||||
const {'1': 'title', '3': 2, '4': 1, '5': 9, '10': 'title'},
|
||||
const {'1': 'url', '3': 3, '4': 1, '5': 9, '10': 'url'},
|
||||
const {'1': 'icon', '3': 4, '4': 1, '5': 9, '10': 'icon'},
|
||||
const {'1': 'packageName', '3': 5, '4': 1, '5': 9, '10': 'packageName'},
|
||||
const {'1': 'dUrl', '3': 6, '4': 1, '5': 9, '10': 'dUrl'},
|
||||
const {'1': 'btype', '3': 7, '4': 1, '5': 5, '10': 'btype'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicDetailUrlProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicDetailUrlProtoDescriptor = $convert.base64Decode('ChNDb21pY0RldGFpbFVybFByb3RvEg4KAmlkGAEgASgDUgJpZBIUCgV0aXRsZRgCIAEoCVIFdGl0bGUSEAoDdXJsGAMgASgJUgN1cmwSEgoEaWNvbhgEIAEoCVIEaWNvbhIgCgtwYWNrYWdlTmFtZRgFIAEoCVILcGFja2FnZU5hbWUSEgoEZFVybBgGIAEoCVIEZFVybBIUCgVidHlwZRgHIAEoBVIFYnR5cGU=');
|
||||
@$core.Deprecated('Use comicRankListResponseProtoDescriptor instead')
|
||||
const ComicRankListResponseProto$json = const {
|
||||
'1': 'ComicRankListResponseProto',
|
||||
'2': const [
|
||||
const {'1': 'errno', '3': 1, '4': 1, '5': 5, '10': 'errno'},
|
||||
const {'1': 'errmsg', '3': 2, '4': 1, '5': 9, '10': 'errmsg'},
|
||||
const {'1': 'data', '3': 3, '4': 3, '5': 11, '6': '.ComicRankListInfoProto', '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicRankListResponseProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicRankListResponseProtoDescriptor = $convert.base64Decode('ChpDb21pY1JhbmtMaXN0UmVzcG9uc2VQcm90bxIUCgVlcnJubxgBIAEoBVIFZXJybm8SFgoGZXJybXNnGAIgASgJUgZlcnJtc2cSKwoEZGF0YRgDIAMoCzIXLkNvbWljUmFua0xpc3RJbmZvUHJvdG9SBGRhdGE=');
|
||||
@$core.Deprecated('Use comicRankListInfoProtoDescriptor instead')
|
||||
const ComicRankListInfoProto$json = const {
|
||||
'1': 'ComicRankListInfoProto',
|
||||
'2': const [
|
||||
const {'1': 'comic_id', '3': 1, '4': 1, '5': 3, '10': 'comicId'},
|
||||
const {'1': 'title', '3': 2, '4': 1, '5': 9, '10': 'title'},
|
||||
const {'1': 'authors', '3': 3, '4': 1, '5': 9, '10': 'authors'},
|
||||
const {'1': 'status', '3': 4, '4': 1, '5': 9, '10': 'status'},
|
||||
const {'1': 'cover', '3': 5, '4': 1, '5': 9, '10': 'cover'},
|
||||
const {'1': 'types', '3': 6, '4': 1, '5': 9, '10': 'types'},
|
||||
const {'1': 'last_updatetime', '3': 7, '4': 1, '5': 3, '10': 'lastUpdatetime'},
|
||||
const {'1': 'last_update_chapter_name', '3': 8, '4': 1, '5': 9, '10': 'lastUpdateChapterName'},
|
||||
const {'1': 'comic_py', '3': 9, '4': 1, '5': 9, '10': 'comicPy'},
|
||||
const {'1': 'num', '3': 10, '4': 1, '5': 3, '10': 'num'},
|
||||
const {'1': 'tag_id', '3': 11, '4': 1, '5': 5, '10': 'tagId'},
|
||||
const {'1': 'chapter_name', '3': 12, '4': 1, '5': 9, '10': 'chapterName'},
|
||||
const {'1': 'chapter_id', '3': 13, '4': 1, '5': 3, '10': 'chapterId'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicRankListInfoProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicRankListInfoProtoDescriptor = $convert.base64Decode('ChZDb21pY1JhbmtMaXN0SW5mb1Byb3RvEhkKCGNvbWljX2lkGAEgASgDUgdjb21pY0lkEhQKBXRpdGxlGAIgASgJUgV0aXRsZRIYCgdhdXRob3JzGAMgASgJUgdhdXRob3JzEhYKBnN0YXR1cxgEIAEoCVIGc3RhdHVzEhQKBWNvdmVyGAUgASgJUgVjb3ZlchIUCgV0eXBlcxgGIAEoCVIFdHlwZXMSJwoPbGFzdF91cGRhdGV0aW1lGAcgASgDUg5sYXN0VXBkYXRldGltZRI3ChhsYXN0X3VwZGF0ZV9jaGFwdGVyX25hbWUYCCABKAlSFWxhc3RVcGRhdGVDaGFwdGVyTmFtZRIZCghjb21pY19weRgJIAEoCVIHY29taWNQeRIQCgNudW0YCiABKANSA251bRIVCgZ0YWdfaWQYCyABKAVSBXRhZ0lkEiEKDGNoYXB0ZXJfbmFtZRgMIAEoCVILY2hhcHRlck5hbWUSHQoKY2hhcHRlcl9pZBgNIAEoA1IJY2hhcHRlcklk');
|
||||
@$core.Deprecated('Use rankTypeFilterResponseProtoDescriptor instead')
|
||||
const RankTypeFilterResponseProto$json = const {
|
||||
'1': 'RankTypeFilterResponseProto',
|
||||
'2': const [
|
||||
const {'1': 'errno', '3': 1, '4': 1, '5': 5, '10': 'errno'},
|
||||
const {'1': 'errmsg', '3': 2, '4': 1, '5': 9, '10': 'errmsg'},
|
||||
const {'1': 'data', '3': 3, '4': 3, '5': 11, '6': '.ComicTagProto', '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RankTypeFilterResponseProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List rankTypeFilterResponseProtoDescriptor = $convert.base64Decode('ChtSYW5rVHlwZUZpbHRlclJlc3BvbnNlUHJvdG8SFAoFZXJybm8YASABKAVSBWVycm5vEhYKBmVycm1zZxgCIAEoCVIGZXJybXNnEiIKBGRhdGEYAyADKAsyDi5Db21pY1RhZ1Byb3RvUgRkYXRh');
|
||||
@$core.Deprecated('Use comicUpdateListResponseProtoDescriptor instead')
|
||||
const ComicUpdateListResponseProto$json = const {
|
||||
'1': 'ComicUpdateListResponseProto',
|
||||
'2': const [
|
||||
const {'1': 'errno', '3': 1, '4': 1, '5': 5, '10': 'errno'},
|
||||
const {'1': 'errmsg', '3': 2, '4': 1, '5': 9, '10': 'errmsg'},
|
||||
const {'1': 'data', '3': 3, '4': 3, '5': 11, '6': '.ComicUpdateListInfoProto', '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicUpdateListResponseProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicUpdateListResponseProtoDescriptor = $convert.base64Decode('ChxDb21pY1VwZGF0ZUxpc3RSZXNwb25zZVByb3RvEhQKBWVycm5vGAEgASgFUgVlcnJubxIWCgZlcnJtc2cYAiABKAlSBmVycm1zZxItCgRkYXRhGAMgAygLMhkuQ29taWNVcGRhdGVMaXN0SW5mb1Byb3RvUgRkYXRh');
|
||||
@$core.Deprecated('Use comicUpdateListInfoProtoDescriptor instead')
|
||||
const ComicUpdateListInfoProto$json = const {
|
||||
'1': 'ComicUpdateListInfoProto',
|
||||
'2': const [
|
||||
const {'1': 'comicId', '3': 1, '4': 1, '5': 3, '10': 'comicId'},
|
||||
const {'1': 'title', '3': 2, '4': 1, '5': 9, '10': 'title'},
|
||||
const {'1': 'islong', '3': 3, '4': 1, '5': 5, '10': 'islong'},
|
||||
const {'1': 'authors', '3': 4, '4': 1, '5': 9, '10': 'authors'},
|
||||
const {'1': 'types', '3': 5, '4': 1, '5': 9, '10': 'types'},
|
||||
const {'1': 'cover', '3': 6, '4': 1, '5': 9, '10': 'cover'},
|
||||
const {'1': 'status', '3': 7, '4': 1, '5': 9, '10': 'status'},
|
||||
const {'1': 'lastUpdateChapterName', '3': 8, '4': 1, '5': 9, '10': 'lastUpdateChapterName'},
|
||||
const {'1': 'lastUpdateChapterId', '3': 9, '4': 1, '5': 3, '10': 'lastUpdateChapterId'},
|
||||
const {'1': 'lastUpdatetime', '3': 10, '4': 1, '5': 3, '10': 'lastUpdatetime'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ComicUpdateListInfoProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List comicUpdateListInfoProtoDescriptor = $convert.base64Decode('ChhDb21pY1VwZGF0ZUxpc3RJbmZvUHJvdG8SGAoHY29taWNJZBgBIAEoA1IHY29taWNJZBIUCgV0aXRsZRgCIAEoCVIFdGl0bGUSFgoGaXNsb25nGAMgASgFUgZpc2xvbmcSGAoHYXV0aG9ycxgEIAEoCVIHYXV0aG9ycxIUCgV0eXBlcxgFIAEoCVIFdHlwZXMSFAoFY292ZXIYBiABKAlSBWNvdmVyEhYKBnN0YXR1cxgHIAEoCVIGc3RhdHVzEjQKFWxhc3RVcGRhdGVDaGFwdGVyTmFtZRgIIAEoCVIVbGFzdFVwZGF0ZUNoYXB0ZXJOYW1lEjAKE2xhc3RVcGRhdGVDaGFwdGVySWQYCSABKANSE2xhc3RVcGRhdGVDaGFwdGVySWQSJgoObGFzdFVwZGF0ZXRpbWUYCiABKANSDmxhc3RVcGRhdGV0aW1l');
|
||||
570
lib/models/proto/news.pb.dart
Normal file
570
lib/models/proto/news.pb.dart
Normal file
@@ -0,0 +1,570 @@
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: news.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name, depend_on_referenced_packages, no_leading_underscores_for_local_identifiers
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class NewsListResponseProto extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
const $core.bool.fromEnvironment('protobuf.omit_message_names')
|
||||
? ''
|
||||
: 'NewsListResponseProto',
|
||||
createEmptyInstance: create)
|
||||
..a<$core.int>(
|
||||
1,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'errno',
|
||||
$pb.PbFieldType.O3)
|
||||
..aOS(
|
||||
2,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'errmsg')
|
||||
..pc<NewsListInfoProto>(
|
||||
3,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'data',
|
||||
$pb.PbFieldType.PM,
|
||||
subBuilder: NewsListInfoProto.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
NewsListResponseProto._() : super();
|
||||
factory NewsListResponseProto({
|
||||
$core.int? errno,
|
||||
$core.String? errmsg,
|
||||
$core.Iterable<NewsListInfoProto>? data,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (errno != null) {
|
||||
_result.errno = errno;
|
||||
}
|
||||
if (errmsg != null) {
|
||||
_result.errmsg = errmsg;
|
||||
}
|
||||
if (data != null) {
|
||||
_result.data.addAll(data);
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory NewsListResponseProto.fromBuffer($core.List<$core.int> i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory NewsListResponseProto.fromJson($core.String i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
NewsListResponseProto clone() =>
|
||||
NewsListResponseProto()..mergeFromMessage(this);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
NewsListResponseProto copyWith(
|
||||
void Function(NewsListResponseProto) updates) =>
|
||||
super.copyWith((message) => updates(message as NewsListResponseProto))
|
||||
as NewsListResponseProto; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static NewsListResponseProto create() => NewsListResponseProto._();
|
||||
NewsListResponseProto createEmptyInstance() => create();
|
||||
static $pb.PbList<NewsListResponseProto> createRepeated() =>
|
||||
$pb.PbList<NewsListResponseProto>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static NewsListResponseProto getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<NewsListResponseProto>(create);
|
||||
static NewsListResponseProto? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get errno => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set errno($core.int v) {
|
||||
$_setSignedInt32(0, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasErrno() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearErrno() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get errmsg => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set errmsg($core.String v) {
|
||||
$_setString(1, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasErrmsg() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearErrmsg() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<NewsListInfoProto> get data => $_getList(2);
|
||||
}
|
||||
|
||||
class NewsListInfoProto extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
const $core.bool.fromEnvironment('protobuf.omit_message_names')
|
||||
? ''
|
||||
: 'NewsListInfoProto',
|
||||
createEmptyInstance: create)
|
||||
..aInt64(
|
||||
1,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'articleId',
|
||||
protoName: 'articleId')
|
||||
..aOS(
|
||||
2,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'title')
|
||||
..aOS(
|
||||
3,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'fromName',
|
||||
protoName: 'fromName')
|
||||
..aOS(
|
||||
4,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'fromUrl',
|
||||
protoName: 'fromUrl')
|
||||
..aInt64(
|
||||
5,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'createTime',
|
||||
protoName: 'createTime')
|
||||
..a<$core.int>(
|
||||
6,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'isForeign',
|
||||
$pb.PbFieldType.O3,
|
||||
protoName: 'isForeign')
|
||||
..aOS(
|
||||
7,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'foreignUrl',
|
||||
protoName: 'foreignUrl')
|
||||
..aOS(
|
||||
8,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'intro')
|
||||
..aInt64(
|
||||
9,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'authorId',
|
||||
protoName: 'authorId')
|
||||
..a<$core.int>(
|
||||
10,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'status',
|
||||
$pb.PbFieldType.O3)
|
||||
..aOS(
|
||||
11,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'rowPicUrl',
|
||||
protoName: 'rowPicUrl')
|
||||
..aOS(
|
||||
12,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'colPicUrl',
|
||||
protoName: 'colPicUrl')
|
||||
..a<$core.int>(
|
||||
13,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'qchatShow',
|
||||
$pb.PbFieldType.O3,
|
||||
protoName: 'qchatShow')
|
||||
..aOS(
|
||||
14,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'pageUrl',
|
||||
protoName: 'pageUrl')
|
||||
..aInt64(
|
||||
15,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'commentAmount',
|
||||
protoName: 'commentAmount')
|
||||
..aInt64(
|
||||
16,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'authorUid',
|
||||
protoName: 'authorUid')
|
||||
..aOS(
|
||||
17,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'cover')
|
||||
..aOS(
|
||||
18,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'nickname')
|
||||
..aInt64(
|
||||
19,
|
||||
const $core.bool.fromEnvironment('protobuf.omit_field_names')
|
||||
? ''
|
||||
: 'moodAmount',
|
||||
protoName: 'moodAmount')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
NewsListInfoProto._() : super();
|
||||
factory NewsListInfoProto({
|
||||
$fixnum.Int64? articleId,
|
||||
$core.String? title,
|
||||
$core.String? fromName,
|
||||
$core.String? fromUrl,
|
||||
$fixnum.Int64? createTime,
|
||||
$core.int? isForeign,
|
||||
$core.String? foreignUrl,
|
||||
$core.String? intro,
|
||||
$fixnum.Int64? authorId,
|
||||
$core.int? status,
|
||||
$core.String? rowPicUrl,
|
||||
$core.String? colPicUrl,
|
||||
$core.int? qchatShow,
|
||||
$core.String? pageUrl,
|
||||
$fixnum.Int64? commentAmount,
|
||||
$fixnum.Int64? authorUid,
|
||||
$core.String? cover,
|
||||
$core.String? nickname,
|
||||
$fixnum.Int64? moodAmount,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (articleId != null) {
|
||||
_result.articleId = articleId;
|
||||
}
|
||||
if (title != null) {
|
||||
_result.title = title;
|
||||
}
|
||||
if (fromName != null) {
|
||||
_result.fromName = fromName;
|
||||
}
|
||||
if (fromUrl != null) {
|
||||
_result.fromUrl = fromUrl;
|
||||
}
|
||||
if (createTime != null) {
|
||||
_result.createTime = createTime;
|
||||
}
|
||||
if (isForeign != null) {
|
||||
_result.isForeign = isForeign;
|
||||
}
|
||||
if (foreignUrl != null) {
|
||||
_result.foreignUrl = foreignUrl;
|
||||
}
|
||||
if (intro != null) {
|
||||
_result.intro = intro;
|
||||
}
|
||||
if (authorId != null) {
|
||||
_result.authorId = authorId;
|
||||
}
|
||||
if (status != null) {
|
||||
_result.status = status;
|
||||
}
|
||||
if (rowPicUrl != null) {
|
||||
_result.rowPicUrl = rowPicUrl;
|
||||
}
|
||||
if (colPicUrl != null) {
|
||||
_result.colPicUrl = colPicUrl;
|
||||
}
|
||||
if (qchatShow != null) {
|
||||
_result.qchatShow = qchatShow;
|
||||
}
|
||||
if (pageUrl != null) {
|
||||
_result.pageUrl = pageUrl;
|
||||
}
|
||||
if (commentAmount != null) {
|
||||
_result.commentAmount = commentAmount;
|
||||
}
|
||||
if (authorUid != null) {
|
||||
_result.authorUid = authorUid;
|
||||
}
|
||||
if (cover != null) {
|
||||
_result.cover = cover;
|
||||
}
|
||||
if (nickname != null) {
|
||||
_result.nickname = nickname;
|
||||
}
|
||||
if (moodAmount != null) {
|
||||
_result.moodAmount = moodAmount;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory NewsListInfoProto.fromBuffer($core.List<$core.int> i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory NewsListInfoProto.fromJson($core.String i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
NewsListInfoProto clone() => NewsListInfoProto()..mergeFromMessage(this);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
NewsListInfoProto copyWith(void Function(NewsListInfoProto) updates) =>
|
||||
super.copyWith((message) => updates(message as NewsListInfoProto))
|
||||
as NewsListInfoProto; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static NewsListInfoProto create() => NewsListInfoProto._();
|
||||
NewsListInfoProto createEmptyInstance() => create();
|
||||
static $pb.PbList<NewsListInfoProto> createRepeated() =>
|
||||
$pb.PbList<NewsListInfoProto>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static NewsListInfoProto getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<NewsListInfoProto>(create);
|
||||
static NewsListInfoProto? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get articleId => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set articleId($fixnum.Int64 v) {
|
||||
$_setInt64(0, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasArticleId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearArticleId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get title => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set title($core.String v) {
|
||||
$_setString(1, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasTitle() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearTitle() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get fromName => $_getSZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set fromName($core.String v) {
|
||||
$_setString(2, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasFromName() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearFromName() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.String get fromUrl => $_getSZ(3);
|
||||
@$pb.TagNumber(4)
|
||||
set fromUrl($core.String v) {
|
||||
$_setString(3, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasFromUrl() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearFromUrl() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$fixnum.Int64 get createTime => $_getI64(4);
|
||||
@$pb.TagNumber(5)
|
||||
set createTime($fixnum.Int64 v) {
|
||||
$_setInt64(4, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasCreateTime() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearCreateTime() => clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.int get isForeign => $_getIZ(5);
|
||||
@$pb.TagNumber(6)
|
||||
set isForeign($core.int v) {
|
||||
$_setSignedInt32(5, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasIsForeign() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearIsForeign() => clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$core.String get foreignUrl => $_getSZ(6);
|
||||
@$pb.TagNumber(7)
|
||||
set foreignUrl($core.String v) {
|
||||
$_setString(6, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$core.bool hasForeignUrl() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearForeignUrl() => clearField(7);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$core.String get intro => $_getSZ(7);
|
||||
@$pb.TagNumber(8)
|
||||
set intro($core.String v) {
|
||||
$_setString(7, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool hasIntro() => $_has(7);
|
||||
@$pb.TagNumber(8)
|
||||
void clearIntro() => clearField(8);
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
$fixnum.Int64 get authorId => $_getI64(8);
|
||||
@$pb.TagNumber(9)
|
||||
set authorId($fixnum.Int64 v) {
|
||||
$_setInt64(8, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
$core.bool hasAuthorId() => $_has(8);
|
||||
@$pb.TagNumber(9)
|
||||
void clearAuthorId() => clearField(9);
|
||||
|
||||
@$pb.TagNumber(10)
|
||||
$core.int get status => $_getIZ(9);
|
||||
@$pb.TagNumber(10)
|
||||
set status($core.int v) {
|
||||
$_setSignedInt32(9, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(10)
|
||||
$core.bool hasStatus() => $_has(9);
|
||||
@$pb.TagNumber(10)
|
||||
void clearStatus() => clearField(10);
|
||||
|
||||
@$pb.TagNumber(11)
|
||||
$core.String get rowPicUrl => $_getSZ(10);
|
||||
@$pb.TagNumber(11)
|
||||
set rowPicUrl($core.String v) {
|
||||
$_setString(10, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(11)
|
||||
$core.bool hasRowPicUrl() => $_has(10);
|
||||
@$pb.TagNumber(11)
|
||||
void clearRowPicUrl() => clearField(11);
|
||||
|
||||
@$pb.TagNumber(12)
|
||||
$core.String get colPicUrl => $_getSZ(11);
|
||||
@$pb.TagNumber(12)
|
||||
set colPicUrl($core.String v) {
|
||||
$_setString(11, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(12)
|
||||
$core.bool hasColPicUrl() => $_has(11);
|
||||
@$pb.TagNumber(12)
|
||||
void clearColPicUrl() => clearField(12);
|
||||
|
||||
@$pb.TagNumber(13)
|
||||
$core.int get qchatShow => $_getIZ(12);
|
||||
@$pb.TagNumber(13)
|
||||
set qchatShow($core.int v) {
|
||||
$_setSignedInt32(12, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(13)
|
||||
$core.bool hasQchatShow() => $_has(12);
|
||||
@$pb.TagNumber(13)
|
||||
void clearQchatShow() => clearField(13);
|
||||
|
||||
@$pb.TagNumber(14)
|
||||
$core.String get pageUrl => $_getSZ(13);
|
||||
@$pb.TagNumber(14)
|
||||
set pageUrl($core.String v) {
|
||||
$_setString(13, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(14)
|
||||
$core.bool hasPageUrl() => $_has(13);
|
||||
@$pb.TagNumber(14)
|
||||
void clearPageUrl() => clearField(14);
|
||||
|
||||
@$pb.TagNumber(15)
|
||||
$fixnum.Int64 get commentAmount => $_getI64(14);
|
||||
@$pb.TagNumber(15)
|
||||
set commentAmount($fixnum.Int64 v) {
|
||||
$_setInt64(14, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(15)
|
||||
$core.bool hasCommentAmount() => $_has(14);
|
||||
@$pb.TagNumber(15)
|
||||
void clearCommentAmount() => clearField(15);
|
||||
|
||||
@$pb.TagNumber(16)
|
||||
$fixnum.Int64 get authorUid => $_getI64(15);
|
||||
@$pb.TagNumber(16)
|
||||
set authorUid($fixnum.Int64 v) {
|
||||
$_setInt64(15, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(16)
|
||||
$core.bool hasAuthorUid() => $_has(15);
|
||||
@$pb.TagNumber(16)
|
||||
void clearAuthorUid() => clearField(16);
|
||||
|
||||
@$pb.TagNumber(17)
|
||||
$core.String get cover => $_getSZ(16);
|
||||
@$pb.TagNumber(17)
|
||||
set cover($core.String v) {
|
||||
$_setString(16, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(17)
|
||||
$core.bool hasCover() => $_has(16);
|
||||
@$pb.TagNumber(17)
|
||||
void clearCover() => clearField(17);
|
||||
|
||||
@$pb.TagNumber(18)
|
||||
$core.String get nickname => $_getSZ(17);
|
||||
@$pb.TagNumber(18)
|
||||
set nickname($core.String v) {
|
||||
$_setString(17, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(18)
|
||||
$core.bool hasNickname() => $_has(17);
|
||||
@$pb.TagNumber(18)
|
||||
void clearNickname() => clearField(18);
|
||||
|
||||
@$pb.TagNumber(19)
|
||||
$fixnum.Int64 get moodAmount => $_getI64(18);
|
||||
@$pb.TagNumber(19)
|
||||
set moodAmount($fixnum.Int64 v) {
|
||||
$_setInt64(18, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(19)
|
||||
$core.bool hasMoodAmount() => $_has(18);
|
||||
@$pb.TagNumber(19)
|
||||
void clearMoodAmount() => clearField(19);
|
||||
}
|
||||
50
lib/models/proto/news.pbjson.dart
Normal file
50
lib/models/proto/news.pbjson.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: news.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name
|
||||
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
@$core.Deprecated('Use newsListResponseProtoDescriptor instead')
|
||||
const NewsListResponseProto$json = const {
|
||||
'1': 'NewsListResponseProto',
|
||||
'2': const [
|
||||
const {'1': 'errno', '3': 1, '4': 1, '5': 5, '10': 'errno'},
|
||||
const {'1': 'errmsg', '3': 2, '4': 1, '5': 9, '10': 'errmsg'},
|
||||
const {'1': 'data', '3': 3, '4': 3, '5': 11, '6': '.NewsListInfoProto', '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `NewsListResponseProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List newsListResponseProtoDescriptor = $convert.base64Decode('ChVOZXdzTGlzdFJlc3BvbnNlUHJvdG8SFAoFZXJybm8YASABKAVSBWVycm5vEhYKBmVycm1zZxgCIAEoCVIGZXJybXNnEiYKBGRhdGEYAyADKAsyEi5OZXdzTGlzdEluZm9Qcm90b1IEZGF0YQ==');
|
||||
@$core.Deprecated('Use newsListInfoProtoDescriptor instead')
|
||||
const NewsListInfoProto$json = const {
|
||||
'1': 'NewsListInfoProto',
|
||||
'2': const [
|
||||
const {'1': 'articleId', '3': 1, '4': 1, '5': 3, '10': 'articleId'},
|
||||
const {'1': 'title', '3': 2, '4': 1, '5': 9, '10': 'title'},
|
||||
const {'1': 'fromName', '3': 3, '4': 1, '5': 9, '10': 'fromName'},
|
||||
const {'1': 'fromUrl', '3': 4, '4': 1, '5': 9, '10': 'fromUrl'},
|
||||
const {'1': 'createTime', '3': 5, '4': 1, '5': 3, '10': 'createTime'},
|
||||
const {'1': 'isForeign', '3': 6, '4': 1, '5': 5, '10': 'isForeign'},
|
||||
const {'1': 'foreignUrl', '3': 7, '4': 1, '5': 9, '10': 'foreignUrl'},
|
||||
const {'1': 'intro', '3': 8, '4': 1, '5': 9, '10': 'intro'},
|
||||
const {'1': 'authorId', '3': 9, '4': 1, '5': 3, '10': 'authorId'},
|
||||
const {'1': 'status', '3': 10, '4': 1, '5': 5, '10': 'status'},
|
||||
const {'1': 'rowPicUrl', '3': 11, '4': 1, '5': 9, '10': 'rowPicUrl'},
|
||||
const {'1': 'colPicUrl', '3': 12, '4': 1, '5': 9, '10': 'colPicUrl'},
|
||||
const {'1': 'qchatShow', '3': 13, '4': 1, '5': 5, '10': 'qchatShow'},
|
||||
const {'1': 'pageUrl', '3': 14, '4': 1, '5': 9, '10': 'pageUrl'},
|
||||
const {'1': 'commentAmount', '3': 15, '4': 1, '5': 3, '10': 'commentAmount'},
|
||||
const {'1': 'authorUid', '3': 16, '4': 1, '5': 3, '10': 'authorUid'},
|
||||
const {'1': 'cover', '3': 17, '4': 1, '5': 9, '10': 'cover'},
|
||||
const {'1': 'nickname', '3': 18, '4': 1, '5': 9, '10': 'nickname'},
|
||||
const {'1': 'moodAmount', '3': 19, '4': 1, '5': 3, '10': 'moodAmount'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `NewsListInfoProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List newsListInfoProtoDescriptor = $convert.base64Decode('ChFOZXdzTGlzdEluZm9Qcm90bxIcCglhcnRpY2xlSWQYASABKANSCWFydGljbGVJZBIUCgV0aXRsZRgCIAEoCVIFdGl0bGUSGgoIZnJvbU5hbWUYAyABKAlSCGZyb21OYW1lEhgKB2Zyb21VcmwYBCABKAlSB2Zyb21VcmwSHgoKY3JlYXRlVGltZRgFIAEoA1IKY3JlYXRlVGltZRIcCglpc0ZvcmVpZ24YBiABKAVSCWlzRm9yZWlnbhIeCgpmb3JlaWduVXJsGAcgASgJUgpmb3JlaWduVXJsEhQKBWludHJvGAggASgJUgVpbnRybxIaCghhdXRob3JJZBgJIAEoA1IIYXV0aG9ySWQSFgoGc3RhdHVzGAogASgFUgZzdGF0dXMSHAoJcm93UGljVXJsGAsgASgJUglyb3dQaWNVcmwSHAoJY29sUGljVXJsGAwgASgJUgljb2xQaWNVcmwSHAoJcWNoYXRTaG93GA0gASgFUglxY2hhdFNob3cSGAoHcGFnZVVybBgOIAEoCVIHcGFnZVVybBIkCg1jb21tZW50QW1vdW50GA8gASgDUg1jb21tZW50QW1vdW50EhwKCWF1dGhvclVpZBgQIAEoA1IJYXV0aG9yVWlkEhQKBWNvdmVyGBEgASgJUgVjb3ZlchIaCghuaWNrbmFtZRgSIAEoCVIIbmlja25hbWUSHgoKbW9vZEFtb3VudBgTIAEoA1IKbW9vZEFtb3VudA==');
|
||||
1030
lib/models/proto/novel.pb.dart
Normal file
1030
lib/models/proto/novel.pb.dart
Normal file
File diff suppressed because it is too large
Load Diff
101
lib/models/proto/novel.pbjson.dart
Normal file
101
lib/models/proto/novel.pbjson.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: novel.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name
|
||||
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
@$core.Deprecated('Use novelChapterDetailProtoDescriptor instead')
|
||||
const NovelChapterDetailProto$json = const {
|
||||
'1': 'NovelChapterDetailProto',
|
||||
'2': const [
|
||||
const {'1': 'chapterId', '3': 1, '4': 1, '5': 3, '10': 'chapterId'},
|
||||
const {'1': 'chapterName', '3': 2, '4': 1, '5': 9, '10': 'chapterName'},
|
||||
const {'1': 'chapterOrder', '3': 3, '4': 1, '5': 5, '10': 'chapterOrder'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `NovelChapterDetailProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List novelChapterDetailProtoDescriptor = $convert.base64Decode('ChdOb3ZlbENoYXB0ZXJEZXRhaWxQcm90bxIcCgljaGFwdGVySWQYASABKANSCWNoYXB0ZXJJZBIgCgtjaGFwdGVyTmFtZRgCIAEoCVILY2hhcHRlck5hbWUSIgoMY2hhcHRlck9yZGVyGAMgASgFUgxjaGFwdGVyT3JkZXI=');
|
||||
@$core.Deprecated('Use novelVolumeProtoDescriptor instead')
|
||||
const NovelVolumeProto$json = const {
|
||||
'1': 'NovelVolumeProto',
|
||||
'2': const [
|
||||
const {'1': 'volume_id', '3': 1, '4': 1, '5': 3, '10': 'volumeId'},
|
||||
const {'1': 'lnovel_id', '3': 2, '4': 1, '5': 3, '10': 'lnovelId'},
|
||||
const {'1': 'volume_name', '3': 3, '4': 1, '5': 9, '10': 'volumeName'},
|
||||
const {'1': 'volume_order', '3': 4, '4': 1, '5': 5, '10': 'volumeOrder'},
|
||||
const {'1': 'addtime', '3': 5, '4': 1, '5': 3, '10': 'addtime'},
|
||||
const {'1': 'sum_chapters', '3': 6, '4': 1, '5': 5, '10': 'sumChapters'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `NovelVolumeProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List novelVolumeProtoDescriptor = $convert.base64Decode('ChBOb3ZlbFZvbHVtZVByb3RvEhsKCXZvbHVtZV9pZBgBIAEoA1IIdm9sdW1lSWQSGwoJbG5vdmVsX2lkGAIgASgDUghsbm92ZWxJZBIfCgt2b2x1bWVfbmFtZRgDIAEoCVIKdm9sdW1lTmFtZRIhCgx2b2x1bWVfb3JkZXIYBCABKAVSC3ZvbHVtZU9yZGVyEhgKB2FkZHRpbWUYBSABKANSB2FkZHRpbWUSIQoMc3VtX2NoYXB0ZXJzGAYgASgFUgtzdW1DaGFwdGVycw==');
|
||||
@$core.Deprecated('Use novelChapterResponseProtoDescriptor instead')
|
||||
const NovelChapterResponseProto$json = const {
|
||||
'1': 'NovelChapterResponseProto',
|
||||
'2': const [
|
||||
const {'1': 'errno', '3': 1, '4': 1, '5': 5, '10': 'errno'},
|
||||
const {'1': 'errmsg', '3': 2, '4': 1, '5': 9, '10': 'errmsg'},
|
||||
const {'1': 'data', '3': 3, '4': 3, '5': 11, '6': '.NovelVolumeDetailProto', '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `NovelChapterResponseProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List novelChapterResponseProtoDescriptor = $convert.base64Decode('ChlOb3ZlbENoYXB0ZXJSZXNwb25zZVByb3RvEhQKBWVycm5vGAEgASgFUgVlcnJubxIWCgZlcnJtc2cYAiABKAlSBmVycm1zZxIrCgRkYXRhGAMgAygLMhcuTm92ZWxWb2x1bWVEZXRhaWxQcm90b1IEZGF0YQ==');
|
||||
@$core.Deprecated('Use novelVolumeDetailProtoDescriptor instead')
|
||||
const NovelVolumeDetailProto$json = const {
|
||||
'1': 'NovelVolumeDetailProto',
|
||||
'2': const [
|
||||
const {'1': 'volume_id', '3': 1, '4': 1, '5': 3, '10': 'volumeId'},
|
||||
const {'1': 'volume_name', '3': 2, '4': 1, '5': 9, '10': 'volumeName'},
|
||||
const {'1': 'volume_order', '3': 3, '4': 1, '5': 5, '10': 'volumeOrder'},
|
||||
const {'1': 'chapters', '3': 4, '4': 3, '5': 11, '6': '.NovelChapterDetailProto', '10': 'chapters'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `NovelVolumeDetailProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List novelVolumeDetailProtoDescriptor = $convert.base64Decode('ChZOb3ZlbFZvbHVtZURldGFpbFByb3RvEhsKCXZvbHVtZV9pZBgBIAEoA1IIdm9sdW1lSWQSHwoLdm9sdW1lX25hbWUYAiABKAlSCnZvbHVtZU5hbWUSIQoMdm9sdW1lX29yZGVyGAMgASgFUgt2b2x1bWVPcmRlchI0CghjaGFwdGVycxgEIAMoCzIYLk5vdmVsQ2hhcHRlckRldGFpbFByb3RvUghjaGFwdGVycw==');
|
||||
@$core.Deprecated('Use novelDetailProtoDescriptor instead')
|
||||
const NovelDetailProto$json = const {
|
||||
'1': 'NovelDetailProto',
|
||||
'2': const [
|
||||
const {'1': 'novel_id', '3': 1, '4': 1, '5': 3, '10': 'novelId'},
|
||||
const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
|
||||
const {'1': 'zone', '3': 3, '4': 1, '5': 9, '10': 'zone'},
|
||||
const {'1': 'status', '3': 4, '4': 1, '5': 9, '10': 'status'},
|
||||
const {'1': 'last_update_volume_name', '3': 5, '4': 1, '5': 9, '10': 'lastUpdateVolumeName'},
|
||||
const {'1': 'last_update_chapter_name', '3': 6, '4': 1, '5': 9, '10': 'lastUpdateChapterName'},
|
||||
const {'1': 'last_update_volume_id', '3': 7, '4': 1, '5': 3, '10': 'lastUpdateVolumeId'},
|
||||
const {'1': 'last_update_chapter_id', '3': 8, '4': 1, '5': 3, '10': 'lastUpdateChapterId'},
|
||||
const {'1': 'last_update_time', '3': 9, '4': 1, '5': 3, '10': 'lastUpdateTime'},
|
||||
const {'1': 'cover', '3': 10, '4': 1, '5': 9, '10': 'cover'},
|
||||
const {'1': 'hot_hits', '3': 11, '4': 1, '5': 3, '10': 'hotHits'},
|
||||
const {'1': 'introduction', '3': 12, '4': 1, '5': 9, '10': 'introduction'},
|
||||
const {'1': 'types', '3': 13, '4': 3, '5': 9, '10': 'types'},
|
||||
const {'1': 'authors', '3': 14, '4': 1, '5': 9, '10': 'authors'},
|
||||
const {'1': 'first_letter', '3': 15, '4': 1, '5': 9, '10': 'firstLetter'},
|
||||
const {'1': 'subscribe_num', '3': 16, '4': 1, '5': 3, '10': 'subscribeNum'},
|
||||
const {'1': 'redis_update_time', '3': 17, '4': 1, '5': 3, '10': 'redisUpdateTime'},
|
||||
const {'1': 'volume', '3': 18, '4': 3, '5': 11, '6': '.NovelVolumeProto', '10': 'volume'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `NovelDetailProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List novelDetailProtoDescriptor = $convert.base64Decode('ChBOb3ZlbERldGFpbFByb3RvEhkKCG5vdmVsX2lkGAEgASgDUgdub3ZlbElkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEem9uZRgDIAEoCVIEem9uZRIWCgZzdGF0dXMYBCABKAlSBnN0YXR1cxI1ChdsYXN0X3VwZGF0ZV92b2x1bWVfbmFtZRgFIAEoCVIUbGFzdFVwZGF0ZVZvbHVtZU5hbWUSNwoYbGFzdF91cGRhdGVfY2hhcHRlcl9uYW1lGAYgASgJUhVsYXN0VXBkYXRlQ2hhcHRlck5hbWUSMQoVbGFzdF91cGRhdGVfdm9sdW1lX2lkGAcgASgDUhJsYXN0VXBkYXRlVm9sdW1lSWQSMwoWbGFzdF91cGRhdGVfY2hhcHRlcl9pZBgIIAEoA1ITbGFzdFVwZGF0ZUNoYXB0ZXJJZBIoChBsYXN0X3VwZGF0ZV90aW1lGAkgASgDUg5sYXN0VXBkYXRlVGltZRIUCgVjb3ZlchgKIAEoCVIFY292ZXISGQoIaG90X2hpdHMYCyABKANSB2hvdEhpdHMSIgoMaW50cm9kdWN0aW9uGAwgASgJUgxpbnRyb2R1Y3Rpb24SFAoFdHlwZXMYDSADKAlSBXR5cGVzEhgKB2F1dGhvcnMYDiABKAlSB2F1dGhvcnMSIQoMZmlyc3RfbGV0dGVyGA8gASgJUgtmaXJzdExldHRlchIjCg1zdWJzY3JpYmVfbnVtGBAgASgDUgxzdWJzY3JpYmVOdW0SKgoRcmVkaXNfdXBkYXRlX3RpbWUYESABKANSD3JlZGlzVXBkYXRlVGltZRIpCgZ2b2x1bWUYEiADKAsyES5Ob3ZlbFZvbHVtZVByb3RvUgZ2b2x1bWU=');
|
||||
@$core.Deprecated('Use novelDetailResponseProtoDescriptor instead')
|
||||
const NovelDetailResponseProto$json = const {
|
||||
'1': 'NovelDetailResponseProto',
|
||||
'2': const [
|
||||
const {'1': 'errno', '3': 1, '4': 1, '5': 5, '10': 'errno'},
|
||||
const {'1': 'errmsg', '3': 2, '4': 1, '5': 9, '10': 'errmsg'},
|
||||
const {'1': 'data', '3': 3, '4': 1, '5': 11, '6': '.NovelDetailProto', '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `NovelDetailResponseProto`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List novelDetailResponseProtoDescriptor = $convert.base64Decode('ChhOb3ZlbERldGFpbFJlc3BvbnNlUHJvdG8SFAoFZXJybm8YASABKAVSBWVycm5vEhYKBmVycm1zZxgCIAEoCVIGZXJybXNnEiUKBGRhdGEYAyABKAsyES5Ob3ZlbERldGFpbFByb3RvUgRkYXRh');
|
||||
34
lib/models/user/bind_status_model.dart
Normal file
34
lib/models/user/bind_status_model.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class UserBindStatusModel {
|
||||
UserBindStatusModel({
|
||||
required this.isBindTel,
|
||||
required this.isSetPwd,
|
||||
});
|
||||
|
||||
factory UserBindStatusModel.fromJson(Map<String, dynamic> json) =>
|
||||
UserBindStatusModel(
|
||||
isBindTel: asT<int>(json['is_bind_tel'])!,
|
||||
isSetPwd: asT<int>(json['is_set_pwd'])!,
|
||||
);
|
||||
|
||||
int isBindTel;
|
||||
int isSetPwd;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'is_bind_tel': isBindTel,
|
||||
'is_set_pwd': isSetPwd,
|
||||
};
|
||||
}
|
||||
63
lib/models/user/comic_history_model.dart
Normal file
63
lib/models/user/comic_history_model.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class UserComicHistoryModel {
|
||||
UserComicHistoryModel({
|
||||
this.uid,
|
||||
this.type,
|
||||
required this.comicId,
|
||||
this.chapterId,
|
||||
this.record,
|
||||
this.viewingTime,
|
||||
required this.comicName,
|
||||
required this.cover,
|
||||
this.chapterName,
|
||||
});
|
||||
|
||||
factory UserComicHistoryModel.fromJson(Map<String, dynamic> json) =>
|
||||
// 接口不知道那些可能为空,所以全部变为可空
|
||||
UserComicHistoryModel(
|
||||
uid: asT<int?>(json['uid']) ?? 0,
|
||||
type: asT<int?>(json['type']) ?? 0,
|
||||
comicId: asT<int?>(json['comic_id']) ?? 0,
|
||||
chapterId: asT<int?>(json['chapter_id']) ?? 0,
|
||||
record: asT<int?>(json['record']) ?? 0,
|
||||
viewingTime: asT<int?>(json['viewing_time']) ?? 0,
|
||||
comicName: asT<String?>(json['comic_name']) ?? "未知漫画",
|
||||
cover: asT<String?>(json['cover']) ?? "",
|
||||
chapterName: asT<String?>(json['chapter_name']) ?? "-",
|
||||
);
|
||||
|
||||
int? uid;
|
||||
int? type;
|
||||
int comicId;
|
||||
int? chapterId;
|
||||
int? record;
|
||||
int? viewingTime;
|
||||
String comicName;
|
||||
String cover;
|
||||
String? chapterName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'uid': uid,
|
||||
'type': type,
|
||||
'comic_id': comicId,
|
||||
'chapter_id': chapterId,
|
||||
'record': record,
|
||||
'viewing_time': viewingTime,
|
||||
'comic_name': comicName,
|
||||
'cover': cover,
|
||||
'chapter_name': chapterName,
|
||||
};
|
||||
}
|
||||
54
lib/models/user/login_result_model.dart
Normal file
54
lib/models/user/login_result_model.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class LoginResultModel {
|
||||
LoginResultModel({
|
||||
required this.uid,
|
||||
required this.nickname,
|
||||
required this.token,
|
||||
required this.photo,
|
||||
required this.bindPhone,
|
||||
required this.email,
|
||||
required this.setPasswd,
|
||||
});
|
||||
|
||||
factory LoginResultModel.fromJson(Map<String, dynamic> json) =>
|
||||
LoginResultModel(
|
||||
uid: asT<int>(json['uid'])!,
|
||||
nickname: asT<String>(json['nickname'])!,
|
||||
token: asT<String>(json['token'])!,
|
||||
photo: asT<String>(json['photo'])!,
|
||||
bindPhone: asT<String>(json['bind_phone'])!,
|
||||
email: asT<String>(json['email'])!,
|
||||
setPasswd: asT<int>(json['setPasswd'])!,
|
||||
);
|
||||
|
||||
int uid;
|
||||
String nickname;
|
||||
String token;
|
||||
String photo;
|
||||
String bindPhone;
|
||||
String email;
|
||||
int setPasswd;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'uid': uid,
|
||||
'nickname': nickname,
|
||||
'token': token,
|
||||
'photo': photo,
|
||||
'bind_phone': bindPhone,
|
||||
'email': email,
|
||||
'setPasswd': setPasswd
|
||||
};
|
||||
}
|
||||
75
lib/models/user/novel_history_model.dart
Normal file
75
lib/models/user/novel_history_model.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class UserNovelHistoryModel {
|
||||
UserNovelHistoryModel({
|
||||
this.uid,
|
||||
this.type,
|
||||
required this.lnovelId,
|
||||
this.volumeId,
|
||||
this.chapterId,
|
||||
this.record,
|
||||
this.viewingTime,
|
||||
this.totalNum,
|
||||
required this.cover,
|
||||
required this.novelName,
|
||||
this.volumeName,
|
||||
this.chapterName,
|
||||
});
|
||||
|
||||
factory UserNovelHistoryModel.fromJson(Map<String, dynamic> json) =>
|
||||
// 接口不知道那些可能为空,所以全部变为可空
|
||||
UserNovelHistoryModel(
|
||||
uid: asT<int?>(json['uid']) ?? 0,
|
||||
type: asT<int?>(json['type']) ?? 0,
|
||||
lnovelId: int.tryParse(json['lnovel_id'].toString()) ?? 0,
|
||||
volumeId: asT<int?>(json['volume_id']) ?? 0,
|
||||
chapterId: asT<int?>(json['chapter_id']) ?? 0,
|
||||
record: asT<int?>(json['record']) ?? 0,
|
||||
viewingTime: asT<int?>(json['viewing_time']) ?? 0,
|
||||
totalNum: asT<int?>(json['total_num']) ?? 0,
|
||||
cover: asT<String?>(json['cover']) ?? "",
|
||||
novelName: asT<String?>(json['novel_name']) ?? "未知小说",
|
||||
volumeName: asT<String?>(json['volume_name']) ?? "-",
|
||||
chapterName: asT<String?>(json['chapter_name']) ?? "-",
|
||||
);
|
||||
|
||||
int? uid;
|
||||
int? type;
|
||||
int lnovelId;
|
||||
int? volumeId;
|
||||
int? chapterId;
|
||||
int? record;
|
||||
int? viewingTime;
|
||||
int? totalNum;
|
||||
String cover;
|
||||
String novelName;
|
||||
String? volumeName;
|
||||
String? chapterName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'uid': uid,
|
||||
'type': type,
|
||||
'lnovel_id': lnovelId,
|
||||
'volume_id': volumeId,
|
||||
'chapter_id': chapterId,
|
||||
'record': record,
|
||||
'viewing_time': viewingTime,
|
||||
'total_num': totalNum,
|
||||
'cover': cover,
|
||||
'novel_name': novelName,
|
||||
'volume_name': volumeName,
|
||||
'chapter_name': chapterName,
|
||||
};
|
||||
}
|
||||
131
lib/models/user/subscribe_comic_model.dart
Normal file
131
lib/models/user/subscribe_comic_model.dart
Normal file
@@ -0,0 +1,131 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class UserSubscribeComicItemModel {
|
||||
UserSubscribeComicItemModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.cover,
|
||||
required this.subReaded,
|
||||
required this.lastUpdateChapterId,
|
||||
required this.lastUpdateChapterName,
|
||||
required this.comicPy,
|
||||
required this.status,
|
||||
required this.readingRecord,
|
||||
required this.hasNew,
|
||||
});
|
||||
|
||||
factory UserSubscribeComicItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
UserSubscribeComicItemModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
cover: asT<String>(json['cover'])!,
|
||||
subReaded: asT<int>(json['sub_readed'])!,
|
||||
lastUpdateChapterId: asT<int>(json['last_update_chapter_id'])!,
|
||||
lastUpdateChapterName: asT<String>(json['last_update_chapter_name'])!,
|
||||
comicPy: asT<String>(json['comic_py'])!,
|
||||
status: asT<String>(json['status'])!,
|
||||
readingRecord: ReadingRecord.fromJson(
|
||||
asT<Map<String, dynamic>>(json['readingRecord'])!),
|
||||
hasNew: (asT<int>(json['sub_readed']) == 0).obs,
|
||||
);
|
||||
|
||||
int id;
|
||||
String title;
|
||||
String cover;
|
||||
int subReaded;
|
||||
int lastUpdateChapterId;
|
||||
String lastUpdateChapterName;
|
||||
String comicPy;
|
||||
String status;
|
||||
ReadingRecord readingRecord;
|
||||
|
||||
var isChecked = false.obs;
|
||||
var hasNew = false.obs;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'cover': cover,
|
||||
'sub_readed': subReaded,
|
||||
'last_update_chapter_id': lastUpdateChapterId,
|
||||
'last_update_chapter_name': lastUpdateChapterName,
|
||||
'comic_py': comicPy,
|
||||
'status': status,
|
||||
'readingRecord': readingRecord,
|
||||
};
|
||||
}
|
||||
|
||||
class ReadingRecord {
|
||||
ReadingRecord({
|
||||
required this.typeName,
|
||||
required this.uid,
|
||||
required this.source,
|
||||
required this.bizId,
|
||||
required this.chapterId,
|
||||
required this.viewingTime,
|
||||
required this.record,
|
||||
required this.volumeId,
|
||||
required this.totalNum,
|
||||
required this.chapterName,
|
||||
required this.volumeName,
|
||||
});
|
||||
|
||||
factory ReadingRecord.fromJson(Map<String, dynamic> json) => ReadingRecord(
|
||||
typeName: asT<String>(json['type_name'])!,
|
||||
uid: asT<int>(json['uid'])!,
|
||||
source: asT<int>(json['source'])!,
|
||||
bizId: asT<int>(json['biz_id'])!,
|
||||
chapterId: asT<int>(json['chapter_id'])!,
|
||||
viewingTime: asT<int>(json['viewing_time'])!,
|
||||
record: asT<int>(json['record'])!,
|
||||
volumeId: asT<int>(json['volume_id'])!,
|
||||
totalNum: asT<int>(json['total_num'])!,
|
||||
chapterName: asT<String>(json['chapter_name'])!,
|
||||
volumeName: asT<String>(json['volume_name'])!,
|
||||
);
|
||||
|
||||
String typeName;
|
||||
int uid;
|
||||
int source;
|
||||
int bizId;
|
||||
int chapterId;
|
||||
int viewingTime;
|
||||
int record;
|
||||
int volumeId;
|
||||
int totalNum;
|
||||
String chapterName;
|
||||
String volumeName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'type_name': typeName,
|
||||
'uid': uid,
|
||||
'source': source,
|
||||
'biz_id': bizId,
|
||||
'chapter_id': chapterId,
|
||||
'viewing_time': viewingTime,
|
||||
'record': record,
|
||||
'volume_id': volumeId,
|
||||
'total_num': totalNum,
|
||||
'chapter_name': chapterName,
|
||||
'volume_name': volumeName,
|
||||
};
|
||||
}
|
||||
78
lib/models/user/subscribe_news_model.dart
Normal file
78
lib/models/user/subscribe_news_model.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class UserSubscribeNewsModel {
|
||||
UserSubscribeNewsModel({
|
||||
required this.subId,
|
||||
required this.subTime,
|
||||
required this.title,
|
||||
required this.authorId,
|
||||
required this.rowPicUrl,
|
||||
required this.colPicUrl,
|
||||
required this.isForeign,
|
||||
required this.foreignUrl,
|
||||
required this.userPhoto,
|
||||
required this.userNickname,
|
||||
required this.pageUrl,
|
||||
required this.commentAmount,
|
||||
required this.moodAmount,
|
||||
});
|
||||
|
||||
factory UserSubscribeNewsModel.fromJson(Map<String, dynamic> json) =>
|
||||
UserSubscribeNewsModel(
|
||||
subId: asT<int>(json['sub_id'])!,
|
||||
subTime: asT<int>(json['sub_time'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
authorId: asT<int>(json['author_id'])!,
|
||||
rowPicUrl: asT<String>(json['row_pic_url'])!,
|
||||
colPicUrl: asT<String>(json['col_pic_url'])!,
|
||||
isForeign: asT<int>(json['is_foreign'])!,
|
||||
foreignUrl: asT<String>(json['foreign_url'])!,
|
||||
userPhoto: asT<String>(json['user_photo'])!,
|
||||
userNickname: asT<String>(json['user_nickname'])!,
|
||||
pageUrl: asT<String>(json['page_url'])!,
|
||||
commentAmount: int.tryParse(json['comment_amount'].toString()) ?? 0,
|
||||
moodAmount: int.tryParse(json['mood_amount'].toString()) ?? 0,
|
||||
);
|
||||
|
||||
int subId;
|
||||
int subTime;
|
||||
String title;
|
||||
int authorId;
|
||||
String rowPicUrl;
|
||||
String colPicUrl;
|
||||
int isForeign;
|
||||
String foreignUrl;
|
||||
String userPhoto;
|
||||
String userNickname;
|
||||
String pageUrl;
|
||||
int commentAmount;
|
||||
int moodAmount;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'sub_id': subId,
|
||||
'sub_time': subTime,
|
||||
'title': title,
|
||||
'author_id': authorId,
|
||||
'row_pic_url': rowPicUrl,
|
||||
'col_pic_url': colPicUrl,
|
||||
'is_foreign': isForeign,
|
||||
'foreign_url': foreignUrl,
|
||||
'user_photo': userPhoto,
|
||||
'user_nickname': userNickname,
|
||||
'page_url': pageUrl,
|
||||
'comment_amount': commentAmount,
|
||||
'mood_amount': moodAmount,
|
||||
};
|
||||
}
|
||||
133
lib/models/user/subscribe_novel_model.dart
Normal file
133
lib/models/user/subscribe_novel_model.dart
Normal file
@@ -0,0 +1,133 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class UserSubscribeNovelModel {
|
||||
UserSubscribeNovelModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.cover,
|
||||
this.subReaded,
|
||||
this.lastUpdateChapterId,
|
||||
this.lastUpdateChapterName,
|
||||
this.comicPy,
|
||||
this.status,
|
||||
required this.readingRecord,
|
||||
required this.hasNew,
|
||||
});
|
||||
|
||||
factory UserSubscribeNovelModel.fromJson(Map<String, dynamic> json) =>
|
||||
UserSubscribeNovelModel(
|
||||
id: asT<int>(json['id'])!,
|
||||
title: asT<String>(json['title'])!,
|
||||
cover: asT<String?>(json['cover']),
|
||||
subReaded: asT<int?>(json['sub_readed']),
|
||||
lastUpdateChapterId: asT<int?>(json['last_update_chapter_id']),
|
||||
lastUpdateChapterName: asT<String?>(json['last_update_chapter_name']),
|
||||
comicPy: asT<String?>(json['comic_py']),
|
||||
status: asT<String?>(json['status']),
|
||||
readingRecord: UserSubscribeNovelReadingRecordModel.fromJson(
|
||||
asT<Map<String, dynamic>>(json['readingRecord'])!),
|
||||
hasNew: (asT<int>(json['sub_readed']) == 0).obs,
|
||||
);
|
||||
|
||||
int id;
|
||||
String title;
|
||||
String? cover;
|
||||
int? subReaded;
|
||||
int? lastUpdateChapterId;
|
||||
String? lastUpdateChapterName;
|
||||
String? comicPy;
|
||||
String? status;
|
||||
UserSubscribeNovelReadingRecordModel readingRecord;
|
||||
|
||||
var isChecked = false.obs;
|
||||
var hasNew = false.obs;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'cover': cover,
|
||||
'sub_readed': subReaded,
|
||||
'last_update_chapter_id': lastUpdateChapterId,
|
||||
'last_update_chapter_name': lastUpdateChapterName,
|
||||
'comic_py': comicPy,
|
||||
'status': status,
|
||||
'readingRecord': readingRecord,
|
||||
};
|
||||
}
|
||||
|
||||
class UserSubscribeNovelReadingRecordModel {
|
||||
UserSubscribeNovelReadingRecordModel({
|
||||
this.typeName,
|
||||
this.uid,
|
||||
this.source,
|
||||
this.bizId,
|
||||
this.chapterId,
|
||||
this.viewingTime,
|
||||
this.record,
|
||||
this.volumeId,
|
||||
this.totalNum,
|
||||
this.chapterName,
|
||||
this.volumeName,
|
||||
});
|
||||
|
||||
factory UserSubscribeNovelReadingRecordModel.fromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
UserSubscribeNovelReadingRecordModel(
|
||||
typeName: asT<String?>(json['type_name']),
|
||||
uid: asT<int?>(json['uid']),
|
||||
source: asT<int?>(json['source']),
|
||||
bizId: asT<int?>(json['biz_id']),
|
||||
chapterId: asT<int?>(json['chapter_id']),
|
||||
viewingTime: asT<int?>(json['viewing_time']),
|
||||
record: asT<int?>(json['record']),
|
||||
volumeId: asT<int?>(json['volume_id']),
|
||||
totalNum: asT<int?>(json['total_num']),
|
||||
chapterName: asT<String?>(json['chapter_name']),
|
||||
volumeName: asT<String?>(json['volume_name']),
|
||||
);
|
||||
|
||||
String? typeName;
|
||||
int? uid;
|
||||
int? source;
|
||||
int? bizId;
|
||||
int? chapterId;
|
||||
int? viewingTime;
|
||||
int? record;
|
||||
int? volumeId;
|
||||
int? totalNum;
|
||||
String? chapterName;
|
||||
String? volumeName;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'type_name': typeName,
|
||||
'uid': uid,
|
||||
'source': source,
|
||||
'biz_id': bizId,
|
||||
'chapter_id': chapterId,
|
||||
'viewing_time': viewingTime,
|
||||
'record': record,
|
||||
'volume_id': volumeId,
|
||||
'total_num': totalNum,
|
||||
'chapter_name': chapterName,
|
||||
'volume_name': volumeName,
|
||||
};
|
||||
}
|
||||
298
lib/models/user/user_profile_model.dart
Normal file
298
lib/models/user/user_profile_model.dart
Normal file
@@ -0,0 +1,298 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class UserProfileModel {
|
||||
UserProfileModel({
|
||||
required this.nickname,
|
||||
this.description,
|
||||
this.birthday,
|
||||
this.sex,
|
||||
this.cover,
|
||||
this.blood,
|
||||
this.constellation,
|
||||
this.bindPhone,
|
||||
this.email,
|
||||
this.channel,
|
||||
this.channelid,
|
||||
this.isVerify,
|
||||
this.status,
|
||||
this.reason,
|
||||
this.submitLogout,
|
||||
this.userDelInfo,
|
||||
this.ip,
|
||||
this.ipRegion,
|
||||
this.isModifyName,
|
||||
this.data,
|
||||
this.amount,
|
||||
this.isSetPwd,
|
||||
this.bind,
|
||||
this.userfeeinfo,
|
||||
this.userLevel,
|
||||
this.cookieVal,
|
||||
this.isBbsAdmin,
|
||||
});
|
||||
|
||||
factory UserProfileModel.fromJson(Map<String, dynamic> json) {
|
||||
final List<Object>? data = json['data'] is List ? <Object>[] : null;
|
||||
if (data != null) {
|
||||
for (final dynamic item in json['data']!) {
|
||||
if (item != null) {
|
||||
data.add(asT<Object>(item)!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<UserPorfileBindModel>? bind =
|
||||
json['bind'] is List ? <UserPorfileBindModel>[] : null;
|
||||
if (bind != null) {
|
||||
for (final dynamic item in json['bind']!) {
|
||||
if (item != null) {
|
||||
bind.add(
|
||||
UserPorfileBindModel.fromJson(asT<Map<String, dynamic>>(item)!));
|
||||
}
|
||||
}
|
||||
}
|
||||
return UserProfileModel(
|
||||
nickname: asT<String>(json['nickname'])!,
|
||||
description: asT<String?>(json['description']),
|
||||
birthday: asT<String?>(json['birthday']),
|
||||
sex: asT<int?>(json['sex']),
|
||||
cover: asT<String?>(json['cover']),
|
||||
blood: asT<int?>(json['blood']),
|
||||
constellation: asT<String?>(json['constellation']),
|
||||
bindPhone: asT<String?>(json['bind_phone']),
|
||||
email: asT<String?>(json['email']),
|
||||
channel: asT<String?>(json['channel']),
|
||||
channelid: asT<String?>(json['channelid']),
|
||||
isVerify: asT<int?>(json['is_verify']),
|
||||
status: asT<int?>(json['status']),
|
||||
reason: asT<String?>(json['reason']),
|
||||
submitLogout: asT<bool?>(json['submit_logout']),
|
||||
userDelInfo: json['user_del_info'] == null
|
||||
? null
|
||||
: UserDelInfoModel.fromJson(
|
||||
asT<Map<String, dynamic>>(json['user_del_info'])!),
|
||||
ip: asT<String?>(json['ip']),
|
||||
ipRegion: json['ip_region'] == null
|
||||
? null
|
||||
: UserIpRegionModel.fromJson(
|
||||
asT<Map<String, dynamic>>(json['ip_region'])!),
|
||||
isModifyName: asT<int?>(json['is_modify_name']),
|
||||
data: data,
|
||||
amount: asT<int?>(json['amount']),
|
||||
isSetPwd: asT<int?>(json['is_set_pwd']),
|
||||
bind: bind,
|
||||
userfeeinfo: json['userFeeInfo'] == null
|
||||
? null
|
||||
: UserfeeInfo.fromJson(
|
||||
asT<Map<String, dynamic>>(json['userFeeInfo'])!),
|
||||
userLevel: asT<String?>(json['user_level']),
|
||||
cookieVal: asT<String?>(json['cookie_val']),
|
||||
isBbsAdmin: asT<int?>(json['is_bbs_admin']),
|
||||
);
|
||||
}
|
||||
|
||||
String nickname;
|
||||
String? description;
|
||||
String? birthday;
|
||||
int? sex;
|
||||
String? cover;
|
||||
int? blood;
|
||||
String? constellation;
|
||||
String? bindPhone;
|
||||
String? email;
|
||||
String? channel;
|
||||
String? channelid;
|
||||
int? isVerify;
|
||||
int? status;
|
||||
String? reason;
|
||||
bool? submitLogout;
|
||||
UserDelInfoModel? userDelInfo;
|
||||
String? ip;
|
||||
UserIpRegionModel? ipRegion;
|
||||
int? isModifyName;
|
||||
List<Object>? data;
|
||||
int? amount;
|
||||
int? isSetPwd;
|
||||
List<UserPorfileBindModel>? bind;
|
||||
UserfeeInfo? userfeeinfo;
|
||||
String? userLevel;
|
||||
String? cookieVal;
|
||||
int? isBbsAdmin;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'nickname': nickname,
|
||||
'description': description,
|
||||
'birthday': birthday,
|
||||
'sex': sex,
|
||||
'cover': cover,
|
||||
'blood': blood,
|
||||
'constellation': constellation,
|
||||
'bind_phone': bindPhone,
|
||||
'email': email,
|
||||
'channel': channel,
|
||||
'channelid': channelid,
|
||||
'is_verify': isVerify,
|
||||
'status': status,
|
||||
'reason': reason,
|
||||
'submit_logout': submitLogout,
|
||||
'user_del_info': userDelInfo,
|
||||
'ip': ip,
|
||||
'ip_region': ipRegion,
|
||||
'is_modify_name': isModifyName,
|
||||
'data': data,
|
||||
'amount': amount,
|
||||
'is_set_pwd': isSetPwd,
|
||||
'bind': bind,
|
||||
'userFeeInfo': userfeeinfo,
|
||||
'user_level': userLevel,
|
||||
'cookie_val': cookieVal,
|
||||
'is_bbs_admin': isBbsAdmin,
|
||||
};
|
||||
}
|
||||
|
||||
class UserDelInfoModel {
|
||||
UserDelInfoModel({
|
||||
this.uid,
|
||||
this.logoutId,
|
||||
this.status,
|
||||
this.subTime,
|
||||
this.cancelTime,
|
||||
this.cancelUserType,
|
||||
this.currentTime,
|
||||
});
|
||||
|
||||
factory UserDelInfoModel.fromJson(Map<String, dynamic> json) =>
|
||||
UserDelInfoModel(
|
||||
uid: asT<int?>(json['uid']),
|
||||
logoutId: asT<int?>(json['logout_id']),
|
||||
status: asT<int?>(json['status']),
|
||||
subTime: asT<int?>(json['sub_time']),
|
||||
cancelTime: asT<int?>(json['cancel_time']),
|
||||
cancelUserType: asT<int?>(json['cancel_user_type']),
|
||||
currentTime: asT<int?>(json['current_time']),
|
||||
);
|
||||
|
||||
int? uid;
|
||||
int? logoutId;
|
||||
int? status;
|
||||
int? subTime;
|
||||
int? cancelTime;
|
||||
int? cancelUserType;
|
||||
int? currentTime;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'uid': uid,
|
||||
'logout_id': logoutId,
|
||||
'status': status,
|
||||
'sub_time': subTime,
|
||||
'cancel_time': cancelTime,
|
||||
'cancel_user_type': cancelUserType,
|
||||
'current_time': currentTime,
|
||||
};
|
||||
}
|
||||
|
||||
class UserIpRegionModel {
|
||||
UserIpRegionModel({
|
||||
this.country,
|
||||
this.province,
|
||||
this.city,
|
||||
this.provider,
|
||||
});
|
||||
|
||||
factory UserIpRegionModel.fromJson(Map<String, dynamic> json) =>
|
||||
UserIpRegionModel(
|
||||
country: asT<String?>(json['country']),
|
||||
province: asT<String?>(json['province']),
|
||||
city: asT<String?>(json['city']),
|
||||
provider: asT<String?>(json['provider']),
|
||||
);
|
||||
|
||||
String? country;
|
||||
String? province;
|
||||
String? city;
|
||||
String? provider;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'country': country,
|
||||
'province': province,
|
||||
'city': city,
|
||||
'provider': provider,
|
||||
};
|
||||
}
|
||||
|
||||
class UserPorfileBindModel {
|
||||
UserPorfileBindModel({
|
||||
this.type,
|
||||
this.name,
|
||||
});
|
||||
|
||||
factory UserPorfileBindModel.fromJson(Map<String, dynamic> json) =>
|
||||
UserPorfileBindModel(
|
||||
type: asT<String?>(json['type']),
|
||||
name: asT<String?>(json['name']),
|
||||
);
|
||||
|
||||
String? type;
|
||||
String? name;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'type': type,
|
||||
'name': name,
|
||||
};
|
||||
}
|
||||
|
||||
class UserfeeInfo {
|
||||
UserfeeInfo({
|
||||
this.mCate,
|
||||
this.mPeriod,
|
||||
});
|
||||
|
||||
factory UserfeeInfo.fromJson(Map<String, dynamic> json) => UserfeeInfo(
|
||||
mCate: asT<int?>(json['m_cate']),
|
||||
mPeriod: asT<int?>(json['m_period']),
|
||||
);
|
||||
|
||||
int? mCate;
|
||||
int? mPeriod;
|
||||
|
||||
bool get isVip => (mCate ?? 0) > 0;
|
||||
DateTime get expiresTime =>
|
||||
DateTime.fromMillisecondsSinceEpoch((mPeriod ?? 0) * 1000);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'm_cate': mCate,
|
||||
'm_period': mPeriod,
|
||||
};
|
||||
}
|
||||
41
lib/models/version_model.dart
Normal file
41
lib/models/version_model.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'dart:convert';
|
||||
|
||||
T? asT<T>(dynamic value) {
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class VersionModel {
|
||||
VersionModel({
|
||||
required this.version,
|
||||
required this.versionNum,
|
||||
required this.versionDesc,
|
||||
required this.downloadUrl,
|
||||
});
|
||||
|
||||
factory VersionModel.fromJson(Map<String, dynamic> json) => VersionModel(
|
||||
version: asT<String>(json['version'])!,
|
||||
versionNum: asT<int>(json['version_num'])!,
|
||||
versionDesc: asT<String>(json['version_desc'])!,
|
||||
downloadUrl: asT<String>(json['download_url'])!,
|
||||
);
|
||||
|
||||
String version;
|
||||
int versionNum;
|
||||
String versionDesc;
|
||||
String downloadUrl;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'version': version,
|
||||
'version_num': versionNum,
|
||||
'version_desc': versionDesc,
|
||||
'download_url': downloadUrl,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:flutter_dmzj/app/app_constant.dart';
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/models/comic/author_model.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_request.dart';
|
||||
import 'package:flutter_dmzj/services/user_service.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicAuthorDetailController extends BaseController {
|
||||
final int id;
|
||||
ComicAuthorDetailController(this.id);
|
||||
|
||||
final ComicRequest request = ComicRequest();
|
||||
|
||||
Rx<ComicAuthorModel?> detail = Rx<ComicAuthorModel?>(null);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
loadData();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
void loadData() async {
|
||||
try {
|
||||
pageLoadding.value = true;
|
||||
pageError.value = false;
|
||||
var result = await request.authorDetail(id: id);
|
||||
detail.value = result;
|
||||
} catch (e) {
|
||||
pageError.value = true;
|
||||
errorMsg.value = e.toString();
|
||||
} finally {
|
||||
pageLoadding.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void subscribeAll() {
|
||||
if (detail.value == null) {
|
||||
return;
|
||||
}
|
||||
UserService.instance.addSubscribe(
|
||||
detail.value!.data.map((e) => e.id).toList(),
|
||||
AppConstant.kTypeComic,
|
||||
);
|
||||
}
|
||||
}
|
||||
152
lib/modules/comic/author_detail/author_detail_page.dart
Normal file
152
lib/modules/comic/author_detail/author_detail_page.dart
Normal file
@@ -0,0 +1,152 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_constant.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/models/comic/author_model.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/author_detail/author_detail_controller.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/services/user_service.dart';
|
||||
import 'package:flutter_dmzj/widgets/net_image.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_error_widget.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_loadding_widget.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
class ComicAuthorDetailPage extends StatelessWidget {
|
||||
final int id;
|
||||
final ComicAuthorDetailController controller;
|
||||
ComicAuthorDetailPage(this.id, {super.key})
|
||||
: controller = Get.put(
|
||||
ComicAuthorDetailController(id),
|
||||
tag: "$id",
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
title: Obx(
|
||||
() => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
NetImage(
|
||||
controller.detail.value?.cover ?? "",
|
||||
borderRadius: 24,
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
Text(controller.detail.value?.nickname ?? "作者"),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton.icon(
|
||||
onPressed: controller.subscribeAll,
|
||||
icon: const Icon(Remix.heart_line),
|
||||
label: const Text("全部订阅"),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx(
|
||||
() => Stack(
|
||||
children: [
|
||||
Offstage(
|
||||
offstage: controller.detail.value == null,
|
||||
child: ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: controller.detail.value?.data.length ?? 0,
|
||||
separatorBuilder: (context, i) => Divider(
|
||||
endIndent: 12,
|
||||
indent: 12,
|
||||
color: Colors.grey.withOpacity(.2),
|
||||
height: 1,
|
||||
),
|
||||
itemBuilder: (_, i) {
|
||||
var item = controller.detail.value!.data[i];
|
||||
return buildItem(item);
|
||||
},
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageLoadding.value,
|
||||
child: const AppLoaddingWidget(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageError.value,
|
||||
child: AppErrorWidget(
|
||||
errorMsg: controller.errorMsg.value,
|
||||
onRefresh: () => controller.loadData(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildItem(ComicAuthorComicModel item) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
AppNavigator.toComicDetail(item.id);
|
||||
},
|
||||
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.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
AppStyle.vGap4,
|
||||
Text(item.status,
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Obx(
|
||||
() => UserService.instance.subscribedComicIds.contains(item.id)
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.favorite),
|
||||
onPressed: () {
|
||||
UserService.instance.cancelSubscribe(
|
||||
[item.id],
|
||||
AppConstant.kTypeComic,
|
||||
);
|
||||
},
|
||||
)
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.favorite_border),
|
||||
onPressed: () {
|
||||
UserService.instance.addSubscribe(
|
||||
[item.id],
|
||||
AppConstant.kTypeComic,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/models/comic/category_comic_model.dart';
|
||||
import 'package:flutter_dmzj/models/comic/category_filter_model.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_request.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class CategoryDetailController
|
||||
extends BasePageController<ComicCategoryComicModel> {
|
||||
final int id;
|
||||
CategoryDetailController(this.id);
|
||||
final ComicRequest request = ComicRequest();
|
||||
RxList<ComicCategoryFilterModel> filters = RxList<ComicCategoryFilterModel>();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
loadFilter();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
String getTitle() {
|
||||
var items = filters.where((x) => x.selectId.value != 0 && x.title != "排序");
|
||||
|
||||
if (items.isEmpty) {
|
||||
return "全部漫画";
|
||||
} else {
|
||||
return items
|
||||
.map((e) =>
|
||||
e.items.firstWhere((x) => x.tagId == e.selectId.value).tagName)
|
||||
.join("-");
|
||||
}
|
||||
}
|
||||
|
||||
void loadFilter() async {
|
||||
try {
|
||||
filters.value = await request.categoryFilter();
|
||||
for (var item in filters) {
|
||||
var tag = item.items.firstWhereOrNull((x) => x.tagId == id);
|
||||
if (tag != null) {
|
||||
item.selectId.value = tag.tagId;
|
||||
}
|
||||
}
|
||||
filters.insert(
|
||||
0,
|
||||
ComicCategoryFilterModel(
|
||||
title: "排序",
|
||||
items: [
|
||||
ComicCategoryFilterItemModel(tagId: 1, tagName: "更新排序"),
|
||||
ComicCategoryFilterItemModel(tagId: 2, tagName: "热度排序"),
|
||||
],
|
||||
)..selectId.value = 1,
|
||||
);
|
||||
filters.insert(
|
||||
1,
|
||||
ComicCategoryFilterModel(
|
||||
title: "状态",
|
||||
items: [
|
||||
ComicCategoryFilterItemModel(tagId: 0, tagName: "全部"),
|
||||
ComicCategoryFilterItemModel(tagId: 1, tagName: "连载中"),
|
||||
ComicCategoryFilterItemModel(tagId: 2, tagName: "已完结"),
|
||||
],
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ComicCategoryComicModel>> getData(int page, int pageSize) async {
|
||||
if (filters.isEmpty) {
|
||||
return await request.categoryComic(id: id, page: page);
|
||||
} else {
|
||||
var sort = filters.first.selectId.value;
|
||||
var status = filters[1].selectId.value;
|
||||
|
||||
return await request.categoryComic(
|
||||
id: filters.last.selectId.value,
|
||||
sort: sort,
|
||||
page: page,
|
||||
status: status,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
193
lib/modules/comic/category_detail/category_detail_page.dart
Normal file
193
lib/modules/comic/category_detail/category_detail_page.dart
Normal file
@@ -0,0 +1,193 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/category_detail/category_detail_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';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
class CategoryDetailPage extends StatelessWidget {
|
||||
final int id;
|
||||
final CategoryDetailController controller;
|
||||
CategoryDetailPage(this.id, {super.key})
|
||||
: controller = Get.put(
|
||||
CategoryDetailController(id),
|
||||
tag: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Obx(
|
||||
() => Text(
|
||||
controller.getTitle(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Builder(
|
||||
builder: (BuildContext context) => IconButton(
|
||||
icon: const Icon(Remix.filter_line),
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openEndDrawer();
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
endDrawer: Drawer(
|
||||
child: Obx(
|
||||
() => SafeArea(
|
||||
child: ListView.builder(
|
||||
padding: AppStyle.edgeInsetsA12.copyWith(top: 12),
|
||||
itemCount: controller.filters.length,
|
||||
itemBuilder: (context, i) {
|
||||
var item = controller.filters[i];
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsV12,
|
||||
child: Text(
|
||||
item.title,
|
||||
style: Get.textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: item.items
|
||||
.map(
|
||||
(x) => OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
foregroundColor: x.tagId == item.selectId.value
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.grey,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(
|
||||
color: x.tagId == item.selectId.value
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
x.tagName,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
item.selectId.value = x.tagId;
|
||||
|
||||
Navigator.pop(context);
|
||||
controller.refreshData();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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 ShadowCard(
|
||||
onTap: () {
|
||||
AppNavigator.toComicDetail(item.id);
|
||||
},
|
||||
radius: 4,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 27 / 36,
|
||||
child: NetImage(
|
||||
item.cover ?? "",
|
||||
borderRadius: 4,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: item.status == "连载中"
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.orange,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(4),
|
||||
bottomLeft: Radius.circular(4),
|
||||
),
|
||||
),
|
||||
padding:
|
||||
AppStyle.edgeInsetsH8.copyWith(top: 2, bottom: 2),
|
||||
child: Text(
|
||||
item.status ?? "",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
AppStyle.vGap4,
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsH4,
|
||||
child: Text(
|
||||
item.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
AppStyle.vGap4,
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsH4,
|
||||
child: Text(
|
||||
item.authors ?? "",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 12.0,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
AppStyle.vGap4,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
318
lib/modules/comic/detail/comic_detail_controller.dart
Normal file
318
lib/modules/comic/detail/comic_detail_controller.dart
Normal file
@@ -0,0 +1,318 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_dmzj/app/app_constant.dart';
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/app/event_bus.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
import 'package:flutter_dmzj/app/utils.dart';
|
||||
import 'package:flutter_dmzj/models/comic/detail_info.dart';
|
||||
import 'package:flutter_dmzj/models/db/comic_history.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/detail/comic_detail_related_page.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_request.dart';
|
||||
import 'package:flutter_dmzj/requests/user_request.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/services/app_settings_service.dart';
|
||||
import 'package:flutter_dmzj/services/db_service.dart';
|
||||
import 'package:flutter_dmzj/services/user_service.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicDetailControler extends BaseController {
|
||||
final int comicId;
|
||||
ComicDetailControler(this.comicId);
|
||||
|
||||
final ComicRequest request = ComicRequest();
|
||||
final UserRequest userRequest = UserRequest();
|
||||
|
||||
Rx<ComicDetailInfo> detail = Rx<ComicDetailInfo>(ComicDetailInfo.empty());
|
||||
|
||||
var expandDescription = false.obs;
|
||||
|
||||
/// 是否已订阅
|
||||
var subscribeStatus = false.obs;
|
||||
|
||||
/// 是否已收藏
|
||||
/// 收藏是收藏到本地的,订阅是同步到动漫之家服务器的
|
||||
var favorited = false.obs;
|
||||
|
||||
/// 阅读记录
|
||||
Rx<ComicHistory?> history = Rx<ComicHistory?>(null);
|
||||
|
||||
/// 更新漫画记录
|
||||
StreamSubscription<dynamic>? updateComicSubscription;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
updateComicSubscription = EventBus.instance.listen(
|
||||
EventBus.kUpdatedComicHistory,
|
||||
(id) {
|
||||
if (id == comicId) {
|
||||
getHistory();
|
||||
}
|
||||
},
|
||||
);
|
||||
favorited.value = DBService.instance.hasComicFavorited(comicId: comicId);
|
||||
// 从本地读取订阅状态
|
||||
subscribeStatus.value =
|
||||
UserService.instance.subscribedComicIds.contains(comicId);
|
||||
getHistory();
|
||||
loadDetail();
|
||||
loadSubscribeStatus();
|
||||
//updateSubscribeRead();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
void refreshDetail() {
|
||||
getHistory();
|
||||
loadDetail();
|
||||
loadSubscribeStatus();
|
||||
}
|
||||
|
||||
/// 更新订阅的阅读状态
|
||||
void updateSubscribeRead() {
|
||||
try {
|
||||
userRequest.subscribeRead(id: comicId, type: AppConstant.kTypeComic);
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
updateComicSubscription?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void getHistory() {
|
||||
var comicHistory = DBService.instance.getComicHistory(comicId);
|
||||
if (comicHistory != null) {
|
||||
history.value = comicHistory;
|
||||
history.update((val) {});
|
||||
}
|
||||
}
|
||||
|
||||
void refreshV1() async {
|
||||
try {
|
||||
var result =
|
||||
await request.comicDetail(comicId: comicId, priorityV1: true);
|
||||
if (result.volumes.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (result.isHide && AppSettingsService.instance.collectHideComic.value) {
|
||||
favorite();
|
||||
}
|
||||
detail.update((val) {
|
||||
val!.volumes = result.volumes;
|
||||
});
|
||||
} catch (e) {
|
||||
SmartDialog.showToast("无法获取章节");
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载信息
|
||||
void loadDetail() async {
|
||||
try {
|
||||
pageLoadding.value = true;
|
||||
pageError.value = false;
|
||||
var result = await request.comicDetail(comicId: comicId);
|
||||
detail.value = result;
|
||||
if (result.volumes.isEmpty && !result.isHide) {
|
||||
refreshV1();
|
||||
}
|
||||
if (result.isHide && AppSettingsService.instance.collectHideComic.value) {
|
||||
favorite();
|
||||
}
|
||||
} catch (e) {
|
||||
pageError.value = true;
|
||||
errorMsg.value = e.toString();
|
||||
} finally {
|
||||
pageLoadding.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查订阅状态
|
||||
void loadSubscribeStatus() async {
|
||||
try {
|
||||
var result = await userRequest.checkSubscribeStatus(
|
||||
objId: comicId,
|
||||
type: AppConstant.kTypeComic,
|
||||
);
|
||||
subscribeStatus.value = result;
|
||||
if (subscribeStatus.value) {
|
||||
UserService.instance.subscribedComicIds.add(comicId);
|
||||
} else {
|
||||
UserService.instance.subscribedComicIds.remove(comicId);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// 查看评论
|
||||
void comment() {
|
||||
AppNavigator.toComment(objId: comicId, type: AppConstant.kTypeComic);
|
||||
}
|
||||
|
||||
/// 分享
|
||||
void share() {
|
||||
if (detail.value.id == 0) {
|
||||
return;
|
||||
}
|
||||
Utils.share(
|
||||
"http://m.idmzj.com/info/${detail.value.comicPy}.html",
|
||||
content: detail.value.title,
|
||||
);
|
||||
}
|
||||
|
||||
/// 订阅
|
||||
void subscribe() async {
|
||||
var result = await (subscribeStatus.value
|
||||
? UserService.instance
|
||||
.cancelSubscribe([comicId], AppConstant.kTypeComic)
|
||||
: UserService.instance.addSubscribe([comicId], AppConstant.kTypeComic));
|
||||
if (result) {
|
||||
subscribeStatus.value = !subscribeStatus.value;
|
||||
}
|
||||
}
|
||||
|
||||
/// 下载
|
||||
void download() {
|
||||
AppNavigator.toComicDownloadSelect(comicId);
|
||||
}
|
||||
|
||||
/// 开始/继续阅读
|
||||
void read() {
|
||||
if (detail.value.volumes.isEmpty) {
|
||||
SmartDialog.showToast("没有可阅读的章节");
|
||||
return;
|
||||
}
|
||||
if (detail.value.volumes.first.chapters.isEmpty) {
|
||||
SmartDialog.showToast("没有可阅读的章节");
|
||||
return;
|
||||
}
|
||||
//查找记录
|
||||
if (history.value != null && history.value!.chapterId != 0) {
|
||||
ComicDetailVolume? volume;
|
||||
ComicDetailChapterItem? chapter;
|
||||
for (var volumeItem in detail.value.volumes) {
|
||||
var chapterItem = volumeItem.chapters.firstWhereOrNull(
|
||||
(x) => x.chapterId == history.value!.chapterId,
|
||||
);
|
||||
if (chapterItem != null) {
|
||||
volume = volumeItem;
|
||||
chapter = chapterItem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (volume != null && chapter != null) {
|
||||
var chapters = List<ComicDetailChapterItem>.from(volume.chapters);
|
||||
//正序
|
||||
chapters.sort((a, b) => a.chapterOrder.compareTo(b.chapterOrder));
|
||||
AppNavigator.toComicReader(
|
||||
comicId: comicId,
|
||||
comicTitle: detail.value.title,
|
||||
comicCover: detail.value.cover,
|
||||
chapters: chapters,
|
||||
chapter: chapter,
|
||||
isLongComic: detail.value.isLong,
|
||||
);
|
||||
} else {
|
||||
SmartDialog.showToast("未找到历史记录对应章节,将从头开始阅读");
|
||||
readStart();
|
||||
}
|
||||
} else {
|
||||
readStart();
|
||||
}
|
||||
}
|
||||
|
||||
void readStart() {
|
||||
//从头开始
|
||||
var volume = detail.value.volumes.first;
|
||||
var chapters = List<ComicDetailChapterItem>.from(volume.chapters);
|
||||
//正序
|
||||
chapters.sort((a, b) => a.chapterOrder.compareTo(b.chapterOrder));
|
||||
var chapter = chapters.first;
|
||||
AppNavigator.toComicReader(
|
||||
comicId: comicId,
|
||||
comicCover: detail.value.cover,
|
||||
comicTitle: detail.value.title,
|
||||
chapters: chapters,
|
||||
chapter: chapter,
|
||||
isLongComic: detail.value.isLong,
|
||||
);
|
||||
}
|
||||
|
||||
void readChapter(ComicDetailVolume volume, ComicDetailChapterItem item) {
|
||||
//禁止观看VIP章节
|
||||
if (item.isVip) {
|
||||
SmartDialog.showToast("请使用动漫之家官方APP观看VIP章节");
|
||||
return;
|
||||
}
|
||||
var chapters = List<ComicDetailChapterItem>.from(volume.chapters);
|
||||
//正序
|
||||
chapters.sort((a, b) => a.chapterOrder.compareTo(b.chapterOrder));
|
||||
AppNavigator.toComicReader(
|
||||
comicId: comicId,
|
||||
comicCover: detail.value.cover,
|
||||
comicTitle: detail.value.title,
|
||||
chapters: chapters,
|
||||
chapter: item,
|
||||
isLongComic: detail.value.isLong,
|
||||
);
|
||||
}
|
||||
|
||||
void related() async {
|
||||
try {
|
||||
SmartDialog.showLoading();
|
||||
var data = await request.related(id: comicId);
|
||||
SmartDialog.dismiss(status: SmartStatus.loading);
|
||||
AppNavigator.showBottomSheet(
|
||||
ComicDetailRelatedPage(data),
|
||||
isScrollControlled: true,
|
||||
);
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
} finally {
|
||||
SmartDialog.dismiss(status: SmartStatus.loading);
|
||||
}
|
||||
}
|
||||
|
||||
void toAuthorDetail(ComicDetailTag e) {
|
||||
if (e.tagId == 0) {
|
||||
//神隐漫画没有ID,直接跳转搜索
|
||||
AppNavigator.toComicSearch(keyword: e.tagName);
|
||||
} else {
|
||||
AppNavigator.toComicAuthorDetail(e.tagId);
|
||||
}
|
||||
}
|
||||
|
||||
void toCategoryDetail(ComicDetailTag e) {
|
||||
if (e.tagId == 0) {
|
||||
//神隐漫画没有ID,直接跳转搜索
|
||||
AppNavigator.toComicSearch(keyword: e.tagName);
|
||||
} else {
|
||||
AppNavigator.toComicCategoryDetail(e.tagId);
|
||||
}
|
||||
}
|
||||
|
||||
void favorite() {
|
||||
if (detail.value.id == 0) {
|
||||
return;
|
||||
}
|
||||
if (!DBService.instance.hasComicFavorited(comicId: comicId)) {
|
||||
DBService.instance.putComicFavorite(
|
||||
comicId: comicId,
|
||||
title: detail.value.title,
|
||||
cover: detail.value.cover,
|
||||
);
|
||||
favorited.value = true;
|
||||
SmartDialog.showToast("已将漫画添加至本地收藏");
|
||||
}
|
||||
}
|
||||
|
||||
void cancelFavorite() {
|
||||
DBService.instance.removeComicFavorite(comicId: comicId);
|
||||
favorited.value = false;
|
||||
SmartDialog.showToast("已从本地收藏删除漫画");
|
||||
}
|
||||
}
|
||||
533
lib/modules/comic/detail/comic_detail_page.dart
Normal file
533
lib/modules/comic/detail/comic_detail_page.dart
Normal file
@@ -0,0 +1,533 @@
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_color.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/app/utils.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/detail/comic_detail_controller.dart';
|
||||
import 'package:flutter_dmzj/widgets/net_image.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_error_widget.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_loadding_widget.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
class ComicDetailPage extends StatelessWidget {
|
||||
final int id;
|
||||
final ComicDetailControler controller;
|
||||
ComicDetailPage(this.id, {super.key})
|
||||
: controller = Get.put(
|
||||
ComicDetailControler(id),
|
||||
tag: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Obx(
|
||||
() => Text(
|
||||
controller.detail.value.title.isEmpty
|
||||
? "漫画详情"
|
||||
: controller.detail.value.title,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Obx(
|
||||
() => IconButton(
|
||||
onPressed: controller.favorited.value
|
||||
? controller.cancelFavorite
|
||||
: controller.favorite,
|
||||
icon: Icon(controller.favorited.value
|
||||
? Remix.star_fill
|
||||
: Remix.star_line),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: controller.share,
|
||||
icon: const Icon(Icons.share),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: controller.detail.value.id == 0,
|
||||
child: EasyRefresh(
|
||||
header: const MaterialHeader(),
|
||||
onRefresh: controller.refreshDetail,
|
||||
child: ListView(
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
children: [
|
||||
_buildHeader(),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: controller.history.value == null,
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
"上次看到:${controller.history.value?.chapterName ?? ""} 第${controller.history.value?.page}页",
|
||||
style: Get.textTheme.titleSmall,
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
controller.read();
|
||||
},
|
||||
),
|
||||
Divider(
|
||||
color: Colors.grey.withOpacity(.2),
|
||||
height: 1.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildChapter(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageLoadding.value,
|
||||
child: const AppLoaddingWidget(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageError.value,
|
||||
child: AppErrorWidget(
|
||||
errorMsg: controller.errorMsg.value,
|
||||
onRefresh: () => controller.loadDetail(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
elevation: 2,
|
||||
onPressed: controller.read,
|
||||
child: const Icon(Icons.play_circle_outline_rounded),
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
),
|
||||
onPressed: controller.subscribe,
|
||||
icon: Icon(
|
||||
controller.subscribeStatus.value
|
||||
? Remix.heart_fill
|
||||
: Remix.heart_line,
|
||||
size: 20,
|
||||
),
|
||||
label: Text(controller.subscribeStatus.value ? "取消" : "订阅"),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
),
|
||||
onPressed: controller.comment,
|
||||
icon: const Icon(
|
||||
Remix.chat_2_line,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text("评论"),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
),
|
||||
onPressed: controller.download,
|
||||
icon: const Icon(
|
||||
Remix.download_line,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text("下载"),
|
||||
),
|
||||
),
|
||||
// Expanded(
|
||||
// child: TextButton.icon(
|
||||
// style: TextButton.styleFrom(
|
||||
// textStyle: const TextStyle(fontSize: 14),
|
||||
// ),
|
||||
// onPressed: controller.related,
|
||||
// icon: const Icon(
|
||||
// Remix.links_line,
|
||||
// size: 20,
|
||||
// ),
|
||||
// label: const Text("相关"),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
//信息
|
||||
Stack(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
NetImage(
|
||||
controller.detail.value.cover,
|
||||
width: 120,
|
||||
height: 160,
|
||||
borderRadius: 4,
|
||||
),
|
||||
AppStyle.hGap12,
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
controller.detail.value.title,
|
||||
style: Get.textTheme.titleMedium,
|
||||
),
|
||||
AppStyle.vGap8,
|
||||
_buildInfoItems(
|
||||
iconData: Remix.user_smile_line,
|
||||
children: controller.detail.value.authors
|
||||
.map(
|
||||
(e) => GestureDetector(
|
||||
onTap: () => controller.toAuthorDetail(e),
|
||||
child: Text(
|
||||
e.tagName,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
height: 1.2,
|
||||
decoration: TextDecoration.underline,
|
||||
color: Get.isDarkMode
|
||||
? Colors.white
|
||||
: AppColor.black333,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
|
||||
// _buildInfo(
|
||||
// title: controller.detail.value.types
|
||||
// .map((e) => e.tagName)
|
||||
// .join("/"),
|
||||
// iconData: Remix.hashtag,
|
||||
// ),
|
||||
_buildInfoItems(
|
||||
iconData: Remix.hashtag,
|
||||
children: controller.detail.value.types
|
||||
.map(
|
||||
(e) => GestureDetector(
|
||||
onTap: () => controller.toCategoryDetail(e),
|
||||
child: Text(
|
||||
e.tagName,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
height: 1.2,
|
||||
decoration: TextDecoration.underline,
|
||||
color: Get.isDarkMode
|
||||
? Colors.white
|
||||
: AppColor.black333,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
// _buildInfo(
|
||||
// title: "人气 ${controller.detail.value.hitNum}",
|
||||
// iconData: Remix.fire_line,
|
||||
// ),
|
||||
// _buildInfo(
|
||||
// title: "订阅 ${controller.detail.value.subscribeNum}",
|
||||
// iconData: Remix.heart_line,
|
||||
// ),
|
||||
_buildInfo(
|
||||
title:
|
||||
"${Utils.formatTimestampToDate(controller.detail.value.lastUpdatetime)} ${controller.detail.value.status.map((e) => e.tagName).join("/")}",
|
||||
iconData: Icons.schedule,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Obx(
|
||||
() => Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: Offstage(
|
||||
offstage: !controller.detail.value.isVip,
|
||||
child: Image.asset(
|
||||
"assets/images/vip_comic.png",
|
||||
width: 36,
|
||||
height: 36,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
controller.expandDescription.value =
|
||||
!controller.expandDescription.value;
|
||||
},
|
||||
child: Text(
|
||||
controller.detail.value.description,
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLines: controller.expandDescription.value ? 999 : 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
Divider(
|
||||
color: Colors.grey.withOpacity(.2),
|
||||
height: 1.0,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChapter() {
|
||||
return Column(
|
||||
children: controller.detail.value.volumes.isEmpty
|
||||
? [
|
||||
const Padding(
|
||||
padding: AppStyle.edgeInsetsA24,
|
||||
child: Text(
|
||||
"(~ ̄▽ ̄)~\n没有可阅读的章节\n漫画可能已下架或您没有阅读的权限",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey, fontSize: 14),
|
||||
),
|
||||
)
|
||||
]
|
||||
: controller.detail.value.volumes
|
||||
.map(
|
||||
(item) => Obx(
|
||||
() => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsV8,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"${item.title}(共${item.chapters.length}话)",
|
||||
style: Get.textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
item.sortType.value == 1
|
||||
? TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () {
|
||||
item.sortType.value = 0;
|
||||
item.sort();
|
||||
},
|
||||
icon: const Icon(
|
||||
Remix.sort_asc,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text("升序"),
|
||||
)
|
||||
: TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () {
|
||||
item.sortType.value = 1;
|
||||
item.sort();
|
||||
},
|
||||
icon: const Icon(
|
||||
Remix.sort_desc,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text("倒序"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
LayoutBuilder(builder: (ctx, constraints) {
|
||||
var count = constraints.maxWidth ~/ 160;
|
||||
if (count < 3) count = 3;
|
||||
|
||||
return Obx(
|
||||
() => MasonryGridView.count(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount:
|
||||
(item.showMoreButton && !item.showAll.value)
|
||||
? 15
|
||||
: item.chapters.length,
|
||||
itemBuilder: (_, i) {
|
||||
if (item.showMoreButton &&
|
||||
!item.showAll.value &&
|
||||
i == 14) {
|
||||
return Tooltip(
|
||||
message: "展开全部章节",
|
||||
child: OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.grey,
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize: const Size.fromHeight(40),
|
||||
),
|
||||
onPressed: () {
|
||||
item.showAll.value = true;
|
||||
},
|
||||
child: const Icon(Icons.arrow_drop_down),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Tooltip(
|
||||
message: item.chapters[i].chapterTitle,
|
||||
child: Obx(
|
||||
() => Stack(
|
||||
children: [
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: item
|
||||
.chapters[i].chapterId ==
|
||||
controller
|
||||
.history.value?.chapterId
|
||||
? Theme.of(ctx).colorScheme.primary
|
||||
: Get.textTheme.bodyMedium!.color,
|
||||
textStyle:
|
||||
const TextStyle(fontSize: 14),
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize:
|
||||
const Size.fromHeight(40),
|
||||
),
|
||||
onPressed: () {
|
||||
controller.readChapter(
|
||||
item, item.chapters[i]);
|
||||
},
|
||||
child: Text(
|
||||
item.chapters[i].chapterTitle,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: -2,
|
||||
top: 0,
|
||||
child: Offstage(
|
||||
offstage: !item.chapters[i].isVip,
|
||||
child: Image.asset(
|
||||
"assets/images/vip_chapter.png",
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
crossAxisCount: count,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
),
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfo({
|
||||
required String title,
|
||||
IconData iconData = Icons.tag,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
iconData,
|
||||
color: Colors.grey,
|
||||
size: 16,
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Get.isDarkMode ? Colors.white : AppColor.black333,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoItems({
|
||||
required List<Widget> children,
|
||||
IconData iconData = Icons.tag,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
iconData,
|
||||
color: Colors.grey,
|
||||
size: 16,
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
164
lib/modules/comic/detail/comic_detail_related_page.dart
Normal file
164
lib/modules/comic/detail/comic_detail_related_page.dart
Normal file
@@ -0,0 +1,164 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/models/comic/comic_related_model.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/widgets/net_image.dart';
|
||||
import 'package:flutter_dmzj/widgets/shadow_card.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicDetailRelatedPage extends StatelessWidget {
|
||||
final ComicRelatedModel related;
|
||||
const ComicDetailRelatedPage(this.related, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text("作品相关"),
|
||||
trailing: IconButton(
|
||||
onPressed: () {
|
||||
AppNavigator.closePage();
|
||||
},
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
contentPadding: AppStyle.edgeInsetsL12,
|
||||
),
|
||||
const Divider(
|
||||
height: 1,
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: AppStyle.edgeInsetsA12.copyWith(top: 0),
|
||||
children: [
|
||||
...related.authorComics
|
||||
.map(
|
||||
(e) =>
|
||||
buildCard("${e.authorName}的其他作品", e.data, onTap: () {
|
||||
AppNavigator.toComicAuthorDetail(e.authorId);
|
||||
}),
|
||||
)
|
||||
.toList(),
|
||||
buildCard("同类题材作品", related.themeComics),
|
||||
buildCard("轻小说", related.novels, isComic: false),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildCard(String title, List<ComicRelatedItemModel> list,
|
||||
{Function()? onTap, bool isComic = true}) {
|
||||
return Visibility(
|
||||
visible: list.isNotEmpty,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsV8,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: Get.textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: onTap != null,
|
||||
child: IconButton(
|
||||
onPressed: onTap,
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
LayoutBuilder(builder: (ctx, constraints) {
|
||||
var count = constraints.maxWidth ~/ 160;
|
||||
if (count < 3) count = 3;
|
||||
|
||||
return MasonryGridView.count(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: list.length,
|
||||
crossAxisCount: count,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
itemBuilder: (_, i) {
|
||||
var item = list[i];
|
||||
return ShadowCard(
|
||||
onTap: () {
|
||||
if (isComic) {
|
||||
AppNavigator.toComicDetail(item.id);
|
||||
} else {
|
||||
AppNavigator.toNovelDetail(item.id);
|
||||
}
|
||||
},
|
||||
radius: 4,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 27 / 36,
|
||||
child: NetImage(
|
||||
item.cover,
|
||||
borderRadius: 4,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: item.status == "连载中"
|
||||
? Get.theme.colorScheme.primary
|
||||
: Colors.orange,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(4),
|
||||
bottomLeft: Radius.circular(4),
|
||||
),
|
||||
),
|
||||
padding: AppStyle.edgeInsetsH8
|
||||
.copyWith(top: 2, bottom: 2),
|
||||
child: Text(
|
||||
item.status,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
AppStyle.vGap4,
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsA4,
|
||||
child: Text(
|
||||
item.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/models/comic/category_item_model.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_request.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
|
||||
class ComicCategoryController
|
||||
extends BasePageController<ComicCategoryItemModel> {
|
||||
final ComicRequest request = ComicRequest();
|
||||
|
||||
@override
|
||||
Future<List<ComicCategoryItemModel>> getData(int page, int pageSize) async {
|
||||
if (page > 1) {
|
||||
return [];
|
||||
}
|
||||
var ls = await request.categores();
|
||||
|
||||
return ls;
|
||||
}
|
||||
|
||||
void toDetail(ComicCategoryItemModel item) {
|
||||
AppNavigator.toComicCategoryDetail(item.tagId);
|
||||
}
|
||||
}
|
||||
61
lib/modules/comic/home/category/comic_category_view.dart
Normal file
61
lib/modules/comic/home/category/comic_category_view.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/category/comic_category_controller.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 ComicCategoryView extends StatelessWidget {
|
||||
final ComicCategoryController controller;
|
||||
ComicCategoryView({Key? key})
|
||||
: controller = Get.put(ComicCategoryController()),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
var count = constraints.maxWidth ~/ 160;
|
||||
if (count < 3) count = 3;
|
||||
return KeepAliveWrapper(
|
||||
child: PageGridView(
|
||||
pageController: controller,
|
||||
firstRefresh: true,
|
||||
loadMore: false,
|
||||
crossAxisCount: count,
|
||||
padding: AppStyle.edgeInsetsH12.copyWith(bottom: 12),
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
itemBuilder: (context, i) {
|
||||
var item = controller.list[i];
|
||||
return ShadowCard(
|
||||
onTap: () {
|
||||
controller.toDetail(item);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: NetImage(
|
||||
item.cover,
|
||||
borderRadius: 8,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsA8,
|
||||
child: Text(
|
||||
item.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(height: 1),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
57
lib/modules/comic/home/comic_home_controller.dart
Normal file
57
lib/modules/comic/home/comic_home_controller.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/app/event_bus.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/category/comic_category_controller.dart';
|
||||
//import 'package:flutter_dmzj/modules/comic/home/category/comic_category_controller.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/latest/comic_latest_controller.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/rank/comic_rank_controller.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/recommend/comic_recommend_controller.dart';
|
||||
//import 'package:flutter_dmzj/modules/comic/home/special/comic_special_controller.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicHomeController extends GetxController
|
||||
with GetTickerProviderStateMixin {
|
||||
late TabController tabController;
|
||||
|
||||
StreamSubscription<dynamic>? streamSubscription;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
streamSubscription = EventBus.instance.listen(
|
||||
EventBus.kBottomNavigationBarClicked,
|
||||
(index) {
|
||||
if (index == 0) {
|
||||
refreshOrScrollTop();
|
||||
}
|
||||
},
|
||||
);
|
||||
tabController = TabController(length: 4, vsync: this);
|
||||
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
void refreshOrScrollTop() {
|
||||
var tabIndex = tabController.index;
|
||||
BasePageController? controller;
|
||||
if (tabIndex == 0) {
|
||||
controller = Get.find<ComicRecommendController>();
|
||||
} else if (tabIndex == 1) {
|
||||
controller = Get.find<ComicLatestController>();
|
||||
} else if (tabIndex == 2) {
|
||||
controller = Get.find<ComicCategoryController>();
|
||||
} else if (tabIndex == 3) {
|
||||
controller = Get.find<ComicRankController>();
|
||||
}
|
||||
// else if (tabIndex == 4) {
|
||||
// controller = Get.find<ComicSpecialController>();
|
||||
// }
|
||||
controller?.scrollToTopOrRefresh();
|
||||
}
|
||||
|
||||
void search() {
|
||||
AppNavigator.toComicSearch();
|
||||
}
|
||||
}
|
||||
62
lib/modules/comic/home/comic_home_page.dart
Normal file
62
lib/modules/comic/home/comic_home_page.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/platform_utils.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/category/comic_category_view.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/comic_home_controller.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/latest/comic_latest_view.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/rank/comic_rank_view.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/recommend/comic_recommend_view.dart';
|
||||
//import 'package:flutter_dmzj/modules/comic/home/special/comic_special_view.dart';
|
||||
import 'package:flutter_dmzj/widgets/tab_appbar.dart';
|
||||
import 'package:flutter_dmzj/widgets/windows_tab_page.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicHomePage extends GetView<ComicHomeController> {
|
||||
const ComicHomePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (PlatformUtils.isWindows) {
|
||||
return WindowsTabPage(
|
||||
tabs: [
|
||||
WindowsTabItem(label: '推荐', body: ComicRecommendView()),
|
||||
WindowsTabItem(label: '更新', body: ComicLatestView()),
|
||||
WindowsTabItem(label: '分类', body: ComicCategoryView()),
|
||||
WindowsTabItem(label: '排行', body: ComicRankView()),
|
||||
],
|
||||
headerAction: IconButton(
|
||||
onPressed: controller.search,
|
||||
icon: const Icon(Icons.search),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: TabAppBar(
|
||||
tabs: const [
|
||||
Tab(text: "推荐"),
|
||||
Tab(text: "更新"),
|
||||
Tab(text: "分类"),
|
||||
Tab(text: "排行"),
|
||||
// Tab(text: "专题"),
|
||||
],
|
||||
controller: controller.tabController,
|
||||
action: IconButton(
|
||||
onPressed: controller.search,
|
||||
icon: const Icon(
|
||||
Icons.search,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: controller.tabController,
|
||||
children: [
|
||||
ComicRecommendView(),
|
||||
ComicLatestView(),
|
||||
ComicCategoryView(),
|
||||
ComicRankView(),
|
||||
//ComicSpecialView(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
21
lib/modules/comic/home/latest/comic_latest_controller.dart
Normal file
21
lib/modules/comic/home/latest/comic_latest_controller.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/models/comic/update_item_model.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_request.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicLatestController extends BasePageController<ComicUpdateItemModel> {
|
||||
final ComicRequest request = ComicRequest();
|
||||
Map types = {
|
||||
"全部漫画": 100,
|
||||
"原创漫画": 1,
|
||||
"译制漫画": 0,
|
||||
};
|
||||
var type = 100.obs;
|
||||
|
||||
@override
|
||||
Future<List<ComicUpdateItemModel>> getData(int page, int pageSize) async {
|
||||
var ls = await request.latest(type: type.value, page: page);
|
||||
|
||||
return ls;
|
||||
}
|
||||
}
|
||||
176
lib/modules/comic/home/latest/comic_latest_view.dart
Normal file
176
lib/modules/comic/home/latest/comic_latest_view.dart
Normal file
@@ -0,0 +1,176 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_constant.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/app/utils.dart';
|
||||
import 'package:flutter_dmzj/models/comic/update_item_model.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/latest/comic_latest_controller.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/services/user_service.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 ComicLatestView extends StatelessWidget {
|
||||
final ComicLatestController controller;
|
||||
ComicLatestView({Key? key})
|
||||
: controller = Get.put(ComicLatestController()),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KeepAliveWrapper(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
AppStyle.hGap12,
|
||||
...controller.types.keys.map(
|
||||
(e) => buildFilterButton(
|
||||
title: e,
|
||||
value: controller.types[e],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
Expanded(
|
||||
child: PageListView(
|
||||
pageController: controller,
|
||||
firstRefresh: true,
|
||||
showPageLoadding: 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(ComicUpdateItemModel item) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
AppNavigator.toComicDetail(item.comicId.toInt());
|
||||
},
|
||||
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.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text.rich(
|
||||
TextSpan(children: [
|
||||
const WidgetSpan(
|
||||
child: Icon(
|
||||
Icons.account_circle,
|
||||
color: Colors.grey,
|
||||
size: 18,
|
||||
)),
|
||||
const TextSpan(
|
||||
text: " ",
|
||||
),
|
||||
TextSpan(
|
||||
text: item.authors,
|
||||
style:
|
||||
const TextStyle(color: Colors.grey, fontSize: 14))
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(item.types ?? '',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 14)),
|
||||
const SizedBox(height: 2),
|
||||
Text(item.lastUpdateChapterName ?? '',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 14)),
|
||||
const SizedBox(height: 2),
|
||||
Text("更新于${Utils.formatTimestamp(item.lastUpdatetime ?? 0)}",
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Obx(
|
||||
() => UserService.instance.subscribedComicIds
|
||||
.contains(item.comicId.toInt())
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.favorite),
|
||||
onPressed: () {
|
||||
UserService.instance.cancelSubscribe(
|
||||
[item.comicId.toInt()],
|
||||
AppConstant.kTypeComic,
|
||||
);
|
||||
},
|
||||
)
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.favorite_border),
|
||||
onPressed: () {
|
||||
UserService.instance.addSubscribe(
|
||||
[item.comicId.toInt()],
|
||||
AppConstant.kTypeComic,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildFilterButton({required String title, required int value}) {
|
||||
return Container(
|
||||
height: 32,
|
||||
margin: AppStyle.edgeInsetsR8,
|
||||
child: Obx(
|
||||
() => TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor:
|
||||
controller.type.value == value ? Get.theme.colorScheme.primary : Colors.grey,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: AppStyle.radius24,
|
||||
side: BorderSide(
|
||||
color:
|
||||
controller.type.value == value ? Get.theme.colorScheme.primary : Colors.grey,
|
||||
),
|
||||
),
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
padding: AppStyle.edgeInsetsH16,
|
||||
),
|
||||
onPressed: () {
|
||||
if (controller.type.value == value) {
|
||||
return;
|
||||
}
|
||||
controller.type.value = value;
|
||||
controller.refreshData();
|
||||
},
|
||||
child: Text(title),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
54
lib/modules/comic/home/rank/comic_rank_controller.dart
Normal file
54
lib/modules/comic/home/rank/comic_rank_controller.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/models/comic/rank_item_model.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_request.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicRankController extends BasePageController<ComicRankListItemModel> {
|
||||
final ComicRequest request = ComicRequest();
|
||||
RxMap<int, String> tags = {
|
||||
0: "全部分类",
|
||||
}.obs;
|
||||
var tag = 0.obs;
|
||||
|
||||
Map<int, String> byTimes = {
|
||||
0: "日排行",
|
||||
1: "周排行",
|
||||
2: "月排行",
|
||||
3: "总排行",
|
||||
};
|
||||
var byTime = 3.obs;
|
||||
|
||||
Map<int, String> rankTypes = {
|
||||
0: "人气排行",
|
||||
1: "吐槽排行",
|
||||
2: "订阅排行",
|
||||
};
|
||||
var rankType = 0.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
loadFilter();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
void loadFilter() async {
|
||||
try {
|
||||
tags.value = await request.rankFilter();
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ComicRankListItemModel>> getData(int page, int pageSize) async {
|
||||
var ls = await request.rank(
|
||||
tagId: tag.value,
|
||||
byTime: byTime.value,
|
||||
rankType: rankType.value,
|
||||
page: page,
|
||||
);
|
||||
|
||||
return ls;
|
||||
}
|
||||
}
|
||||
200
lib/modules/comic/home/rank/comic_rank_view.dart
Normal file
200
lib/modules/comic/home/rank/comic_rank_view.dart
Normal file
@@ -0,0 +1,200 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_constant.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/app/utils.dart';
|
||||
import 'package:flutter_dmzj/models/comic/rank_item_model.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/rank/comic_rank_controller.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/services/user_service.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 ComicRankView extends StatelessWidget {
|
||||
final ComicRankController controller;
|
||||
ComicRankView({Key? key})
|
||||
: controller = Get.put(ComicRankController()),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KeepAliveWrapper(
|
||||
child: Column(
|
||||
children: [
|
||||
Obx(
|
||||
() => Row(
|
||||
children: [
|
||||
buildFilter(
|
||||
// ignore: invalid_use_of_protected_member
|
||||
types: controller.tags.value,
|
||||
value: controller.tag.value,
|
||||
onSelected: (e) {
|
||||
controller.tag.value = e;
|
||||
controller.refreshData();
|
||||
},
|
||||
),
|
||||
buildFilter(
|
||||
types: controller.byTimes,
|
||||
value: controller.byTime.value,
|
||||
onSelected: (e) {
|
||||
controller.byTime.value = e;
|
||||
controller.refreshData();
|
||||
},
|
||||
),
|
||||
buildFilter(
|
||||
types: controller.rankTypes,
|
||||
value: controller.rankType.value,
|
||||
onSelected: (e) {
|
||||
controller.rankType.value = e;
|
||||
controller.refreshData();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
Expanded(
|
||||
child: PageListView(
|
||||
pageController: controller,
|
||||
firstRefresh: true,
|
||||
showPageLoadding: 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 buildFilter({
|
||||
required Map<int, String> types,
|
||||
required int value,
|
||||
required Function(int) onSelected,
|
||||
}) {
|
||||
return Expanded(
|
||||
child: PopupMenuButton<int>(
|
||||
onSelected: onSelected,
|
||||
itemBuilder: (c) => types.keys
|
||||
.map(
|
||||
(k) => CheckedPopupMenuItem<int>(
|
||||
value: k,
|
||||
checked: k == value,
|
||||
child: Text(types[k] ?? ""),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
types[value] ?? "",
|
||||
),
|
||||
const Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: Colors.grey,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildItem(ComicRankListItemModel item) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
AppNavigator.toComicDetail(item.comicId.toInt());
|
||||
},
|
||||
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.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text.rich(
|
||||
TextSpan(children: [
|
||||
const WidgetSpan(
|
||||
child: Icon(
|
||||
Icons.account_circle,
|
||||
color: Colors.grey,
|
||||
size: 18,
|
||||
)),
|
||||
const TextSpan(
|
||||
text: " ",
|
||||
),
|
||||
TextSpan(
|
||||
text: item.authors,
|
||||
style:
|
||||
const TextStyle(color: Colors.grey, fontSize: 14))
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(item.types ?? '-',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 14)),
|
||||
const SizedBox(height: 2),
|
||||
Text(item.lastUpdateChapterName ?? '-',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 14)),
|
||||
const SizedBox(height: 2),
|
||||
Text("更新于${Utils.formatTimestamp(item.lastUpdatetime ?? 0)}",
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Obx(
|
||||
() => UserService.instance.subscribedComicIds
|
||||
.contains(item.comicId.toInt())
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.favorite),
|
||||
onPressed: () {
|
||||
UserService.instance.cancelSubscribe(
|
||||
[item.comicId.toInt()],
|
||||
AppConstant.kTypeComic,
|
||||
);
|
||||
},
|
||||
)
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.favorite_border),
|
||||
onPressed: () {
|
||||
UserService.instance.addSubscribe(
|
||||
[item.comicId.toInt()],
|
||||
AppConstant.kTypeComic,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
188
lib/modules/comic/home/recommend/comic_recommend_controller.dart
Normal file
188
lib/modules/comic/home/recommend/comic_recommend_controller.dart
Normal file
@@ -0,0 +1,188 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
import 'package:flutter_dmzj/models/comic/recommend_model.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/comic_home_controller.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_request.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/services/user_service.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class ComicRecommendController extends BasePageController<ComicRecommendModel> {
|
||||
final ComicRequest request = ComicRequest();
|
||||
StreamSubscription<dynamic>? subLogin;
|
||||
StreamSubscription<dynamic>? subLogout;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
subLogin = UserService.loginedStream.listen((event) {
|
||||
loadSubscribe();
|
||||
});
|
||||
subLogout = UserService.logoutStream.listen((event) {
|
||||
list.removeWhere((x) => x.categoryId == 49);
|
||||
});
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ComicRecommendModel>> getData(int page, int pageSize) async {
|
||||
var ls = await request.recommend();
|
||||
|
||||
// ls.insert(
|
||||
// ls.length > 3 ? 2 : 1,
|
||||
// ComicRecommendModel(
|
||||
// categoryId: 50,
|
||||
// title: "随便看看",
|
||||
// sort: 6,
|
||||
// data: [],
|
||||
// ),
|
||||
// );
|
||||
//loadRandom();
|
||||
if (UserService.instance.logined.value) {
|
||||
loadSubscribe();
|
||||
}
|
||||
return ls;
|
||||
}
|
||||
|
||||
// /// 加载随机漫画
|
||||
// Future<void> loadRandom() async {
|
||||
// try {
|
||||
// var result = await request.refreshRecommend(50);
|
||||
// var index = list.indexWhere((x) => x.categoryId == 50);
|
||||
// if (index != -1) {
|
||||
// list[index].data = result;
|
||||
// } else {
|
||||
// list.insert(
|
||||
// list.length > 3 ? 2 : 1,
|
||||
// result,
|
||||
// );
|
||||
// }
|
||||
// } catch (e) {
|
||||
// Log.logPrint(e);
|
||||
// }
|
||||
// }
|
||||
|
||||
/// 刷新国漫
|
||||
Future<void> refreshGuoman() async {
|
||||
try {
|
||||
var index = list.indexWhere((x) => x.categoryId == 111);
|
||||
var result =
|
||||
await request.refreshRecommend(111, size: 6, page: list[index].page);
|
||||
|
||||
if (index != -1) {
|
||||
list[index].data = result;
|
||||
list[index].page++;
|
||||
list.refresh();
|
||||
}
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// 刷新近期必看
|
||||
Future<void> refreshRecommend() async {
|
||||
try {
|
||||
var index = list.indexWhere((x) => x.categoryId == 110);
|
||||
|
||||
var result = await request.refreshRecommend(110, page: list[index].page);
|
||||
|
||||
if (index != -1) {
|
||||
list[index].data = result;
|
||||
list[index].page++;
|
||||
list.refresh();
|
||||
}
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载订阅
|
||||
void loadSubscribe() async {
|
||||
try {
|
||||
var result = await request.recommendSubscribe();
|
||||
var index = list.indexWhere((x) => x.categoryId == 49);
|
||||
if (index != -1) {
|
||||
list[index] = result;
|
||||
} else {
|
||||
list.insert(1, result);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// 刷新热门连载
|
||||
Future<void> refreshHot() async {
|
||||
try {
|
||||
var index = list.indexWhere((x) => x.categoryId == 112);
|
||||
var result =
|
||||
await request.refreshRecommend(112, page: list[index].page, size: 6);
|
||||
|
||||
if (index != -1) {
|
||||
list[index].data = result;
|
||||
list[index].page++;
|
||||
list.refresh();
|
||||
}
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
}
|
||||
}
|
||||
|
||||
void openDetail(ComicRecommendItemModel item) {
|
||||
//漫画=1
|
||||
if (item.type == null || item.type == 1) {
|
||||
AppNavigator.toComicDetail(
|
||||
item.objId ?? item.id ?? 0,
|
||||
);
|
||||
} else if (item.type == 5) {
|
||||
//专题=5
|
||||
AppNavigator.toSpecialDetail(
|
||||
item.objId ?? 0,
|
||||
);
|
||||
} else if (item.type == 6) {
|
||||
//网页=6
|
||||
AppNavigator.toWebView(item.url ?? "");
|
||||
} else if (item.type == 7) {
|
||||
//新闻=7
|
||||
AppNavigator.toNewsDetail(
|
||||
url: item.url ?? "",
|
||||
newsId: item.objId ?? 0,
|
||||
title: item.title,
|
||||
);
|
||||
} else if (item.type == 8) {
|
||||
//作者=8
|
||||
AppNavigator.toComicAuthorDetail(item.objId ?? 0);
|
||||
} else if (item.type == 13) {
|
||||
//社区=13
|
||||
//直接跳转至网页
|
||||
launchUrlString(
|
||||
"http://m.forum.idmzj.com/thread/detail?tid=${item.objId}",
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
// AppNavigator.toWebView(
|
||||
// "http://m.forum.dmzj.com/thread/detail?tid=${item.objId}",
|
||||
// );
|
||||
} else {
|
||||
SmartDialog.showToast("未知类型,无法跳转");
|
||||
}
|
||||
}
|
||||
|
||||
void toSpecial() {
|
||||
var homeController = Get.find<ComicHomeController>();
|
||||
homeController.tabController.animateTo(4);
|
||||
}
|
||||
|
||||
void toMySubscribe() {
|
||||
AppNavigator.toUserSubscribe();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
subLogin?.cancel();
|
||||
subLogout?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
372
lib/modules/comic/home/recommend/comic_recommend_view.dart
Normal file
372
lib/modules/comic/home/recommend/comic_recommend_view.dart
Normal file
@@ -0,0 +1,372 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/models/comic/recommend_model.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/home/recommend/comic_recommend_controller.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:flutter_dmzj/widgets/refresh_until_widget.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:flutter_swiper_view/flutter_swiper_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicRecommendView extends StatelessWidget {
|
||||
final ComicRecommendController controller;
|
||||
ComicRecommendView({Key? key})
|
||||
: controller = Get.put(ComicRecommendController()),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KeepAliveWrapper(
|
||||
child: PageListView(
|
||||
pageController: controller,
|
||||
padding: AppStyle.edgeInsetsH12,
|
||||
firstRefresh: true,
|
||||
loadMore: false,
|
||||
showPageLoadding: true,
|
||||
itemBuilder: (context, i) {
|
||||
var item = controller.list[i];
|
||||
//大图推荐
|
||||
if (item.categoryId == 109) {
|
||||
return buildBanner(item);
|
||||
}
|
||||
//随便看看
|
||||
// if (item.categoryId == 50) {
|
||||
// return buildCard(
|
||||
// context,
|
||||
// child: buildTreeColumnGridView(item.data),
|
||||
// title: item.title.toString(),
|
||||
// action: buildRefresh(onRefresh: controller.loadRandom),
|
||||
// );
|
||||
// }
|
||||
//我的订阅
|
||||
if (item.categoryId == 49) {
|
||||
return buildCard(
|
||||
context,
|
||||
child: buildTreeColumnGridView(item.data),
|
||||
title: item.title.toString(),
|
||||
action: buildShowMore(onTap: controller.toMySubscribe),
|
||||
);
|
||||
}
|
||||
//近期必看\国漫\热门连载\最新上架
|
||||
if (item.categoryId == 110 ||
|
||||
item.categoryId == 111 ||
|
||||
item.categoryId == 112 ||
|
||||
item.categoryId == 56) {
|
||||
Widget? action;
|
||||
//刷新国漫
|
||||
if (item.categoryId == 110) {
|
||||
action = buildRefresh(onRefresh: controller.refreshRecommend);
|
||||
}
|
||||
if (item.categoryId == 111) {
|
||||
action = buildRefresh(onRefresh: controller.refreshGuoman);
|
||||
}
|
||||
if (item.categoryId == 112) {
|
||||
action = buildRefresh(onRefresh: controller.refreshHot);
|
||||
}
|
||||
return buildCard(
|
||||
context,
|
||||
child: buildTreeColumnGridView(item.data),
|
||||
title: item.title.toString(),
|
||||
action: action,
|
||||
);
|
||||
}
|
||||
//火热专题\美漫大事件\条漫
|
||||
if (item.categoryId == 48 ||
|
||||
item.categoryId == 53 ||
|
||||
item.categoryId == 55) {
|
||||
return buildCard(
|
||||
context,
|
||||
child: buildTwoColumnGridView(item.data),
|
||||
title: item.title.toString(),
|
||||
action: item.categoryId == 48
|
||||
? buildShowMore(onTap: controller.toSpecial)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
//大师
|
||||
if (item.categoryId == 51) {
|
||||
return buildCard(
|
||||
context,
|
||||
child: buildAuthorGridView(item.data),
|
||||
title: item.title.toString(),
|
||||
);
|
||||
}
|
||||
return buildCard(
|
||||
context,
|
||||
child: buildTreeColumnGridView(item.data),
|
||||
title: item.title.toString(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildCard(
|
||||
BuildContext context, {
|
||||
required Widget child,
|
||||
required String title,
|
||||
Widget? action,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: AppStyle.edgeInsetsB8,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: AppStyle.radius8,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16, height: 1.0, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: action,
|
||||
),
|
||||
],
|
||||
),
|
||||
child,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildShowMore({required Function() onTap}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
"查看更多",
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
),
|
||||
Icon(Icons.chevron_right, size: 18, color: Colors.grey),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildRefresh({required Future Function() onRefresh}) {
|
||||
return RefreshUntilWidget(onRefresh: onRefresh, text: "换一批");
|
||||
}
|
||||
|
||||
Widget buildBanner(ComicRecommendModel item) {
|
||||
return Padding(
|
||||
padding: AppStyle.edgeInsetsB12,
|
||||
child: ClipRRect(
|
||||
borderRadius: AppStyle.radius4,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 75 / 40,
|
||||
child: Swiper(
|
||||
itemWidth: 750,
|
||||
itemHeight: 400,
|
||||
autoplay: true,
|
||||
itemCount: item.data.length,
|
||||
itemBuilder: (_, i) => NetImage(
|
||||
item.data[i].cover,
|
||||
width: 750,
|
||||
height: 400,
|
||||
),
|
||||
onTap: (i) {
|
||||
controller.openDetail(item.data[i]);
|
||||
},
|
||||
pagination: SwiperCustomPagination(
|
||||
builder: (BuildContext context, SwiperPluginConfig config) {
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8,
|
||||
right: 12,
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
),
|
||||
//color: Colors.black12,
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
colors: [
|
||||
Colors.black38,
|
||||
Colors.transparent,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.data[config.activeIndex].title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 14, color: Colors.white),
|
||||
),
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
PageIndicator(
|
||||
controller: config.pageController!,
|
||||
count: config.itemCount,
|
||||
size: 10,
|
||||
layout: PageIndicatorLayout.SCALE,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildTreeColumnGridView(List<ComicRecommendItemModel> items) {
|
||||
return MasonryGridView.count(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (_, i) {
|
||||
var item = items[i];
|
||||
return InkWell(
|
||||
onTap: () => controller.openDetail(item),
|
||||
borderRadius: AppStyle.radius4,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: AppStyle.radius4,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 27 / 36,
|
||||
child: NetImage(
|
||||
item.cover,
|
||||
width: 270,
|
||||
height: 360,
|
||||
),
|
||||
),
|
||||
),
|
||||
AppStyle.vGap8,
|
||||
Text(
|
||||
item.title,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(height: 1.2),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
item.subTitle ?? item.status ?? '',
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
height: 1.2,
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
AppStyle.vGap8,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildAuthorGridView(List<ComicRecommendItemModel> items) {
|
||||
return MasonryGridView.count(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (_, i) {
|
||||
var item = items[i];
|
||||
return InkWell(
|
||||
onTap: () => controller.openDetail(item),
|
||||
borderRadius: AppStyle.radius8,
|
||||
child: Padding(
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
NetImage(
|
||||
item.cover,
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 32,
|
||||
),
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsV8,
|
||||
child: Text(
|
||||
item.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(height: 1.2, fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildTwoColumnGridView(List<ComicRecommendItemModel> items) {
|
||||
return MasonryGridView.count(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (_, i) {
|
||||
var item = items[i];
|
||||
return InkWell(
|
||||
onTap: () => controller.openDetail(item),
|
||||
borderRadius: AppStyle.radius4,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: AppStyle.radius4,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 32 / 17,
|
||||
child: NetImage(
|
||||
item.cover,
|
||||
width: 320,
|
||||
height: 170,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsV8,
|
||||
child: Text(
|
||||
item.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(height: 1.2),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
23
lib/modules/comic/home/special/comic_special_controller.dart
Normal file
23
lib/modules/comic/home/special/comic_special_controller.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/models/comic/special_model.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_request.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
|
||||
class ComicSpecialController extends BasePageController<ComicSpecialModel> {
|
||||
final ComicRequest request = ComicRequest();
|
||||
|
||||
@override
|
||||
Future<List<ComicSpecialModel>> getData(int page, int pageSize) async {
|
||||
var ls = await request.special(page: page - 1);
|
||||
|
||||
return ls;
|
||||
}
|
||||
|
||||
void toDetail(ComicSpecialModel item) {
|
||||
if (item.pageType == 3) {
|
||||
AppNavigator.toSpecialDetail(item.id);
|
||||
} else {
|
||||
AppNavigator.toWebView(item.pageUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
lib/modules/comic/home/special/comic_special_view.dart
Normal file
65
lib/modules/comic/home/special/comic_special_view.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
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/comic/home/special/comic_special_controller.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:flutter_dmzj/widgets/shadow_card.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicSpecialView extends StatelessWidget {
|
||||
final ComicSpecialController controller;
|
||||
ComicSpecialView({Key? key})
|
||||
: controller = Get.put(ComicSpecialController()),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KeepAliveWrapper(
|
||||
child: PageListView(
|
||||
pageController: controller,
|
||||
firstRefresh: true,
|
||||
showPageLoadding: false,
|
||||
padding: AppStyle.edgeInsetsA12.copyWith(top: 0),
|
||||
separatorBuilder: (context, i) => AppStyle.vGap12,
|
||||
itemBuilder: (context, i) {
|
||||
var item = controller.list[i];
|
||||
return ShadowCard(
|
||||
onTap: () {
|
||||
controller.toDetail(item);
|
||||
},
|
||||
radius: 8,
|
||||
child: Column(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 710 / 284,
|
||||
child: NetImage(
|
||||
item.smallCover,
|
||||
width: 710,
|
||||
height: 354,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsA8,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(item.title)),
|
||||
Text(
|
||||
Utils.formatTimestampToDate(item.createTime),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
843
lib/modules/comic/reader/comic_reader_controller.dart
Normal file
843
lib/modules/comic/reader/comic_reader_controller.dart
Normal file
@@ -0,0 +1,843 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:battery_plus/battery_plus.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dmzj/app/app_constant.dart';
|
||||
import 'package:flutter_dmzj/app/app_error.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/app/utils.dart';
|
||||
import 'package:flutter_dmzj/services/app_settings_service.dart';
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
import 'package:flutter_dmzj/models/comic/chapter_info.dart';
|
||||
import 'package:flutter_dmzj/models/comic/detail_info.dart';
|
||||
import 'package:flutter_dmzj/models/comic/view_point_model.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_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';
|
||||
import 'package:preload_page_view/preload_page_view.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
class ComicReaderController extends BaseController {
|
||||
/// 是否为条漫
|
||||
final bool isLongComic;
|
||||
final int comicId;
|
||||
final String comicTitle;
|
||||
final String comicCover;
|
||||
final ComicDetailChapterItem chapter;
|
||||
final List<ComicDetailChapterItem> chapters;
|
||||
final FocusNode focusNode = FocusNode();
|
||||
final ComicRequest request = ComicRequest();
|
||||
ComicReaderController({
|
||||
required this.comicId,
|
||||
required this.comicTitle,
|
||||
required this.chapters,
|
||||
required this.chapter,
|
||||
required this.comicCover,
|
||||
required this.isLongComic,
|
||||
}) {
|
||||
chapterIndex.value = chapters.indexOf(chapter);
|
||||
}
|
||||
|
||||
/// APP设置控制器
|
||||
final settings = AppSettingsService.instance;
|
||||
|
||||
/// 预加载控制器
|
||||
final PreloadPageController preloadPageController = PreloadPageController();
|
||||
|
||||
/// 上下模式控制器
|
||||
final ItemScrollController itemScrollController = ItemScrollController();
|
||||
|
||||
/// 监听上下滚动
|
||||
final ItemPositionsListener itemPositionsListener =
|
||||
ItemPositionsListener.create();
|
||||
|
||||
/// 章节详情
|
||||
Rx<ComicChapterDetail> detail =
|
||||
Rx<ComicChapterDetail>(ComicChapterDetail.empty());
|
||||
|
||||
/// 连接信息监听
|
||||
StreamSubscription<ConnectivityResult>? connectivitySubscription;
|
||||
|
||||
/// 电量信息监听
|
||||
StreamSubscription<BatteryState>? batterySubscription;
|
||||
|
||||
/// 当处于放大图片时,锁定滑动手势
|
||||
var lockSwipe = false.obs;
|
||||
|
||||
/// 当前章节索引
|
||||
var chapterIndex = 0.obs;
|
||||
|
||||
/// 当前页面
|
||||
var currentIndex = 0.obs;
|
||||
|
||||
/// 初始化
|
||||
var initialIndex = 0;
|
||||
|
||||
/// 是否显示控制器
|
||||
var showControls = false.obs;
|
||||
|
||||
/// 阅读方向
|
||||
var direction = 0.obs;
|
||||
|
||||
/// 左手模式
|
||||
bool get leftHandMode => settings.comicReaderLeftHandMode.value;
|
||||
|
||||
/// 翻页动画
|
||||
bool get pageAnimation => settings.comicReaderPageAnimation.value;
|
||||
|
||||
/// 观点、吐槽
|
||||
RxList<ComicViewPointModel> viewPoints = RxList<ComicViewPointModel>();
|
||||
|
||||
/// 连接类型
|
||||
Rx<ConnectivityResult> connectivityType =
|
||||
Rx<ConnectivityResult>(ConnectivityResult.other);
|
||||
|
||||
/// 电量信息
|
||||
Rx<int> batteryLevel = 0.obs;
|
||||
|
||||
/// 显示电量
|
||||
RxBool showBattery = true.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
initConnectivity();
|
||||
initBattery();
|
||||
if (isLongComic) {
|
||||
direction.value = ReaderDirection.kUpToDown;
|
||||
} else {
|
||||
direction.value = settings.comicReaderDirection.value;
|
||||
}
|
||||
|
||||
if (settings.comicReaderFullScreen.value) {
|
||||
setFull();
|
||||
}
|
||||
|
||||
itemPositionsListener.itemPositions.addListener(updateItemPosition);
|
||||
loadDetail();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
/// 初始化电池信息
|
||||
void initBattery() async {
|
||||
try {
|
||||
//没有电池的Mac似乎会闪退,暂时屏蔽Mac
|
||||
//https://github.com/xiaoyaocz/flutter_dmzj/discussions/146
|
||||
if (Platform.isMacOS) {
|
||||
showBattery.value = false;
|
||||
return;
|
||||
}
|
||||
var battery = Battery();
|
||||
batterySubscription =
|
||||
battery.onBatteryStateChanged.listen((BatteryState state) async {
|
||||
try {
|
||||
var level = await battery.batteryLevel;
|
||||
batteryLevel.value = level;
|
||||
showBattery.value = true;
|
||||
} catch (e) {
|
||||
showBattery.value = false;
|
||||
}
|
||||
});
|
||||
batteryLevel.value = await battery.batteryLevel;
|
||||
showBattery.value = true;
|
||||
} catch (e) {
|
||||
showBattery.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 初始化连接状态
|
||||
void initConnectivity() async {
|
||||
var connectivity = Connectivity();
|
||||
connectivitySubscription =
|
||||
connectivity.onConnectivityChanged.listen((ConnectivityResult result) {
|
||||
//提醒
|
||||
if (connectivityType.value != result &&
|
||||
result == ConnectivityResult.mobile) {
|
||||
SmartDialog.showToast("您已切换至数据网络,请注意流量消耗");
|
||||
}
|
||||
connectivityType.value = result;
|
||||
});
|
||||
connectivityType.value = await connectivity.checkConnectivity();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
focusNode.dispose();
|
||||
connectivitySubscription?.cancel();
|
||||
batterySubscription?.cancel();
|
||||
exitFull();
|
||||
itemPositionsListener.itemPositions.removeListener(updateItemPosition);
|
||||
uploadHistory();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void updateItemPosition() {
|
||||
var items = itemPositionsListener.itemPositions.value;
|
||||
if (items.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
var index = items
|
||||
.where((ItemPosition position) => position.itemTrailingEdge > 0)
|
||||
.reduce((ItemPosition min, ItemPosition position) =>
|
||||
position.itemTrailingEdge < min.itemTrailingEdge ? position : min)
|
||||
.index;
|
||||
|
||||
currentIndex.value = index;
|
||||
}
|
||||
|
||||
/// 加载信息
|
||||
void loadDetail() async {
|
||||
try {
|
||||
pageLoadding.value = true;
|
||||
pageError.value = false;
|
||||
|
||||
detail.value = ComicChapterDetail.empty();
|
||||
var chapterId = chapters[chapterIndex.value].chapterId;
|
||||
if (chapters[chapterIndex.value].isVip) {
|
||||
//禁止观看VIP章节
|
||||
throw AppError("请使用动漫之家官方APP观看VIP章节");
|
||||
}
|
||||
//loadViewPoints();
|
||||
|
||||
var result = await request.chapterDetail(
|
||||
comicId: comicId,
|
||||
chapterId: chapterId,
|
||||
useHD: AppSettingsService.instance.comicReaderHD.value,
|
||||
);
|
||||
var his = DBService.instance.getComicHistory(comicId);
|
||||
if (his != null && his.chapterId == chapterId && his.page != 0) {
|
||||
var hisIndex = (his.page - 1) < 0 ? 0 : his.page - 1;
|
||||
if (hisIndex >= result.pageUrls.length - 1) {
|
||||
hisIndex = 0;
|
||||
}
|
||||
initialIndex = hisIndex;
|
||||
} else {
|
||||
initialIndex = 0;
|
||||
}
|
||||
currentIndex.value = initialIndex;
|
||||
// if (settings.comicReaderShowViewPoint.value) {
|
||||
// result.pageUrls.add("TC");
|
||||
// }
|
||||
|
||||
detail.value = result;
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
jumpToPage(initialIndex);
|
||||
});
|
||||
//上传记录
|
||||
uploadHistory();
|
||||
} catch (e) {
|
||||
pageError.value = true;
|
||||
errorMsg.value = e.toString();
|
||||
setShowControls();
|
||||
} finally {
|
||||
pageLoadding.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载吐槽、观点
|
||||
void loadViewPoints() async {
|
||||
try {
|
||||
viewPoints.clear();
|
||||
var result = await request.viewPoints(
|
||||
comicId: comicId,
|
||||
chapterId: chapters[chapterIndex.value].chapterId,
|
||||
);
|
||||
result.sort((a, b) => b.num.value.compareTo(a.num.value));
|
||||
viewPoints.value = result;
|
||||
} catch (e) {
|
||||
//SmartDialog.showToast("读取吐槽失败");
|
||||
Log.logPrint(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置显示/隐藏控制按钮
|
||||
void setShowControls() {
|
||||
if (settings.comicReaderFullScreen.value) {
|
||||
if (showControls.value) {
|
||||
setFull();
|
||||
} else {
|
||||
setFullEdge();
|
||||
}
|
||||
}
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
showControls.value = !showControls.value;
|
||||
});
|
||||
}
|
||||
|
||||
/// 显示目录
|
||||
void showMenu() async {
|
||||
setShowControls();
|
||||
showModalBottomSheet(
|
||||
context: Get.context!,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500,
|
||||
),
|
||||
builder: (context) => Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text("目录(${chapters.length})"),
|
||||
trailing: IconButton(
|
||||
onPressed: Get.back,
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
contentPadding: AppStyle.edgeInsetsL12,
|
||||
),
|
||||
Divider(
|
||||
height: 1.0,
|
||||
color: Theme.of(context).dividerColor.withOpacity(.2),
|
||||
),
|
||||
Expanded(
|
||||
child: ScrollablePositionedList.separated(
|
||||
initialScrollIndex: chapterIndex.value,
|
||||
itemCount: chapters.length,
|
||||
separatorBuilder: (_, i) => Divider(
|
||||
indent: 12,
|
||||
endIndent: 12,
|
||||
height: 1.0,
|
||||
color: Theme.of(context).dividerColor.withOpacity(.2),
|
||||
),
|
||||
itemBuilder: (_, i) {
|
||||
var item = chapters[i];
|
||||
return ListTile(
|
||||
selected: i == chapterIndex.value,
|
||||
selectedTileColor:
|
||||
Theme.of(context).colorScheme.secondaryContainer,
|
||||
selectedColor:
|
||||
Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
title: Text(item.chapterTitle),
|
||||
subtitle: item.updateTime != 0
|
||||
? Text(
|
||||
"更新于${Utils.formatTimestampToDate(item.updateTime)}")
|
||||
: null,
|
||||
onTap: () {
|
||||
chapterIndex.value = i;
|
||||
loadDetail();
|
||||
Get.back();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
routeSettings: const RouteSettings(name: "/modalBottomSheet"),
|
||||
);
|
||||
}
|
||||
|
||||
/// 下一章
|
||||
void nextChapter() {
|
||||
if (chapterIndex.value == chapters.length - 1) {
|
||||
SmartDialog.showToast("后面没有了");
|
||||
return;
|
||||
}
|
||||
|
||||
chapterIndex.value += 1;
|
||||
loadDetail();
|
||||
}
|
||||
|
||||
/// 上一章
|
||||
void forwardChapter() {
|
||||
if (chapterIndex.value == 0) {
|
||||
SmartDialog.showToast("前面没有了");
|
||||
return;
|
||||
}
|
||||
|
||||
chapterIndex.value -= 1;
|
||||
loadDetail();
|
||||
}
|
||||
|
||||
/// 下一页
|
||||
void nextPage() {
|
||||
var value = currentIndex.value;
|
||||
Log.w("下一页$value");
|
||||
var max = detail.value.pageUrls.length;
|
||||
if (value >= max - 1) {
|
||||
nextChapter();
|
||||
} else {
|
||||
jumpToPage(value + 1, anime: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// 上一页
|
||||
void forwardPage() {
|
||||
var value = currentIndex.value;
|
||||
Log.w("上一页$value");
|
||||
if (value == 0) {
|
||||
forwardChapter();
|
||||
} else {
|
||||
jumpToPage(value - 1, anime: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// 跳转页数
|
||||
void jumpToPage(int page, {bool anime = false}) {
|
||||
//竖向
|
||||
if (direction.value == ReaderDirection.kUpToDown) {
|
||||
itemScrollController.jumpTo(index: page);
|
||||
} else {
|
||||
anime && pageAnimation
|
||||
? preloadPageController.animateToPage(page,
|
||||
duration: const Duration(milliseconds: 200), curve: Curves.linear)
|
||||
: preloadPageController.jumpToPage(page);
|
||||
}
|
||||
}
|
||||
|
||||
/// 查看吐槽
|
||||
void showComment() {
|
||||
setShowControls();
|
||||
TextEditingController tucaoController = TextEditingController();
|
||||
showModalBottomSheet(
|
||||
context: Get.context!,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500,
|
||||
),
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
builder: (context) => Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text("吐槽(${viewPoints.length})"),
|
||||
trailing: IconButton(
|
||||
onPressed: Get.back,
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
contentPadding: AppStyle.edgeInsetsL12,
|
||||
),
|
||||
Divider(
|
||||
height: 1.0,
|
||||
color: Theme.of(context).dividerColor.withOpacity(.2),
|
||||
),
|
||||
Expanded(
|
||||
child: EasyRefresh(
|
||||
header: const MaterialHeader(),
|
||||
onRefresh: () async {
|
||||
loadViewPoints();
|
||||
},
|
||||
child: Obx(
|
||||
() => settings.comicReaderOldViewPoint.value
|
||||
? SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: viewPoints.map<Widget>((item) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
likeViewPoint(item);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
borderRadius: AppStyle.radius8,
|
||||
),
|
||||
child: Text(
|
||||
item.content,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: viewPoints.length,
|
||||
separatorBuilder: (_, i) => Divider(
|
||||
indent: 12,
|
||||
endIndent: 12,
|
||||
height: 1.0,
|
||||
color: Theme.of(context).dividerColor.withOpacity(.2),
|
||||
),
|
||||
itemBuilder: (_, i) {
|
||||
var item = viewPoints[i];
|
||||
return Padding(
|
||||
padding: AppStyle.edgeInsetsA12
|
||||
.copyWith(top: 8, bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.content,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
AppStyle.hGap12,
|
||||
TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () {
|
||||
likeViewPoint(item);
|
||||
},
|
||||
icon: const Icon(
|
||||
Remix.thumb_up_line,
|
||||
size: 16,
|
||||
),
|
||||
label: Obx(() => Text("${item.num.value}")),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: AppStyle.edgeInsetsA8.copyWith(
|
||||
bottom: 8 + AppStyle.bottomBarHeight,
|
||||
),
|
||||
child: TextField(
|
||||
controller: tucaoController,
|
||||
onSubmitted: (e) {
|
||||
sendViewPoint(e);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: "发表吐槽",
|
||||
contentPadding: AppStyle.edgeInsetsH12,
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: TextButton(
|
||||
onPressed: () {
|
||||
sendViewPoint(tucaoController.text);
|
||||
},
|
||||
child: const Text("发布"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
routeSettings: const RouteSettings(name: "/modalBottomSheet"),
|
||||
);
|
||||
}
|
||||
|
||||
/// 显示设置
|
||||
void showSettings() {
|
||||
setShowControls();
|
||||
|
||||
showModalBottomSheet(
|
||||
context: Get.context!,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500,
|
||||
),
|
||||
builder: (context) => Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text("设置"),
|
||||
trailing: IconButton(
|
||||
onPressed: Get.back,
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
contentPadding: AppStyle.edgeInsetsL12,
|
||||
),
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => ListView(
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
children: [
|
||||
buildBGItem(
|
||||
context,
|
||||
child: SwitchListTile(
|
||||
value: settings.comicReaderHD.value,
|
||||
onChanged: (e) {
|
||||
settings.setComicReaderHD(e);
|
||||
loadDetail();
|
||||
},
|
||||
title: const Text("优先加载高清图"),
|
||||
subtitle: const Text("部分单行本可能未分页"),
|
||||
),
|
||||
),
|
||||
//AppStyle.vGap12,
|
||||
Visibility(
|
||||
//条漫不允许修改阅读方向
|
||||
visible: !isLongComic,
|
||||
child: Padding(
|
||||
padding: AppStyle.edgeInsetsT12,
|
||||
child: buildBGItem(
|
||||
context,
|
||||
child: ListTile(
|
||||
title: const Text("阅读方向"),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
buildSelectedButton(
|
||||
onTap: () {
|
||||
setDirection(ReaderDirection.kLeftToRight);
|
||||
},
|
||||
selected: settings.comicReaderDirection.value ==
|
||||
ReaderDirection.kLeftToRight,
|
||||
child: const Icon(Remix.arrow_right_line),
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
buildSelectedButton(
|
||||
onTap: () {
|
||||
setDirection(ReaderDirection.kRightToLeft);
|
||||
},
|
||||
selected: settings.comicReaderDirection.value ==
|
||||
ReaderDirection.kRightToLeft,
|
||||
child: const Icon(Remix.arrow_left_line),
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
buildSelectedButton(
|
||||
onTap: () {
|
||||
setDirection(ReaderDirection.kUpToDown);
|
||||
},
|
||||
selected: settings.comicReaderDirection.value ==
|
||||
ReaderDirection.kUpToDown,
|
||||
child: const Icon(Remix.arrow_down_line),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
buildBGItem(
|
||||
context,
|
||||
child: SwitchListTile(
|
||||
value: settings.comicReaderLeftHandMode.value,
|
||||
onChanged: (e) {
|
||||
settings.setComicReaderLeftHandMode(e);
|
||||
},
|
||||
title: const Text("操作反转"),
|
||||
subtitle: const Text("点击左侧下一页,右侧上一页"),
|
||||
),
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
buildBGItem(
|
||||
context,
|
||||
child: SwitchListTile(
|
||||
value: settings.comicReaderFullScreen.value,
|
||||
onChanged: (e) {
|
||||
settings.setComicReaderFullScreen(e);
|
||||
if (e) {
|
||||
setFull();
|
||||
} else {
|
||||
exitFull();
|
||||
}
|
||||
},
|
||||
title: const Text("全屏阅读"),
|
||||
),
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
buildBGItem(
|
||||
context,
|
||||
child: SwitchListTile(
|
||||
value: settings.comicReaderShowStatus.value,
|
||||
onChanged: (e) {
|
||||
settings.setComicReaderShowStatus(e);
|
||||
},
|
||||
title: const Text("显示状态信息"),
|
||||
),
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
buildBGItem(
|
||||
context,
|
||||
child: SwitchListTile(
|
||||
value: settings.comicReaderPageAnimation.value,
|
||||
onChanged: (e) {
|
||||
settings.setComicReaderPageAnimation(e);
|
||||
},
|
||||
title: const Text("翻页动画"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBGItem(BuildContext context, {required Widget child}) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: AppStyle.radius8,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSelectedButton(
|
||||
{required Widget child, bool selected = false, Function()? onTap}) {
|
||||
return Builder(builder: (context) {
|
||||
return OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor:
|
||||
selected ? Theme.of(context).colorScheme.primary : Colors.grey,
|
||||
side: BorderSide(
|
||||
color:
|
||||
selected ? Theme.of(context).colorScheme.primary : Colors.grey,
|
||||
),
|
||||
),
|
||||
onPressed: onTap,
|
||||
child: child,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void setDirection(int value) {
|
||||
initialIndex = currentIndex.value;
|
||||
settings.setComicReaderDirection(value);
|
||||
direction.value = value;
|
||||
if (initialIndex != 0) {
|
||||
Future.delayed(const Duration(milliseconds: 200), () {
|
||||
jumpToPage(initialIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void setShowViewPoint(bool value) {
|
||||
if (value) {
|
||||
if (!detail.value.pageUrls.contains("TC")) {
|
||||
detail.update((val) {
|
||||
val!.pageUrls.add("TC");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (detail.value.pageUrls.contains("TC")) {
|
||||
detail.update((val) {
|
||||
val!.pageUrls.remove("TC");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uploadHistory() {
|
||||
var chapter = chapters[chapterIndex.value];
|
||||
UserService.instance.updateComicHistory(
|
||||
comicId: comicId,
|
||||
chapterId: chapter.chapterId,
|
||||
page: currentIndex.value + 1,
|
||||
comicName: comicTitle,
|
||||
comicCover: comicCover,
|
||||
chapterName: chapter.chapterTitle,
|
||||
);
|
||||
}
|
||||
|
||||
/// 进入全屏
|
||||
void setFull() {
|
||||
SystemChrome.setEnabledSystemUIMode(
|
||||
SystemUiMode.manual,
|
||||
overlays: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// 进入全屏edgeToEdge模式
|
||||
void setFullEdge() {
|
||||
SystemChrome.setEnabledSystemUIMode(
|
||||
SystemUiMode.edgeToEdge,
|
||||
overlays: SystemUiOverlay.values,
|
||||
);
|
||||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarIconBrightness: Brightness.light,
|
||||
));
|
||||
}
|
||||
|
||||
/// 退出全屏
|
||||
void exitFull() {
|
||||
SystemChrome.setEnabledSystemUIMode(
|
||||
SystemUiMode.edgeToEdge,
|
||||
overlays: SystemUiOverlay.values,
|
||||
);
|
||||
}
|
||||
|
||||
void likeViewPoint(ComicViewPointModel item) async {
|
||||
try {
|
||||
await request.likeViewPoint(comicId: comicId, id: item.id);
|
||||
|
||||
item.num.value += 1;
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void sendViewPoint(String content) async {
|
||||
if (!await UserService.instance.login()) {
|
||||
SmartDialog.showToast("请先登录");
|
||||
return;
|
||||
}
|
||||
if (content.isEmpty) {
|
||||
SmartDialog.showToast("内容不能为空");
|
||||
return;
|
||||
}
|
||||
Get.back();
|
||||
try {
|
||||
SmartDialog.showLoading();
|
||||
await request.sendViewPoint(
|
||||
comicId: comicId,
|
||||
chapterId: chapters[chapterIndex.value].chapterId,
|
||||
content: content,
|
||||
page: currentIndex.value + 1,
|
||||
);
|
||||
loadViewPoints();
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
} finally {
|
||||
SmartDialog.dismiss(status: SmartStatus.loading);
|
||||
}
|
||||
}
|
||||
|
||||
void keyDown(LogicalKeyboardKey key) {
|
||||
if (key == LogicalKeyboardKey.arrowLeft ||
|
||||
key == LogicalKeyboardKey.pageUp) {
|
||||
if (leftHandMode) {
|
||||
nextPage();
|
||||
} else {
|
||||
forwardPage();
|
||||
}
|
||||
} else if (key == LogicalKeyboardKey.arrowRight ||
|
||||
key == LogicalKeyboardKey.pageDown) {
|
||||
if (leftHandMode) {
|
||||
forwardPage();
|
||||
} else {
|
||||
nextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
612
lib/modules/comic/reader/comic_reader_page.dart
Normal file
612
lib/modules/comic/reader/comic_reader_page.dart
Normal file
@@ -0,0 +1,612 @@
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dmzj/app/app_constant.dart';
|
||||
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/reader/comic_reader_controller.dart';
|
||||
import 'package:flutter_dmzj/widgets/custom_header.dart';
|
||||
import 'package:flutter_dmzj/widgets/local_image.dart';
|
||||
import 'package:flutter_dmzj/widgets/net_image.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_error_widget.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_loadding_widget.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:preload_page_view/preload_page_view.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
class ComicReaderPage extends GetView<ComicReaderController> {
|
||||
const ComicReaderPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KeyboardListener(
|
||||
onKeyEvent: (e) {
|
||||
if (e.runtimeType == KeyUpEvent) {
|
||||
controller.keyDown(e.logicalKey);
|
||||
Log.d(e.toString());
|
||||
}
|
||||
},
|
||||
focusNode: controller.focusNode,
|
||||
autofocus: true,
|
||||
child: Theme(
|
||||
data: Theme.of(context),
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Stack(
|
||||
children: [
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: controller.detail.value.chapterId == 0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
controller.setShowControls();
|
||||
},
|
||||
child:
|
||||
controller.direction.value == ReaderDirection.kUpToDown
|
||||
? buildVertical(context)
|
||||
: buildHorizontal(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
controller.leftHandMode
|
||||
? controller.nextPage()
|
||||
: controller.forwardPage();
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 8,
|
||||
child: Container(),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
controller.leftHandMode
|
||||
? controller.forwardPage()
|
||||
: controller.nextPage();
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageLoadding.value,
|
||||
child: const AppLoaddingWidget(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageError.value,
|
||||
child: AppErrorWidget(
|
||||
errorMsg: controller.errorMsg.value,
|
||||
onRefresh: () => controller.loadDetail(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.settings.comicReaderShowStatus.value,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.black54,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
padding:
|
||||
AppStyle.edgeInsetsA12.copyWith(top: 4, bottom: 4),
|
||||
child: Obx(
|
||||
() => Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
buildConnectivity(),
|
||||
buildBattery(),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 100),
|
||||
child: Text(
|
||||
controller.detail.value.chapterTitle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
Text(
|
||||
"${controller.currentIndex.value + 1} / ${controller.detail.value.pageUrls.length}",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
//顶部
|
||||
Obx(
|
||||
() => AnimatedPositioned(
|
||||
top: controller.showControls.value
|
||||
? 0
|
||||
: -(64 + AppStyle.statusBarHeight),
|
||||
left: 0,
|
||||
right: 0,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
height: 64 + AppStyle.statusBarHeight,
|
||||
padding: EdgeInsets.only(top: AppStyle.statusBarHeight),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: Get.back,
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
AppStyle.hGap12,
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => Text(
|
||||
controller.chapters[controller.chapterIndex.value]
|
||||
.chapterTitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
//底部
|
||||
Obx(
|
||||
() => AnimatedPositioned(
|
||||
bottom: controller.showControls.value
|
||||
? 0
|
||||
: -(136 + AppStyle.bottomBarHeight),
|
||||
left: 0,
|
||||
right: 0,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
height: 136 + AppStyle.bottomBarHeight,
|
||||
padding: EdgeInsets.only(bottom: AppStyle.bottomBarHeight),
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 600,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
buildSilderBar(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
IconButton.filledTonal(
|
||||
onPressed: controller.forwardChapter,
|
||||
icon: const Icon(Remix.skip_back_line),
|
||||
tooltip: "上一话",
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: controller.showMenu,
|
||||
icon: const Icon(Remix.file_list_line),
|
||||
tooltip: "目录",
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: controller.showSettings,
|
||||
icon: const Icon(Remix.settings_line),
|
||||
tooltip: "设置",
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: controller.nextChapter,
|
||||
icon: const Icon(Remix.skip_forward_line),
|
||||
tooltip: "下一话",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildHorizontal(BuildContext context) {
|
||||
return EasyRefresh(
|
||||
header: MaterialHeader2(
|
||||
triggerOffset: 80,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: AppStyle.radius24,
|
||||
),
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
child: Icon(
|
||||
Icons.arrow_circle_left,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
footer: MaterialFooter2(
|
||||
triggerOffset: 80,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: AppStyle.radius24,
|
||||
),
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
child: Icon(
|
||||
Icons.arrow_circle_right,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
refreshOnStart: false,
|
||||
onRefresh: () async {
|
||||
controller.forwardChapter();
|
||||
},
|
||||
onLoad: () async {
|
||||
controller.nextChapter();
|
||||
},
|
||||
child: PreloadPageView.builder(
|
||||
controller: controller.preloadPageController,
|
||||
onPageChanged: (e) {
|
||||
controller.currentIndex.value = e;
|
||||
},
|
||||
reverse: controller.direction.value == ReaderDirection.kRightToLeft,
|
||||
physics: controller.lockSwipe.value
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
itemCount: controller.detail.value.pageUrls.length,
|
||||
preloadPagesCount: 4,
|
||||
itemBuilder: (_, i) {
|
||||
var url = controller.detail.value.pageUrls[i];
|
||||
// if (i == controller.detail.value.pageUrls.length - 1 && url == "TC") {
|
||||
// return buildViewPoints();
|
||||
// }
|
||||
return PhotoView.customChild(
|
||||
wantKeepAlive: true,
|
||||
initialScale: 1.0,
|
||||
onScaleEnd: (context, detail, e) {
|
||||
controller.lockSwipe.value = (e.scale ?? 1) > 1.0;
|
||||
},
|
||||
child: controller.detail.value.isLocal
|
||||
? LocalImage(url, fit: BoxFit.contain)
|
||||
: NetImage(
|
||||
url,
|
||||
fit: BoxFit.contain,
|
||||
progress: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildVertical(BuildContext context) {
|
||||
return EasyRefresh(
|
||||
header: MaterialHeader2(
|
||||
triggerOffset: 80,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: AppStyle.radius24,
|
||||
),
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
child: Icon(
|
||||
Icons.arrow_circle_up,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
footer: MaterialFooter2(
|
||||
triggerOffset: 80,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: AppStyle.radius24,
|
||||
),
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
child: Icon(
|
||||
Icons.arrow_circle_down,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
refreshOnStart: false,
|
||||
onRefresh: () async {
|
||||
controller.forwardChapter();
|
||||
},
|
||||
onLoad: () async {
|
||||
controller.nextChapter();
|
||||
},
|
||||
child: ScrollablePositionedList.builder(
|
||||
itemScrollController: controller.itemScrollController,
|
||||
itemCount: controller.detail.value.pageUrls.length,
|
||||
itemPositionsListener: controller.itemPositionsListener,
|
||||
itemBuilder: (_, i) {
|
||||
// if (i == controller.detail.value.pageUrls.length - 1 &&
|
||||
// controller.detail.value.pageUrls[i] == "TC") {
|
||||
// return buildViewPoints(shrinkWrap: true);
|
||||
// }
|
||||
var url = controller.detail.value.pageUrls[i];
|
||||
return Container(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 200,
|
||||
),
|
||||
child: controller.detail.value.isLocal
|
||||
? LocalImage(url, fit: BoxFit.contain)
|
||||
: NetImage(
|
||||
url,
|
||||
fit: BoxFit.fitWidth,
|
||||
progress: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSilderBar() {
|
||||
return Obx(
|
||||
() {
|
||||
var value = controller.currentIndex.value + 1.0;
|
||||
var max = controller.detail.value.pageUrls.length.toDouble();
|
||||
if (value > max) {
|
||||
return const SizedBox(
|
||||
height: 48,
|
||||
);
|
||||
}
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: value,
|
||||
max: max,
|
||||
onChanged: (e) {
|
||||
controller.jumpToPage((e - 1).toInt());
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildViewPoints({bool shrinkWrap = false}) {
|
||||
return Obx(
|
||||
() => ListView(
|
||||
shrinkWrap: shrinkWrap,
|
||||
physics: shrinkWrap ? const NeverScrollableScrollPhysics() : null,
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text("吐槽(${controller.viewPoints.length})"),
|
||||
),
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsH12,
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: controller.viewPoints
|
||||
.take(10)
|
||||
.map(
|
||||
(e) => OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () {
|
||||
controller.likeViewPoint(e);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
e.content,
|
||||
style: const TextStyle(
|
||||
fontSize: 14, color: Colors.white),
|
||||
),
|
||||
AppStyle.hGap12,
|
||||
const Icon(
|
||||
Remix.thumb_up_line,
|
||||
size: 16,
|
||||
),
|
||||
AppStyle.hGap4,
|
||||
Obx(
|
||||
() => Text(
|
||||
"${e.num.value}",
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
width: 100,
|
||||
margin: AppStyle.edgeInsetsA12,
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
controller.showComment();
|
||||
},
|
||||
child: const Text("查看更多"),
|
||||
),
|
||||
),
|
||||
AppStyle.vGap12,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildConnectivity() {
|
||||
var connectivityType = controller.connectivityType.value;
|
||||
IconData icon = Remix.wifi_line;
|
||||
var name = "WiFi";
|
||||
switch (connectivityType) {
|
||||
case ConnectivityResult.bluetooth:
|
||||
icon = Remix.wifi_line;
|
||||
name = "蓝牙";
|
||||
break;
|
||||
case ConnectivityResult.ethernet:
|
||||
icon = Remix.computer_line;
|
||||
name = "有线";
|
||||
break;
|
||||
case ConnectivityResult.mobile:
|
||||
icon = Remix.base_station_line;
|
||||
name = "流量";
|
||||
break;
|
||||
case ConnectivityResult.wifi:
|
||||
icon = Remix.wifi_line;
|
||||
name = "WiFi";
|
||||
break;
|
||||
case ConnectivityResult.vpn:
|
||||
icon = Remix.shield_keyhole_line;
|
||||
name = "VPN";
|
||||
break;
|
||||
case ConnectivityResult.none:
|
||||
icon = Remix.wifi_off_line;
|
||||
name = "无网络";
|
||||
break;
|
||||
case ConnectivityResult.other:
|
||||
icon = Remix.question_line;
|
||||
name = "未知";
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
AppStyle.hGap4,
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBattery() {
|
||||
var battery = controller.batteryLevel.value;
|
||||
// IconData icon = Icons.battery_0_bar;
|
||||
// if (battery >= 90) {
|
||||
// icon = Icons.battery_full;
|
||||
// } else if (battery < 90 && battery >= 80) {
|
||||
// icon = Icons.battery_6_bar;
|
||||
// } else if (battery < 80 && battery >= 70) {
|
||||
// icon = Icons.battery_5_bar;
|
||||
// } else if (battery < 70 && battery >= 50) {
|
||||
// icon = Icons.battery_4_bar;
|
||||
// } else if (battery < 50 && battery >= 30) {
|
||||
// icon = Icons.battery_3_bar;
|
||||
// } else if (battery < 30 && battery >= 20) {
|
||||
// icon = Icons.battery_2_bar;
|
||||
// } else if (battery < 20 && battery >= 10) {
|
||||
// icon = Icons.battery_1_bar;
|
||||
// } else {
|
||||
// icon = Icons.battery_0_bar;
|
||||
// }
|
||||
return Visibility(
|
||||
visible: controller.showBattery.value,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Icon(
|
||||
// icon,
|
||||
// size: 16,
|
||||
// ),
|
||||
// AppStyle.hGap4,
|
||||
Text(
|
||||
"电量 $battery%",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
AppStyle.hGap8,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
98
lib/modules/comic/search/comic_search_controller.dart
Normal file
98
lib/modules/comic/search/comic_search_controller.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/dialog_utils.dart';
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
import 'package:flutter_dmzj/models/comic/search_item.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_request.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicSearchController extends BasePageController<SearchComicItem> {
|
||||
final String keyword;
|
||||
ComicSearchController(this.keyword) {
|
||||
searchController = TextEditingController(text: keyword);
|
||||
showHotWord.value = keyword.isEmpty;
|
||||
}
|
||||
late TextEditingController searchController;
|
||||
final ComicRequest request = ComicRequest();
|
||||
|
||||
String _keyword = "";
|
||||
|
||||
RxMap<int, String> hotWords = <int, String>{}.obs;
|
||||
|
||||
var showHotWord = true.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
// loadHotWord();
|
||||
if (keyword.isNotEmpty) {
|
||||
submit();
|
||||
}
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
void submit() async {
|
||||
if (searchController.text.isEmpty) {
|
||||
list.clear();
|
||||
showHotWord.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (int.tryParse(searchController.text) != null &&
|
||||
await numberJumpComic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchController.text.startsWith("id:\\") && await handelJumpComic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
showHotWord.value = false;
|
||||
_keyword = searchController.text;
|
||||
refreshData();
|
||||
}
|
||||
|
||||
Future<bool> handelJumpComic() async {
|
||||
var id = int.tryParse(searchController.text.replaceAll("id:\\", "")) ?? 0;
|
||||
if (id != 0) {
|
||||
AppNavigator.toComicDetail(id);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future numberJumpComic() async {
|
||||
if (!await DialogUtils.showAlertDialog(
|
||||
"你输入了纯数字,是否跳转至对应的漫画?",
|
||||
title: "漫画ID跳转",
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
return await handelJumpComic();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SearchComicItem>> getData(int page, int pageSize) async {
|
||||
if (searchController.text.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
// if (AppSettingsService.instance.comicSearchUseWebApi.value) {
|
||||
// //WEB接口不能分页
|
||||
// if (page > 1) {
|
||||
// return [];
|
||||
// }
|
||||
// return await request.searchWeb(keyword: _keyword);
|
||||
// } else {
|
||||
return await request.search(keyword: _keyword, page: page);
|
||||
//}
|
||||
}
|
||||
|
||||
void loadHotWord() async {
|
||||
try {
|
||||
hotWords.value = await request.searchHotWord();
|
||||
} catch (e) {
|
||||
Log.logPrint(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
171
lib/modules/comic/search/comic_search_page.dart
Normal file
171
lib/modules/comic/search/comic_search_page.dart
Normal file
@@ -0,0 +1,171 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/models/comic/search_item.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/search/comic_search_controller.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/widgets/net_image.dart';
|
||||
import 'package:flutter_dmzj/widgets/page_list_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicSearchPage extends StatelessWidget {
|
||||
final String keyword;
|
||||
final ComicSearchController controller;
|
||||
ComicSearchPage({this.keyword = "", super.key})
|
||||
: controller = Get.put(ComicSearchController(keyword));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 8,
|
||||
title: SizedBox(
|
||||
height: 40,
|
||||
child: TextField(
|
||||
controller: controller.searchController,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: "搜索漫画",
|
||||
contentPadding: AppStyle.edgeInsetsH12,
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: SizedBox(
|
||||
width: 48,
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
AppNavigator.closePage();
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
),
|
||||
suffixIcon: SizedBox(
|
||||
width: 48,
|
||||
child: IconButton(
|
||||
onPressed: controller.submit,
|
||||
icon: const Icon(Icons.search),
|
||||
),
|
||||
),
|
||||
),
|
||||
onSubmitted: (e) {
|
||||
controller.submit();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
PageListView(
|
||||
pageController: controller,
|
||||
firstRefresh: false,
|
||||
showPageLoadding: 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 buildItem(item);
|
||||
},
|
||||
),
|
||||
// Positioned.fill(
|
||||
// child: Obx(
|
||||
// () => Offstage(
|
||||
// offstage: !controller.showHotWord.value,
|
||||
// child: SingleChildScrollView(
|
||||
// child: Column(
|
||||
// children: [
|
||||
// const ListTile(
|
||||
// title: Text("热门搜索"),
|
||||
// ),
|
||||
// Padding(
|
||||
// padding: AppStyle.edgeInsetsH12.copyWith(bottom: 12),
|
||||
// child: Wrap(
|
||||
// spacing: 8,
|
||||
// runSpacing: 8,
|
||||
// children: controller.hotWords.keys
|
||||
// .map(
|
||||
// (e) => OutlinedButton(
|
||||
// style: OutlinedButton.styleFrom(
|
||||
// tapTargetSize:
|
||||
// MaterialTapTargetSize.shrinkWrap,
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// AppNavigator.toComicDetail(e);
|
||||
// },
|
||||
// child: Text(controller.hotWords[e] ?? ""),
|
||||
// ),
|
||||
// )
|
||||
// .toList(),
|
||||
// ),
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildItem(SearchComicItem 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.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text.rich(
|
||||
TextSpan(children: [
|
||||
const WidgetSpan(
|
||||
child: Icon(
|
||||
Icons.account_circle,
|
||||
color: Colors.grey,
|
||||
size: 18,
|
||||
)),
|
||||
const TextSpan(
|
||||
text: " ",
|
||||
),
|
||||
TextSpan(
|
||||
text: item.author,
|
||||
style:
|
||||
const TextStyle(color: Colors.grey, fontSize: 14))
|
||||
]),
|
||||
),
|
||||
AppStyle.vGap4,
|
||||
Text(item.tags,
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 14)),
|
||||
AppStyle.vGap4,
|
||||
Text(item.lastChapterName,
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import 'package:flutter_dmzj/app/controller/base_controller.dart';
|
||||
import 'package:flutter_dmzj/models/comic/detail_info.dart';
|
||||
import 'package:flutter_dmzj/requests/comic_request.dart';
|
||||
import 'package:flutter_dmzj/routes/app_navigator.dart';
|
||||
import 'package:flutter_dmzj/services/comic_download_service.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ComicSelectChapterController extends BaseController {
|
||||
final int comicId;
|
||||
ComicSelectChapterController(this.comicId);
|
||||
final ComicRequest request = ComicRequest();
|
||||
|
||||
RxList<ComicDetailVolume> volumes = RxList<ComicDetailVolume>();
|
||||
|
||||
RxSet<int> chapterIds = RxSet<int>();
|
||||
|
||||
String comicTitle = "";
|
||||
String comicCover = "";
|
||||
bool islong = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
loadDetail();
|
||||
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
void refreshV1() async {
|
||||
try {
|
||||
var result =
|
||||
await request.comicDetail(comicId: comicId, priorityV1: true);
|
||||
if (result.volumes.isEmpty) {
|
||||
SmartDialog.showToast("没有找到任何章节");
|
||||
return;
|
||||
}
|
||||
comicTitle = result.title;
|
||||
comicCover = result.cover;
|
||||
islong = result.isLong;
|
||||
for (var volume in result.volumes) {
|
||||
volume.sortType.value = 1;
|
||||
volume.sort();
|
||||
}
|
||||
volumes.value = result.volumes;
|
||||
} catch (e) {
|
||||
SmartDialog.showToast("无法获取章节");
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载信息
|
||||
void loadDetail() async {
|
||||
try {
|
||||
pageLoadding.value = true;
|
||||
pageError.value = false;
|
||||
var result = await request.comicDetail(comicId: comicId);
|
||||
comicTitle = result.title;
|
||||
comicCover = result.cover;
|
||||
islong = result.isLong;
|
||||
if (result.volumes.isEmpty && !result.isHide) {
|
||||
refreshV1();
|
||||
} else {
|
||||
for (var volume in result.volumes) {
|
||||
volume.sortType.value = 1;
|
||||
volume.sort();
|
||||
}
|
||||
volumes.value = result.volumes;
|
||||
}
|
||||
} catch (e) {
|
||||
pageError.value = true;
|
||||
errorMsg.value = e.toString();
|
||||
} finally {
|
||||
pageLoadding.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void selectItem(ComicDetailChapterItem item) {
|
||||
//禁止下载VIP章节
|
||||
if (item.isVip) {
|
||||
SmartDialog.showToast("请使用动漫之家官方APP下载VIP章节");
|
||||
return;
|
||||
}
|
||||
if (chapterIds.contains(item.chapterId)) {
|
||||
chapterIds.remove(item.chapterId);
|
||||
} else {
|
||||
chapterIds.add(item.chapterId);
|
||||
}
|
||||
}
|
||||
|
||||
void selectAll() {
|
||||
for (var volume in volumes) {
|
||||
for (var chapter in volume.chapters) {
|
||||
if (chapter.isVip) {
|
||||
continue;
|
||||
}
|
||||
var id = "${comicId}_${chapter.chapterId}";
|
||||
if (!ComicDownloadService.instance.downloadIds.contains(id)) {
|
||||
chapterIds.add(chapter.chapterId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cleanAll() {
|
||||
chapterIds.clear();
|
||||
}
|
||||
|
||||
void toDownloadManage() {
|
||||
AppNavigator.toComicDownloadManage(1);
|
||||
}
|
||||
|
||||
void startDownload() {
|
||||
if (chapterIds.isEmpty) {
|
||||
SmartDialog.showToast("请选择需要下载的章节");
|
||||
return;
|
||||
}
|
||||
for (var id in chapterIds) {
|
||||
//搜索章节
|
||||
ComicDetailVolume? volume;
|
||||
ComicDetailChapterItem? chapter;
|
||||
for (var item in volumes) {
|
||||
var chapterItem =
|
||||
item.chapters.firstWhereOrNull((y) => y.chapterId == id);
|
||||
if (chapterItem != null) {
|
||||
volume = item;
|
||||
chapter = chapterItem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (volume == null || chapter == null) {
|
||||
continue;
|
||||
}
|
||||
ComicDownloadService.instance.addTask(
|
||||
comicId: comicId,
|
||||
chapterId: chapter.chapterId,
|
||||
chapterSort: chapter.chapterOrder,
|
||||
volumeName: volume.title,
|
||||
comicTitle: comicTitle,
|
||||
comicCover: comicCover,
|
||||
chapterName: chapter.chapterTitle,
|
||||
isVip: chapter.isVip,
|
||||
isLongComic: islong,
|
||||
);
|
||||
}
|
||||
chapterIds.clear();
|
||||
SmartDialog.showToast("已添加到下载列表,下载过程中请保持APP在前台运行");
|
||||
}
|
||||
}
|
||||
259
lib/modules/comic/select_chapter/comic_select_chapter_page.dart
Normal file
259
lib/modules/comic/select_chapter/comic_select_chapter_page.dart
Normal file
@@ -0,0 +1,259 @@
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dmzj/app/app_style.dart';
|
||||
import 'package:flutter_dmzj/models/comic/detail_info.dart';
|
||||
import 'package:flutter_dmzj/modules/comic/select_chapter/comic_select_chapter_controller.dart';
|
||||
import 'package:flutter_dmzj/services/comic_download_service.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_error_widget.dart';
|
||||
import 'package:flutter_dmzj/widgets/status/app_loadding_widget.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
class ComicSelectChapterPage extends StatelessWidget {
|
||||
final int comicId;
|
||||
final ComicSelectChapterController controller;
|
||||
ComicSelectChapterPage(this.comicId, {super.key})
|
||||
: controller = Get.put(
|
||||
ComicSelectChapterController(comicId),
|
||||
tag: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("选择下载章节"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: controller.toDownloadManage,
|
||||
child: const Text("下载管理"),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
EasyRefresh(
|
||||
header: const MaterialHeader(),
|
||||
onRefresh: controller.loadDetail,
|
||||
child: _buildVolumes(),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageLoadding.value,
|
||||
child: const AppLoaddingWidget(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: !controller.pageError.value,
|
||||
child: AppErrorWidget(
|
||||
errorMsg: controller.errorMsg.value,
|
||||
onRefresh: () => controller.loadDetail(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
),
|
||||
onPressed: controller.selectAll,
|
||||
icon: const Icon(
|
||||
Remix.checkbox_line,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text("全选"),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
),
|
||||
onPressed: controller.cleanAll,
|
||||
icon: const Icon(
|
||||
Remix.checkbox_blank_line,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text("取消选中"),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
),
|
||||
onPressed: controller.startDownload,
|
||||
icon: const Icon(
|
||||
Remix.download_line,
|
||||
size: 20,
|
||||
),
|
||||
label:
|
||||
Obx(() => Text("下载选中(${controller.chapterIds.length})")),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVolumes() {
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
padding: AppStyle.edgeInsetsA12,
|
||||
itemCount: controller.volumes.length,
|
||||
itemBuilder: (_, i) {
|
||||
var item = controller.volumes[i];
|
||||
return _buildChapters(item);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChapters(ComicDetailVolume item) {
|
||||
return Obx(
|
||||
() => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: AppStyle.edgeInsetsV8,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"${item.title}(共${item.chapters.length}话)",
|
||||
style: Get.textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
item.sortType.value == 1
|
||||
? TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () {
|
||||
item.sortType.value = 0;
|
||||
item.sort();
|
||||
},
|
||||
icon: const Icon(
|
||||
Remix.sort_asc,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text("升序"),
|
||||
)
|
||||
: TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () {
|
||||
item.sortType.value = 1;
|
||||
item.sort();
|
||||
},
|
||||
icon: const Icon(
|
||||
Remix.sort_desc,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text("倒序"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
LayoutBuilder(builder: (ctx, constraints) {
|
||||
var count = constraints.maxWidth ~/ 160;
|
||||
if (count < 3) count = 3;
|
||||
|
||||
return Obx(
|
||||
() => MasonryGridView.count(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: (item.showMoreButton && !item.showAll.value)
|
||||
? 15
|
||||
: item.chapters.length,
|
||||
itemBuilder: (_, i) {
|
||||
if (item.showMoreButton && !item.showAll.value && i == 14) {
|
||||
return Tooltip(
|
||||
message: "展开全部章节",
|
||||
child: OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.grey,
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize: const Size.fromHeight(40),
|
||||
),
|
||||
onPressed: () {
|
||||
item.showAll.value = true;
|
||||
},
|
||||
child: const Icon(Icons.arrow_drop_down),
|
||||
),
|
||||
);
|
||||
}
|
||||
var chapter = item.chapters[i];
|
||||
|
||||
return Tooltip(
|
||||
message: chapter.chapterTitle,
|
||||
child: Obx(
|
||||
() => Stack(
|
||||
children: [
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: controller.chapterIds
|
||||
.contains(chapter.chapterId)
|
||||
? Get.theme.colorScheme.primary
|
||||
: Get.textTheme.bodyMedium!.color,
|
||||
side: controller.chapterIds
|
||||
.contains(chapter.chapterId)
|
||||
? BorderSide(color: Get.theme.colorScheme.primary)
|
||||
: null,
|
||||
textStyle: const TextStyle(fontSize: 14),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
minimumSize: const Size.fromHeight(40),
|
||||
),
|
||||
onPressed: ComicDownloadService.instance.downloadIds
|
||||
.contains("${comicId}_${chapter.chapterId}")
|
||||
? null
|
||||
: () => controller.selectItem(chapter),
|
||||
child: Text(
|
||||
item.chapters[i].chapterTitle,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: -2,
|
||||
top: 0,
|
||||
child: Offstage(
|
||||
offstage: !item.chapters[i].isVip,
|
||||
child: Image.asset(
|
||||
"assets/images/vip_chapter.png",
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
crossAxisCount: count,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
),
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user