diff --git a/README.md b/README.md
index a5274cc1..fac2a885 100644
--- a/README.md
+++ b/README.md
@@ -11,10 +11,11 @@
+
-### 开发环境
+## 开发环境
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: