diff --git a/README.md b/README.md index a5274cc1..fac2a885 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ home home
+ home
-### 开发环境 +## 开发环境 Xcode 13.4 不支持**auto_orientation**,请注释相关代码 ```bash @@ -30,7 +31,7 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
-### 功能 +## 功能 目前着重移动端(Android、iOS),暂时没有适配桌面端、Pad端、手表端等 @@ -98,17 +99,18 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码 - [x] 图片质量设定 - [x] 主题模式:亮色/暗色/跟随系统 - [x] 震动反馈(可选) + - [x] 高帧率 - [ ] 等等
-### 下载 +## 下载 可以通过右侧release进行下载或拉取代码到本地进行编译
-### 声明 +## 声明 此项目(PiliPala)是个人为了兴趣而开发, 仅用于学习和测试。 所用API皆从官方网站收集, 不提供任何破解内容。 @@ -117,7 +119,13 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
-### 致谢 +## 技术交流 + +Telegram https://t.me/+lm_oOVmF0RJiODk1 + +
+ +## 致谢 - [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect) - [flutter_meedu_videoplayer](https://github.com/zezo357/flutter_meedu_videoplayer) diff --git a/assets/sreenshot/main_screen.png b/assets/sreenshot/main_screen.png new file mode 100644 index 00000000..1f72277e Binary files /dev/null and b/assets/sreenshot/main_screen.png differ diff --git a/change_log/1.0.4.0822.md b/change_log/1.0.4.0822.md new file mode 100644 index 00000000..e2c3553b --- /dev/null +++ b/change_log/1.0.4.0822.md @@ -0,0 +1,21 @@ +## 1.0.4 + +### 新功能 ++ 热搜刷新 ++ 视频搜索排序、筛选 ++ app字体大小自定义 ++ app主题色自定义 ++ 「课堂」类动态渲染 + + +### 修复 ++ 搜索词联想richText渲染异常 ++ 部分动态点赞异常 ++ 默认视频解码格式 ++ 搜索页面返回搜索词未清空 ++ 动态详情评论加载异常 ++ 动态页面下拉刷新数据异常 + +### 优化 ++ 一些样式修改 ++ 取消热搜词缓存 \ No newline at end of file diff --git a/change_log/1.0.5.0826.md b/change_log/1.0.5.0826.md new file mode 100644 index 00000000..c76e9d61 --- /dev/null +++ b/change_log/1.0.5.0826.md @@ -0,0 +1,30 @@ +## 1.0.5 + +主要是bug修复跟一部分小功能,弹幕功能需要下一版。 +问题反馈请前往QQ频道或提交issues。 +感谢🙏酷友「无力感*」「斤斤计较呀」「Pseudopamine」 + +### 新功能 ++ 高帧率支持 ++ 默认评论排序设置 ++ 默认动态类别设置 ++ 动态合集查看 ++ 同时观看人数 ++ iOS路由切换效果 + + +### 修复 ++ 收藏夹翻页 ++ 首页搜索框频繁点击消失 ++ 评论排序切换空白 ++ 快速返回首页 ++ 重复进入个人中心页面数据未刷新 ++ 动态goods数据异常 ++ 大会员切换番剧 ++ 高画质codes匹配 + + +### 优化 ++ 倍速选择 ++ 播放器亮度记忆 ++ 下载对应版本apk \ No newline at end of file diff --git a/lib/common/widgets/app_expansion_panel_list.dart b/lib/common/widgets/app_expansion_panel_list.dart new file mode 100644 index 00000000..4698310a --- /dev/null +++ b/lib/common/widgets/app_expansion_panel_list.dart @@ -0,0 +1,351 @@ +import 'package:flutter/material.dart'; + +const double _kPanelHeaderCollapsedHeight = kMinInteractiveDimension; + +class _SaltedKey extends LocalKey { + const _SaltedKey(this.salt, this.value); + + final S salt; + final V value; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) return false; + return other is _SaltedKey && + other.salt == salt && + other.value == value; + } + + @override + int get hashCode => Object.hash(runtimeType, salt, value); + + @override + String toString() { + final String saltString = S == String ? "<'$salt'>" : '<$salt>'; + final String valueString = V == String ? "<'$value'>" : '<$value>'; + return '[$saltString $valueString]'; + } +} + +class AppExpansionPanelList extends StatefulWidget { + /// Creates an expansion panel list widget. The [expansionCallback] is + /// triggered when an expansion panel expand/collapse button is pushed. + /// + /// The [children] and [animationDuration] arguments must not be null. + const AppExpansionPanelList({ + super.key, + required this.children, + this.expansionCallback, + this.animationDuration = kThemeAnimationDuration, + this.expandedHeaderPadding = EdgeInsets.zero, + this.dividerColor, + this.elevation = 2, + }) : _allowOnlyOnePanelOpen = false, + initialOpenPanelValue = null; + + /// The children of the expansion panel list. They are laid out in a similar + /// fashion to [ListBody]. + final List children; + + /// The callback that gets called whenever one of the expand/collapse buttons + /// is pressed. The arguments passed to the callback are the index of the + /// pressed panel and whether the panel is currently expanded or not. + /// + /// If AppExpansionPanelList.radio is used, the callback may be called a + /// second time if a different panel was previously open. The arguments + /// passed to the second callback are the index of the panel that will close + /// and false, marking that it will be closed. + /// + /// For AppExpansionPanelList, the callback needs to setState when it's notified + /// about the closing/opening panel. On the other hand, the callback for + /// AppExpansionPanelList.radio is simply meant to inform the parent widget of + /// changes, as the radio panels' open/close states are managed internally. + /// + /// This callback is useful in order to keep track of the expanded/collapsed + /// panels in a parent widget that may need to react to these changes. + final ExpansionPanelCallback? expansionCallback; + + /// The duration of the expansion animation. + final Duration animationDuration; + + // Whether multiple panels can be open simultaneously + final bool _allowOnlyOnePanelOpen; + + /// The value of the panel that initially begins open. (This value is + /// only used when initializing with the [AppExpansionPanelList.radio] + /// constructor.) + final Object? initialOpenPanelValue; + + /// The padding that surrounds the panel header when expanded. + /// + /// By default, 16px of space is added to the header vertically (above and below) + /// during expansion. + final EdgeInsets expandedHeaderPadding; + + /// Defines color for the divider when [AppExpansionPanel.isExpanded] is false. + /// + /// If `dividerColor` is null, then [DividerThemeData.color] is used. If that + /// is null, then [ThemeData.dividerColor] is used. + final Color? dividerColor; + + /// Defines elevation for the [AppExpansionPanel] while it's expanded. + /// + /// By default, the value of elevation is 2. + final double elevation; + + @override + State createState() => _AppExpansionPanelListState(); +} + +class _AppExpansionPanelListState extends State { + ExpansionPanelRadio? _currentOpenPanel; + + @override + void initState() { + super.initState(); + if (widget._allowOnlyOnePanelOpen) { + assert(_allIdentifiersUnique(), + 'All ExpansionPanelRadio identifier values must be unique.'); + if (widget.initialOpenPanelValue != null) { + _currentOpenPanel = searchPanelByValue( + widget.children.cast(), + widget.initialOpenPanelValue); + } + } + } + + @override + void didUpdateWidget(AppExpansionPanelList oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget._allowOnlyOnePanelOpen) { + assert(_allIdentifiersUnique(), + 'All ExpansionPanelRadio identifier values must be unique.'); + // If the previous widget was non-radio AppExpansionPanelList, initialize the + // open panel to widget.initialOpenPanelValue + if (!oldWidget._allowOnlyOnePanelOpen) { + _currentOpenPanel = searchPanelByValue( + widget.children.cast(), + widget.initialOpenPanelValue); + } + } else { + _currentOpenPanel = null; + } + } + + bool _allIdentifiersUnique() { + final Map identifierMap = {}; + for (final ExpansionPanelRadio child + in widget.children.cast()) { + identifierMap[child.value] = true; + } + return identifierMap.length == widget.children.length; + } + + bool _isChildExpanded(int index) { + if (widget._allowOnlyOnePanelOpen) { + final ExpansionPanelRadio radioWidget = + widget.children[index] as ExpansionPanelRadio; + return _currentOpenPanel?.value == radioWidget.value; + } + return widget.children[index].isExpanded; + } + + void _handlePressed(bool isExpanded, int index) { + widget.expansionCallback?.call(index, isExpanded); + + if (widget._allowOnlyOnePanelOpen) { + final ExpansionPanelRadio pressedChild = + widget.children[index] as ExpansionPanelRadio; + + // If another ExpansionPanelRadio was already open, apply its + // expansionCallback (if any) to false, because it's closing. + for (int childIndex = 0; + childIndex < widget.children.length; + childIndex += 1) { + final ExpansionPanelRadio child = + widget.children[childIndex] as ExpansionPanelRadio; + if (widget.expansionCallback != null && + childIndex != index && + child.value == _currentOpenPanel?.value) { + widget.expansionCallback!(childIndex, false); + } + } + + setState(() { + _currentOpenPanel = isExpanded ? null : pressedChild; + }); + } + } + + ExpansionPanelRadio? searchPanelByValue( + List panels, Object? value) { + for (final ExpansionPanelRadio panel in panels) { + if (panel.value == value) return panel; + } + return null; + } + + @override + Widget build(BuildContext context) { + assert( + kElevationToShadow.containsKey(widget.elevation), + 'Invalid value for elevation. See the kElevationToShadow constant for' + ' possible elevation values.', + ); + + final List items = []; + + for (int index = 0; index < widget.children.length; index += 1) { + //todo: Uncomment to add gap between selected panels + /*if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1)) + items.add(MaterialGap(key: _SaltedKey(context, index * 2 - 1)));*/ + + final AppExpansionPanel child = widget.children[index]; + final Widget headerWidget = child.headerBuilder( + context, + _isChildExpanded(index), + ); + + Widget? expandIconContainer = ExpandIcon( + isExpanded: _isChildExpanded(index), + onPressed: !child.canTapOnHeader + ? (bool isExpanded) => _handlePressed(isExpanded, index) + : null, + ); + if (!child.canTapOnHeader) { + final MaterialLocalizations localizations = + MaterialLocalizations.of(context); + expandIconContainer = Semantics( + label: _isChildExpanded(index) + ? localizations.expandedIconTapHint + : localizations.collapsedIconTapHint, + container: true, + child: expandIconContainer, + ); + } + + final iconContainer = child.iconBuilder; + if (iconContainer != null) { + expandIconContainer = iconContainer( + expandIconContainer, + _isChildExpanded(index), + ); + } + + Widget header = Row( + children: [ + Expanded( + child: AnimatedContainer( + duration: widget.animationDuration, + curve: Curves.fastOutSlowIn, + margin: _isChildExpanded(index) + ? widget.expandedHeaderPadding + : EdgeInsets.zero, + child: ConstrainedBox( + constraints: const BoxConstraints( + minHeight: _kPanelHeaderCollapsedHeight), + child: headerWidget, + ), + ), + ), + if (expandIconContainer != null) expandIconContainer, + ], + ); + if (child.canTapOnHeader) { + header = MergeSemantics( + child: InkWell( + onTap: () => _handlePressed(_isChildExpanded(index), index), + child: header, + ), + ); + } + items.add( + MaterialSlice( + key: _SaltedKey(context, index * 2), + color: child.backgroundColor, + child: Column( + children: [ + header, + AnimatedCrossFade( + firstChild: Container(height: 0.0), + secondChild: child.body, + firstCurve: + const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn), + secondCurve: + const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn), + sizeCurve: Curves.fastOutSlowIn, + crossFadeState: _isChildExpanded(index) + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + duration: widget.animationDuration, + ), + ], + ), + ), + ); + + if (_isChildExpanded(index) && index != widget.children.length - 1) { + items.add(MaterialGap( + key: _SaltedKey(context, index * 2 + 1))); + } + } + + return MergeableMaterial( + hasDividers: true, + dividerColor: widget.dividerColor, + elevation: widget.elevation, + children: items, + ); + } +} + +typedef ExpansionPanelIconBuilder = Widget? Function( + Widget child, + bool isExpanded, +); + +class AppExpansionPanel { + /// Creates an expansion panel to be used as a child for [ExpansionPanelList]. + /// See [ExpansionPanelList] for an example on how to use this widget. + /// + /// The [headerBuilder], [body], and [isExpanded] arguments must not be null. + AppExpansionPanel({ + required this.headerBuilder, + required this.body, + this.iconBuilder, + this.isExpanded = false, + this.canTapOnHeader = false, + this.backgroundColor, + }); + + /// The widget builder that builds the expansion panels' header. + final ExpansionPanelHeaderBuilder headerBuilder; + + /// The widget builder that builds the expansion panels' icon. + /// + /// If not pass any function, then default icon will be displayed. + /// + /// If builder function return null, then icon will not displayed. + final ExpansionPanelIconBuilder? iconBuilder; + + /// The body of the expansion panel that's displayed below the header. + /// + /// This widget is visible only when the panel is expanded. + final Widget body; + + /// Whether the panel is expanded. + /// + /// Defaults to false. + final bool isExpanded; + + /// Whether tapping on the panel's header will expand/collapse it. + /// + /// Defaults to false. + final bool canTapOnHeader; + + /// Defines the background color of the panel. + /// + /// Defaults to [ThemeData.cardColor]. + final Color? backgroundColor; +} diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index ed7b299e..6d4fa61a 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -58,8 +58,11 @@ class VideoCardH extends StatelessWidget { StyleString.safeSpace, 5, StyleString.safeSpace, 5), child: LayoutBuilder( builder: (context, boxConstraints) { - double width = - (boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; + double width = (boxConstraints.maxWidth - + StyleString.cardSpace * + 6 / + MediaQuery.of(context).textScaleFactor) / + 2; return Container( constraints: const BoxConstraints(minHeight: 88), height: width / StyleString.aspectRatio, @@ -123,7 +126,7 @@ class VideoContent extends StatelessWidget { Widget build(BuildContext context) { return Expanded( child: Padding( - padding: const EdgeInsets.fromLTRB(10, 2, 6, 0), + padding: const EdgeInsets.fromLTRB(10, 0, 6, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -132,7 +135,6 @@ class VideoContent extends StatelessWidget { videoItem.title, textAlign: TextAlign.start, style: const TextStyle( - fontSize: 13, fontWeight: FontWeight.w500, ), maxLines: 2, @@ -147,7 +149,6 @@ class VideoContent extends StatelessWidget { TextSpan( text: i['text'], style: TextStyle( - fontSize: 13, fontWeight: FontWeight.w500, letterSpacing: 0.3, color: i['type'] == 'em' @@ -177,7 +178,7 @@ class VideoContent extends StatelessWidget { // color: Theme.of(context).colorScheme.surfaceTint), // ), // ), - const SizedBox(height: 4), + // const SizedBox(height: 4), Row( children: [ Text( diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 53300d3e..eb37d0e1 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -77,11 +77,8 @@ class VideoCardV extends StatelessWidget { Widget build(BuildContext context) { String heroTag = Utils.makeHeroTag(videoItem.id); return Card( - elevation: 0, + elevation: 1, clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: StyleString.mdRadius, - ), margin: EdgeInsets.zero, child: GestureDetector( onLongPress: () { @@ -129,14 +126,13 @@ class VideoContent extends StatelessWidget { Widget build(BuildContext context) { return Expanded( child: Padding( - padding: const EdgeInsets.fromLTRB(4, 8, 0, 3), + padding: const EdgeInsets.fromLTRB(9, 8, 9, 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( videoItem.title, - style: const TextStyle(fontSize: 13), maxLines: 2, overflow: TextOverflow.ellipsis, ), diff --git a/lib/http/api.dart b/lib/http/api.dart index 5074ccfe..b32c7e40 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -288,4 +288,8 @@ class Api { // github 获取最新版 static const String latestApp = 'https://api.github.com/repos/guozhigq/pilipala/releases/latest'; + + // 多少人在看 + // https://api.bilibili.com/x/player/online/total?aid=913663681&cid=1203559746&bvid=BV1MM4y1s7NZ&ts=56427838 + static const String onlineTotal = '/x/player/online/total'; } diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index 8ec7bc3d..3d0d2506 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -22,10 +22,18 @@ class DynamicsHttp { } var res = await Request().get(Api.followDynamic, data: data); if (res.data['code'] == 0) { - return { - 'status': true, - 'data': DynamicsDataModel.fromJson(res.data['data']), - }; + try { + return { + 'status': true, + 'data': DynamicsDataModel.fromJson(res.data['data']), + }; + } catch (err) { + return { + 'status': false, + 'data': [], + 'msg': err.toString(), + }; + } } else { return { 'status': false, diff --git a/lib/http/user.dart b/lib/http/user.dart index 26739b02..8fceea41 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -51,10 +51,17 @@ class UserHttp { 'up_mid': mid, }); if (res.data['code'] == 0) { - FavFolderData data = FavFolderData.fromJson(res.data['data']); - return {'status': true, 'data': data}; + late FavFolderData data; + if (res.data['data'] != null) { + data = FavFolderData.fromJson(res.data['data']); + return {'status': true, 'data': data}; + } } else { - return {'status': false, 'data': [], 'msg': '账号未登录'}; + return { + 'status': false, + 'data': [], + 'msg': res.data['message'] ?? '账号未登录' + }; } } diff --git a/lib/http/video.dart b/lib/http/video.dart index f67702f0..a6084a6c 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -399,4 +399,16 @@ class VideoHttp { return {'status': false, 'msg': res.data['result']['toast']}; } } + + // 查看视频同时在看人数 + static Future onlineTotal({int? aid, String? bvid, int? cid}) async { + var res = await Request().get(Api.onlineTotal, data: { + 'aid': aid, + 'bvid': bvid, + 'cid': cid, + }); + if (res.data['code'] == 0) { + return {'status': true, 'data': res.data['data']}; + } + } } diff --git a/lib/main.dart b/lib/main.dart index d35d8399..0bb42d79 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:dynamic_color/dynamic_color.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/widgets/custom_toast.dart'; import 'package:pilipala/http/init.dart'; +import 'package:pilipala/models/common/color_type.dart'; import 'package:pilipala/models/common/theme_type.dart'; import 'package:pilipala/pages/search/index.dart'; import 'package:pilipala/pages/video/detail/index.dart'; @@ -27,6 +28,13 @@ void main() async { await Request.setCookie(); await Data.init(); await GStrorage.lazyInit(); + // 小白条、导航栏沉浸 + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( + systemNavigationBarColor: Colors.transparent, + systemNavigationBarDividerColor: Colors.transparent, + statusBarColor: Colors.transparent, + )); }); } @@ -35,15 +43,27 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - Color brandColor = const Color.fromARGB(255, 92, 182, 123); Box setting = GStrorage.setting; + // 主题色 + Color defaultColor = + colorThemeTypes[setting.get(SettingBoxKey.customColor, defaultValue: 0)] + ['color']; + Color brandColor = defaultColor; + // 主题模式 ThemeType currentThemeValue = ThemeType.values[setting .get(SettingBoxKey.themeMode, defaultValue: ThemeType.system.code)]; + // 是否动态取色 + bool isDynamicColor = + setting.get(SettingBoxKey.dynamicColor, defaultValue: true); + // 字体缩放大小 + double textScale = + setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0); + return DynamicColorBuilder( builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) { ColorScheme? lightColorScheme; ColorScheme? darkColorScheme; - if (lightDynamic != null && darkDynamic != null) { + if (lightDynamic != null && darkDynamic != null && isDynamicColor) { // dynamic取色成功 lightColorScheme = lightDynamic.harmonized(); darkColorScheme = darkDynamic.harmonized(); @@ -93,9 +113,17 @@ class MyApp extends StatelessWidget { fallbackLocale: const Locale("zh", "CN"), getPages: Routes.getPages, home: const MainApp(), - builder: FlutterSmartDialog.init( - toastBuilder: (String msg) => CustomToast(msg: msg), - ), + builder: (BuildContext context, Widget? child) { + return FlutterSmartDialog( + toastBuilder: (String msg) => CustomToast(msg: msg), + child: MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaleFactor: + MediaQuery.of(context).textScaleFactor * textScale), + child: child!, + ), + ); + }, navigatorObservers: [ VideoDetailPage.routeObserver, SearchPage.routeObserver, diff --git a/lib/models/common/color_type.dart b/lib/models/common/color_type.dart new file mode 100644 index 00000000..25d5caa9 --- /dev/null +++ b/lib/models/common/color_type.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +final List> colorThemeTypes = [ + {'color': const Color.fromARGB(255, 92, 182, 123), 'label': '默认绿'}, + {'color': Colors.pink, 'label': '粉红色'}, + {'color': Colors.red, 'label': '红色'}, + {'color': Colors.orange, 'label': '橙色'}, + {'color': Colors.amber, 'label': '琥珀色'}, + {'color': Colors.yellow, 'label': '黄色'}, + {'color': Colors.lime, 'label': '酸橙色'}, + {'color': Colors.lightGreen, 'label': '浅绿色'}, + {'color': Colors.green, 'label': '绿色'}, + {'color': Colors.teal, 'label': '青色'}, + {'color': Colors.cyan, 'label': '蓝绿色'}, + {'color': Colors.lightBlue, 'label': '浅蓝色'}, + {'color': Colors.blue, 'label': '蓝色'}, + {'color': Colors.indigo, 'label': '靛蓝色'}, + {'color': Colors.purple, 'label': '紫色'}, + {'color': Colors.deepPurple, 'label': '深紫色'}, + {'color': Colors.blueGrey, 'label': '蓝灰色'}, + {'color': Colors.brown, 'label': '棕色'}, + {'color': Colors.grey, 'label': '灰色'}, +]; diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index 79a385b3..05c5245e 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -360,7 +360,7 @@ class GoodItem { String? brief; String? cover; - String? id; + dynamic id; String? jumpDesc; String? jumpUrl; String? name; diff --git a/lib/models/video/play/quality.dart b/lib/models/video/play/quality.dart index 4d9d7d6e..6cae84cc 100644 --- a/lib/models/video/play/quality.dart +++ b/lib/models/video/play/quality.dart @@ -93,26 +93,19 @@ extension AudioQualityDesc on AudioQuality { } enum VideoDecodeFormats { + DVH1, AV1, HEVC, AVC, } extension VideoDecodeFormatsDesc on VideoDecodeFormats { - static final List _descList = [ - 'AV1', - 'HEVC', - 'AVC', - ]; + static final List _descList = ['DVH1', 'AV1', 'HEVC', 'AVC']; get description => _descList[index]; } extension VideoDecodeFormatsCode on VideoDecodeFormats { - static final List _codeList = [ - 'av01', - 'hev1', - 'avc1', - ]; + static final List _codeList = ['dvh1', 'av01', 'hev1', 'avc1']; get code => _codeList[index]; static VideoDecodeFormats? fromCode(String code) { diff --git a/lib/pages/about/index.dart b/lib/pages/about/index.dart index 04a5efec..34bfda13 100644 --- a/lib/pages/about/index.dart +++ b/lib/pages/about/index.dart @@ -47,6 +47,11 @@ class _AboutPageState extends State { 'PiliPala', style: Theme.of(context).textTheme.titleMedium, ), + const SizedBox(height: 6), + Text( + '使用Flutter开发的哔哩哔哩第三方客户端', + style: TextStyle(color: Theme.of(context).colorScheme.outline), + ), const SizedBox(height: 20), Obx( () => ListTile( @@ -82,16 +87,6 @@ class _AboutPageState extends State { height: 30, color: Theme.of(context).colorScheme.onInverseSurface, ), - ListTile( - onTap: () {}, - title: const Text('作者'), - trailing: Text('guozhigq', style: subTitleStyle), - ), - ListTile( - onTap: () {}, - title: const Text('酷安'), - trailing: Text('影若风', style: subTitleStyle), - ), ListTile( onTap: () => _aboutController.githubUrl(), title: const Text('Github'), @@ -123,6 +118,11 @@ class _AboutPageState extends State { title: const Text('TG频道'), trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline), ), + ListTile( + onTap: () => _aboutController.aPay(), + title: const Text('赞助'), + trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline), + ), Divider( thickness: 8, height: 30, @@ -141,11 +141,12 @@ class AboutController extends GetxController { late LatestDataModel remoteAppInfo; RxBool isUpdate = true.obs; RxBool isLoading = true.obs; + late LatestDataModel data; @override void onInit() { super.onInit(); - init(); + // init(); // 获取当前版本 getCurrentApp(); // 获取最新的版本 @@ -153,16 +154,16 @@ class AboutController extends GetxController { } // 获取设备信息 - Future init() async { - DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); - if (Platform.isAndroid) { - AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; - print(androidInfo.supportedAbis); - } else if (Platform.isIOS) { - IosDeviceInfo iosInfo = await deviceInfo.iosInfo; - print(iosInfo); - } - } + // Future init() async { + // DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + // if (Platform.isAndroid) { + // AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + // print(androidInfo.supportedAbis); + // } else if (Platform.isIOS) { + // IosDeviceInfo iosInfo = await deviceInfo.iosInfo; + // print(iosInfo); + // } + // } // 获取当前版本 Future getCurrentApp() async { @@ -173,7 +174,7 @@ class AboutController extends GetxController { // 获取远程版本 Future getRemoteApp() async { var result = await Request().get(Api.latestApp); - LatestDataModel data = LatestDataModel.fromJson(result.data); + data = LatestDataModel.fromJson(result.data); remoteAppInfo = data; remoteVersion.value = data.tagName!; isUpdate.value = @@ -183,15 +184,7 @@ class AboutController extends GetxController { // 跳转下载/本地更新 Future onUpdate() async { - // final dir = await getApplicationSupportDirectory(); - // final path = '${dir.path}/pilipala.apk'; - // var result = await Request() - // .downloadFile(remoteAppInfo.assets!.first.downloadUrl, path); - // print(result); - launchUrl( - Uri.parse('https://github.com/guozhigq/pilipala/releases'), - mode: LaunchMode.externalApplication, - ); + Utils.matchVersion(data); } // 跳转github @@ -242,4 +235,16 @@ class AboutController extends GetxController { ), ); } + + aPay() { + try { + launchUrl( + Uri.parse( + 'alipayqr://platformapi/startapp?saId=10000007&qrcode=https://qr.alipay.com/fkx14623ddwl1ping3ddd73'), + mode: LaunchMode.externalApplication, + ); + } catch (e) { + print(e); + } + } } diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart index 3032bb4d..f9c3e37d 100644 --- a/lib/pages/bangumi/view.dart +++ b/lib/pages/bangumi/view.dart @@ -213,7 +213,8 @@ class _BangumiPageState extends State crossAxisSpacing: StyleString.cardSpace, // 列数 crossAxisCount: 3, - mainAxisExtent: Get.size.width / 3 / 0.65 + 30, + mainAxisExtent: Get.size.width / 3 / 0.65 + + 32 * MediaQuery.of(context).textScaleFactor, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { diff --git a/lib/pages/bangumi/widgets/bangumi_panel.dart b/lib/pages/bangumi/widgets/bangumi_panel.dart index aca5f086..9c55448d 100644 --- a/lib/pages/bangumi/widgets/bangumi_panel.dart +++ b/lib/pages/bangumi/widgets/bangumi_panel.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:hive/hive.dart'; import 'package:pilipala/models/bangumi/info.dart'; +import 'package:pilipala/utils/storage.dart'; class BangumiPanel extends StatefulWidget { final List pages; @@ -24,12 +26,20 @@ class _BangumiPanelState extends State { late int currentIndex; final ScrollController listViewScrollCtr = ScrollController(); final ScrollController listViewScrollCtr_2 = ScrollController(); + Box userInfoCache = GStrorage.userInfo; + dynamic userInfo; + // 默认未开通 + int vipStatus = 0; @override void initState() { super.initState(); currentIndex = widget.pages.indexWhere((e) => e.cid == widget.cid!); scrollToIndex(); + userInfo = userInfoCache.get('userInfoCache'); + if (userInfo != null) { + vipStatus = userInfo.vipStatus; + } } @override @@ -126,7 +136,7 @@ class _BangumiPanelState extends State { } void changeFucCall(item, i) async { - if (item.badge != null) { + if (item.badge != null && vipStatus != 1) { SmartDialog.showToast('需要大会员'); return; } diff --git a/lib/pages/bangumi/widgets/bangumu_card_v.dart b/lib/pages/bangumi/widgets/bangumu_card_v.dart index 47629331..937d9d40 100644 --- a/lib/pages/bangumi/widgets/bangumu_card_v.dart +++ b/lib/pages/bangumi/widgets/bangumu_card_v.dart @@ -29,9 +29,6 @@ class BangumiCardV extends StatelessWidget { return Card( elevation: 0, clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: StyleString.mdRadius, - ), margin: EdgeInsets.zero, child: GestureDetector( // onLongPress: () { @@ -149,7 +146,6 @@ class BangumiContent extends StatelessWidget { bangumiItem.title, textAlign: TextAlign.start, style: const TextStyle( - fontSize: 13, fontWeight: FontWeight.w500, letterSpacing: 0.3, ), @@ -158,6 +154,7 @@ class BangumiContent extends StatelessWidget { )), ], ), + const SizedBox(height: 1), if (bangumiItem.indexShow != null) Text( bangumiItem.indexShow, diff --git a/lib/pages/dynamics/controller.dart b/lib/pages/dynamics/controller.dart index d8417d50..5b524510 100644 --- a/lib/pages/dynamics/controller.dart +++ b/lib/pages/dynamics/controller.dart @@ -13,6 +13,7 @@ import 'package:pilipala/models/dynamics/result.dart'; import 'package:pilipala/models/dynamics/up.dart'; import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; @@ -50,17 +51,21 @@ class DynamicsController extends GetxController { }, ]; bool flag = false; - RxInt initialValue = 1.obs; + RxInt initialValue = 0.obs; Box userInfoCache = GStrorage.userInfo; RxBool userLogin = false.obs; var userInfo; RxBool isLoadingDynamic = false.obs; + Box setting = GStrorage.setting; @override void onInit() { userInfo = userInfoCache.get('userInfoCache'); userLogin.value = userInfo != null; super.onInit(); + initialValue.value = + setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0); + dynamicsType = DynamicsType.values[initialValue.value].obs; } Future queryFollowDynamic({type = 'init'}) async { @@ -99,7 +104,7 @@ class DynamicsController extends GetxController { } onSelectType(value) async { - dynamicsType.value = filterTypeList[value - 1]['value']; + dynamicsType.value = filterTypeList[value]['value']; dynamicsList.value = [DynamicItemModel()]; page = 1; initialValue.value = value; @@ -109,16 +114,21 @@ class DynamicsController extends GetxController { pushDetail(item, floor, {action = 'all'}) async { feedBack(); + + /// 点击评论action 直接查看评论 if (action == 'comment') { Get.toNamed('/dynamicDetail', arguments: {'item': item, 'floor': floor, 'action': action}); return false; } switch (item!.type) { + /// 转发的动态 case 'DYNAMIC_TYPE_FORWARD': Get.toNamed('/dynamicDetail', arguments: {'item': item, 'floor': floor}); break; + + /// 图文动态查看 case 'DYNAMIC_TYPE_DRAW': Get.toNamed('/dynamicDetail', arguments: {'item': item, 'floor': floor}); @@ -134,6 +144,8 @@ class DynamicsController extends GetxController { SmartDialog.showToast(err.toString()); } break; + + /// 专栏文章查看 case 'DYNAMIC_TYPE_ARTICLE': String title = item.modules.moduleDynamic.major.opus.title; String url = item.modules.moduleDynamic.major.opus.jumpUrl; @@ -144,7 +156,10 @@ class DynamicsController extends GetxController { break; case 'DYNAMIC_TYPE_PGC': print('番剧'); + SmartDialog.showToast('暂未支持的类型,请联系开发者'); break; + + /// 纯文字动态查看 case 'DYNAMIC_TYPE_WORD': print('纯文本'); Get.toNamed('/dynamicDetail', @@ -168,10 +183,19 @@ class DynamicsController extends GetxController { }); break; - /// TODO + /// 合集查看 case 'DYNAMIC_TYPE_UGC_SEASON': - print('合集'); + DynamicArchiveModel ugcSeason = + item.modules.moduleDynamic.major.ugcSeason; + int aid = ugcSeason.aid!; + String bvid = IdUtils.av2bv(aid); + String cover = ugcSeason.cover!; + int cid = await SearchHttp.ab2c(bvid: bvid); + Get.toNamed('/video?bvid=$bvid&cid=$cid', + arguments: {'pic': cover, 'heroTag': bvid}); break; + + /// 番剧查看 case 'DYNAMIC_TYPE_PGC_UNION': print('DYNAMIC_TYPE_PGC_UNION 番剧'); DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc; @@ -247,7 +271,7 @@ class DynamicsController extends GetxController { void resetSearch() { mid.value = -1; dynamicsType.value = DynamicsType.values[0]; - initialValue.value = 1; + initialValue.value = 0; SmartDialog.showToast('还原默认加载'); dynamicsList.value = [DynamicItemModel()]; queryFollowDynamic(); diff --git a/lib/pages/dynamics/deatil/controller.dart b/lib/pages/dynamics/deatil/controller.dart index c2e1abe5..96ab65a6 100644 --- a/lib/pages/dynamics/deatil/controller.dart +++ b/lib/pages/dynamics/deatil/controller.dart @@ -1,8 +1,10 @@ import 'package:get/get.dart'; +import 'package:hive/hive.dart'; import 'package:pilipala/http/reply.dart'; import 'package:pilipala/models/common/reply_sort_type.dart'; import 'package:pilipala/models/video/reply/item.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/storage.dart'; class DynamicDetailController extends GetxController { DynamicDetailController(this.oid, this.type); @@ -16,9 +18,10 @@ class DynamicDetailController extends GetxController { RxList replyList = [ReplyItemModel()].obs; RxInt acount = 0.obs; - ReplySortType sortType = ReplySortType.time; + ReplySortType _sortType = ReplySortType.time; RxString sortTypeTitle = ReplySortType.time.titles.obs; RxString sortTypeLabel = ReplySortType.time.labels.obs; + Box setting = GStrorage.setting; @override void onInit() { @@ -29,6 +32,11 @@ class DynamicDetailController extends GetxController { acount.value = int.parse(item!.modules!.moduleStat!.comment!.count ?? '0'); } + int deaultReplySortIndex = + setting.get(SettingBoxKey.replySortType, defaultValue: 0); + _sortType = ReplySortType.values[deaultReplySortIndex]; + sortTypeTitle.value = _sortType.titles; + sortTypeLabel.value = _sortType.labels; } Future queryReplyList({reqType = 'init'}) async { @@ -39,7 +47,7 @@ class DynamicDetailController extends GetxController { oid: oid!, pageNum: currentPage + 1, type: type!, - sort: sortType.index, + sort: _sortType.index, ); if (res['status']) { List replies = res['data'].replies; @@ -76,20 +84,20 @@ class DynamicDetailController extends GetxController { // 排序搜索评论 queryBySort() { feedBack(); - switch (sortType) { + switch (_sortType) { case ReplySortType.time: - sortType = ReplySortType.like; + _sortType = ReplySortType.like; break; case ReplySortType.like: - sortType = ReplySortType.reply; + _sortType = ReplySortType.reply; break; case ReplySortType.reply: - sortType = ReplySortType.time; + _sortType = ReplySortType.time; break; default: } - sortTypeTitle.value = sortType.titles; - sortTypeLabel.value = sortType.labels; + sortTypeTitle.value = _sortType.titles; + sortTypeLabel.value = _sortType.labels; replyList.clear(); queryReplyList(reqType: 'init'); } diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index a29e1f91..20a14e6a 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -132,7 +132,7 @@ class _DynamicsPageState extends State initialValue: _dynamicsController.initialValue.value, children: { - 1: Text( + 0: Text( '全部', style: TextStyle( fontSize: Theme.of(context) @@ -140,19 +140,19 @@ class _DynamicsPageState extends State .labelMedium! .fontSize), ), - 2: Text('投稿', + 1: Text('投稿', style: TextStyle( fontSize: Theme.of(context) .textTheme .labelMedium! .fontSize)), - 3: Text('番剧', + 2: Text('番剧', style: TextStyle( fontSize: Theme.of(context) .textTheme .labelMedium! .fontSize)), - 4: Text('专栏', + 3: Text('专栏', style: TextStyle( fontSize: Theme.of(context) .textTheme diff --git a/lib/pages/fav/controller.dart b/lib/pages/fav/controller.dart index 41923449..c3f76186 100644 --- a/lib/pages/fav/controller.dart +++ b/lib/pages/fav/controller.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/http/user.dart'; @@ -6,23 +8,45 @@ import 'package:pilipala/models/user/info.dart'; import 'package:pilipala/utils/storage.dart'; class FavController extends GetxController { + final ScrollController scrollController = ScrollController(); Rx favFolderData = FavFolderData().obs; Box userInfoCache = GStrorage.userInfo; UserInfoData? userInfo; + int currentPage = 1; + int pageSize = 10; + RxBool hasMore = true.obs; - Future queryFavFolder() async { + Future queryFavFolder({type = 'init'}) async { userInfo = userInfoCache.get('userInfoCache'); if (userInfo == null) { return {'status': false, 'msg': '账号未登录'}; } + if (!hasMore.value) { + return; + } var res = await await UserHttp.userfavFolder( - pn: 1, - ps: 10, + pn: currentPage, + ps: pageSize, mid: userInfo!.mid!, ); if (res['status']) { - favFolderData.value = res['data']; + if (type == 'init') { + favFolderData.value = res['data']; + } else { + if (res['data'].list.isNotEmpty) { + favFolderData.value.list!.addAll(res['data'].list); + favFolderData.update((val) {}); + } + } + hasMore.value = res['data'].hasMore; + currentPage++; + } else { + SmartDialog.showToast(res['msg']); } return res; } + + Future onLoad() async { + queryFavFolder(type: 'onload'); + } } diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 8c242862..1196efc9 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -1,3 +1,4 @@ +import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/http_error.dart'; @@ -14,11 +15,23 @@ class FavPage extends StatefulWidget { class _FavPageState extends State { final FavController _favController = Get.put(FavController()); late Future _futureBuilderFuture; + late ScrollController scrollController; @override void initState() { super.initState(); _futureBuilderFuture = _favController.queryFavFolder(); + scrollController = _favController.scrollController; + scrollController.addListener( + () { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 300) { + EasyThrottle.throttle('history', const Duration(seconds: 1), () { + _favController.onLoad(); + }); + } + }, + ); } @override @@ -40,6 +53,7 @@ class _FavPageState extends State { if (data['status']) { return Obx( () => ListView.builder( + controller: scrollController, itemCount: _favController.favFolderData.value.list!.length, itemBuilder: (context, index) { return FavItem( diff --git a/lib/pages/fav/widgets/item.dart b/lib/pages/fav/widgets/item.dart index 816153c2..08730d7b 100644 --- a/lib/pages/fav/widgets/item.dart +++ b/lib/pages/fav/widgets/item.dart @@ -77,7 +77,6 @@ class VideoContent extends StatelessWidget { favFolderItem.title, textAlign: TextAlign.start, style: const TextStyle( - fontSize: 13, fontWeight: FontWeight.w500, letterSpacing: 0.3, ), diff --git a/lib/pages/favDetail/view.dart b/lib/pages/favDetail/view.dart index d90d4f11..a99d1c03 100644 --- a/lib/pages/favDetail/view.dart +++ b/lib/pages/favDetail/view.dart @@ -127,32 +127,34 @@ class _FavDetailPageState extends State { ), ), const SizedBox(width: 14), - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Text( - _favDetailController.item!.title!, - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .titleMedium! - .fontSize, - fontWeight: FontWeight.bold), - ), - const SizedBox(height: 4), - Text( - _favDetailController.item!.upper!.name!, - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize, - color: Theme.of(context).colorScheme.outline), - ) - ], - ) + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Text( + _favDetailController.item!.title!, + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .titleMedium! + .fontSize, + fontWeight: FontWeight.bold), + ), + const SizedBox(height: 4), + Text( + _favDetailController.item!.upper!.name!, + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + color: Theme.of(context).colorScheme.outline), + ) + ], + ), + ), ], ), ), diff --git a/lib/pages/favDetail/widget/fav_video_card.dart b/lib/pages/favDetail/widget/fav_video_card.dart index 084a8681..61ac06f1 100644 --- a/lib/pages/favDetail/widget/fav_video_card.dart +++ b/lib/pages/favDetail/widget/fav_video_card.dart @@ -159,7 +159,6 @@ class VideoContent extends StatelessWidget { videoItem.title, textAlign: TextAlign.start, style: const TextStyle( - fontSize: 13, fontWeight: FontWeight.w500, letterSpacing: 0.3, ), diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index a368a978..2d801668 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -205,7 +205,6 @@ class VideoContent extends StatelessWidget { videoItem.title, textAlign: TextAlign.start, style: const TextStyle( - fontSize: 13, fontWeight: FontWeight.w500, letterSpacing: 0.3, ), diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index 385d3272..1acf0dc7 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -146,7 +146,8 @@ class _LivePageState extends State { // 列数 crossAxisCount: crossAxisCount, mainAxisExtent: - Get.size.width / crossAxisCount / StyleString.aspectRatio + 66, + Get.size.width / crossAxisCount / StyleString.aspectRatio + + 68 * MediaQuery.of(context).textScaleFactor, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { diff --git a/lib/pages/live/widgets/live_item.dart b/lib/pages/live/widgets/live_item.dart index f676a877..a8185be7 100644 --- a/lib/pages/live/widgets/live_item.dart +++ b/lib/pages/live/widgets/live_item.dart @@ -23,11 +23,8 @@ class LiveCardV extends StatelessWidget { Widget build(BuildContext context) { String heroTag = Utils.makeHeroTag(liveItem.roomId); return Card( - elevation: 0, + elevation: 1, clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: StyleString.mdRadius, - ), margin: EdgeInsets.zero, child: GestureDetector( onLongPress: () { @@ -103,7 +100,7 @@ class LiveContent extends StatelessWidget { return Expanded( child: Padding( // 多列 - padding: const EdgeInsets.fromLTRB(4, 8, 0, 6), + padding: const EdgeInsets.fromLTRB(9, 9, 9, 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -112,7 +109,6 @@ class LiveContent extends StatelessWidget { liveItem.title, textAlign: TextAlign.start, style: const TextStyle( - fontSize: 13, fontWeight: FontWeight.w500, letterSpacing: 0.3, ), diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index 3657dbb5..42ade71c 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -138,7 +138,7 @@ class _MediaPageState extends State // const SizedBox(height: 10), SizedBox( width: double.infinity, - height: 170, + height: 170 * MediaQuery.of(context).textScaleFactor, child: FutureBuilder( future: _futureBuilderFuture, builder: (context, snapshot) { diff --git a/lib/pages/member/archive/view.dart b/lib/pages/member/archive/view.dart index 48416774..430f5ede 100644 --- a/lib/pages/member/archive/view.dart +++ b/lib/pages/member/archive/view.dart @@ -142,7 +142,8 @@ class _ArchivePanelState extends State } class LoadMoreListSource extends LoadingMoreBase { - final ArchiveController _archiveController = Get.put(ArchiveController()); + final ArchiveController _archiveController = + Get.put(ArchiveController(), tag: Get.arguments['heroTag']); @override Future loadData([bool isloadMoreAction = false]) async { diff --git a/lib/pages/member/dynamic/view.dart b/lib/pages/member/dynamic/view.dart index 867970ea..1c48baa9 100644 --- a/lib/pages/member/dynamic/view.dart +++ b/lib/pages/member/dynamic/view.dart @@ -118,7 +118,8 @@ class _MemberDynamicPanelState extends State } class LoadMoreListSource extends LoadingMoreBase { - final _dynamicController = Get.put(MemberDynamicPanelController()); + final _dynamicController = + Get.put(MemberDynamicPanelController(), tag: Get.arguments['heroTag']); @override Future loadData([bool isloadMoreAction = false]) async { diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index d8d7c57a..0ed66ac9 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -159,7 +159,8 @@ class _RcmdPageState extends State // 列数 crossAxisCount: crossAxisCount, mainAxisExtent: - Get.size.width / crossAxisCount / StyleString.aspectRatio + 66, + (Get.size.width / crossAxisCount / StyleString.aspectRatio) + + 68 * MediaQuery.of(context).textScaleFactor, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index fdd18352..04af9ed4 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -45,11 +45,7 @@ class _SearchPageState extends State with RouteAware { return OpenContainer( closedElevation: 0, openElevation: 0, - onClosed: (_) async { - // 在 openBuilder 关闭时触发的回调函数 - await Future.delayed(const Duration(milliseconds: 500)); - _searchController.onClear(); - }, + onClosed: (_) => _searchController.onClear(), openColor: Theme.of(context).colorScheme.background, middleColor: Theme.of(context).colorScheme.background, closedColor: Theme.of(context).colorScheme.background, diff --git a/lib/pages/searchPanel/widgets/live_panel.dart b/lib/pages/searchPanel/widgets/live_panel.dart index 02b56d3a..6cd9f4c2 100644 --- a/lib/pages/searchPanel/widgets/live_panel.dart +++ b/lib/pages/searchPanel/widgets/live_panel.dart @@ -12,13 +12,12 @@ Widget searchLivePanel(BuildContext context, ctr, list) { primary: false, controller: ctr!.scrollController, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: StyleString.cardSpace + 2, - mainAxisSpacing: StyleString.cardSpace + 3, - mainAxisExtent: - MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio + - 60, - ), + crossAxisCount: 2, + crossAxisSpacing: StyleString.cardSpace + 2, + mainAxisSpacing: StyleString.cardSpace + 3, + mainAxisExtent: + MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio + + 66 * MediaQuery.of(context).textScaleFactor), itemCount: list.length, itemBuilder: (context, index) { return LiveItem(liveItem: list![index]); @@ -35,11 +34,8 @@ class LiveItem extends StatelessWidget { Widget build(BuildContext context) { String heroTag = Utils.makeHeroTag(liveItem.roomid); return Card( - elevation: 0, + elevation: 1, clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: StyleString.mdRadius, - ), margin: EdgeInsets.zero, child: InkWell( onTap: () async { @@ -104,7 +100,7 @@ class LiveContent extends StatelessWidget { Widget build(BuildContext context) { return Expanded( child: Padding( - padding: const EdgeInsets.fromLTRB(4, 5, 6, 6), + padding: const EdgeInsets.fromLTRB(9, 8, 9, 6), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -116,7 +112,6 @@ class LiveContent extends StatelessWidget { TextSpan( text: i['text'], style: TextStyle( - fontSize: 13, fontWeight: FontWeight.w500, letterSpacing: 0.3, color: i['type'] == 'em' diff --git a/lib/pages/searchPanel/widgets/media_bangumi_panel.dart b/lib/pages/searchPanel/widgets/media_bangumi_panel.dart index b17c74de..b8ae8d2e 100644 --- a/lib/pages/searchPanel/widgets/media_bangumi_panel.dart +++ b/lib/pages/searchPanel/widgets/media_bangumi_panel.dart @@ -68,9 +68,10 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) { text: i['text'], style: TextStyle( fontSize: Theme.of(context) - .textTheme - .titleSmall! - .fontSize, + .textTheme + .titleSmall! + .fontSize! * + MediaQuery.of(context).textScaleFactor, fontWeight: FontWeight.bold, color: i['type'] == 'em' ? Theme.of(context).colorScheme.primary diff --git a/lib/pages/setting/extra_setting.dart b/lib/pages/setting/extra_setting.dart index a46cefaf..29ad5aac 100644 --- a/lib/pages/setting/extra_setting.dart +++ b/lib/pages/setting/extra_setting.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/models/common/dynamics_type.dart'; +import 'package:pilipala/models/common/reply_sort_type.dart'; import 'package:pilipala/utils/storage.dart'; import 'widgets/switch_item.dart'; @@ -11,8 +14,28 @@ class ExtraSetting extends StatefulWidget { } class _ExtraSettingState extends State { + Box setting = GStrorage.setting; + late dynamic defaultReplySort; + late dynamic defaultDynamicType; + + @override + void initState() { + super.initState(); + // 默认优先显示最新评论 + defaultReplySort = + setting.get(SettingBoxKey.replySortType, defaultValue: 0); + // 优先展示全部动态 all + defaultDynamicType = + setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0); + } + @override Widget build(BuildContext context) { + TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; + TextStyle subTitleStyle = Theme.of(context) + .textTheme + .labelMedium! + .copyWith(color: Theme.of(context).colorScheme.outline); return Scaffold( appBar: AppBar( centerTitle: false, @@ -23,8 +46,58 @@ class _ExtraSettingState extends State { ), ), body: ListView( - children: const [ - SetSwitchItem( + children: [ + ListTile( + dense: false, + title: Text('评论展示', style: titleStyle), + subtitle: Text( + '当前优先展示「${ReplySortType.values[defaultReplySort].titles}」', + style: subTitleStyle, + ), + trailing: PopupMenuButton( + initialValue: defaultReplySort, + icon: const Icon(Icons.more_vert_outlined, size: 22), + onSelected: (item) { + defaultReplySort = item; + setting.put(SettingBoxKey.replySortType, item); + setState(() {}); + }, + itemBuilder: (BuildContext context) => [ + for (var i in ReplySortType.values) ...[ + PopupMenuItem( + value: i.index, + child: Text(i.titles), + ), + ] + ], + ), + ), + ListTile( + dense: false, + title: Text('动态展示', style: titleStyle), + subtitle: Text( + '当前优先展示「${DynamicsType.values[defaultDynamicType].labels}」', + style: subTitleStyle, + ), + trailing: PopupMenuButton( + initialValue: defaultDynamicType, + icon: const Icon(Icons.more_vert_outlined, size: 22), + onSelected: (item) { + defaultDynamicType = item; + setting.put(SettingBoxKey.defaultDynamicType, item); + setState(() {}); + }, + itemBuilder: (BuildContext context) => [ + for (var i in DynamicsType.values) ...[ + PopupMenuItem( + value: i.index, + child: Text(i.labels), + ), + ] + ], + ), + ), + const SetSwitchItem( title: '检查更新', subTitle: '每次启动时检查是否需要更新', setKey: SettingBoxKey.autoUpdate, diff --git a/lib/pages/setting/pages/color_select.dart b/lib/pages/setting/pages/color_select.dart new file mode 100644 index 00000000..d0e34bcb --- /dev/null +++ b/lib/pages/setting/pages/color_select.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/models/common/color_type.dart'; +import 'package:pilipala/utils/storage.dart'; + +class ColorSelectPage extends StatefulWidget { + const ColorSelectPage({super.key}); + + @override + State createState() => _ColorSelectPageState(); +} + +class Item { + Item({ + required this.expandedValue, + required this.headerValue, + this.isExpanded = false, + }); + + String expandedValue; + String headerValue; + bool isExpanded; +} + +List generateItems(int count) { + return List.generate(count, (int index) { + return Item( + headerValue: 'Panel $index', + expandedValue: 'This is item number $index', + ); + }); +} + +class _ColorSelectPageState extends State { + final ColorSelectController ctr = Get.put(ColorSelectController()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: false, + title: const Text('选择应用主题'), + ), + body: ListView( + children: [ + Obx( + () => RadioListTile( + value: 0, + title: const Text('动态取色'), + groupValue: ctr.type.value, + onChanged: (dynamic val) async { + ctr.type.value = 0; + ctr.setting.put(SettingBoxKey.dynamicColor, true); + }, + ), + ), + Obx( + () => RadioListTile( + value: 1, + title: const Text('指定颜色'), + groupValue: ctr.type.value, + onChanged: (dynamic val) async { + ctr.type.value = 1; + ctr.setting.put(SettingBoxKey.dynamicColor, false); + }, + ), + ), + Obx( + () { + int type = ctr.type.value; + return AnimatedOpacity( + opacity: type == 1 ? 1 : 0, + duration: const Duration(milliseconds: 200), + child: Padding( + padding: const EdgeInsets.only(top: 12, left: 12, right: 12), + child: Wrap( + alignment: WrapAlignment.center, + spacing: 22, + runSpacing: 18, + children: [ + ...ctr.colorThemes.map( + (e) { + final index = ctr.colorThemes.indexOf(e); + return GestureDetector( + onTap: () { + ctr.currentColor.value = index; + ctr.setting.put(SettingBoxKey.customColor, index); + Get.forceAppUpdate(); + }, + child: Column( + children: [ + Container( + width: 46, + height: 46, + decoration: BoxDecoration( + color: e['color'].withOpacity(0.8), + borderRadius: BorderRadius.circular(50), + border: Border.all( + width: 2, + color: ctr.currentColor.value == index + ? Colors.black + : e['color'].withOpacity(0.8), + ), + ), + child: AnimatedOpacity( + opacity: + ctr.currentColor.value == index ? 1 : 0, + duration: const Duration(milliseconds: 200), + child: const Icon( + Icons.done, + color: Colors.black, + size: 20, + ), + ), + ), + const SizedBox(height: 3), + Text( + e['label'], + style: TextStyle( + fontSize: 12, + color: ctr.currentColor.value != index + ? Theme.of(context).colorScheme.outline + : null, + ), + ), + ], + ), + ); + }, + ) + ], + ), + ), + ); + }, + ), + ], + ), + ); + } +} + +class ColorSelectController extends GetxController { + Box setting = GStrorage.setting; + RxBool dynamicColor = true.obs; + RxInt type = 0.obs; + late final List> colorThemes; + RxInt currentColor = 0.obs; + + @override + void onInit() { + colorThemes = colorThemeTypes; + // 默认使用动态取色 + dynamicColor.value = + setting.get(SettingBoxKey.dynamicColor, defaultValue: true); + type.value = dynamicColor.value ? 0 : 1; + currentColor.value = + setting.get(SettingBoxKey.customColor, defaultValue: 0); + super.onInit(); + } +} diff --git a/lib/pages/setting/pages/display_mode.dart b/lib/pages/setting/pages/display_mode.dart new file mode 100644 index 00000000..4e84e956 --- /dev/null +++ b/lib/pages/setting/pages/display_mode.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_displaymode/flutter_displaymode.dart'; + +class SetDiaplayMode extends StatefulWidget { + const SetDiaplayMode({super.key}); + + @override + State createState() => _SetDiaplayModeState(); +} + +class _SetDiaplayModeState extends State { + List modes = []; + DisplayMode? active; + DisplayMode? preferred; + + final ValueNotifier page = ValueNotifier(0); + late final PageController controller = PageController() + ..addListener(() { + page.value = controller.page!.round(); + }); + @override + void initState() { + super.initState(); + init(); + SchedulerBinding.instance.addPostFrameCallback((_) { + fetchAll(); + }); + } + + Future fetchAll() async { + preferred = await FlutterDisplayMode.preferred; + active = await FlutterDisplayMode.active; + // GStorage().setDisplayModeType(preferred!); + setState(() {}); + } + + Future init() async { + try { + modes = await FlutterDisplayMode.supported; + } on PlatformException catch (e) { + print(e); + } + // var res = await GStorage().getDisplayModeType(); + // preferred = modes.toList().firstWhere((el) => el == res); + FlutterDisplayMode.setPreferredMode(preferred!); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('屏幕帧率设置')), + body: SafeArea( + top: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (modes.isEmpty) const Text('Nothing here'), + Padding( + padding: const EdgeInsets.only(left: 25, top: 10, bottom: 5), + child: Text( + '没有生效?重启app试试', + style: TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), + Expanded( + child: ListView.builder( + itemCount: modes.length, + itemBuilder: (_, int i) { + final DisplayMode mode = modes[i]; + return RadioListTile( + value: mode, + title: mode == DisplayMode.auto + ? const Text('自动') + : Text('$mode${mode == active ? " [系统]" : ""}'), + groupValue: preferred, + onChanged: (DisplayMode? newMode) async { + await FlutterDisplayMode.setPreferredMode(newMode!); + await Future.delayed( + const Duration(milliseconds: 100), + ); + await fetchAll(); + }, + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/setting/pages/font_size_select.dart b/lib/pages/setting/pages/font_size_select.dart new file mode 100644 index 00000000..04d6794d --- /dev/null +++ b/lib/pages/setting/pages/font_size_select.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/utils/storage.dart'; + +class FontSizeSelectPage extends StatefulWidget { + const FontSizeSelectPage({super.key}); + + @override + State createState() => _FontSizeSelectPageState(); +} + +class _FontSizeSelectPageState extends State { + Box setting = GStrorage.setting; + List list = [0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3]; + late double minsize; + late double maxSize; + late double currentSize; + + @override + void initState() { + super.initState(); + minsize = list.first; + maxSize = list.last; + currentSize = + setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0); + } + + setFontSize() { + setting.put(SettingBoxKey.defaultTextScale, currentSize); + Get.forceAppUpdate(); + Get.back(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + actions: [ + TextButton(onPressed: () => setFontSize(), child: const Text('确定')), + const SizedBox(width: 12) + ], + ), + body: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Center( + child: Text( + '当前字体大小:${currentSize == 1.0 ? '默认' : currentSize}', + style: TextStyle(fontSize: 14 * currentSize), + ), + ), + ), + ), + Container( + width: double.infinity, + padding: EdgeInsets.only( + left: 20, + right: 20, + top: 20, + bottom: MediaQuery.of(context).padding.bottom + 20, + ), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.3))), + color: Theme.of(context).colorScheme.background, + ), + child: Row( + children: [ + const Text('小'), + Expanded( + child: Slider( + min: minsize, + value: currentSize, + max: maxSize, + divisions: list.length - 1, + secondaryTrackValue: 1, + onChanged: (double val) { + currentSize = double.parse(val.toStringAsFixed(2)); + setState(() {}); + }, + ), + ), + const SizedBox(width: 5), + const Text( + '大', + style: TextStyle(fontSize: 20), + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index 524b014a..8f9d8226 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -66,6 +66,18 @@ class _PlaySettingState extends State { setKey: SettingBoxKey.enableHA, defaultVal: true, ), + const SetSwitchItem( + title: '观看人数', + subTitle: '展示同时在看人数', + setKey: SettingBoxKey.enableOnlineTotal, + defaultVal: false, + ), + const SetSwitchItem( + title: '亮度记忆', + subTitle: '返回时自动调整视频亮度', + setKey: SettingBoxKey.enableAutoBrightness, + defaultVal: false, + ), ListTile( dense: false, title: Text('默认画质', style: titleStyle), diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart index 397edbbf..73cad841 100644 --- a/lib/pages/setting/style_setting.dart +++ b/lib/pages/setting/style_setting.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; @@ -5,6 +7,7 @@ import 'package:pilipala/models/common/theme_type.dart'; import 'package:pilipala/utils/storage.dart'; import 'controller.dart'; +import 'widgets/switch_item.dart'; class StyleSetting extends StatefulWidget { const StyleSetting({super.key}); @@ -66,6 +69,12 @@ class _StyleSettingState extends State { ), ), ), + const SetSwitchItem( + title: 'iOS路由切换', + subTitle: 'iOS路由切换样式,需重启', + setKey: SettingBoxKey.iosTransition, + defaultVal: false, + ), ListTile( dense: false, onTap: () { @@ -184,6 +193,25 @@ class _StyleSettingState extends State { style: subTitleStyle)), trailing: const Icon(Icons.arrow_right_alt_outlined), ), + ListTile( + dense: false, + onTap: () => Get.toNamed('/colorSetting'), + title: Text('应用主题', style: titleStyle), + trailing: const Icon(Icons.arrow_forward_ios, size: 17), + ), + ListTile( + dense: false, + onTap: () => Get.toNamed('/fontSizeSetting'), + title: Text('字体大小', style: titleStyle), + trailing: const Icon(Icons.arrow_forward_ios, size: 17), + ), + if (Platform.isAndroid) + ListTile( + dense: false, + onTap: () => Get.toNamed('/displayModeSetting'), + title: Text('屏幕帧率', style: titleStyle), + trailing: const Icon(Icons.arrow_forward_ios, size: 17), + ) ], ), ); diff --git a/lib/pages/setting/widgets/switch_item.dart b/lib/pages/setting/widgets/switch_item.dart index 3e41e9ee..1b2cc620 100644 --- a/lib/pages/setting/widgets/switch_item.dart +++ b/lib/pages/setting/widgets/switch_item.dart @@ -32,6 +32,15 @@ class _SetSwitchItemState extends State { val = Setting.get(widget.setKey, defaultValue: widget.defaultVal ?? false); } + void switchChange(value) { + val = value ?? !val; + Setting.put(widget.setKey, val); + if (widget.setKey == SettingBoxKey.autoUpdate && value == true) { + Utils.checkUpdata(); + } + setState(() {}); + } + @override Widget build(BuildContext context) { TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; @@ -41,9 +50,7 @@ class _SetSwitchItemState extends State { .copyWith(color: Theme.of(context).colorScheme.outline); return ListTile( enableFeedback: true, - onTap: () { - Setting.put(widget.setKey, !val); - }, + onTap: () => switchChange(null), title: Text(widget.title!, style: titleStyle), subtitle: widget.subTitle != null ? Text(widget.subTitle!, style: subTitleStyle) @@ -51,22 +58,16 @@ class _SetSwitchItemState extends State { trailing: Transform.scale( scale: 0.8, child: Switch( - thumbIcon: MaterialStateProperty.resolveWith( - (Set states) { - if (states.isNotEmpty && states.first == MaterialState.selected) { - return const Icon(Icons.done); - } - return null; // All other states will use the default thumbIcon. - }), - value: val, - onChanged: (value) { - val = value; - Setting.put(widget.setKey, value); - if (widget.setKey == SettingBoxKey.autoUpdate && value == true) { - Utils.checkUpdata(); - } - setState(() {}); - }), + thumbIcon: MaterialStateProperty.resolveWith( + (Set states) { + if (states.isNotEmpty && states.first == MaterialState.selected) { + return const Icon(Icons.done); + } + return null; // All other states will use the default thumbIcon. + }), + value: val, + onChanged: (val) => switchChange(val), + ), ), ); } diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index a7ea10f4..e4fd89dc 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -14,6 +14,7 @@ import 'package:pilipala/pages/video/detail/replyReply/index.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; +import 'package:screen_brightness/screen_brightness.dart'; class VideoDetailController extends GetxController with GetSingleTickerProviderStateMixin { @@ -68,6 +69,8 @@ class VideoDetailController extends GetxController late String videoUrl; late String audioUrl; late Duration defaultST; + // 亮度 + double? brightness; // 默认记录历史记录 bool enableHeart = true; var userInfo; @@ -137,8 +140,17 @@ class VideoDetailController extends GetxController /// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl List videoList = data.dash!.video!.where((i) => i.id == currentVideoQa.code).toList(); - firstVideo = videoList - .firstWhere((i) => i.codecs!.startsWith(currentDecodeFormats.code)); + try { + firstVideo = videoList + .firstWhere((i) => i.codecs!.startsWith(currentDecodeFormats.code)); + } catch (_) { + // 当前格式不可用 + currentDecodeFormats = VideoDecodeFormatsCode.fromString(setting.get( + SettingBoxKey.defaultDecode, + defaultValue: VideoDecodeFormats.values.last.code))!; + firstVideo = videoList + .firstWhere((i) => i.codecs!.startsWith(currentDecodeFormats.code)); + } videoUrl = firstVideo.baseUrl!; /// 根据currentAudioQa 重新设置audioUrl @@ -152,6 +164,12 @@ class VideoDetailController extends GetxController } Future playerInit({video, audio, seekToTime, duration}) async { + /// 设置/恢复 屏幕亮度 + if (brightness != null) { + ScreenBrightness().setScreenBrightness(brightness!); + } else { + ScreenBrightness().resetScreenBrightness(); + } await plPlayerController.setDataSource( DataSource( videoSource: video ?? videoUrl, @@ -234,17 +252,25 @@ class VideoDetailController extends GetxController defaultValue: VideoDecodeFormats.values.last.code))!; try { // 当前视频没有对应格式返回第一个 - currentDecodeFormats = - supportDecodeFormats.contains(supportDecodeFormats) - ? supportDecodeFormats - : supportDecodeFormats.first; - } catch (_) {} + currentDecodeFormats = supportDecodeFormats + .contains(currentDecodeFormats) + ? currentDecodeFormats + : VideoDecodeFormatsCode.fromString(supportDecodeFormats.first)!; + } catch (e) { + print(e); + } /// 取出符合当前解码格式的videoItem - firstVideo = videosList - .firstWhere((e) => e.codecs!.startsWith(currentDecodeFormats.code)); + try { + firstVideo = videosList.firstWhere( + (e) => e.codecs!.startsWith(currentDecodeFormats.code)); + } catch (_) { + firstVideo = videosList.first; + } videoUrl = firstVideo.baseUrl!; - } catch (_) {} + } catch (err) { + print(err); + } /// 优先顺序 设置中指定质量 -> 当前可选的最高质量 late AudioItem firstAudio; diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 0817a046..ea63c14d 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -51,6 +53,12 @@ class VideoIntroController extends GetxController { RxInt lastPlayCid = 0.obs; var userInfo; + // 同时观看 + bool isShowOnlineTotal = false; + RxInt totel = 1.obs; + Timer? timer; + bool isPaused = false; + @override void onInit() { super.onInit(); @@ -78,6 +86,12 @@ class VideoIntroController extends GetxController { } userLogin = userInfo != null; lastPlayCid.value = int.parse(Get.parameters['cid']!); + isShowOnlineTotal = + setting.get(SettingBoxKey.enableOnlineTotal, defaultValue: false); + if (isShowOnlineTotal) { + queryOnlineTotal(); + startTimer(); // 在页面加载时启动定时器 + } } // 获取视频简介&分p @@ -417,4 +431,33 @@ class VideoIntroController extends GetxController { lastPlayCid.value = cid; await queryVideoIntro(); } + + void startTimer() { + const duration = Duration(seconds: 10); // 设置定时器间隔为10秒 + timer = Timer.periodic(duration, (Timer timer) { + if (!isPaused) { + queryOnlineTotal(); // 定时器回调函数,发起请求 + } + }); + } + + // 查看同时在看人数 + Future queryOnlineTotal() async { + var result = await VideoHttp.onlineTotal( + aid: IdUtils.bv2av(bvid), + bvid: bvid, + cid: lastPlayCid.value, + ); + if (result['status']) { + totel.value = int.parse(result['data']['total']); + } + } + + @override + void onClose() { + if (timer != null) { + timer!.cancel(); // 销毁页面时取消定时器 + } + super.onClose(); + } } diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 5f99dd9b..c993fda9 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -111,6 +111,8 @@ class _VideoInfoState extends State with TickerProviderStateMixin { late final dynamic owner; late final dynamic follower; late final dynamic followStatus; + late int mid; + late String memberHeroTag; @override void initState() { @@ -160,14 +162,15 @@ class _VideoInfoState extends State with TickerProviderStateMixin { // 用户主页 onPushMember() { feedBack(); - int mid = !loadingStatus + mid = !loadingStatus ? widget.videoDetail!.owner!.mid : videoItem['owner'].mid; + memberHeroTag = Utils.makeHeroTag(mid); String face = !loadingStatus ? widget.videoDetail!.owner!.face : videoItem['owner'].face; Get.toNamed('/member?mid=$mid', - arguments: {'face': face, 'heroTag': (mid + 99).toString()}); + arguments: {'face': face, 'heroTag': memberHeroTag}); } @override @@ -255,6 +258,17 @@ class _VideoInfoState extends State with TickerProviderStateMixin { color: t.colorScheme.outline, ), ), + const SizedBox(width: 10), + if (videoIntroController.isShowOnlineTotal) + Obx( + () => Text( + '${videoIntroController.totel.value}人在看', + style: TextStyle( + fontSize: 12, + color: t.colorScheme.outline, + ), + ), + ), ], ), ), diff --git a/lib/pages/video/detail/introduction/widgets/intro_detail.dart b/lib/pages/video/detail/introduction/widgets/intro_detail.dart index 7b1029f3..d9846b19 100644 --- a/lib/pages/video/detail/introduction/widgets/intro_detail.dart +++ b/lib/pages/video/detail/introduction/widgets/intro_detail.dart @@ -100,7 +100,7 @@ class IntroDetail extends StatelessWidget { Text.rich( style: const TextStyle( height: 1.4, - fontSize: 13, + // fontSize: 13, ), TextSpan( children: [ diff --git a/lib/pages/video/detail/introduction/widgets/season.dart b/lib/pages/video/detail/introduction/widgets/season.dart index 876dac74..3f3a1475 100644 --- a/lib/pages/video/detail/introduction/widgets/season.dart +++ b/lib/pages/video/detail/introduction/widgets/season.dart @@ -28,7 +28,25 @@ class _SeasonPanelState extends State { @override void initState() { super.initState(); - episodes = widget.ugcSeason.sections!.first.episodes!; + + /// 根据 cid 找到对应集,找到对应 episodes + /// 有多个episodes时,只显示其中一个 + /// TODO 同时显示多个合集 + List sections = widget.ugcSeason.sections!; + for (int i = 0; i < sections.length; i++) { + List episodesList = sections[i].episodes!; + for (int j = 0; j < episodesList.length; j++) { + if (episodesList[j].cid == widget.cid) { + episodes = episodesList; + continue; + } + } + } + + /// 取对应 season_id 的 episodes + // episodes = widget.ugcSeason.sections! + // .firstWhere((e) => e.seasonId == widget.ugcSeason.id) + // .episodes!; currentIndex = episodes.indexWhere((e) => e.cid == widget.cid); } @@ -136,7 +154,7 @@ class _SeasonPanelState extends State { ), const SizedBox(width: 10), Text( - '${currentIndex + 1}/${widget.ugcSeason.epCount}', + '${currentIndex + 1}/${episodes.length}', style: Theme.of(context).textTheme.labelMedium, ), const SizedBox(width: 6), diff --git a/lib/pages/video/detail/reply/controller.dart b/lib/pages/video/detail/reply/controller.dart index a81674af..16c7838f 100644 --- a/lib/pages/video/detail/reply/controller.dart +++ b/lib/pages/video/detail/reply/controller.dart @@ -1,10 +1,13 @@ +import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:hive/hive.dart'; import 'package:pilipala/http/reply.dart'; import 'package:pilipala/models/common/reply_sort_type.dart'; import 'package:pilipala/models/common/reply_type.dart'; import 'package:pilipala/models/video/reply/item.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/storage.dart'; class VideoReplyController extends GetxController { VideoReplyController( @@ -25,13 +28,26 @@ class VideoReplyController extends GetxController { bool isLoadingMore = false; RxString noMore = ''.obs; int ps = 20; + RxInt count = 0.obs; // 当前回复的回复 ReplyItemModel? currentReplyItem; - ReplySortType sortType = ReplySortType.time; + ReplySortType _sortType = ReplySortType.time; RxString sortTypeTitle = ReplySortType.time.titles.obs; RxString sortTypeLabel = ReplySortType.time.labels.obs; + Box setting = GStrorage.setting; + + @override + void onInit() { + super.onInit(); + int deaultReplySortIndex = + setting.get(SettingBoxKey.replySortType, defaultValue: 0); + _sortType = ReplySortType.values[deaultReplySortIndex]; + sortTypeTitle.value = _sortType.titles; + sortTypeLabel.value = _sortType.labels; + } + Future queryReplyList({type = 'init'}) async { isLoadingMore = true; if (type == 'init') { @@ -45,7 +61,7 @@ class VideoReplyController extends GetxController { pageNum: currentPage + 1, ps: ps, type: ReplyType.video.index, - sort: sortType.index, + sort: _sortType.index, ); if (res['status']) { List replies = res['data'].replies; @@ -81,6 +97,7 @@ class VideoReplyController extends GetxController { replyList.addAll(replies); } } + count.value = res['data'].page.count; isLoadingMore = false; return res; } @@ -92,23 +109,26 @@ class VideoReplyController extends GetxController { // 排序搜索评论 queryBySort() { - feedBack(); - switch (sortType) { - case ReplySortType.time: - sortType = ReplySortType.like; - break; - case ReplySortType.like: - sortType = ReplySortType.reply; - break; - case ReplySortType.reply: - sortType = ReplySortType.time; - break; - default: - } - sortTypeTitle.value = sortType.titles; - sortTypeLabel.value = sortType.labels; - currentPage = 0; - replyList.clear(); - queryReplyList(type: 'init'); + EasyThrottle.throttle('queryBySort', const Duration(seconds: 1), () { + feedBack(); + switch (_sortType) { + case ReplySortType.time: + _sortType = ReplySortType.like; + break; + case ReplySortType.like: + _sortType = ReplySortType.reply; + break; + case ReplySortType.reply: + _sortType = ReplySortType.time; + break; + default: + } + sortTypeTitle.value = _sortType.titles; + sortTypeLabel.value = _sortType.labels; + currentPage = 0; + noMore.value = ''; + replyList.clear(); + queryReplyList(type: 'init'); + }); } } diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index 149a2f30..7b960014 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -160,9 +160,9 @@ class _VideoReplyPanelState extends State scale: animation, child: child); }, child: Text( - _videoReplyController.sortTypeTitle.value, - key: ValueKey( - _videoReplyController.sortTypeTitle.value), + '共${_videoReplyController.count.value}条回复', + key: ValueKey( + _videoReplyController.count.value), ), ), ), diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index abebf5cb..c45d56dd 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -591,7 +591,8 @@ InlineSpan buildContent( if (content.jumpUrl.isNotEmpty && hasMatchMember) { List urlKeys = content.jumpUrl.keys.toList(); matchUrl = matchMember.splitMapJoin( - RegExp("(?:${urlKeys.join("|")})"), + /// RegExp.escape() 转义特殊字符 + RegExp(RegExp.escape(urlKeys.join("|"))), onMatch: (Match match) { String matchStr = match[0]!; spanChilds.add( diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index ddb53a79..d45bcfb9 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -37,12 +37,15 @@ class _VideoDetailPageState extends State PlPlayerController? plPlayerController; final ScrollController _extendNestCtr = ScrollController(); late StreamController appbarStream; + final VideoIntroController videoIntroController = + Get.put(VideoIntroController(), tag: Get.arguments['heroTag']); PlayerStatus playerStatus = PlayerStatus.playing; // bool isShowCover = true; double doubleOffset = 0; Box localCache = GStrorage.localCache; + Box setting = GStrorage.setting; late double statusBarHeight; final videoHeight = Get.size.width * 9 / 16; late Future _futureBuilderFuture; @@ -95,7 +98,6 @@ class _VideoDetailPageState extends State @override void dispose() { - plPlayerController!.pause(); plPlayerController!.dispose(); super.dispose(); } @@ -103,7 +105,12 @@ class _VideoDetailPageState extends State @override // 离开当前页面时 void didPushNext() async { + /// 开启 + if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: false)) { + videoDetailController.brightness = plPlayerController!.brightness.value; + } videoDetailController.defaultST = plPlayerController!.position.value; + videoIntroController.isPaused = true; plPlayerController!.pause(); super.didPushNext(); } @@ -112,6 +119,7 @@ class _VideoDetailPageState extends State // 返回当前页面时 void didPopNext() async { videoDetailController.playerInit(); + videoIntroController.isPaused = false; if (_extendNestCtr.position.pixels == 0) { await Future.delayed(const Duration(milliseconds: 300)); plPlayerController!.play(); diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 58281b69..77619043 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -387,9 +387,12 @@ class _HeaderControlState extends State { // 当前选中的解码格式 VideoDecodeFormats currentDecodeFormats = widget.videoDetailCtr!.currentDecodeFormats; + VideoItem firstVideo = widget.videoDetailCtr!.firstVideo; // 当前视频可用的解码格式 List videoFormat = videoInfo.supportFormats!; - List list = videoFormat.first.codecs!; + List list = videoFormat + .firstWhere((e) => e.quality == firstVideo.quality!.code) + .codecs!; showModalBottomSheet( context: context, @@ -483,10 +486,12 @@ class _HeaderControlState extends State { size: 15, color: Colors.white, ), - fuc: () { + fuc: () async { // 销毁播放器实例 - widget.controller!.dispose(type: 'all'); - Get.offAll(const MainApp()); + await widget.controller!.dispose(type: 'all'); + if (mounted) { + Navigator.popUntil(context, (route) => route.isFirst); + } }, ), const Spacer(), @@ -506,9 +511,7 @@ class _HeaderControlState extends State { style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), - onPressed: () { - _.togglePlaybackSpeed(); - }, + onPressed: () => showSetSpeedSheet(), child: Text( '${_.playbackSpeed.toString()}X', style: textStyle, diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index a72e0df2..861c7898 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -619,7 +619,7 @@ class PlPlayerController { try { brightness.value = brightnes; ScreenBrightness().setScreenBrightness(brightnes); - setVideoBrightness(); + // setVideoBrightness(); } catch (e) { throw 'Failed to set brightness'; } @@ -662,27 +662,24 @@ class PlPlayerController { } /// 缓存fit - Future setVideoFit() async { - videoStorage.put(VideoBoxKey.videoBrightness, _videoFit.value.name); - } + // Future setVideoFit() async { + // videoStorage.put(VideoBoxKey.videoBrightness, _videoFit.value.name); + // } /// 读取fit - Future getVideoFit() async { - String fitValue = - videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 'contain'); - _videoFit.value = videoFitType - .firstWhere((element) => element['attr'] == fitValue)['attr']; - } - - /// 缓存亮度 - Future setVideoBrightness() async {} + // Future getVideoFit() async { + // String fitValue = + // videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 'contain'); + // _videoFit.value = videoFitType + // .firstWhere((element) => element['attr'] == fitValue)['attr']; + // } /// 读取亮度 - Future getVideoBrightness() async { - double brightnessValue = - videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 0.5); - setBrightness(brightnessValue); - } + // Future getVideoBrightness() async { + // double brightnessValue = + // videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 0.5); + // setBrightness(brightnessValue); + // } set controls(bool visible) { _showControls.value = visible; @@ -760,36 +757,41 @@ class PlPlayerController { Future dispose({String type = 'single'}) async { // 每次减1,最后销毁 - if (type == 'single') { + if (type == 'single' && playerCount.value > 1) { _playerCount.value -= 1; _heartDuration = 0; - if (playerCount.value > 0) { - return; - } + pause(); + return; } + _playerCount.value = 0; + try { + _timer?.cancel(); + _timerForVolume?.cancel(); + _timerForGettingVolume?.cancel(); + timerForTrackingMouse?.cancel(); + _timerForSeek?.cancel(); + videoFitChangedTimer?.cancel(); + // _position.close(); + _playerEventSubs?.cancel(); + // _sliderPosition.close(); + // _sliderTempPosition.close(); + // _isSliderMoving.close(); + // _duration.close(); + // _buffered.close(); + // _showControls.close(); + // _controlsLock.close(); - _timer?.cancel(); - _timerForVolume?.cancel(); - _timerForGettingVolume?.cancel(); - timerForTrackingMouse?.cancel(); - _timerForSeek?.cancel(); - videoFitChangedTimer?.cancel(); - _position.close(); - _playerEventSubs?.cancel(); - _sliderPosition.close(); - _sliderTempPosition.close(); - _isSliderMoving.close(); - _duration.close(); - _buffered.close(); - _showControls.close(); - _controlsLock.close(); + // playerStatus.status.close(); + // dataStatus.status.close(); - playerStatus.status.close(); - dataStatus.status.close(); - - removeListeners(); - await _videoPlayerController?.dispose(); - _videoPlayerController = null; - _instance = null; + removeListeners(); + await _videoPlayerController?.dispose(); + _videoPlayerController = null; + _instance = null; + // 关闭所有视频页面恢复亮度 + resetBrightness(); + } catch (err) { + print(err); + } } } diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 7ba5de18..48c016c9 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -156,6 +156,7 @@ class _PLVideoPlayerState extends State }); } }); + widget.controller.brightness.value = value; } Future triggerFullScreen() async { diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index f525e4e5..7edb435b 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -1,4 +1,8 @@ +// ignore_for_file: must_be_immutable + +import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:hive/hive.dart'; import 'package:pilipala/pages/about/index.dart'; import 'package:pilipala/pages/blacklist/index.dart'; import 'package:pilipala/pages/dynamics/deatil/index.dart'; @@ -17,6 +21,9 @@ import 'package:pilipala/pages/preview/index.dart'; import 'package:pilipala/pages/search/index.dart'; import 'package:pilipala/pages/searchResult/index.dart'; import 'package:pilipala/pages/setting/extra_setting.dart'; +import 'package:pilipala/pages/setting/pages/color_select.dart'; +import 'package:pilipala/pages/setting/pages/display_mode.dart'; +import 'package:pilipala/pages/setting/pages/font_size_select.dart'; import 'package:pilipala/pages/setting/play_setting.dart'; import 'package:pilipala/pages/setting/privacy_setting.dart'; import 'package:pilipala/pages/setting/style_setting.dart'; @@ -25,15 +32,20 @@ import 'package:pilipala/pages/video/detail/replyReply/index.dart'; import 'package:pilipala/pages/webview/index.dart'; import 'package:pilipala/pages/setting/index.dart'; import 'package:pilipala/pages/media/index.dart'; +import 'package:pilipala/utils/storage.dart'; + +Box setting = GStrorage.setting; +bool iosTransition = + setting.get(SettingBoxKey.iosTransition, defaultValue: false); class Routes { static final List getPages = [ // 首页(推荐) - GetPage(name: '/', page: () => const HomePage()), + CustomGetPage(name: '/', page: () => const HomePage()), // 热门 - GetPage(name: '/hot', page: () => const HotPage()), + CustomGetPage(name: '/hot', page: () => const HotPage()), // 视频详情 - GetPage(name: '/video', page: () => const VideoDetailPage()), + CustomGetPage(name: '/video', page: () => const VideoDetailPage()), // 图片预览 GetPage( name: '/preview', @@ -43,49 +55,77 @@ class Routes { showCupertinoParallax: false, ), // - GetPage(name: '/webview', page: () => const WebviewPage()), + CustomGetPage(name: '/webview', page: () => const WebviewPage()), // 设置 - GetPage(name: '/setting', page: () => const SettingPage()), + CustomGetPage(name: '/setting', page: () => const SettingPage()), // - GetPage(name: '/media', page: () => const MediaPage()), + CustomGetPage(name: '/media', page: () => const MediaPage()), // - GetPage(name: '/fav', page: () => const FavPage()), + CustomGetPage(name: '/fav', page: () => const FavPage()), // - GetPage(name: '/favDetail', page: () => const FavDetailPage()), + CustomGetPage(name: '/favDetail', page: () => const FavDetailPage()), // 稍后再看 - GetPage(name: '/later', page: () => const LaterPage()), + CustomGetPage(name: '/later', page: () => const LaterPage()), // 历史记录 - GetPage(name: '/history', page: () => const HistoryPage()), + CustomGetPage(name: '/history', page: () => const HistoryPage()), // 搜索页面 - GetPage(name: '/search', page: () => const SearchPage()), + CustomGetPage(name: '/search', page: () => const SearchPage()), // 搜索结果 - GetPage(name: '/searchResult', page: () => const SearchResultPage()), + CustomGetPage(name: '/searchResult', page: () => const SearchResultPage()), // 动态 - GetPage(name: '/dynamics', page: () => const DynamicsPage()), + CustomGetPage(name: '/dynamics', page: () => const DynamicsPage()), // 动态详情 - GetPage(name: '/dynamicDetail', page: () => const DynamicDetailPage()), + CustomGetPage( + name: '/dynamicDetail', page: () => const DynamicDetailPage()), // 关注 - GetPage(name: '/follow', page: () => const FollowPage()), + CustomGetPage(name: '/follow', page: () => const FollowPage()), // 粉丝 - GetPage(name: '/fan', page: () => const FansPage()), + CustomGetPage(name: '/fan', page: () => const FansPage()), // 直播详情 - GetPage(name: '/liveRoom', page: () => const LiveRoomPage()), + CustomGetPage(name: '/liveRoom', page: () => const LiveRoomPage()), // 用户中心 - GetPage(name: '/member', page: () => const MemberPage()), + CustomGetPage(name: '/member', page: () => const MemberPage()), // 二级回复 - GetPage(name: '/replyReply', page: () => const VideoReplyReplyPanel()), + CustomGetPage( + name: '/replyReply', page: () => const VideoReplyReplyPanel()), // 播放设置 - GetPage(name: '/playSetting', page: () => const PlaySetting()), + CustomGetPage(name: '/playSetting', page: () => const PlaySetting()), // 外观设置 - GetPage(name: '/styleSetting', page: () => const StyleSetting()), + CustomGetPage(name: '/styleSetting', page: () => const StyleSetting()), // 隐私设置 - GetPage(name: '/privacySetting', page: () => const PrivacySetting()), + CustomGetPage(name: '/privacySetting', page: () => const PrivacySetting()), // 其他设置 - GetPage(name: '/extraSetting', page: () => const ExtraSetting()), + CustomGetPage(name: '/extraSetting', page: () => const ExtraSetting()), // - GetPage(name: '/blackListPage', page: () => const BlackListPage()), + CustomGetPage(name: '/blackListPage', page: () => const BlackListPage()), + CustomGetPage(name: '/colorSetting', page: () => const ColorSelectPage()), + CustomGetPage( + name: '/fontSizeSetting', page: () => const FontSizeSelectPage()), + // 屏幕帧率 + CustomGetPage( + name: '/displayModeSetting', page: () => const SetDiaplayMode()), // 关于 - GetPage(name: '/about', page: () => const AboutPage()), + CustomGetPage(name: '/about', page: () => const AboutPage()), ]; } + +class CustomGetPage extends GetPage { + bool? fullscreen = false; + + CustomGetPage({ + name, + page, + this.fullscreen, + transitionDuration, + }) : super( + name: name, + page: page, + curve: Curves.linear, + transition: iosTransition ? Transition.cupertino : Transition.native, + showCupertinoParallax: false, + popGesture: false, + transitionDuration: transitionDuration, + fullscreenDialog: fullscreen != null && fullscreen, + ); +} diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index b70589a5..a117760e 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -82,24 +82,36 @@ class GStrorage { } class SettingBoxKey { - static const String themeMode = 'themeMode'; - static const String feedBackEnable = 'feedBackEnable'; - static const String defaultFontSize = 'fontSize'; - static const String defaultVideoQa = 'defaultVideoQa'; - static const String defaultAudioQa = 'defaultAudioQa'; - static const String defaultDecode = 'defaultDecode'; + /// 播放器 + static const String btmProgressBehavior = 'btmProgressBehavior'; static const String defaultVideoSpeed = 'defaultVideoSpeed'; static const String autoUpgradeEnable = 'autoUpgradeEnable'; + static const String feedBackEnable = 'feedBackEnable'; + static const String defaultVideoQa = 'defaultVideoQa'; + static const String defaultAudioQa = 'defaultAudioQa'; static const String autoPlayEnable = 'autoPlayEnable'; - static const String enableHA = 'enableHA'; - static const String defaultPicQa = 'defaultPicQa'; - - static const String danmakuEnable = 'danmakuEnable'; static const String fullScreenMode = 'fullScreenMode'; + static const String defaultDecode = 'defaultDecode'; + static const String danmakuEnable = 'danmakuEnable'; + static const String defaultPicQa = 'defaultPicQa'; + static const String enableHA = 'enableHA'; + static const String enableOnlineTotal = 'enableOnlineTotal'; + static const String enableAutoBrightness = 'enableAutoBrightness'; + /// 隐私 static const String blackMidsList = 'blackMidsList'; + + /// 其他 static const String autoUpdate = 'autoUpdate'; - static const String btmProgressBehavior = 'btmProgressBehavior'; + static const String replySortType = 'replySortType'; + static const String defaultDynamicType = 'defaultDynamicType'; + + /// 外观 + static const String themeMode = 'themeMode'; + static const String defaultTextScale = 'textScale'; + static const String dynamicColor = 'dynamicColor'; // bool + static const String customColor = 'customColor'; // 自定义主题色 + static const String iosTransition = 'iosTransition'; // ios路由 } class LocalCacheKey { diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index cb32b38e..27310d31 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:math'; +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'; @@ -210,6 +211,7 @@ class Utils { bool isUpdate = Utils.needUpdate(currentInfo.version, data.tagName!); if (isUpdate) { SmartDialog.show( + animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) { return AlertDialog( title: const Text('🎉 发现新版本 '), @@ -228,22 +230,27 @@ class Utils { ), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), - child: Text( - '稍后', - style: - TextStyle(color: Theme.of(context).colorScheme.outline), - )), + onPressed: () => SmartDialog.dismiss(), + child: Text( + '稍后', + style: + TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), TextButton( - onPressed: () async { - await SmartDialog.dismiss(); - launchUrl( - Uri.parse( - 'https://github.com/guozhigq/pilipala/releases'), - mode: LaunchMode.externalApplication, - ); - }, - child: const Text('去下载')), + onPressed: () async { + await SmartDialog.dismiss(); + launchUrl( + Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'), + mode: LaunchMode.externalApplication, + ); + }, + child: const Text('网盘下载'), + ), + TextButton( + onPressed: () => matchVersion(data), + child: const Text('Github下载'), + ), ], ); }, @@ -251,4 +258,27 @@ class Utils { } return true; } + + // 下载适用于当前系统的安装包 + static Future matchVersion(data) async { + await SmartDialog.dismiss(); + // 获取设备信息 + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + if (Platform.isAndroid) { + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + // [arm64-v8a] + String abi = androidInfo.supportedAbis.first; + late String downloadUrl; + for (var i in data.assets) { + if (i.downloadUrl.contains(abi)) { + downloadUrl = i.downloadUrl; + } + } + // 应用外下载 + launchUrl( + Uri.parse(downloadUrl), + mode: LaunchMode.externalApplication, + ); + } + } } diff --git a/pubspec.lock b/pubspec.lock index dd13be73..eddd41e7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -438,6 +438,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.1" + flutter_displaymode: + dependency: "direct main" + description: + name: flutter_displaymode + sha256: "42c5e9abd13d28ed74f701b60529d7f8416947e58256e6659c5550db719c57ef" + url: "https://pub.dev" + source: hosted + version: "0.6.0" flutter_launcher_icons: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5d6fde5d..a49569a2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.3 +version: 1.0.5 environment: sdk: ">=2.19.6 <3.0.0" @@ -117,6 +117,8 @@ dependencies: flutter_svg: ^2.0.7 # 防抖节流 easy_debounce: ^2.0.3 + # 高帧率 + flutter_displaymode: ^0.6.0 dev_dependencies: flutter_test: