diff --git a/lib/page/comic_info/method/export_comic.dart b/lib/page/comic_info/method/export_comic.dart index 54eae9d..e2801fd 100644 --- a/lib/page/comic_info/method/export_comic.dart +++ b/lib/page/comic_info/method/export_comic.dart @@ -12,9 +12,9 @@ import '../../download/json/comic_all_info_json/comic_all_info_json.dart'; /// 导出漫画为文件夹 Future exportComicAsFolder(ComicAllInfoJson comicInfo) async { + var processedComicInfo = comicInfoProcess(comicInfo); var downloadPath = await createDownloadDir(); - var comicDir = - '$downloadPath/${comicInfo.comic.title.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_')}'; + var comicDir = '$downloadPath/${processedComicInfo.comic.title}'; if (!await Directory(comicDir).exists()) { await Directory(comicDir).create(recursive: true); @@ -22,13 +22,20 @@ Future exportComicAsFolder(ComicAllInfoJson comicInfo) async { // 保存漫画下载信息 var comicInfoString = comicAllInfoJsonToJson(comicInfo); - var comicInfoFile = File('$comicDir/info.json'); + var comicInfoFile = File('$comicDir/original_comic_info.json'); if (!await comicInfoFile.exists()) { await comicInfoFile.create(recursive: true); } await comicInfoFile.writeAsString(comicInfoString); - if (comicInfo.comic.thumb.path.isNotEmpty) { + var processedComicInfoString = comicAllInfoJsonToJson(processedComicInfo); + var processedComicInfoFile = File('$comicDir/processed_comic_info.json'); + if (!await processedComicInfoFile.exists()) { + await processedComicInfoFile.create(recursive: true); + } + await processedComicInfoFile.writeAsString(processedComicInfoString); + + if (processedComicInfo.comic.thumb.path.isNotEmpty) { var coverDir = '$comicDir/cover'; var coverFile = File('$coverDir/cover.jpg'); if (!await coverFile.exists()) { @@ -36,22 +43,20 @@ Future exportComicAsFolder(ComicAllInfoJson comicInfo) async { } var coverDownloadFile = await downloadPicture( from: 'bika', - url: comicInfo.comic.thumb.fileServer, - path: comicInfo.comic.thumb.path, - cartoonId: comicInfo.comic.id, + url: processedComicInfo.comic.thumb.fileServer, + path: processedComicInfo.comic.thumb.path, + cartoonId: processedComicInfo.comic.id, pictureType: 'cover', - chapterId: comicInfo.comic.id, + chapterId: processedComicInfo.comic.id, ); await File(coverDownloadFile).copy(coverFile.path); } final List> downloadTasks = []; - for (var ep in comicInfo.eps.docs) { - var epDir = - '$comicDir/eps/${ep.order}.${ep.title.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_')}'; + for (var ep in processedComicInfo.eps.docs) { + var epDir = '$comicDir/eps/${ep.title}'; for (var page in ep.pages.docs) { - var pageFile = - '$epDir/${page.media.originalName.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_')}'; + var pageFile = '$epDir/${page.media.originalName}'; downloadTask() async { try { var pageDownloadFile = await downloadPicture( @@ -77,53 +82,53 @@ Future exportComicAsFolder(ComicAllInfoJson comicInfo) async { await Future.wait(downloadTasks); - debugPrint( - '漫画${comicInfo.comic.title.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_')}导出为文件夹完成'); - EasyLoading.showSuccess( - '漫画${comicInfo.comic.title.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_')}导出为文件夹完成'); + debugPrint('漫画${comicInfo.comic.title}导出为文件夹完成'); + EasyLoading.showSuccess('漫画${comicInfo.comic.title}导出为文件夹完成'); } /// 导出漫画为 ZIP Future exportComicAsZip(ComicAllInfoJson comicInfo) async { + var processedComicInfo = comicInfoProcess(comicInfo); var downloadPath = await createDownloadDir(); - var comicDir = - comicInfo.comic.title.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_'); + var comicDir = processedComicInfo.comic.title; - // 保存漫画下载信息 - var comicInfoString = comicAllInfoJsonToJson(comicInfo); - var comicInfoBytes = utf8.encode(comicInfoString); + // 保存原始漫画信息 + var originalComicInfoString = comicAllInfoJsonToJson(comicInfo); + var originalComicInfoBytes = utf8.encode(originalComicInfoString); + + // 保存处理后的漫画信息 + var processedComicInfoString = comicAllInfoJsonToJson(processedComicInfo); + var processedComicInfoBytes = utf8.encode(processedComicInfoString); // 封面图片 Uint8List? coverBytes; - if (comicInfo.comic.thumb.path.isNotEmpty) { + if (processedComicInfo.comic.thumb.path.isNotEmpty) { var coverDownloadFile = await downloadPicture( from: 'bika', - url: comicInfo.comic.thumb.fileServer, - path: comicInfo.comic.thumb.path, - cartoonId: comicInfo.comic.id, + url: processedComicInfo.comic.thumb.fileServer, + path: processedComicInfo.comic.thumb.path, + cartoonId: processedComicInfo.comic.id, pictureType: 'cover', - chapterId: comicInfo.comic.id, + chapterId: processedComicInfo.comic.id, ); coverBytes = await File(coverDownloadFile).readAsBytes(); } // 漫画页面 final List> pages = []; - for (var ep in comicInfo.eps.docs) { - var epDir = - 'eps/${ep.order}.${ep.title.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_')}'; + for (var ep in processedComicInfo.eps.docs) { + var epDir = 'eps/${ep.title}'; for (var page in ep.pages.docs) { var pageDownloadFile = await downloadPicture( from: 'bika', url: page.media.fileServer, path: page.media.path, - cartoonId: comicInfo.comic.id, + cartoonId: processedComicInfo.comic.id, pictureType: 'comic', chapterId: ep.id, ); var pageBytes = await File(pageDownloadFile).readAsBytes(); - var fileName = - page.media.originalName.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_'); + var fileName = page.media.originalName; pages.add({ 'path': join(epDir, fileName), 'bytes': pageBytes, @@ -137,30 +142,31 @@ Future exportComicAsZip(ComicAllInfoJson comicInfo) async { _CompressToZipParams( downloadPath: downloadPath, comicDir: comicDir, - comicInfoBytes: comicInfoBytes, + originalComicInfoBytes: originalComicInfoBytes, + processedComicInfoBytes: processedComicInfoBytes, coverBytes: coverBytes, pages: pages, ), ); - debugPrint( - '漫画${comicInfo.comic.title.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_')}导出为ZIP完成'); - EasyLoading.showSuccess( - '漫画${comicInfo.comic.title.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_')}导出为ZIP完成'); + debugPrint('漫画${comicInfo.comic.title}导出为ZIP完成'); + EasyLoading.showSuccess('漫画${comicInfo.comic.title}导出为ZIP完成'); } /// 压缩任务参数类 class _CompressToZipParams { final String downloadPath; final String comicDir; - final Uint8List comicInfoBytes; + final Uint8List originalComicInfoBytes; // 原始漫画信息 + final Uint8List processedComicInfoBytes; // 处理后的漫画信息 final Uint8List? coverBytes; final List> pages; _CompressToZipParams({ required this.downloadPath, required this.comicDir, - required this.comicInfoBytes, + required this.originalComicInfoBytes, + required this.processedComicInfoBytes, this.coverBytes, required this.pages, }); @@ -170,11 +176,18 @@ class _CompressToZipParams { void _compressToZip(_CompressToZipParams params) { var archive = Archive(); - // 添加漫画信息文件 + // 添加原始漫画信息文件 archive.addFile(ArchiveFile( - 'info.json', - params.comicInfoBytes.length, - params.comicInfoBytes, + 'original_comic_info.json', + params.originalComicInfoBytes.length, + params.originalComicInfoBytes, + )); + + // 添加处理后的漫画信息文件 + archive.addFile(ArchiveFile( + 'processed_comic_info.json', + params.processedComicInfoBytes.length, + params.processedComicInfoBytes, )); // 添加封面图片 @@ -242,3 +255,60 @@ Future createDownloadDir() async { rethrow; } } + +ComicAllInfoJson comicInfoProcess(ComicAllInfoJson comicInfo) { + return ComicAllInfoJson( + comic: Comic( + id: comicInfo.comic.id, + creator: comicInfo.comic.creator, + title: comicInfo.comic.title.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_'), + description: comicInfo.comic.description, + thumb: comicInfo.comic.thumb, + author: comicInfo.comic.author, + chineseTeam: comicInfo.comic.chineseTeam, + categories: comicInfo.comic.categories, + tags: comicInfo.comic.tags, + pagesCount: comicInfo.comic.pagesCount, + epsCount: comicInfo.comic.epsCount, + finished: comicInfo.comic.finished, + updatedAt: comicInfo.comic.updatedAt, + createdAt: comicInfo.comic.createdAt, + allowDownload: comicInfo.comic.allowDownload, + allowComment: comicInfo.comic.allowComment, + totalLikes: comicInfo.comic.totalLikes, + totalViews: comicInfo.comic.totalViews, + totalComments: comicInfo.comic.totalComments, + viewsCount: comicInfo.comic.viewsCount, + likesCount: comicInfo.comic.likesCount, + commentsCount: comicInfo.comic.commentsCount, + isFavourite: comicInfo.comic.isFavourite, + isLiked: comicInfo.comic.isLiked, + ), + eps: Eps( + docs: comicInfo.eps.docs + .map((ep) => EpsDoc( + id: ep.id, + title: + "${ep.order}.${ep.title.replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_')}", + order: ep.order, + updatedAt: ep.updatedAt, + docId: ep.docId, + pages: Pages( + docs: ep.pages.docs + .map((page) => PagesDoc( + id: page.id, + media: Thumb( + originalName: page.media.originalName + .replaceAll(RegExp(r'[<>:"/\\|?* ]'), '_'), + path: page.media.path, + fileServer: page.media.fileServer, + ), + docId: page.docId, + )) + .toList(), + ), + )) + .toList(), + ), + ); +} diff --git a/lib/page/comic_info/view/comic_info.dart b/lib/page/comic_info/view/comic_info.dart index 2fc9e04..b16827e 100644 --- a/lib/page/comic_info/view/comic_info.dart +++ b/lib/page/comic_info/view/comic_info.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -79,18 +81,27 @@ class _ComicInfoState extends State<_ComicInfo> super.initState(); _type = type ?? ComicEntryType.normal; // 首先查询一下有没有记录 - final query = objectbox.bikaHistoryBox - .query(BikaComicHistory_.comicId.equals(widget.comicId)); - comicHistory = query.build().findFirst(); + comicHistory = objectbox.bikaHistoryBox + .query(BikaComicHistory_.comicId.equals(widget.comicId)) + .build() + .findFirst(); + + var temp = jsonEncode(comicHistory?.toJson()); + + debugPrint('comicHistory: $temp'); + if (_type == ComicEntryType.download) { - final query = objectbox.bikaDownloadBox - .query(BikaComicDownload_.comicId.equals(widget.comicId)); - comicDownload = query.build().findFirst(); + comicDownload = objectbox.bikaDownloadBox + .query(BikaComicDownload_.comicId.equals(widget.comicId)) + .build() + .findFirst(); + if (comicDownload != null) { comicAllInfo = comic_all_info_json .comicAllInfoJsonFromJson(comicDownload!.comicInfoAll); comicInfo = comicAllInfo2Comic(comicAllInfo!); } + var epsDoc = comicAllInfo!.eps.docs; for (var epDoc in epsDoc) { epsInfo.add(Doc( diff --git a/lib/page/comic_info/widgets/cover.dart b/lib/page/comic_info/widgets/cover.dart index 49e4949..3b528b5 100644 --- a/lib/page/comic_info/widgets/cover.dart +++ b/lib/page/comic_info/widgets/cover.dart @@ -6,6 +6,7 @@ import 'package:loading_animation_widget/loading_animation_widget.dart'; import 'package:zephyr/widgets/picture_bloc/models/picture_info.dart'; import '../../../config/global.dart'; +import '../../../main.dart'; import '../../../widgets/full_screen_image_view.dart'; import '../../../widgets/picture_bloc/bloc/picture_bloc.dart'; @@ -42,7 +43,7 @@ class Cover extends StatelessWidget { return Padding( padding: const EdgeInsets.all(16.0), child: LoadingAnimationWidget.waveDots( - color: Colors.blue, + color: materialColorScheme.primaryFixedDim, size: 50, ), ); diff --git a/lib/page/comic_info/widgets/creator_info.dart b/lib/page/comic_info/widgets/creator_info.dart index e9c48b4..4b7f873 100644 --- a/lib/page/comic_info/widgets/creator_info.dart +++ b/lib/page/comic_info/widgets/creator_info.dart @@ -22,12 +22,18 @@ class CreatorInfoWidget extends StatelessWidget { const CreatorInfoWidget({super.key, required this.comicInfo}); String timeDecode(DateTime originalTime) { - // 加上8个小时 - DateTime newDateTime = originalTime.add(const Duration(hours: 8)); + // 获取当前设备的时区偏移量 + Duration timeZoneOffset = DateTime.now().timeZoneOffset; + + // 根据时区偏移量调整时间 + DateTime newDateTime = originalTime.add(timeZoneOffset); // 按照指定格式输出 String formattedTime = - '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ${newDateTime.hour.toString().padLeft(2, '0')}:${newDateTime.minute.toString().padLeft(2, '0')}:${newDateTime.second.toString().padLeft(2, '0')}'; + '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ' + '${newDateTime.hour.toString().padLeft(2, '0')}:' + '${newDateTime.minute.toString().padLeft(2, '0')}:' + '${newDateTime.second.toString().padLeft(2, '0')}'; return "$formattedTime 更新"; } diff --git a/lib/page/comic_info/widgets/eps.dart b/lib/page/comic_info/widgets/eps.dart index a2f46a2..e0c204b 100644 --- a/lib/page/comic_info/widgets/eps.dart +++ b/lib/page/comic_info/widgets/eps.dart @@ -211,11 +211,24 @@ class EpButtonWidget extends StatelessWidget { String timeDecode(DateTime originalTime, {bool history = false}) { DateTime newDateTime; - history - ? newDateTime = originalTime - : newDateTime = originalTime.add(const Duration(hours: 8)); + + if (history) { + // 如果是历史记录,直接使用原始时间 + newDateTime = originalTime; + } else { + // 如果不是历史记录,获取当前设备的时区偏移量并调整时间 + Duration timeZoneOffset = DateTime.now().timeZoneOffset; + newDateTime = originalTime.add(timeZoneOffset); + } + + // 格式化时间 String formattedTime = - '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ${newDateTime.hour.toString().padLeft(2, '0')}:${newDateTime.minute.toString().padLeft(2, '0')}:${newDateTime.second.toString().padLeft(2, '0')}'; + '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ' + '${newDateTime.hour.toString().padLeft(2, '0')}:' + '${newDateTime.minute.toString().padLeft(2, '0')}:' + '${newDateTime.second.toString().padLeft(2, '0')}'; + + // 返回格式化后的时间 return history ? "$formattedTime 观看" : "$formattedTime 更新"; } } diff --git a/lib/page/comic_info/widgets/recommend.dart b/lib/page/comic_info/widgets/recommend.dart index fd96287..05f8783 100644 --- a/lib/page/comic_info/widgets/recommend.dart +++ b/lib/page/comic_info/widgets/recommend.dart @@ -188,7 +188,7 @@ class _Cover extends StatelessWidget { return Padding( padding: const EdgeInsets.all(16.0), child: LoadingAnimationWidget.waveDots( - color: Colors.blue, + color: materialColorScheme.primaryFixedDim, size: 50, ), ); diff --git a/lib/page/comic_read/method/method.dart b/lib/page/comic_read/method/method.dart index 0b83d57..9f8c58b 100644 --- a/lib/page/comic_read/method/method.dart +++ b/lib/page/comic_read/method/method.dart @@ -62,7 +62,7 @@ BikaComicHistory comicToBikaComicHistory( commentsCount: comic.commentsCount, isFavourite: comic.isFavourite, isLiked: comic.isLiked, - history: DateTime.now().toUtc().add(Duration(hours: 8)), + history: DateTime.now().toUtc(), epTitle: "", order: 0, epPageCount: 0, diff --git a/lib/page/comments/widgets/comments.dart b/lib/page/comments/widgets/comments.dart index de845b9..2e166ea 100644 --- a/lib/page/comments/widgets/comments.dart +++ b/lib/page/comments/widgets/comments.dart @@ -99,7 +99,7 @@ class _CommentsWidgetState extends State mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(commentInfo.user.name), + SelectableText(commentInfo.user.name), Text( "level:${commentInfo.user.level} (${commentInfo.user.title})", style: TextStyle( @@ -108,7 +108,7 @@ class _CommentsWidgetState extends State : materialColorScheme.tertiary, ), ), - Text( + SelectableText( commentInfo.content, style: TextStyle( color: globalSetting.textColor, @@ -190,12 +190,18 @@ class _CommentsWidgetState extends State } String timeDecode(DateTime originalTime) { - // 加上8个小时 - DateTime newDateTime = originalTime.add(const Duration(hours: 8)); + // 获取当前设备的时区偏移量 + Duration timeZoneOffset = DateTime.now().timeZoneOffset; + + // 根据时区偏移量调整时间 + DateTime newDateTime = originalTime.add(timeZoneOffset); // 按照指定格式输出 String formattedTime = - '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ${newDateTime.hour.toString().padLeft(2, '0')}:${newDateTime.minute.toString().padLeft(2, '0')}:${newDateTime.second.toString().padLeft(2, '0')}'; + '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ' + '${newDateTime.hour.toString().padLeft(2, '0')}:' + '${newDateTime.minute.toString().padLeft(2, '0')}:' + '${newDateTime.second.toString().padLeft(2, '0')}'; return formattedTime; } @@ -296,7 +302,7 @@ class _ImagerWidgetState extends State { case PictureLoadStatus.initial: return Center( child: LoadingAnimationWidget.waveDots( - color: globalSetting.textColor, + color: materialColorScheme.primaryFixedDim, size: 25, ), ); diff --git a/lib/page/comments_children/widgets/comments_children.dart b/lib/page/comments_children/widgets/comments_children.dart index 786af39..65986ea 100644 --- a/lib/page/comments_children/widgets/comments_children.dart +++ b/lib/page/comments_children/widgets/comments_children.dart @@ -98,7 +98,7 @@ class _CommentsChildrenWidgetState extends State mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(commentInfo.user.name), + SelectableText(commentInfo.user.name), Text( "level:${commentInfo.user.level} (${commentInfo.user.title})", style: TextStyle( @@ -107,7 +107,7 @@ class _CommentsChildrenWidgetState extends State : Colors.yellow, ), ), - Text( + SelectableText( commentInfo.content, style: TextStyle( color: globalSetting.textColor, @@ -169,12 +169,18 @@ class _CommentsChildrenWidgetState extends State } String timeDecode(DateTime originalTime) { - // 加上8个小时 - DateTime newDateTime = originalTime.add(const Duration(hours: 8)); + // 获取当前设备的时区偏移量 + Duration timeZoneOffset = DateTime.now().timeZoneOffset; + + // 根据时区偏移量调整时间 + DateTime newDateTime = originalTime.add(timeZoneOffset); // 按照指定格式输出 String formattedTime = - '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ${newDateTime.hour.toString().padLeft(2, '0')}:${newDateTime.minute.toString().padLeft(2, '0')}:${newDateTime.second.toString().padLeft(2, '0')}'; + '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ' + '${newDateTime.hour.toString().padLeft(2, '0')}:' + '${newDateTime.minute.toString().padLeft(2, '0')}:' + '${newDateTime.second.toString().padLeft(2, '0')}'; return formattedTime; } @@ -266,7 +272,7 @@ class ImagerWidget extends StatelessWidget { case PictureLoadStatus.initial: return Center( child: LoadingAnimationWidget.waveDots( - color: globalSetting.textColor, + color: materialColorScheme.primaryFixedDim, size: 25, ), ); diff --git a/lib/page/comments_children/widgets/father_comments.dart b/lib/page/comments_children/widgets/father_comments.dart index 55c6c1f..26fff7d 100644 --- a/lib/page/comments_children/widgets/father_comments.dart +++ b/lib/page/comments_children/widgets/father_comments.dart @@ -86,14 +86,14 @@ class _FatherCommentsWidgetState extends State mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(commentInfo.user.name), + SelectableText(commentInfo.user.name), Text( "level:${commentInfo.user.level} (${commentInfo.user.title})", style: TextStyle( color: materialColorScheme.tertiary, ), ), - Text( + SelectableText( commentInfo.content, style: TextStyle( color: globalSetting.textColor, @@ -147,12 +147,18 @@ class _FatherCommentsWidgetState extends State } String timeDecode(DateTime originalTime) { - // 加上8个小时 - DateTime newDateTime = originalTime.add(const Duration(hours: 8)); + // 获取当前设备的时区偏移量 + Duration timeZoneOffset = DateTime.now().timeZoneOffset; + + // 根据时区偏移量调整时间 + DateTime newDateTime = originalTime.add(timeZoneOffset); // 按照指定格式输出 String formattedTime = - '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ${newDateTime.hour.toString().padLeft(2, '0')}:${newDateTime.minute.toString().padLeft(2, '0')}:${newDateTime.second.toString().padLeft(2, '0')}'; + '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ' + '${newDateTime.hour.toString().padLeft(2, '0')}:' + '${newDateTime.minute.toString().padLeft(2, '0')}:' + '${newDateTime.second.toString().padLeft(2, '0')}'; return formattedTime; } @@ -243,7 +249,7 @@ class _ImagerWidget extends StatelessWidget { case PictureLoadStatus.initial: return Center( child: LoadingAnimationWidget.waveDots( - color: globalSetting.textColor, + color: materialColorScheme.primaryFixedDim, size: 25, ), ); diff --git a/lib/page/download/json/comic_all_info_json_no_freeze/comic_all_info_json_no_freeze.dart b/lib/page/download/json/comic_all_info_json_no_freeze/comic_all_info_json_no_freeze.dart index 9f8f2a5..2c96c3b 100644 --- a/lib/page/download/json/comic_all_info_json_no_freeze/comic_all_info_json_no_freeze.dart +++ b/lib/page/download/json/comic_all_info_json_no_freeze/comic_all_info_json_no_freeze.dart @@ -93,8 +93,8 @@ class Comic { pagesCount = 0, epsCount = 0, finished = false, - updatedAt = DateTime.now(), - createdAt = DateTime.now(), + updatedAt = DateTime.now().toUtc(), + createdAt = DateTime.now().toUtc(), allowDownload = false, allowComment = false, totalLikes = 0, diff --git a/lib/page/download/view/download.dart b/lib/page/download/view/download.dart index 8e360c5..64c96fa 100644 --- a/lib/page/download/view/download.dart +++ b/lib/page/download/view/download.dart @@ -432,7 +432,7 @@ class _DownloadPageState extends State { commentsCount: comicInfo.commentsCount, isFavourite: comicInfo.isFavourite, isLiked: comicInfo.isLiked, - downloadTime: DateTime.now(), + downloadTime: DateTime.now().toUtc(), epsTitle: epsTitle, comicInfoAll: comicAllInfoStr, ); diff --git a/lib/page/download/widgets/eps.dart b/lib/page/download/widgets/eps.dart index 2d42e69..44ec4fd 100644 --- a/lib/page/download/widgets/eps.dart +++ b/lib/page/download/widgets/eps.dart @@ -119,10 +119,19 @@ class _EpsWidgetState extends State { } String timeDecode(DateTime originalTime) { - DateTime newDateTime; - newDateTime = originalTime.add(const Duration(hours: 8)); + // 获取当前设备的时区偏移量 + Duration timeZoneOffset = DateTime.now().timeZoneOffset; + + // 根据时区偏移量调整时间 + DateTime newDateTime = originalTime.add(timeZoneOffset); + + // 按照指定格式输出 String formattedTime = - '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ${newDateTime.hour.toString().padLeft(2, '0')}:${newDateTime.minute.toString().padLeft(2, '0')}:${newDateTime.second.toString().padLeft(2, '0')}'; + '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ' + '${newDateTime.hour.toString().padLeft(2, '0')}:' + '${newDateTime.minute.toString().padLeft(2, '0')}:' + '${newDateTime.second.toString().padLeft(2, '0')}'; + return "$formattedTime 更新"; } } diff --git a/lib/page/home/widgets/category.dart b/lib/page/home/widgets/category.dart index cd38f81..00232c3 100644 --- a/lib/page/home/widgets/category.dart +++ b/lib/page/home/widgets/category.dart @@ -87,7 +87,7 @@ class CategoryWidget extends StatelessWidget { case PictureLoadStatus.initial: return Center( child: LoadingAnimationWidget.waveDots( - color: globalSetting.textColor, + color: materialColorScheme.primaryFixedDim, size: 25, ), ); diff --git a/lib/page/ranking_list/widgets/comic_picture.dart b/lib/page/ranking_list/widgets/comic_picture.dart index 59f93de..0729460 100644 --- a/lib/page/ranking_list/widgets/comic_picture.dart +++ b/lib/page/ranking_list/widgets/comic_picture.dart @@ -51,7 +51,7 @@ class ComicPictureWidget extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(16.0), child: LoadingAnimationWidget.waveDots( - color: Colors.blue, + color: materialColorScheme.primaryFixedDim, size: 50, ), ), diff --git a/lib/page/ranking_list/widgets/creator_picture.dart b/lib/page/ranking_list/widgets/creator_picture.dart index e1eeb91..d8ea2c8 100644 --- a/lib/page/ranking_list/widgets/creator_picture.dart +++ b/lib/page/ranking_list/widgets/creator_picture.dart @@ -44,7 +44,7 @@ class CreatorPictureWidget extends StatelessWidget { width: 50, height: 50, child: LoadingAnimationWidget.waveDots( - color: globalSetting.textColor, + color: materialColorScheme.primaryFixedDim, size: 25, ), ), diff --git a/lib/page/setting/view/global_setting.dart b/lib/page/setting/view/global_setting.dart index 3ed8b8f..af2b5fd 100644 --- a/lib/page/setting/view/global_setting.dart +++ b/lib/page/setting/view/global_setting.dart @@ -1,13 +1,11 @@ -import 'dart:convert'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:zephyr/main.dart'; -import '../../../object_box/model.dart'; import '../../../util/router/router.gr.dart'; +import '../../../util/webdav.dart'; import 'global/widgets.dart'; @RoutePage() @@ -48,12 +46,12 @@ class _GlobalSettingPageState extends State { ], _isAMOLED(), // divider(), - SizedBox(height: 10), + // SizedBox(height: 10), // webdavSync(context), - SizedBox(height: 10), - if (globalSetting.webdavHost.isNotEmpty) ...[ - _autoSync(), - ], + // SizedBox(height: 10), + // if (globalSetting.webdavHost.isNotEmpty) ...[ + // _autoSync(), + // ], if (kDebugMode) ...[ ElevatedButton( onPressed: () { @@ -65,15 +63,8 @@ class _GlobalSettingPageState extends State { if (kDebugMode) ...[ ElevatedButton( onPressed: () async { - var temp = - jsonEncode(objectbox.bikaHistoryBox.getAll()[1].toJson()); - - debugPrint(temp); - - final history = BikaComicHistory.fromJson( - objectbox.bikaHistoryBox.getAll()[1].toJson(), - ); - debugPrint(history.toString()); + var temp = await getLastModifiedTime("/Breeze/history.zst"); + debugPrint("temp: ${temp.toString()}"); }, child: Text("测试用的玩意儿"), ), diff --git a/lib/page/setting/view/setting.dart b/lib/page/setting/view/setting.dart index 9626e5f..178d857 100644 --- a/lib/page/setting/view/setting.dart +++ b/lib/page/setting/view/setting.dart @@ -1,6 +1,5 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:zephyr/config/global.dart'; import 'package:zephyr/util/router/router.gr.dart'; @@ -14,78 +13,76 @@ class SettingsPage extends StatelessWidget { return Scaffold( appBar: AppBar(title: const Text('设置')), - body: Observer(builder: (context) { - return Row( - children: [ - SizedBox(width: 16), - Column( - children: [ - SizedBox(height: 20), - GestureDetector( - onTap: () { - router.push(BikaSettingRoute()); - debugPrint("哔咔设置"); - }, - behavior: HitTestBehavior.opaque, // 使得所有透明区域也可以响应点击 - child: SizedBox( - width: screenWidth - 16 - 16, - height: 40, // 设置固定高度 - child: Row( - children: [ - Icon(Icons.person), - SizedBox(width: 10), - Text("哔咔设置", style: TextStyle(fontSize: 22)), - Spacer(), // 填充剩余空间,但不影响点击 - ], - ), + body: Row( + children: [ + SizedBox(width: 16), + Column( + children: [ + SizedBox(height: 20), + GestureDetector( + onTap: () { + router.push(BikaSettingRoute()); + debugPrint("哔咔设置"); + }, + behavior: HitTestBehavior.opaque, // 使得所有透明区域也可以响应点击 + child: SizedBox( + width: screenWidth - 16 - 16, + height: 40, // 设置固定高度 + child: Row( + children: [ + Icon(Icons.person), + SizedBox(width: 10), + Text("哔咔设置", style: TextStyle(fontSize: 22)), + Spacer(), // 填充剩余空间,但不影响点击 + ], ), ), - SizedBox(height: 15), - GestureDetector( - onTap: () { - router.push(GlobalSettingRoute()); - debugPrint("全局设置"); - }, - behavior: HitTestBehavior.opaque, // 使得所有透明区域也可以响应点击 - child: SizedBox( - width: screenWidth - 16 - 16, - height: 40, // 设置固定高度 - child: Row( - children: [ - Icon(Icons.settings), - SizedBox(width: 10), - Text("全局设置", style: TextStyle(fontSize: 22)), - Spacer(), // 填充剩余空间,但不影响点击 - ], - ), + ), + SizedBox(height: 15), + GestureDetector( + onTap: () { + router.push(GlobalSettingRoute()); + debugPrint("全局设置"); + }, + behavior: HitTestBehavior.opaque, // 使得所有透明区域也可以响应点击 + child: SizedBox( + width: screenWidth - 16 - 16, + height: 40, // 设置固定高度 + child: Row( + children: [ + Icon(Icons.settings), + SizedBox(width: 10), + Text("全局设置", style: TextStyle(fontSize: 22)), + Spacer(), // 填充剩余空间,但不影响点击 + ], ), ), - SizedBox(height: 15), - GestureDetector( - onTap: () { - router.push(AboutRoute()); - debugPrint("关于"); - }, - behavior: HitTestBehavior.opaque, // 使得所有透明区域也可以响应点击 - child: SizedBox( - width: screenWidth - 16 - 16, - height: 40, // 设置固定高度 - child: Row( - children: [ - Icon(Icons.info), - SizedBox(width: 10), - Text("关于", style: TextStyle(fontSize: 22)), - Spacer(), // 填充剩余空间,但不影响点击 - ], - ), + ), + SizedBox(height: 15), + GestureDetector( + onTap: () { + router.push(AboutRoute()); + debugPrint("关于"); + }, + behavior: HitTestBehavior.opaque, // 使得所有透明区域也可以响应点击 + child: SizedBox( + width: screenWidth - 16 - 16, + height: 40, // 设置固定高度 + child: Row( + children: [ + Icon(Icons.info), + SizedBox(width: 10), + Text("关于", style: TextStyle(fontSize: 22)), + Spacer(), // 填充剩余空间,但不影响点击 + ], ), ), - ], - ), - SizedBox(width: 16), - ], - ); - }), + ), + ], + ), + SizedBox(width: 16), + ], + ), ); } } diff --git a/lib/page/user_comments/widgets/comment.dart b/lib/page/user_comments/widgets/comment.dart index 6b6bc9b..044d6de 100644 --- a/lib/page/user_comments/widgets/comment.dart +++ b/lib/page/user_comments/widgets/comment.dart @@ -96,7 +96,7 @@ class _CommentsWidgetState extends State mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(userInfo.name), + SelectableText(userInfo.name), Text( "level:${userInfo.level} (${userInfo.title})", style: TextStyle( @@ -105,7 +105,7 @@ class _CommentsWidgetState extends State : materialColorScheme.tertiary, ), ), - Text( + SelectableText( commentInfo.content, style: TextStyle( color: globalSetting.textColor, @@ -210,12 +210,18 @@ class _CommentsWidgetState extends State } String timeDecode(DateTime originalTime) { - // 加上8个小时 - DateTime newDateTime = originalTime.add(const Duration(hours: 8)); + // 获取当前设备的时区偏移量 + Duration timeZoneOffset = DateTime.now().timeZoneOffset; + + // 根据时区偏移量调整时间 + DateTime newDateTime = originalTime.add(timeZoneOffset); // 按照指定格式输出 String formattedTime = - '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ${newDateTime.hour.toString().padLeft(2, '0')}:${newDateTime.minute.toString().padLeft(2, '0')}:${newDateTime.second.toString().padLeft(2, '0')}'; + '${newDateTime.year}年${newDateTime.month}月${newDateTime.day}日 ' + '${newDateTime.hour.toString().padLeft(2, '0')}:' + '${newDateTime.minute.toString().padLeft(2, '0')}:' + '${newDateTime.second.toString().padLeft(2, '0')}'; return formattedTime; } @@ -347,7 +353,7 @@ class _ImagerWidgetState extends State { case PictureLoadStatus.initial: return Center( child: LoadingAnimationWidget.waveDots( - color: globalSetting.textColor, + color: materialColorScheme.primaryFixedDim, size: 25, ), ); diff --git a/lib/page/user_favourite/widgets/entry.dart b/lib/page/user_favourite/widgets/entry.dart index 015dbfb..0256782 100644 --- a/lib/page/user_favourite/widgets/entry.dart +++ b/lib/page/user_favourite/widgets/entry.dart @@ -41,14 +41,6 @@ class ComicEntryWidget extends StatelessWidget { } } - // 截断过长的标题 - String _getLimitedTitle(String title, int maxLength) { - if (title.length > maxLength) { - return '${title.substring(0, maxLength)}...'; - } - return title; - } - @override Widget build(BuildContext context) { final router = AutoRouter.of(context); // 获取 router 实例 @@ -107,12 +99,11 @@ class ComicEntryWidget extends StatelessWidget { if (comicEntryInfo.author.toString() != '') ...[ const SizedBox(height: 4), Text( - _getLimitedTitle( - comicEntryInfo.author.toString(), 40), + comicEntryInfo.author.toString(), + overflow: TextOverflow.ellipsis, + maxLines: 1, style: TextStyle( - color: globalSetting.themeType - ? Colors.red - : Colors.yellow, + color: materialColorScheme.primary, ), ), ], @@ -139,9 +130,7 @@ class ComicEntryWidget extends StatelessWidget { Text( comicEntryInfo.finished ? "完结" : "", style: TextStyle( - color: globalSetting.themeType - ? Colors.red - : Colors.yellow, + color: materialColorScheme.tertiary, ), ), ], @@ -201,7 +190,7 @@ class _ImageWidget extends StatelessWidget { child: Padding( padding: const EdgeInsets.only(left: 20, right: 20), child: LoadingAnimationWidget.waveDots( - color: Colors.blue, + color: materialColorScheme.primaryFixedDim, size: 25, ), ), diff --git a/lib/page/webdav_sync/method/method.dart b/lib/page/webdav_sync/method/method.dart index f88affc..f5222e3 100644 --- a/lib/page/webdav_sync/method/method.dart +++ b/lib/page/webdav_sync/method/method.dart @@ -3,15 +3,14 @@ import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; -import '../../../main.dart'; - +// 测试 WebDAV 服务是否可用 Future testWebDavServer( String host, String username, String password, ) async { final dio = Dio(BaseOptions( - baseUrl: host, // WebDAV服务器地址 + baseUrl: host, // WebDAV 服务器地址 headers: { 'Authorization': 'Basic ${base64Encode( utf8.encode('$username:$password'), @@ -22,80 +21,26 @@ Future testWebDavServer( )); try { - // 测试 PROPFIND 请求 - final response = await dio.request( - '', - options: Options( - method: 'PROPFIND', // 指定请求方法 - headers: { - 'Depth': '1', // 查询根目录及其直接子资源 - }, - ), - data: ''' - - - - - - -''', - ); + // 发送 HEAD 请求测试服务是否可用 + final response = await dio.head('/'); // 检查状态码 - if (response.statusCode == 207) { - debugPrint('WebDAV server is accessible.'); - debugPrint('Response data: ${response.data}'); + if (response.statusCode == 200) { + debugPrint('WebDAV 服务可用'); } else { - // 如果状态码不是 207,抛出包含响应信息的异常 - throw DioException( - requestOptions: response.requestOptions, - response: response, - type: DioExceptionType.badResponse, - error: '服务器返回错误: ${response.statusCode}', - ); + debugPrint('WebDAV 服务返回异常状态码: ${response.statusCode}'); } } on DioException catch (e) { // 捕获 Dio 的错误 if (e.response != null) { - // 如果服务器返回了响应,抛出包含响应信息的异常 - throw DioException( - requestOptions: e.requestOptions, - response: e.response, - type: DioExceptionType.badResponse, - error: '服务器返回错误: ${e.response?.statusCode}\n${e.response?.data}', - ); + // 如果服务器返回了响应 + debugPrint('WebDAV 服务返回错误: ${e.response?.statusCode}'); } else { - // 如果只是 Dio 的错误(如网络连接失败、超时等),抛出 Dio 的错误 - throw DioException( - requestOptions: e.requestOptions, - type: e.type, - error: '连接失败: ${e.message}', - ); + // 如果只是 Dio 的错误(如网络连接失败、超时等) + debugPrint('连接失败: ${e.message}'); } } catch (e) { // 捕获其他未知错误 debugPrint('未知错误: $e'); - throw Exception('未知错误: $e'); - } -} - -Dio? getWebDavDio() { - if (globalSetting.webdavHost.isEmpty || - globalSetting.webdavUsername.isEmpty || - globalSetting.webdavPassword.isEmpty) { - return null; } - - final dio = Dio(BaseOptions( - baseUrl: globalSetting.webdavHost, // WebDAV服务器地址 - headers: { - 'Authorization': 'Basic ${base64Encode( - utf8.encode( - '${globalSetting.webdavUsername}:${globalSetting.webdavPassword}', - ), - )}', - }, - )); - - return dio; } diff --git a/lib/util/webdav.dart b/lib/util/webdav.dart new file mode 100644 index 0000000..21efe6e --- /dev/null +++ b/lib/util/webdav.dart @@ -0,0 +1,359 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:xml/xml.dart' as xml; + +import '../../../main.dart'; + +// 测试 WebDAV 服务是否可用 +Future testWebDavServer() async { + if (globalSetting.webdavHost.isEmpty || + globalSetting.webdavUsername.isEmpty || + globalSetting.webdavPassword.isEmpty) { + throw Exception('WebDAV 配置不完整'); + } + + final dio = Dio(BaseOptions( + baseUrl: globalSetting.webdavHost, // WebDAV 服务器地址 + headers: { + 'Authorization': 'Basic ${base64Encode( + utf8.encode( + '${globalSetting.webdavUsername}:${globalSetting.webdavPassword}'), + )}', + }, + connectTimeout: const Duration(seconds: 10), // 连接超时时间 + receiveTimeout: const Duration(seconds: 10), // 接收超时时间 + )); + + try { + // 发送 HEAD 请求测试服务是否可用 + final response = await dio.head('/'); + + // 检查状态码 + if (response.statusCode == 200) { + debugPrint('WebDAV 服务可用'); + } else { + throw Exception('WebDAV 服务返回异常状态码: ${response.statusCode}'); + } + } on DioException catch (e) { + // 捕获 Dio 的错误 + if (e.response != null) { + // 如果服务器返回了响应,将错误信息和返回值合并后抛出 + throw Exception( + 'WebDAV 服务返回错误: ${e.response?.statusCode}\n${e.response?.data}'); + } else { + // 如果只是 Dio 的错误(如网络连接失败、超时等),直接抛出错误 + throw Exception('连接失败: ${e.message}'); + } + } catch (e) { + // 捕获其他未知错误 + throw Exception('未知错误: $e'); + } +} + +Dio? getWebDavDio() { + if (globalSetting.webdavHost.isEmpty || + globalSetting.webdavUsername.isEmpty || + globalSetting.webdavPassword.isEmpty) { + throw Exception('WebDAV 配置不完整'); + } + + final dio = Dio(BaseOptions( + baseUrl: globalSetting.webdavHost, // WebDAV服务器地址 + headers: { + 'Authorization': 'Basic ${base64Encode( + utf8.encode( + '${globalSetting.webdavUsername}:${globalSetting.webdavPassword}', + ), + )}', + }, + )); + + return dio; +} + +// 创建目录(如果不存在) +Future createParentDirectory(String path) async { + Dio? dio = getWebDavDio(); + if (dio == null) { + throw Exception('WebDAV 配置不完整'); + } + + try { + // 发送 MKCOL 请求创建目录 + Response response = await dio.request( + path, + options: Options(method: 'MKCOL'), + ); + + if (response.statusCode == 201 || response.statusCode == 405) { + debugPrint('目录已存在或创建成功: $path'); + } else { + throw Exception('目录创建失败,状态码: ${response.statusCode}'); + } + } on DioException catch (e) { + if (e.response != null) { + throw Exception( + '目录创建失败: ${e.message}\n${e.response?.statusCode}\n${e.response?.data}'); + } else { + throw Exception('目录创建失败: ${e.message}'); + } + } +} + +// 检查路径是否是文件夹 +Future isDirectory(String path) async { + Dio? dio = getWebDavDio(); + if (dio == null) { + throw Exception('WebDAV 配置不完整'); + } + + try { + // 发送 PROPFIND 请求获取路径属性 + Response response = await dio.request( + path, + options: Options(method: 'PROPFIND'), + ); + + // 解析响应,检查是否是文件夹 + if (response.statusCode == 207) { + debugPrint('路径是文件夹: $path'); + return true; + } else { + throw Exception('路径不是文件夹,状态码: ${response.statusCode}'); + } + } on DioException catch (e) { + if (e.response != null) { + throw Exception( + '检查路径失败: ${e.message}\n${e.response?.statusCode}\n${e.response?.data}', + ); + } else { + throw Exception('检查路径失败: ${e.message}'); + } + } +} + +// 检查文件是否存在,如果不存在则创建 +Future checkOrCreateFile(String filePath) async { + // 获取 Dio 实例 + Dio? dio = getWebDavDio(); + if (dio == null) { + throw Exception('WebDAV 配置不完整'); + } + + // 确保路径是绝对路径 + if (!filePath.startsWith('/')) { + filePath = '/$filePath'; + } + + try { + // 发送 HEAD 请求检查文件是否存在 + Response headResponse = await dio.head(filePath); + + // 如果文件存在 + if (headResponse.statusCode == 200) { + debugPrint('文件已存在,无需创建'); + return; + } + } on DioException catch (e) { + // 如果文件不存在(状态码为 404) + if (e.response?.statusCode == 404) { + debugPrint('文件不存在,尝试创建新文件'); + + // 获取父目录路径 + String parentDir = filePath + .split('/') + .sublist(0, filePath.split('/').length - 1) + .join('/'); + + // 检查父目录是否存在 + if (parentDir.isNotEmpty) { + await createParentDirectory(parentDir); + } + + try { + // 发送 PUT 请求创建新文件 + Response putResponse = await dio.put( + filePath, + data: '', // 可以是一个空文件,或者传入初始内容 + ); + + // 检查 PUT 请求是否成功 + if (putResponse.statusCode == 201 || putResponse.statusCode == 204) { + debugPrint('文件创建成功'); + } else { + throw Exception('文件创建失败,状态码: ${putResponse.statusCode}'); + } + } on DioException catch (e) { + if (e.response != null) { + throw Exception( + '文件创建失败: ${e.message}\n${e.response?.statusCode}\n${e.response?.data}', + ); + } else { + throw Exception('文件创建失败: ${e.message}'); + } + } + } else if (e.response?.statusCode == 409) { + debugPrint('路径冲突,可能是父目录不存在或路径已存在同名文件夹'); + + // 直接尝试创建父目录 + String parentDir = filePath + .split('/') + .sublist(0, filePath.split('/').length - 1) + .join('/'); + if (parentDir.isNotEmpty) { + await createParentDirectory(parentDir); + } + + // 再次尝试创建文件 + try { + Response putResponse = await dio.put( + filePath, + data: '', // 可以是一个空文件,或者传入初始内容 + ); + + if (putResponse.statusCode == 201 || putResponse.statusCode == 204) { + debugPrint('文件创建成功'); + } else { + throw Exception('文件创建失败,状态码: ${putResponse.statusCode}'); + } + } on DioException catch (e) { + if (e.response != null) { + throw Exception( + '文件创建失败: ${e.message}\n${e.response?.statusCode}\n${e.response?.data}', + ); + } else { + throw Exception('文件创建失败: ${e.message}'); + } + } + } else { + if (e.response != null) { + throw Exception( + '请求失败: ${e.message}\n${e.response?.statusCode}\n${e.response?.data}', + ); + } else { + throw Exception('请求失败: ${e.message}'); + } + } + } +} + +// 获取文件的最后修改时间 +Future getLastModifiedTime(String filePath) async { + Dio? dio = getWebDavDio(); + if (dio == null) { + throw Exception('WebDAV 配置不完整'); + } + + try { + // 发送 PROPFIND 请求 + final response = await dio.request( + filePath, + options: Options( + method: 'PROPFIND', // 指定请求方法 + headers: { + 'Depth': '0', // 只查询当前资源 + }, + ), + data: ''' + + + + +''', + ); + + // 检查状态码 + if (response.statusCode == 207) { + // 解析 XML 响应 + final document = xml.XmlDocument.parse(response.data); + + // 定义命名空间 + final davNamespace = 'DAV:'; + + // 查找 节点 + final lastModifiedString = document + .findAllElements('response', namespace: davNamespace) + .expand((response) => + response.findElements('propstat', namespace: davNamespace)) + .expand((propstat) => + propstat.findElements('prop', namespace: davNamespace)) + .expand((prop) => + prop.findElements('getlastmodified', namespace: davNamespace)) + .map((element) => element.innerText) + .firstOrNull; + + if (lastModifiedString != null) { + debugPrint('文件的最后修改时间 (字符串): $lastModifiedString'); + + // 解析 RFC 1123 格式的时间字符串 + final lastModifiedDateTime = parseRfc1123DateTime(lastModifiedString); + if (lastModifiedDateTime != null) { + debugPrint('文件的最后修改时间 (DateTime): $lastModifiedDateTime'); + return lastModifiedDateTime; + } else { + throw Exception('时间解析失败'); + } + } else { + throw Exception('未找到最后修改时间'); + } + } else { + throw Exception('请求失败,状态码: ${response.statusCode}'); + } + } on DioException catch (e) { + if (e.response != null) { + throw Exception( + '请求失败: ${e.message}\n${e.response?.statusCode}\n${e.response?.data}'); + } else { + throw Exception('请求失败: ${e.message}'); + } + } catch (e) { + throw Exception('未知错误: $e'); + } +} + +// 解析 RFC 1123 格式的时间字符串 +DateTime? parseRfc1123DateTime(String dateString) { + try { + // RFC 1123 格式:Sun, 19 Jan 2025 11:18:13 GMT + final months = { + 'Jan': 1, + 'Feb': 2, + 'Mar': 3, + 'Apr': 4, + 'May': 5, + 'Jun': 6, + 'Jul': 7, + 'Aug': 8, + 'Sep': 9, + 'Oct': 10, + 'Nov': 11, + 'Dec': 12, + }; + + // 分割字符串 + final parts = dateString.split(' '); + if (parts.length != 6) { + throw Exception('时间格式不正确: $dateString'); + } + + // 解析日期和时间 + final day = int.parse(parts[1]); + final month = months[parts[2]]; + final year = int.parse(parts[3]); + final time = parts[4].split(':'); + final hour = int.parse(time[0]); + final minute = int.parse(time[1]); + final second = int.parse(time[2]); + + if (month == null) { + throw Exception('月份解析失败: ${parts[2]}'); + } + + // 创建 DateTime 对象 + return DateTime.utc(year, month, day, hour, minute, second); + } catch (e) { + throw Exception('解析时间失败: $e'); + } +} diff --git a/lib/widgets/comic_entry/comic_entry.dart b/lib/widgets/comic_entry/comic_entry.dart index 0762889..67d91ec 100644 --- a/lib/widgets/comic_entry/comic_entry.dart +++ b/lib/widgets/comic_entry/comic_entry.dart @@ -326,7 +326,7 @@ class ImageWidget extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(16.0), child: LoadingAnimationWidget.waveDots( - color: Colors.blue, + color: materialColorScheme.primaryFixedDim, size: 50, ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 0f29678..0e45863 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: zephyr description: "使用Flutter开发的三方哔咔应用。" -version: 1.23.1+66 +version: 1.23.3+67 environment: sdk: ">=3.0.0"