This commit is contained in:
2026-03-07 17:24:59 +08:00
parent 4418ebecac
commit b0ec8ab4bd
417 changed files with 42546 additions and 2 deletions

41
lib/app/app_color.dart Normal file
View 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
View 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
View 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
View 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;
}

View 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
View 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
View 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
View 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
View 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);
}
}
}

View 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
View 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
View 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!,
),
),
),
);
},
);
}
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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;
}

View 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,
};
}

View 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;
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
);
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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;
}

View 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,
};
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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,
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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,
};
}

View 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,
};
}

View 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是stringmood_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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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;
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

File diff suppressed because it is too large Load Diff

View 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');

View 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);
}

View 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==');

File diff suppressed because it is too large Load Diff

View 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');

View 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,
};
}

View 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,
};
}

View 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
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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,
};
}

View File

@@ -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,
);
}
}

View 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,
);
},
),
),
)
],
),
),
);
}
}

View File

@@ -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,
);
}
}
}

View 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,
],
),
);
},
);
}),
);
}
}

View 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("已从本地收藏删除漫画");
}
}

View 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,
),
),
],
),
);
}
}

View 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,
),
),
),
],
),
);
},
);
})
],
),
);
}
}

View File

@@ -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);
}
}

View 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),
),
),
],
),
);
},
),
);
});
}
}

View 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();
}
}

View 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(),
],
),
);
}
}

View 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;
}
}

View 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),
),
),
);
}
}

View 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;
}
}

View 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,
);
},
),
),
)
],
),
),
);
}
}

View 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();
}
}

View 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),
),
),
],
),
);
},
);
}
}

View 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);
}
}
}

View 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,
),
),
],
),
),
],
),
);
},
),
);
}
}

View 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();
}
}
}
}

View 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,
],
),
);
}
}

View 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);
}
}
}

View 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)),
],
),
),
],
),
),
);
}
}

View File

@@ -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在前台运行");
}
}

View 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