From 9122dd7f3a16c3259b262ee52558fb9b0ca426df Mon Sep 17 00:00:00 2001 From: orz12 Date: Sat, 20 Jan 2024 17:07:10 +0800 Subject: [PATCH] =?UTF-8?q?mod:=20=E6=96=B0=E5=A2=9E=E6=8E=A8=E8=8D=90?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=99=A8=EF=BC=8C=E5=9B=9E=E9=80=80model?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=E4=BF=AE=E6=94=B9=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84futureBuilder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/video_card_h.dart | 5 +- lib/common/widgets/video_card_v.dart | 12 +- lib/http/video.dart | 16 ++- lib/main.dart | 2 + lib/models/home/rcmd/result.dart | 20 ++- lib/models/home/rcmd/result.g.dart | 2 +- lib/models/model_rec_video_item.dart | 11 +- lib/models/model_rec_video_item.g.dart | 4 +- lib/pages/rcmd/controller.dart | 19 ++- lib/pages/rcmd/view.dart | 24 ++-- lib/pages/setting/recommend_setting.dart | 151 ++++++++++++++++++++--- lib/utils/recommend_filter.dart | 52 ++++++++ lib/utils/storage.dart | 5 + lib/utils/utils.dart | 11 +- 14 files changed, 274 insertions(+), 60 deletions(-) create mode 100644 lib/utils/recommend_filter.dart diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index b00f1759..5d39bc65 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -324,8 +324,9 @@ class VideoContent extends StatelessWidget { reSrc: 11, ); SmartDialog.dismiss(); - SmartDialog.showToast( - res['msg'] ?? '成功'); + SmartDialog.showToast(res['code'] == 0 + ? '成功' + : res['msg']); }, child: const Text('确认'), ) diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index c5577af3..dfc7eb94 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -158,12 +158,12 @@ class VideoCardV extends StatelessWidget { height: maxHeight, ), ), - if (videoItem.duration != null) + if (videoItem.duration > 0) if (crossAxisCount == 1) ...[ PBadge( bottom: 10, right: 10, - text: videoItem.duration, + text: Utils.timeFormat(videoItem.duration), ) ] else ...[ PBadge( @@ -171,7 +171,7 @@ class VideoCardV extends StatelessWidget { right: 7, size: 'small', type: 'gray', - text: videoItem.duration, + text: Utils.timeFormat(videoItem.duration), ) ], ], @@ -330,10 +330,8 @@ class VideoStat extends StatelessWidget { color: Theme.of(context).colorScheme.outline, ), children: [ - if (videoItem.stat.view != '-') - TextSpan(text: '${videoItem.stat.view}观看'), - if (videoItem.stat.danmu != '-') - TextSpan(text: ' • ${videoItem.stat.danmu}弹幕'), + TextSpan(text: '${Utils.numFormat(videoItem.stat.view)}观看'), + TextSpan(text: ' • ${Utils.numFormat(videoItem.stat.danmu)}弹幕'), ], ), ); diff --git a/lib/http/video.dart b/lib/http/video.dart index 39c961cd..a48dd11b 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -9,6 +9,7 @@ import '../models/user/fav_folder.dart'; import '../models/video/ai.dart'; import '../models/video/play/url.dart'; import '../models/video_detail_res.dart'; +import '../utils/recommend_filter.dart'; import '../utils/storage.dart'; import '../utils/wbi_sign.dart'; import 'api.dart'; @@ -49,7 +50,10 @@ class VideoHttp { if (i['goto'] == 'av' && (i['owner'] != null && !blackMidsList.contains(i['owner']['mid']))) { - list.add(RecVideoItemModel.fromJson(i)); + RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i); + if (!RecommendFilter.filter(videoItem)){ + list.add(videoItem); + } } } return {'status': true, 'data': list}; @@ -93,7 +97,10 @@ class VideoHttp { (!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) && (i['args'] != null && !blackMidsList.contains(i['args']['up_mid']))) { - list.add(RecVideoItemAppModel.fromJson(i)); + RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i); + if (!RecommendFilter.filter(videoItem)){ + list.add(videoItem); + } } } return {'status': true, 'data': list}; @@ -209,7 +216,10 @@ class VideoHttp { if (res.data['code'] == 0) { List list = []; for (var i in res.data['data']) { - list.add(HotVideoItemModel.fromJson(i)); + HotVideoItemModel videoItem = HotVideoItemModel.fromJson(i); + if (!RecommendFilter.filter(videoItem, relatedVideos: true)){ + list.add(videoItem); + } } return {'status': true, 'data': list}; } else { diff --git a/lib/main.dart b/lib/main.dart index 4e1482d7..29b8d118 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,6 +21,7 @@ import 'package:pilipala/utils/app_scheme.dart'; import 'package:pilipala/utils/data.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc. +import 'package:pilipala/utils/recommend_filter.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -32,6 +33,7 @@ void main() async { await setupServiceLocator(); Request(); await Request.setCookie(); + RecommendFilter(); runApp(const MyApp()); // 小白条、导航栏沉浸 SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); diff --git a/lib/models/home/rcmd/result.dart b/lib/models/home/rcmd/result.dart index a2a8006d..4e810491 100644 --- a/lib/models/home/rcmd/result.dart +++ b/lib/models/home/rcmd/result.dart @@ -40,7 +40,7 @@ class RecVideoItemAppModel { @HiveField(5) RcmdStat? stat; @HiveField(6) - String? duration; + int? duration; @HiveField(7) String? title; @HiveField(8) @@ -79,13 +79,27 @@ class RecVideoItemAppModel { cid = json['player_args'] != null ? json['player_args']['cid'] : -1; pic = json['cover']; stat = RcmdStat.fromJson(json); - duration = json['cover_right_text']; + // 改用player_args中的duration作为原始数据(秒数) + duration = json['player_args'] != null + ? json['player_args']['duration'] + : -1; + //duration = json['cover_right_text']; title = json['title']; - isFollowed = 0; owner = RcmdOwner.fromJson(json); rcmdReason = json['rcmd_reason_style'] != null ? RcmdReason.fromJson(json['rcmd_reason_style']) : null; + // 由于app端api并不会直接返回与owner的关注状态 + // 所以借用推荐原因是否为“已关注”、“新关注”等判别关注状态,从而与web端接口等效 + isFollowed = rcmdReason != null && + rcmdReason!.content != null && + rcmdReason!.content!.contains('关注') + ? 1 + : 0; + // 如果是,就无需再显示推荐原因,交由view统一处理即可 + if (isFollowed == 1) { + rcmdReason = null; + } goto = json['goto']; param = int.parse(json['param']); uri = json['uri']; diff --git a/lib/models/home/rcmd/result.g.dart b/lib/models/home/rcmd/result.g.dart index 43bf4bcf..f46886e8 100644 --- a/lib/models/home/rcmd/result.g.dart +++ b/lib/models/home/rcmd/result.g.dart @@ -23,7 +23,7 @@ class RecVideoItemAppModelAdapter extends TypeAdapter { cid: fields[3] as int?, pic: fields[4] as String?, stat: fields[5] as RcmdStat?, - duration: fields[6] as String?, + duration: fields[6] as int?, title: fields[7] as String?, isFollowed: fields[8] as int?, owner: fields[9] as RcmdOwner?, diff --git a/lib/models/model_rec_video_item.dart b/lib/models/model_rec_video_item.dart index bd42fd82..1503f192 100644 --- a/lib/models/model_rec_video_item.dart +++ b/lib/models/model_rec_video_item.dart @@ -1,5 +1,3 @@ -import 'package:pilipala/utils/utils.dart'; - import './model_owner.dart'; import 'package:hive/hive.dart'; @@ -38,7 +36,7 @@ class RecVideoItemModel { @HiveField(6) String? title = ''; @HiveField(7) - String? duration = ''; + int? duration = -1; @HiveField(8) int? pubdate = -1; @HiveField(9) @@ -58,7 +56,7 @@ class RecVideoItemModel { uri = json["uri"]; pic = json["pic"]; title = json["title"]; - duration = Utils.tampToSeektime(json["duration"]); + duration = json["duration"]; pubdate = json["pubdate"]; owner = Owner.fromJson(json["owner"]); stat = Stat.fromJson(json["stat"]); @@ -77,14 +75,15 @@ class Stat { this.danmu, }); @HiveField(0) - String? view; + int? view; @HiveField(1) int? like; @HiveField(2) int? danmu; Stat.fromJson(Map json) { - view = Utils.numFormat(json["view"]); + // 无需在model中转换以保留原始数据,在view层处理即可 + view = json["view"]; like = json["like"]; danmu = json['danmaku']; } diff --git a/lib/models/model_rec_video_item.g.dart b/lib/models/model_rec_video_item.g.dart index 1de6ab03..dc614354 100644 --- a/lib/models/model_rec_video_item.g.dart +++ b/lib/models/model_rec_video_item.g.dart @@ -24,7 +24,7 @@ class RecVideoItemModelAdapter extends TypeAdapter { uri: fields[4] as String?, pic: fields[5] as String?, title: fields[6] as String?, - duration: fields[7] as String?, + duration: fields[7] as int?, pubdate: fields[8] as int?, owner: fields[9] as Owner?, stat: fields[10] as Stat?, @@ -87,7 +87,7 @@ class StatAdapter extends TypeAdapter { for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; return Stat( - view: fields[0] as String?, + view: fields[0] as int?, like: fields[1] as int?, danmu: fields[2] as int?, ); diff --git a/lib/pages/rcmd/controller.dart b/lib/pages/rcmd/controller.dart index ed98606a..54cb8c14 100644 --- a/lib/pages/rcmd/controller.dart +++ b/lib/pages/rcmd/controller.dart @@ -55,12 +55,13 @@ class RcmdController extends GetxController { } late final Map res; switch (defaultRcmdType) { - case 'app': case 'notLogin': - res = await VideoHttp.rcmdVideoListApp( - loginStatus: defaultRcmdType != 'notLogin', - freshIdx: _currentPage, - ); - break; + case 'app': + case 'notLogin': + res = await VideoHttp.rcmdVideoListApp( + loginStatus: defaultRcmdType != 'notLogin', + freshIdx: _currentPage, + ); + break; default: //'web' res = await VideoHttp.rcmdVideoList( freshIdx: _currentPage, @@ -83,10 +84,16 @@ class RcmdController extends GetxController { } else if (type == 'onLoad') { videoList.addAll(res['data']); } + // 目前仅支持app端系列保存缓存 if (defaultRcmdType != 'web') { recVideo.put('cacheList', res['data']); } _currentPage += 1; + // 若videoList数量太小,可能会影响翻页,此时再次请求 + // 为避免请求到的数据太少时还在反复请求,要求本次返回数据大于1条才触发 + if (res['data'].length > 1 && videoList.length < 10){ + queryRcmdFeed('onLoad'); + } } else { Get.snackbar('提示', res['msg']); } diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 0a345511..687eacd1 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; @@ -8,7 +7,7 @@ import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/skeleton/video_card_v.dart'; import 'package:pilipala/common/widgets/animated_dialog.dart'; -import 'package:pilipala/common/widgets/http_error.dart'; +// import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/overlay_pop.dart'; import 'package:pilipala/common/widgets/video_card_v.dart'; import 'package:pilipala/pages/home/index.dart'; @@ -26,7 +25,6 @@ class RcmdPage extends StatefulWidget { class _RcmdPageState extends State with AutomaticKeepAliveClientMixin { final RcmdController _rcmdController = Get.put(RcmdController()); - late Future _futureBuilderFuture; @override bool get wantKeepAlive => true; @@ -34,7 +32,7 @@ class _RcmdPageState extends State @override void initState() { super.initState(); - _futureBuilderFuture = _rcmdController.queryRcmdFeed('init'); + _rcmdController.queryRcmdFeed('init'); ScrollController scrollController = _rcmdController.scrollController; StreamController mainStream = Get.find().bottomBarStream; @@ -90,21 +88,21 @@ class _RcmdPageState extends State slivers: [ SliverPadding( padding: - const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0), - sliver: Obx(() { - // 使用Obx来监听数据的变化 - if (_rcmdController.isLoadingMore) { - // 如果正在加载,则显示骨架屏 + const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0), + sliver: Obx(() { // 使用Obx来监听数据的变化 + if (_rcmdController.isLoadingMore && _rcmdController.videoList.isEmpty) { return contentGrid(_rcmdController, []); + // 如果正在加载并且列表为空,则显示加载指示器 + // return const SliverToBoxAdapter( + // child: Center(child: CircularProgressIndicator()), + // ); } else { // 显示视频列表 - return contentGrid( - _rcmdController, - _rcmdController.videoList); + return contentGrid(_rcmdController, _rcmdController.videoList); } }), ), - LoadingMore(ctr: _rcmdController) + LoadingMore(ctr: _rcmdController), ], ), ), diff --git a/lib/pages/setting/recommend_setting.dart b/lib/pages/setting/recommend_setting.dart index 8e049bc4..ab8ec063 100644 --- a/lib/pages/setting/recommend_setting.dart +++ b/lib/pages/setting/recommend_setting.dart @@ -4,6 +4,7 @@ import 'package:hive/hive.dart'; import 'package:pilipala/http/member.dart'; import 'package:pilipala/models/common/rcmd_type.dart'; import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; +import 'package:pilipala/utils/recommend_filter.dart'; import 'package:pilipala/utils/storage.dart'; import 'widgets/switch_item.dart'; @@ -23,6 +24,9 @@ class _RecommendSettingState extends State { late dynamic userInfo; bool userLogin = false; late dynamic accessKeyInfo; + // late int filterUnfollowedRatio; + late int minDurationForRcmd; + late int minLikeRatioForRecommend; @override void initState() { @@ -33,6 +37,12 @@ class _RecommendSettingState extends State { userInfo = userInfoCache.get('userInfoCache'); userLogin = userInfo != null; accessKeyInfo = localCache.get(LocalCacheKey.accessKey, defaultValue: null); + // filterUnfollowedRatio = setting + // .get(SettingBoxKey.filterUnfollowedRatio, defaultValue: 0); + minDurationForRcmd = + setting.get(SettingBoxKey.minDurationForRcmd, defaultValue: 0); + minLikeRatioForRecommend = + setting.get(SettingBoxKey.minLikeRatioForRecommend, defaultValue: 0); } @override @@ -53,23 +63,11 @@ class _RecommendSettingState extends State { ), body: ListView( children: [ - const SetSwitchItem( - title: '推荐动态', - subTitle: '是否在推荐内容中展示动态', - setKey: SettingBoxKey.enableRcmdDynamic, - defaultVal: true, - ), - const SetSwitchItem( - title: '首页推荐刷新', - subTitle: '下拉刷新时保留上次内容', - setKey: SettingBoxKey.enableSaveLastData, - defaultVal: false, - ), ListTile( dense: false, title: Text('首页推荐类型', style: titleStyle), subtitle: Text( - '当前使用「$defaultRcmdType端」推荐', + '当前使用「$defaultRcmdType端」推荐¹', style: subTitleStyle, ), onTap: () async { @@ -100,7 +98,7 @@ class _RecommendSettingState extends State { return AlertDialog( title: const Text('提示'), content: const Text( - '使用app端推荐需获取access_key,有小概率触发风控导致账号退出(在官方app重新登录即可解除),是否继续?'), + '使用app端推荐需获取access_key,有小概率触发风控导致账号退出(在官方版本app重新登录即可解除),是否继续?'), actions: [ TextButton( onPressed: () { @@ -130,6 +128,131 @@ class _RecommendSettingState extends State { } }, ), + const SetSwitchItem( + title: '推荐动态', + subTitle: '是否在推荐内容中展示动态(仅app端)', + setKey: SettingBoxKey.enableRcmdDynamic, + defaultVal: true, + ), + const SetSwitchItem( + title: '首页推荐刷新', + subTitle: '下拉刷新时保留上次内容', + setKey: SettingBoxKey.enableSaveLastData, + defaultVal: false, + ), + // 分割线 + const Divider(height: 1), + ListTile( + dense: false, + title: Text('点赞率过滤', style: titleStyle), + subtitle: Text( + '过滤掉点赞数/播放量「小于$minLikeRatioForRecommend%」的推荐视频(仅web端)', + style: subTitleStyle, + ), + onTap: () async { + int? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '选择点赞率(0即不过滤)', + value: minLikeRatioForRecommend, + values: [0, 1, 2, 3, 4].map((e) { + return {'title': '$e %', 'value': e}; + }).toList()); + }, + ); + if (result != null) { + minLikeRatioForRecommend = result; + setting.put(SettingBoxKey.minLikeRatioForRecommend, result); + RecommendFilter.update(); + setState(() {}); + } + }, + ), + ListTile( + dense: false, + title: Text('视频时长过滤', style: titleStyle), + subtitle: Text( + '过滤掉时长「小于$minDurationForRcmd秒」的推荐视频', + style: subTitleStyle, + ), + onTap: () async { + int? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '选择时长(0即不过滤)', + value: minDurationForRcmd, + values: [0, 30, 60, 90, 120].map((e) { + return {'title': '$e 秒', 'value': e}; + }).toList()); + }, + ); + if (result != null) { + minDurationForRcmd = result; + setting.put(SettingBoxKey.minDurationForRcmd, result); + RecommendFilter.update(); + setState(() {}); + } + }, + ), + SetSwitchItem( + title: '已关注Up豁免推荐过滤', + subTitle: '推荐中已关注用户发布的内容不会被过滤', + setKey: SettingBoxKey.exemptFilterForFollowed, + defaultVal: true, + callFn: (_) => {RecommendFilter.update}, + ), + // ListTile( + // dense: false, + // title: Text('按比例过滤未关注Up', style: titleStyle), + // subtitle: Text( + // '滤除推荐中占比「$filterUnfollowedRatio%」的未关注用户发布的内容', + // style: subTitleStyle, + // ), + // onTap: () async { + // int? result = await showDialog( + // context: context, + // builder: (context) { + // return SelectDialog( + // title: '选择滤除比例(0即不过滤)', + // value: filterUnfollowedRatio, + // values: [0, 16, 32, 48, 64].map((e) { + // return {'title': '$e %', 'value': e}; + // }).toList()); + // }, + // ); + // if (result != null) { + // filterUnfollowedRatio = result; + // setting.put( + // SettingBoxKey.filterUnfollowedRatio, result); + // RecommendFilter.update(); + // setState(() {}); + // } + // }, + // ), + SetSwitchItem( + title: '过滤器也应用于相关视频', + subTitle: '视频详情页的相关视频也进行过滤²', + setKey: SettingBoxKey.applyFilterToRelatedVideos, + defaultVal: true, + callFn: (_) => {RecommendFilter.update}, + ), + ListTile( + dense: true, + subtitle: Text( + '¹ 若默认web端推荐不太符合预期,可尝试切换至app端。\n' + '¹ 选择“模拟未登录(notLogin)”,将以空的key请求推荐接口,但播放页仍会携带用户信息,保证账号能正常记录进度、点赞投币等。\n\n' + '² 由于接口未提供关注信息,无法豁免相关视频中的已关注Up。\n\n' + '* 其它(如热门视频、手动搜索、链接跳转等)均不受过滤器影响。\n' + '* 设定较严苛的条件可导致推荐项数锐减或多次请求,请酌情选择。\n' + '* 后续可能会增加更多过滤条件,敬请期待。', + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith(color: Theme.of(context).colorScheme.outline.withOpacity(0.7)), + ), + ) ], ), ); diff --git a/lib/utils/recommend_filter.dart b/lib/utils/recommend_filter.dart new file mode 100644 index 00000000..113e2261 --- /dev/null +++ b/lib/utils/recommend_filter.dart @@ -0,0 +1,52 @@ +import 'dart:math'; + +import 'storage.dart'; + +class RecommendFilter { + // static late int filterUnfollowedRatio; + static late int minDurationForRcmd; + static late int minLikeRatioForRecommend; + static late bool exemptFilterForFollowed; + static late bool applyFilterToRelatedVideos; + RecommendFilter() { + update(); + } + + static void update() { + var setting = GStrorage.setting; + // filterUnfollowedRatio = + // setting.get(SettingBoxKey.filterUnfollowedRatio, defaultValue: 0); + minDurationForRcmd = + setting.get(SettingBoxKey.minDurationForRcmd, defaultValue: 0); + minLikeRatioForRecommend = + setting.get(SettingBoxKey.minLikeRatioForRecommend, defaultValue: 0); + exemptFilterForFollowed = + setting.get(SettingBoxKey.exemptFilterForFollowed, defaultValue: true); + applyFilterToRelatedVideos = setting + .get(SettingBoxKey.applyFilterToRelatedVideos, defaultValue: true); + } + + static bool filter(dynamic videoItem, {bool relatedVideos = false}) { + if (relatedVideos && !applyFilterToRelatedVideos) { + return false; + } + //由于相关视频中没有已关注标签,只能视为非关注视频 + if (!relatedVideos && + videoItem.isFollowed == 1 && + exemptFilterForFollowed) { + return false; + } + if (videoItem.duration > 0 && videoItem.duration < minDurationForRcmd) { + return true; + } + if (videoItem.stat.view is int && + videoItem.stat.view > -1 && + videoItem.stat.like is int && + videoItem.stat.like > -1 && + videoItem.stat.like * 100 < + minLikeRatioForRecommend * videoItem.stat.view) { + return true; + } + return false; + } +} diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index c412606f..fdb9ee02 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -124,6 +124,11 @@ class SettingBoxKey { enableRcmdDynamic = 'enableRcmdDynamic', defaultRcmdType = 'defaultRcmdType', enableSaveLastData = 'enableSaveLastData', + minDurationForRcmd = 'minDurationForRcmd', + minLikeRatioForRecommend = 'minLikeRatioForRecommend', + exemptFilterForFollowed = 'exemptFilterForFollowed', + //filterUnfollowedRatio = 'filterUnfollowedRatio', + applyFilterToRelatedVideos = 'applyFilterToRelatedVideos', /// 其他 autoUpdate = 'autoUpdate', diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 08693d24..f0b56fc4 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -9,7 +9,6 @@ import 'package:crypto/crypto.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get_utils/get_utils.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -28,10 +27,16 @@ class Utils { return tempPath; } - static String numFormat(int number) { + static String numFormat(dynamic number) { + if (number == null){ + return '0'; + } + if (number is String) { + return number; + } final String res = (number / 10000).toString(); if (int.parse(res.split('.')[0]) >= 1) { - return '${(number / 10000).toPrecision(1)}万'; + return '${(number / 10000).toStringAsFixed(1)}万'; } else { return number.toString(); }