Merge branch 'main' into feature-danmaku
This commit is contained in:
18
README.md
18
README.md
@ -11,10 +11,11 @@
|
|||||||
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/174shots_so.png" width="32%" alt="home" />
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/174shots_so.png" width="32%" alt="home" />
|
||||||
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/850shots_so.png" width="32%" alt="home" />
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/850shots_so.png" width="32%" alt="home" />
|
||||||
<br/>
|
<br/>
|
||||||
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/main_screen.png" width="96%" alt="home" />
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### 开发环境
|
## 开发环境
|
||||||
Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -30,7 +31,7 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
|||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
### 功能
|
## 功能
|
||||||
|
|
||||||
目前着重移动端(Android、iOS),暂时没有适配桌面端、Pad端、手表端等
|
目前着重移动端(Android、iOS),暂时没有适配桌面端、Pad端、手表端等
|
||||||
|
|
||||||
@ -98,17 +99,18 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
|||||||
- [x] 图片质量设定
|
- [x] 图片质量设定
|
||||||
- [x] 主题模式:亮色/暗色/跟随系统
|
- [x] 主题模式:亮色/暗色/跟随系统
|
||||||
- [x] 震动反馈(可选)
|
- [x] 震动反馈(可选)
|
||||||
|
- [x] 高帧率
|
||||||
- [ ] 等等
|
- [ ] 等等
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
### 下载
|
## 下载
|
||||||
|
|
||||||
可以通过右侧release进行下载或拉取代码到本地进行编译
|
可以通过右侧release进行下载或拉取代码到本地进行编译
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
### 声明
|
## 声明
|
||||||
|
|
||||||
此项目(PiliPala)是个人为了兴趣而开发, 仅用于学习和测试。
|
此项目(PiliPala)是个人为了兴趣而开发, 仅用于学习和测试。
|
||||||
所用API皆从官方网站收集, 不提供任何破解内容。
|
所用API皆从官方网站收集, 不提供任何破解内容。
|
||||||
@ -117,7 +119,13 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
|||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
### 致谢
|
## 技术交流
|
||||||
|
|
||||||
|
Telegram https://t.me/+lm_oOVmF0RJiODk1
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
## 致谢
|
||||||
|
|
||||||
- [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
|
- [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
|
||||||
- [flutter_meedu_videoplayer](https://github.com/zezo357/flutter_meedu_videoplayer)
|
- [flutter_meedu_videoplayer](https://github.com/zezo357/flutter_meedu_videoplayer)
|
||||||
|
BIN
assets/sreenshot/main_screen.png
Normal file
BIN
assets/sreenshot/main_screen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
21
change_log/1.0.4.0822.md
Normal file
21
change_log/1.0.4.0822.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
## 1.0.4
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 热搜刷新
|
||||||
|
+ 视频搜索排序、筛选
|
||||||
|
+ app字体大小自定义
|
||||||
|
+ app主题色自定义
|
||||||
|
+ 「课堂」类动态渲染
|
||||||
|
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 搜索词联想richText渲染异常
|
||||||
|
+ 部分动态点赞异常
|
||||||
|
+ 默认视频解码格式
|
||||||
|
+ 搜索页面返回搜索词未清空
|
||||||
|
+ 动态详情评论加载异常
|
||||||
|
+ 动态页面下拉刷新数据异常
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 一些样式修改
|
||||||
|
+ 取消热搜词缓存
|
30
change_log/1.0.5.0826.md
Normal file
30
change_log/1.0.5.0826.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
## 1.0.5
|
||||||
|
|
||||||
|
主要是bug修复跟一部分小功能,弹幕功能需要下一版。
|
||||||
|
问题反馈请前往QQ频道或提交issues。
|
||||||
|
感谢🙏酷友「无力感*」「斤斤计较呀」「Pseudopamine」
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 高帧率支持
|
||||||
|
+ 默认评论排序设置
|
||||||
|
+ 默认动态类别设置
|
||||||
|
+ 动态合集查看
|
||||||
|
+ 同时观看人数
|
||||||
|
+ iOS路由切换效果
|
||||||
|
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 收藏夹翻页
|
||||||
|
+ 首页搜索框频繁点击消失
|
||||||
|
+ 评论排序切换空白
|
||||||
|
+ 快速返回首页
|
||||||
|
+ 重复进入个人中心页面数据未刷新
|
||||||
|
+ 动态goods数据异常
|
||||||
|
+ 大会员切换番剧
|
||||||
|
+ 高画质codes匹配
|
||||||
|
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 倍速选择
|
||||||
|
+ 播放器亮度记忆
|
||||||
|
+ 下载对应版本apk
|
351
lib/common/widgets/app_expansion_panel_list.dart
Normal file
351
lib/common/widgets/app_expansion_panel_list.dart
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
const double _kPanelHeaderCollapsedHeight = kMinInteractiveDimension;
|
||||||
|
|
||||||
|
class _SaltedKey<S, V> 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<S, V> &&
|
||||||
|
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<AppExpansionPanel> 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<AppExpansionPanelList> createState() => _AppExpansionPanelListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppExpansionPanelListState extends State<AppExpansionPanelList> {
|
||||||
|
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<ExpansionPanelRadio>(),
|
||||||
|
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<ExpansionPanelRadio>(),
|
||||||
|
widget.initialOpenPanelValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_currentOpenPanel = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _allIdentifiersUnique() {
|
||||||
|
final Map<Object, bool> identifierMap = <Object, bool>{};
|
||||||
|
for (final ExpansionPanelRadio child
|
||||||
|
in widget.children.cast<ExpansionPanelRadio>()) {
|
||||||
|
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<ExpansionPanelRadio> 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<MergeableMaterialItem> items = <MergeableMaterialItem>[];
|
||||||
|
|
||||||
|
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<BuildContext, int>(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: <Widget>[
|
||||||
|
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<BuildContext, int>(context, index * 2),
|
||||||
|
color: child.backgroundColor,
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
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<BuildContext, int>(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;
|
||||||
|
}
|
@ -58,8 +58,11 @@ class VideoCardH extends StatelessWidget {
|
|||||||
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, boxConstraints) {
|
builder: (context, boxConstraints) {
|
||||||
double width =
|
double width = (boxConstraints.maxWidth -
|
||||||
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
StyleString.cardSpace *
|
||||||
|
6 /
|
||||||
|
MediaQuery.of(context).textScaleFactor) /
|
||||||
|
2;
|
||||||
return Container(
|
return Container(
|
||||||
constraints: const BoxConstraints(minHeight: 88),
|
constraints: const BoxConstraints(minHeight: 88),
|
||||||
height: width / StyleString.aspectRatio,
|
height: width / StyleString.aspectRatio,
|
||||||
@ -123,7 +126,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
|
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -132,7 +135,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
videoItem.title,
|
videoItem.title,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
@ -147,7 +149,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
text: i['text'],
|
text: i['text'],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
color: i['type'] == 'em'
|
color: i['type'] == 'em'
|
||||||
@ -177,7 +178,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
// color: Theme.of(context).colorScheme.surfaceTint),
|
// color: Theme.of(context).colorScheme.surfaceTint),
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
const SizedBox(height: 4),
|
// const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
|
@ -77,11 +77,8 @@ class VideoCardV extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String heroTag = Utils.makeHeroTag(videoItem.id);
|
String heroTag = Utils.makeHeroTag(videoItem.id);
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 0,
|
elevation: 1,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: StyleString.mdRadius,
|
|
||||||
),
|
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
@ -129,14 +126,13 @@ class VideoContent extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(4, 8, 0, 3),
|
padding: const EdgeInsets.fromLTRB(9, 8, 9, 4),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
videoItem.title,
|
videoItem.title,
|
||||||
style: const TextStyle(fontSize: 13),
|
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
@ -288,4 +288,8 @@ class Api {
|
|||||||
// github 获取最新版
|
// github 获取最新版
|
||||||
static const String latestApp =
|
static const String latestApp =
|
||||||
'https://api.github.com/repos/guozhigq/pilipala/releases/latest';
|
'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';
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,18 @@ class DynamicsHttp {
|
|||||||
}
|
}
|
||||||
var res = await Request().get(Api.followDynamic, data: data);
|
var res = await Request().get(Api.followDynamic, data: data);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
try {
|
||||||
'status': true,
|
return {
|
||||||
'data': DynamicsDataModel.fromJson(res.data['data']),
|
'status': true,
|
||||||
};
|
'data': DynamicsDataModel.fromJson(res.data['data']),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': err.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
|
@ -51,10 +51,17 @@ class UserHttp {
|
|||||||
'up_mid': mid,
|
'up_mid': mid,
|
||||||
});
|
});
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
FavFolderData data = FavFolderData.fromJson(res.data['data']);
|
late FavFolderData data;
|
||||||
return {'status': true, 'data': data};
|
if (res.data['data'] != null) {
|
||||||
|
data = FavFolderData.fromJson(res.data['data']);
|
||||||
|
return {'status': true, 'data': data};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'data': [], 'msg': '账号未登录'};
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'] ?? '账号未登录'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,4 +399,16 @@ class VideoHttp {
|
|||||||
return {'status': false, 'msg': res.data['result']['toast']};
|
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']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import 'package:dynamic_color/dynamic_color.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/custom_toast.dart';
|
import 'package:pilipala/common/widgets/custom_toast.dart';
|
||||||
import 'package:pilipala/http/init.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/models/common/theme_type.dart';
|
||||||
import 'package:pilipala/pages/search/index.dart';
|
import 'package:pilipala/pages/search/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
@ -27,6 +28,13 @@ void main() async {
|
|||||||
await Request.setCookie();
|
await Request.setCookie();
|
||||||
await Data.init();
|
await Data.init();
|
||||||
await GStrorage.lazyInit();
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Color brandColor = const Color.fromARGB(255, 92, 182, 123);
|
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
|
// 主题色
|
||||||
|
Color defaultColor =
|
||||||
|
colorThemeTypes[setting.get(SettingBoxKey.customColor, defaultValue: 0)]
|
||||||
|
['color'];
|
||||||
|
Color brandColor = defaultColor;
|
||||||
|
// 主题模式
|
||||||
ThemeType currentThemeValue = ThemeType.values[setting
|
ThemeType currentThemeValue = ThemeType.values[setting
|
||||||
.get(SettingBoxKey.themeMode, defaultValue: ThemeType.system.code)];
|
.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(
|
return DynamicColorBuilder(
|
||||||
builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
||||||
ColorScheme? lightColorScheme;
|
ColorScheme? lightColorScheme;
|
||||||
ColorScheme? darkColorScheme;
|
ColorScheme? darkColorScheme;
|
||||||
if (lightDynamic != null && darkDynamic != null) {
|
if (lightDynamic != null && darkDynamic != null && isDynamicColor) {
|
||||||
// dynamic取色成功
|
// dynamic取色成功
|
||||||
lightColorScheme = lightDynamic.harmonized();
|
lightColorScheme = lightDynamic.harmonized();
|
||||||
darkColorScheme = darkDynamic.harmonized();
|
darkColorScheme = darkDynamic.harmonized();
|
||||||
@ -93,9 +113,17 @@ class MyApp extends StatelessWidget {
|
|||||||
fallbackLocale: const Locale("zh", "CN"),
|
fallbackLocale: const Locale("zh", "CN"),
|
||||||
getPages: Routes.getPages,
|
getPages: Routes.getPages,
|
||||||
home: const MainApp(),
|
home: const MainApp(),
|
||||||
builder: FlutterSmartDialog.init(
|
builder: (BuildContext context, Widget? child) {
|
||||||
toastBuilder: (String msg) => CustomToast(msg: msg),
|
return FlutterSmartDialog(
|
||||||
),
|
toastBuilder: (String msg) => CustomToast(msg: msg),
|
||||||
|
child: MediaQuery(
|
||||||
|
data: MediaQuery.of(context).copyWith(
|
||||||
|
textScaleFactor:
|
||||||
|
MediaQuery.of(context).textScaleFactor * textScale),
|
||||||
|
child: child!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
navigatorObservers: [
|
navigatorObservers: [
|
||||||
VideoDetailPage.routeObserver,
|
VideoDetailPage.routeObserver,
|
||||||
SearchPage.routeObserver,
|
SearchPage.routeObserver,
|
||||||
|
23
lib/models/common/color_type.dart
Normal file
23
lib/models/common/color_type.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
final List<Map<String, dynamic>> 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': '灰色'},
|
||||||
|
];
|
@ -360,7 +360,7 @@ class GoodItem {
|
|||||||
|
|
||||||
String? brief;
|
String? brief;
|
||||||
String? cover;
|
String? cover;
|
||||||
String? id;
|
dynamic id;
|
||||||
String? jumpDesc;
|
String? jumpDesc;
|
||||||
String? jumpUrl;
|
String? jumpUrl;
|
||||||
String? name;
|
String? name;
|
||||||
|
@ -93,26 +93,19 @@ extension AudioQualityDesc on AudioQuality {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum VideoDecodeFormats {
|
enum VideoDecodeFormats {
|
||||||
|
DVH1,
|
||||||
AV1,
|
AV1,
|
||||||
HEVC,
|
HEVC,
|
||||||
AVC,
|
AVC,
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VideoDecodeFormatsDesc on VideoDecodeFormats {
|
extension VideoDecodeFormatsDesc on VideoDecodeFormats {
|
||||||
static final List<String> _descList = [
|
static final List<String> _descList = ['DVH1', 'AV1', 'HEVC', 'AVC'];
|
||||||
'AV1',
|
|
||||||
'HEVC',
|
|
||||||
'AVC',
|
|
||||||
];
|
|
||||||
get description => _descList[index];
|
get description => _descList[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VideoDecodeFormatsCode on VideoDecodeFormats {
|
extension VideoDecodeFormatsCode on VideoDecodeFormats {
|
||||||
static final List<String> _codeList = [
|
static final List<String> _codeList = ['dvh1', 'av01', 'hev1', 'avc1'];
|
||||||
'av01',
|
|
||||||
'hev1',
|
|
||||||
'avc1',
|
|
||||||
];
|
|
||||||
get code => _codeList[index];
|
get code => _codeList[index];
|
||||||
|
|
||||||
static VideoDecodeFormats? fromCode(String code) {
|
static VideoDecodeFormats? fromCode(String code) {
|
||||||
|
@ -47,6 +47,11 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
'PiliPala',
|
'PiliPala',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
'使用Flutter开发的哔哩哔哩第三方客户端',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Obx(
|
Obx(
|
||||||
() => ListTile(
|
() => ListTile(
|
||||||
@ -82,16 +87,6 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
height: 30,
|
height: 30,
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
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(
|
ListTile(
|
||||||
onTap: () => _aboutController.githubUrl(),
|
onTap: () => _aboutController.githubUrl(),
|
||||||
title: const Text('Github'),
|
title: const Text('Github'),
|
||||||
@ -123,6 +118,11 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
title: const Text('TG频道'),
|
title: const Text('TG频道'),
|
||||||
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
|
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(
|
Divider(
|
||||||
thickness: 8,
|
thickness: 8,
|
||||||
height: 30,
|
height: 30,
|
||||||
@ -141,11 +141,12 @@ class AboutController extends GetxController {
|
|||||||
late LatestDataModel remoteAppInfo;
|
late LatestDataModel remoteAppInfo;
|
||||||
RxBool isUpdate = true.obs;
|
RxBool isUpdate = true.obs;
|
||||||
RxBool isLoading = true.obs;
|
RxBool isLoading = true.obs;
|
||||||
|
late LatestDataModel data;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
init();
|
// init();
|
||||||
// 获取当前版本
|
// 获取当前版本
|
||||||
getCurrentApp();
|
getCurrentApp();
|
||||||
// 获取最新的版本
|
// 获取最新的版本
|
||||||
@ -153,16 +154,16 @@ class AboutController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取设备信息
|
// 获取设备信息
|
||||||
Future init() async {
|
// Future init() async {
|
||||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
// DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||||
if (Platform.isAndroid) {
|
// if (Platform.isAndroid) {
|
||||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
// AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||||
print(androidInfo.supportedAbis);
|
// print(androidInfo.supportedAbis);
|
||||||
} else if (Platform.isIOS) {
|
// } else if (Platform.isIOS) {
|
||||||
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
// IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
||||||
print(iosInfo);
|
// print(iosInfo);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 获取当前版本
|
// 获取当前版本
|
||||||
Future getCurrentApp() async {
|
Future getCurrentApp() async {
|
||||||
@ -173,7 +174,7 @@ class AboutController extends GetxController {
|
|||||||
// 获取远程版本
|
// 获取远程版本
|
||||||
Future getRemoteApp() async {
|
Future getRemoteApp() async {
|
||||||
var result = await Request().get(Api.latestApp);
|
var result = await Request().get(Api.latestApp);
|
||||||
LatestDataModel data = LatestDataModel.fromJson(result.data);
|
data = LatestDataModel.fromJson(result.data);
|
||||||
remoteAppInfo = data;
|
remoteAppInfo = data;
|
||||||
remoteVersion.value = data.tagName!;
|
remoteVersion.value = data.tagName!;
|
||||||
isUpdate.value =
|
isUpdate.value =
|
||||||
@ -183,15 +184,7 @@ class AboutController extends GetxController {
|
|||||||
|
|
||||||
// 跳转下载/本地更新
|
// 跳转下载/本地更新
|
||||||
Future onUpdate() async {
|
Future onUpdate() async {
|
||||||
// final dir = await getApplicationSupportDirectory();
|
Utils.matchVersion(data);
|
||||||
// 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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳转github
|
// 跳转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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,7 +213,8 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
crossAxisSpacing: StyleString.cardSpace,
|
crossAxisSpacing: StyleString.cardSpace,
|
||||||
// 列数
|
// 列数
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
mainAxisExtent: Get.size.width / 3 / 0.65 + 30,
|
mainAxisExtent: Get.size.width / 3 / 0.65 +
|
||||||
|
32 * MediaQuery.of(context).textScaleFactor,
|
||||||
),
|
),
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.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/models/bangumi/info.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class BangumiPanel extends StatefulWidget {
|
class BangumiPanel extends StatefulWidget {
|
||||||
final List<EpisodeItem> pages;
|
final List<EpisodeItem> pages;
|
||||||
@ -24,12 +26,20 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
late int currentIndex;
|
late int currentIndex;
|
||||||
final ScrollController listViewScrollCtr = ScrollController();
|
final ScrollController listViewScrollCtr = ScrollController();
|
||||||
final ScrollController listViewScrollCtr_2 = ScrollController();
|
final ScrollController listViewScrollCtr_2 = ScrollController();
|
||||||
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
|
dynamic userInfo;
|
||||||
|
// 默认未开通
|
||||||
|
int vipStatus = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
currentIndex = widget.pages.indexWhere((e) => e.cid == widget.cid!);
|
currentIndex = widget.pages.indexWhere((e) => e.cid == widget.cid!);
|
||||||
scrollToIndex();
|
scrollToIndex();
|
||||||
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
|
if (userInfo != null) {
|
||||||
|
vipStatus = userInfo.vipStatus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -126,7 +136,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void changeFucCall(item, i) async {
|
void changeFucCall(item, i) async {
|
||||||
if (item.badge != null) {
|
if (item.badge != null && vipStatus != 1) {
|
||||||
SmartDialog.showToast('需要大会员');
|
SmartDialog.showToast('需要大会员');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,6 @@ class BangumiCardV extends StatelessWidget {
|
|||||||
return Card(
|
return Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: StyleString.mdRadius,
|
|
||||||
),
|
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
// onLongPress: () {
|
// onLongPress: () {
|
||||||
@ -149,7 +146,6 @@ class BangumiContent extends StatelessWidget {
|
|||||||
bangumiItem.title,
|
bangumiItem.title,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
),
|
),
|
||||||
@ -158,6 +154,7 @@ class BangumiContent extends StatelessWidget {
|
|||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 1),
|
||||||
if (bangumiItem.indexShow != null)
|
if (bangumiItem.indexShow != null)
|
||||||
Text(
|
Text(
|
||||||
bangumiItem.indexShow,
|
bangumiItem.indexShow,
|
||||||
|
@ -13,6 +13,7 @@ import 'package:pilipala/models/dynamics/result.dart';
|
|||||||
import 'package:pilipala/models/dynamics/up.dart';
|
import 'package:pilipala/models/dynamics/up.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
import 'package:pilipala/utils/feed_back.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/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
@ -50,17 +51,21 @@ class DynamicsController extends GetxController {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
bool flag = false;
|
bool flag = false;
|
||||||
RxInt initialValue = 1.obs;
|
RxInt initialValue = 0.obs;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
RxBool userLogin = false.obs;
|
RxBool userLogin = false.obs;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
RxBool isLoadingDynamic = false.obs;
|
RxBool isLoadingDynamic = false.obs;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
userLogin.value = userInfo != null;
|
userLogin.value = userInfo != null;
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
initialValue.value =
|
||||||
|
setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0);
|
||||||
|
dynamicsType = DynamicsType.values[initialValue.value].obs;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future queryFollowDynamic({type = 'init'}) async {
|
Future queryFollowDynamic({type = 'init'}) async {
|
||||||
@ -99,7 +104,7 @@ class DynamicsController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSelectType(value) async {
|
onSelectType(value) async {
|
||||||
dynamicsType.value = filterTypeList[value - 1]['value'];
|
dynamicsType.value = filterTypeList[value]['value'];
|
||||||
dynamicsList.value = [DynamicItemModel()];
|
dynamicsList.value = [DynamicItemModel()];
|
||||||
page = 1;
|
page = 1;
|
||||||
initialValue.value = value;
|
initialValue.value = value;
|
||||||
@ -109,16 +114,21 @@ class DynamicsController extends GetxController {
|
|||||||
|
|
||||||
pushDetail(item, floor, {action = 'all'}) async {
|
pushDetail(item, floor, {action = 'all'}) async {
|
||||||
feedBack();
|
feedBack();
|
||||||
|
|
||||||
|
/// 点击评论action 直接查看评论
|
||||||
if (action == 'comment') {
|
if (action == 'comment') {
|
||||||
Get.toNamed('/dynamicDetail',
|
Get.toNamed('/dynamicDetail',
|
||||||
arguments: {'item': item, 'floor': floor, 'action': action});
|
arguments: {'item': item, 'floor': floor, 'action': action});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
switch (item!.type) {
|
switch (item!.type) {
|
||||||
|
/// 转发的动态
|
||||||
case 'DYNAMIC_TYPE_FORWARD':
|
case 'DYNAMIC_TYPE_FORWARD':
|
||||||
Get.toNamed('/dynamicDetail',
|
Get.toNamed('/dynamicDetail',
|
||||||
arguments: {'item': item, 'floor': floor});
|
arguments: {'item': item, 'floor': floor});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/// 图文动态查看
|
||||||
case 'DYNAMIC_TYPE_DRAW':
|
case 'DYNAMIC_TYPE_DRAW':
|
||||||
Get.toNamed('/dynamicDetail',
|
Get.toNamed('/dynamicDetail',
|
||||||
arguments: {'item': item, 'floor': floor});
|
arguments: {'item': item, 'floor': floor});
|
||||||
@ -134,6 +144,8 @@ class DynamicsController extends GetxController {
|
|||||||
SmartDialog.showToast(err.toString());
|
SmartDialog.showToast(err.toString());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/// 专栏文章查看
|
||||||
case 'DYNAMIC_TYPE_ARTICLE':
|
case 'DYNAMIC_TYPE_ARTICLE':
|
||||||
String title = item.modules.moduleDynamic.major.opus.title;
|
String title = item.modules.moduleDynamic.major.opus.title;
|
||||||
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
|
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
|
||||||
@ -144,7 +156,10 @@ class DynamicsController extends GetxController {
|
|||||||
break;
|
break;
|
||||||
case 'DYNAMIC_TYPE_PGC':
|
case 'DYNAMIC_TYPE_PGC':
|
||||||
print('番剧');
|
print('番剧');
|
||||||
|
SmartDialog.showToast('暂未支持的类型,请联系开发者');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/// 纯文字动态查看
|
||||||
case 'DYNAMIC_TYPE_WORD':
|
case 'DYNAMIC_TYPE_WORD':
|
||||||
print('纯文本');
|
print('纯文本');
|
||||||
Get.toNamed('/dynamicDetail',
|
Get.toNamed('/dynamicDetail',
|
||||||
@ -168,10 +183,19 @@ class DynamicsController extends GetxController {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/// TODO
|
/// 合集查看
|
||||||
case 'DYNAMIC_TYPE_UGC_SEASON':
|
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;
|
break;
|
||||||
|
|
||||||
|
/// 番剧查看
|
||||||
case 'DYNAMIC_TYPE_PGC_UNION':
|
case 'DYNAMIC_TYPE_PGC_UNION':
|
||||||
print('DYNAMIC_TYPE_PGC_UNION 番剧');
|
print('DYNAMIC_TYPE_PGC_UNION 番剧');
|
||||||
DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc;
|
DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc;
|
||||||
@ -247,7 +271,7 @@ class DynamicsController extends GetxController {
|
|||||||
void resetSearch() {
|
void resetSearch() {
|
||||||
mid.value = -1;
|
mid.value = -1;
|
||||||
dynamicsType.value = DynamicsType.values[0];
|
dynamicsType.value = DynamicsType.values[0];
|
||||||
initialValue.value = 1;
|
initialValue.value = 0;
|
||||||
SmartDialog.showToast('还原默认加载');
|
SmartDialog.showToast('还原默认加载');
|
||||||
dynamicsList.value = [DynamicItemModel()];
|
dynamicsList.value = [DynamicItemModel()];
|
||||||
queryFollowDynamic();
|
queryFollowDynamic();
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/reply.dart';
|
import 'package:pilipala/http/reply.dart';
|
||||||
import 'package:pilipala/models/common/reply_sort_type.dart';
|
import 'package:pilipala/models/common/reply_sort_type.dart';
|
||||||
import 'package:pilipala/models/video/reply/item.dart';
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class DynamicDetailController extends GetxController {
|
class DynamicDetailController extends GetxController {
|
||||||
DynamicDetailController(this.oid, this.type);
|
DynamicDetailController(this.oid, this.type);
|
||||||
@ -16,9 +18,10 @@ class DynamicDetailController extends GetxController {
|
|||||||
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
||||||
RxInt acount = 0.obs;
|
RxInt acount = 0.obs;
|
||||||
|
|
||||||
ReplySortType sortType = ReplySortType.time;
|
ReplySortType _sortType = ReplySortType.time;
|
||||||
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
||||||
RxString sortTypeLabel = ReplySortType.time.labels.obs;
|
RxString sortTypeLabel = ReplySortType.time.labels.obs;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -29,6 +32,11 @@ class DynamicDetailController extends GetxController {
|
|||||||
acount.value =
|
acount.value =
|
||||||
int.parse(item!.modules!.moduleStat!.comment!.count ?? '0');
|
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 {
|
Future queryReplyList({reqType = 'init'}) async {
|
||||||
@ -39,7 +47,7 @@ class DynamicDetailController extends GetxController {
|
|||||||
oid: oid!,
|
oid: oid!,
|
||||||
pageNum: currentPage + 1,
|
pageNum: currentPage + 1,
|
||||||
type: type!,
|
type: type!,
|
||||||
sort: sortType.index,
|
sort: _sortType.index,
|
||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
List<ReplyItemModel> replies = res['data'].replies;
|
List<ReplyItemModel> replies = res['data'].replies;
|
||||||
@ -76,20 +84,20 @@ class DynamicDetailController extends GetxController {
|
|||||||
// 排序搜索评论
|
// 排序搜索评论
|
||||||
queryBySort() {
|
queryBySort() {
|
||||||
feedBack();
|
feedBack();
|
||||||
switch (sortType) {
|
switch (_sortType) {
|
||||||
case ReplySortType.time:
|
case ReplySortType.time:
|
||||||
sortType = ReplySortType.like;
|
_sortType = ReplySortType.like;
|
||||||
break;
|
break;
|
||||||
case ReplySortType.like:
|
case ReplySortType.like:
|
||||||
sortType = ReplySortType.reply;
|
_sortType = ReplySortType.reply;
|
||||||
break;
|
break;
|
||||||
case ReplySortType.reply:
|
case ReplySortType.reply:
|
||||||
sortType = ReplySortType.time;
|
_sortType = ReplySortType.time;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
sortTypeTitle.value = sortType.titles;
|
sortTypeTitle.value = _sortType.titles;
|
||||||
sortTypeLabel.value = sortType.labels;
|
sortTypeLabel.value = _sortType.labels;
|
||||||
replyList.clear();
|
replyList.clear();
|
||||||
queryReplyList(reqType: 'init');
|
queryReplyList(reqType: 'init');
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
initialValue:
|
initialValue:
|
||||||
_dynamicsController.initialValue.value,
|
_dynamicsController.initialValue.value,
|
||||||
children: {
|
children: {
|
||||||
1: Text(
|
0: Text(
|
||||||
'全部',
|
'全部',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
@ -140,19 +140,19 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
.labelMedium!
|
.labelMedium!
|
||||||
.fontSize),
|
.fontSize),
|
||||||
),
|
),
|
||||||
2: Text('投稿',
|
1: Text('投稿',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.labelMedium!
|
.labelMedium!
|
||||||
.fontSize)),
|
.fontSize)),
|
||||||
3: Text('番剧',
|
2: Text('番剧',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.labelMedium!
|
.labelMedium!
|
||||||
.fontSize)),
|
.fontSize)),
|
||||||
4: Text('专栏',
|
3: Text('专栏',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
|
@ -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:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/user.dart';
|
import 'package:pilipala/http/user.dart';
|
||||||
@ -6,23 +8,45 @@ import 'package:pilipala/models/user/info.dart';
|
|||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class FavController extends GetxController {
|
class FavController extends GetxController {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
UserInfoData? userInfo;
|
UserInfoData? userInfo;
|
||||||
|
int currentPage = 1;
|
||||||
|
int pageSize = 10;
|
||||||
|
RxBool hasMore = true.obs;
|
||||||
|
|
||||||
Future<dynamic> queryFavFolder() async {
|
Future<dynamic> queryFavFolder({type = 'init'}) async {
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
if (userInfo == null) {
|
if (userInfo == null) {
|
||||||
return {'status': false, 'msg': '账号未登录'};
|
return {'status': false, 'msg': '账号未登录'};
|
||||||
}
|
}
|
||||||
|
if (!hasMore.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var res = await await UserHttp.userfavFolder(
|
var res = await await UserHttp.userfavFolder(
|
||||||
pn: 1,
|
pn: currentPage,
|
||||||
ps: 10,
|
ps: pageSize,
|
||||||
mid: userInfo!.mid!,
|
mid: userInfo!.mid!,
|
||||||
);
|
);
|
||||||
if (res['status']) {
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future onLoad() async {
|
||||||
|
queryFavFolder(type: 'onload');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
@ -14,11 +15,23 @@ class FavPage extends StatefulWidget {
|
|||||||
class _FavPageState extends State<FavPage> {
|
class _FavPageState extends State<FavPage> {
|
||||||
final FavController _favController = Get.put(FavController());
|
final FavController _favController = Get.put(FavController());
|
||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_futureBuilderFuture = _favController.queryFavFolder();
|
_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
|
@override
|
||||||
@ -40,6 +53,7 @@ class _FavPageState extends State<FavPage> {
|
|||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
return Obx(
|
return Obx(
|
||||||
() => ListView.builder(
|
() => ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
itemCount: _favController.favFolderData.value.list!.length,
|
itemCount: _favController.favFolderData.value.list!.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return FavItem(
|
return FavItem(
|
||||||
|
@ -77,7 +77,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
favFolderItem.title,
|
favFolderItem.title,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
),
|
),
|
||||||
|
@ -127,32 +127,34 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 14),
|
const SizedBox(width: 14),
|
||||||
Column(
|
Expanded(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
const SizedBox(height: 4),
|
children: [
|
||||||
Text(
|
const SizedBox(height: 4),
|
||||||
_favDetailController.item!.title!,
|
Text(
|
||||||
style: TextStyle(
|
_favDetailController.item!.title!,
|
||||||
fontSize: Theme.of(context)
|
style: TextStyle(
|
||||||
.textTheme
|
fontSize: Theme.of(context)
|
||||||
.titleMedium!
|
.textTheme
|
||||||
.fontSize,
|
.titleMedium!
|
||||||
fontWeight: FontWeight.bold),
|
.fontSize,
|
||||||
),
|
fontWeight: FontWeight.bold),
|
||||||
const SizedBox(height: 4),
|
),
|
||||||
Text(
|
const SizedBox(height: 4),
|
||||||
_favDetailController.item!.upper!.name!,
|
Text(
|
||||||
style: TextStyle(
|
_favDetailController.item!.upper!.name!,
|
||||||
fontSize: Theme.of(context)
|
style: TextStyle(
|
||||||
.textTheme
|
fontSize: Theme.of(context)
|
||||||
.labelSmall!
|
.textTheme
|
||||||
.fontSize,
|
.labelSmall!
|
||||||
color: Theme.of(context).colorScheme.outline),
|
.fontSize,
|
||||||
)
|
color: Theme.of(context).colorScheme.outline),
|
||||||
],
|
)
|
||||||
)
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -159,7 +159,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
videoItem.title,
|
videoItem.title,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
),
|
),
|
||||||
|
@ -205,7 +205,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
videoItem.title,
|
videoItem.title,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
),
|
),
|
||||||
|
@ -146,7 +146,8 @@ class _LivePageState extends State<LivePage> {
|
|||||||
// 列数
|
// 列数
|
||||||
crossAxisCount: crossAxisCount,
|
crossAxisCount: crossAxisCount,
|
||||||
mainAxisExtent:
|
mainAxisExtent:
|
||||||
Get.size.width / crossAxisCount / StyleString.aspectRatio + 66,
|
Get.size.width / crossAxisCount / StyleString.aspectRatio +
|
||||||
|
68 * MediaQuery.of(context).textScaleFactor,
|
||||||
),
|
),
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
|
@ -23,11 +23,8 @@ class LiveCardV extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String heroTag = Utils.makeHeroTag(liveItem.roomId);
|
String heroTag = Utils.makeHeroTag(liveItem.roomId);
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 0,
|
elevation: 1,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: StyleString.mdRadius,
|
|
||||||
),
|
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
@ -103,7 +100,7 @@ class LiveContent extends StatelessWidget {
|
|||||||
return Expanded(
|
return Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
// 多列
|
// 多列
|
||||||
padding: const EdgeInsets.fromLTRB(4, 8, 0, 6),
|
padding: const EdgeInsets.fromLTRB(9, 9, 9, 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@ -112,7 +109,6 @@ class LiveContent extends StatelessWidget {
|
|||||||
liveItem.title,
|
liveItem.title,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
),
|
),
|
||||||
|
@ -138,7 +138,7 @@ class _MediaPageState extends State<MediaPage>
|
|||||||
// const SizedBox(height: 10),
|
// const SizedBox(height: 10),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 170,
|
height: 170 * MediaQuery.of(context).textScaleFactor,
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
@ -142,7 +142,8 @@ class _ArchivePanelState extends State<ArchivePanel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
|
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
|
||||||
final ArchiveController _archiveController = Get.put(ArchiveController());
|
final ArchiveController _archiveController =
|
||||||
|
Get.put(ArchiveController(), tag: Get.arguments['heroTag']);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||||
|
@ -118,7 +118,8 @@ class _MemberDynamicPanelState extends State<MemberDynamicPanel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LoadMoreListSource extends LoadingMoreBase<DynamicItemModel> {
|
class LoadMoreListSource extends LoadingMoreBase<DynamicItemModel> {
|
||||||
final _dynamicController = Get.put(MemberDynamicPanelController());
|
final _dynamicController =
|
||||||
|
Get.put(MemberDynamicPanelController(), tag: Get.arguments['heroTag']);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||||
|
@ -159,7 +159,8 @@ class _RcmdPageState extends State<RcmdPage>
|
|||||||
// 列数
|
// 列数
|
||||||
crossAxisCount: crossAxisCount,
|
crossAxisCount: crossAxisCount,
|
||||||
mainAxisExtent:
|
mainAxisExtent:
|
||||||
Get.size.width / crossAxisCount / StyleString.aspectRatio + 66,
|
(Get.size.width / crossAxisCount / StyleString.aspectRatio) +
|
||||||
|
68 * MediaQuery.of(context).textScaleFactor,
|
||||||
),
|
),
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
|
@ -45,11 +45,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
|||||||
return OpenContainer(
|
return OpenContainer(
|
||||||
closedElevation: 0,
|
closedElevation: 0,
|
||||||
openElevation: 0,
|
openElevation: 0,
|
||||||
onClosed: (_) async {
|
onClosed: (_) => _searchController.onClear(),
|
||||||
// 在 openBuilder 关闭时触发的回调函数
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
_searchController.onClear();
|
|
||||||
},
|
|
||||||
openColor: Theme.of(context).colorScheme.background,
|
openColor: Theme.of(context).colorScheme.background,
|
||||||
middleColor: Theme.of(context).colorScheme.background,
|
middleColor: Theme.of(context).colorScheme.background,
|
||||||
closedColor: Theme.of(context).colorScheme.background,
|
closedColor: Theme.of(context).colorScheme.background,
|
||||||
|
@ -12,13 +12,12 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
|
|||||||
primary: false,
|
primary: false,
|
||||||
controller: ctr!.scrollController,
|
controller: ctr!.scrollController,
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
crossAxisSpacing: StyleString.cardSpace + 2,
|
crossAxisSpacing: StyleString.cardSpace + 2,
|
||||||
mainAxisSpacing: StyleString.cardSpace + 3,
|
mainAxisSpacing: StyleString.cardSpace + 3,
|
||||||
mainAxisExtent:
|
mainAxisExtent:
|
||||||
MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio +
|
MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio +
|
||||||
60,
|
66 * MediaQuery.of(context).textScaleFactor),
|
||||||
),
|
|
||||||
itemCount: list.length,
|
itemCount: list.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return LiveItem(liveItem: list![index]);
|
return LiveItem(liveItem: list![index]);
|
||||||
@ -35,11 +34,8 @@ class LiveItem extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String heroTag = Utils.makeHeroTag(liveItem.roomid);
|
String heroTag = Utils.makeHeroTag(liveItem.roomid);
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 0,
|
elevation: 1,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: StyleString.mdRadius,
|
|
||||||
),
|
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
@ -104,7 +100,7 @@ class LiveContent extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(4, 5, 6, 6),
|
padding: const EdgeInsets.fromLTRB(9, 8, 9, 6),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@ -116,7 +112,6 @@ class LiveContent extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
text: i['text'],
|
text: i['text'],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
color: i['type'] == 'em'
|
color: i['type'] == 'em'
|
||||||
|
@ -68,9 +68,10 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
|||||||
text: i['text'],
|
text: i['text'],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.titleSmall!
|
.titleSmall!
|
||||||
.fontSize,
|
.fontSize! *
|
||||||
|
MediaQuery.of(context).textScaleFactor,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: i['type'] == 'em'
|
color: i['type'] == 'em'
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
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 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
import 'widgets/switch_item.dart';
|
import 'widgets/switch_item.dart';
|
||||||
@ -11,8 +14,28 @@ class ExtraSetting extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ExtraSettingState extends State<ExtraSetting> {
|
class _ExtraSettingState extends State<ExtraSetting> {
|
||||||
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
@ -23,8 +46,58 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: const [
|
children: [
|
||||||
SetSwitchItem(
|
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) => <PopupMenuEntry>[
|
||||||
|
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) => <PopupMenuEntry>[
|
||||||
|
for (var i in DynamicsType.values) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
value: i.index,
|
||||||
|
child: Text(i.labels),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SetSwitchItem(
|
||||||
title: '检查更新',
|
title: '检查更新',
|
||||||
subTitle: '每次启动时检查是否需要更新',
|
subTitle: '每次启动时检查是否需要更新',
|
||||||
setKey: SettingBoxKey.autoUpdate,
|
setKey: SettingBoxKey.autoUpdate,
|
||||||
|
162
lib/pages/setting/pages/color_select.dart
Normal file
162
lib/pages/setting/pages/color_select.dart
Normal file
@ -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<ColorSelectPage> createState() => _ColorSelectPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Item {
|
||||||
|
Item({
|
||||||
|
required this.expandedValue,
|
||||||
|
required this.headerValue,
|
||||||
|
this.isExpanded = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
String expandedValue;
|
||||||
|
String headerValue;
|
||||||
|
bool isExpanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Item> generateItems(int count) {
|
||||||
|
return List<Item>.generate(count, (int index) {
|
||||||
|
return Item(
|
||||||
|
headerValue: 'Panel $index',
|
||||||
|
expandedValue: 'This is item number $index',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ColorSelectPageState extends State<ColorSelectPage> {
|
||||||
|
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<Map<String, dynamic>> 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();
|
||||||
|
}
|
||||||
|
}
|
94
lib/pages/setting/pages/display_mode.dart
Normal file
94
lib/pages/setting/pages/display_mode.dart
Normal file
@ -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<SetDiaplayMode> createState() => _SetDiaplayModeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SetDiaplayModeState extends State<SetDiaplayMode> {
|
||||||
|
List<DisplayMode> modes = <DisplayMode>[];
|
||||||
|
DisplayMode? active;
|
||||||
|
DisplayMode? preferred;
|
||||||
|
|
||||||
|
final ValueNotifier<int> page = ValueNotifier<int>(0);
|
||||||
|
late final PageController controller = PageController()
|
||||||
|
..addListener(() {
|
||||||
|
page.value = controller.page!.round();
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
init();
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
|
fetchAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchAll() async {
|
||||||
|
preferred = await FlutterDisplayMode.preferred;
|
||||||
|
active = await FlutterDisplayMode.active;
|
||||||
|
// GStorage().setDisplayModeType(preferred!);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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: <Widget>[
|
||||||
|
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<DisplayMode>(
|
||||||
|
value: mode,
|
||||||
|
title: mode == DisplayMode.auto
|
||||||
|
? const Text('自动')
|
||||||
|
: Text('$mode${mode == active ? " [系统]" : ""}'),
|
||||||
|
groupValue: preferred,
|
||||||
|
onChanged: (DisplayMode? newMode) async {
|
||||||
|
await FlutterDisplayMode.setPreferredMode(newMode!);
|
||||||
|
await Future<dynamic>.delayed(
|
||||||
|
const Duration(milliseconds: 100),
|
||||||
|
);
|
||||||
|
await fetchAll();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
101
lib/pages/setting/pages/font_size_select.dart
Normal file
101
lib/pages/setting/pages/font_size_select.dart
Normal file
@ -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<FontSizeSelectPage> createState() => _FontSizeSelectPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FontSizeSelectPageState extends State<FontSizeSelectPage> {
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
List<double> 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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -66,6 +66,18 @@ class _PlaySettingState extends State<PlaySetting> {
|
|||||||
setKey: SettingBoxKey.enableHA,
|
setKey: SettingBoxKey.enableHA,
|
||||||
defaultVal: true,
|
defaultVal: true,
|
||||||
),
|
),
|
||||||
|
const SetSwitchItem(
|
||||||
|
title: '观看人数',
|
||||||
|
subTitle: '展示同时在看人数',
|
||||||
|
setKey: SettingBoxKey.enableOnlineTotal,
|
||||||
|
defaultVal: false,
|
||||||
|
),
|
||||||
|
const SetSwitchItem(
|
||||||
|
title: '亮度记忆',
|
||||||
|
subTitle: '返回时自动调整视频亮度',
|
||||||
|
setKey: SettingBoxKey.enableAutoBrightness,
|
||||||
|
defaultVal: false,
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
title: Text('默认画质', style: titleStyle),
|
title: Text('默认画质', style: titleStyle),
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.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 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
import 'widgets/switch_item.dart';
|
||||||
|
|
||||||
class StyleSetting extends StatefulWidget {
|
class StyleSetting extends StatefulWidget {
|
||||||
const StyleSetting({super.key});
|
const StyleSetting({super.key});
|
||||||
@ -66,6 +69,12 @@ class _StyleSettingState extends State<StyleSetting> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SetSwitchItem(
|
||||||
|
title: 'iOS路由切换',
|
||||||
|
subTitle: 'iOS路由切换样式,需重启',
|
||||||
|
setKey: SettingBoxKey.iosTransition,
|
||||||
|
defaultVal: false,
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -184,6 +193,25 @@ class _StyleSettingState extends State<StyleSetting> {
|
|||||||
style: subTitleStyle)),
|
style: subTitleStyle)),
|
||||||
trailing: const Icon(Icons.arrow_right_alt_outlined),
|
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),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -32,6 +32,15 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
|
|||||||
val = Setting.get(widget.setKey, defaultValue: widget.defaultVal ?? false);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!;
|
TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!;
|
||||||
@ -41,9 +50,7 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
|
|||||||
.copyWith(color: Theme.of(context).colorScheme.outline);
|
.copyWith(color: Theme.of(context).colorScheme.outline);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
enableFeedback: true,
|
enableFeedback: true,
|
||||||
onTap: () {
|
onTap: () => switchChange(null),
|
||||||
Setting.put(widget.setKey, !val);
|
|
||||||
},
|
|
||||||
title: Text(widget.title!, style: titleStyle),
|
title: Text(widget.title!, style: titleStyle),
|
||||||
subtitle: widget.subTitle != null
|
subtitle: widget.subTitle != null
|
||||||
? Text(widget.subTitle!, style: subTitleStyle)
|
? Text(widget.subTitle!, style: subTitleStyle)
|
||||||
@ -51,22 +58,16 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
|
|||||||
trailing: Transform.scale(
|
trailing: Transform.scale(
|
||||||
scale: 0.8,
|
scale: 0.8,
|
||||||
child: Switch(
|
child: Switch(
|
||||||
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
||||||
(Set<MaterialState> states) {
|
(Set<MaterialState> states) {
|
||||||
if (states.isNotEmpty && states.first == MaterialState.selected) {
|
if (states.isNotEmpty && states.first == MaterialState.selected) {
|
||||||
return const Icon(Icons.done);
|
return const Icon(Icons.done);
|
||||||
}
|
}
|
||||||
return null; // All other states will use the default thumbIcon.
|
return null; // All other states will use the default thumbIcon.
|
||||||
}),
|
}),
|
||||||
value: val,
|
value: val,
|
||||||
onChanged: (value) {
|
onChanged: (val) => switchChange(val),
|
||||||
val = value;
|
),
|
||||||
Setting.put(widget.setKey, value);
|
|
||||||
if (widget.setKey == SettingBoxKey.autoUpdate && value == true) {
|
|
||||||
Utils.checkUpdata();
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
|||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
|
|
||||||
class VideoDetailController extends GetxController
|
class VideoDetailController extends GetxController
|
||||||
with GetSingleTickerProviderStateMixin {
|
with GetSingleTickerProviderStateMixin {
|
||||||
@ -68,6 +69,8 @@ class VideoDetailController extends GetxController
|
|||||||
late String videoUrl;
|
late String videoUrl;
|
||||||
late String audioUrl;
|
late String audioUrl;
|
||||||
late Duration defaultST;
|
late Duration defaultST;
|
||||||
|
// 亮度
|
||||||
|
double? brightness;
|
||||||
// 默认记录历史记录
|
// 默认记录历史记录
|
||||||
bool enableHeart = true;
|
bool enableHeart = true;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
@ -137,8 +140,17 @@ class VideoDetailController extends GetxController
|
|||||||
/// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl
|
/// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl
|
||||||
List<VideoItem> videoList =
|
List<VideoItem> videoList =
|
||||||
data.dash!.video!.where((i) => i.id == currentVideoQa.code).toList();
|
data.dash!.video!.where((i) => i.id == currentVideoQa.code).toList();
|
||||||
firstVideo = videoList
|
try {
|
||||||
.firstWhere((i) => i.codecs!.startsWith(currentDecodeFormats.code));
|
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!;
|
videoUrl = firstVideo.baseUrl!;
|
||||||
|
|
||||||
/// 根据currentAudioQa 重新设置audioUrl
|
/// 根据currentAudioQa 重新设置audioUrl
|
||||||
@ -152,6 +164,12 @@ class VideoDetailController extends GetxController
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future playerInit({video, audio, seekToTime, duration}) async {
|
Future playerInit({video, audio, seekToTime, duration}) async {
|
||||||
|
/// 设置/恢复 屏幕亮度
|
||||||
|
if (brightness != null) {
|
||||||
|
ScreenBrightness().setScreenBrightness(brightness!);
|
||||||
|
} else {
|
||||||
|
ScreenBrightness().resetScreenBrightness();
|
||||||
|
}
|
||||||
await plPlayerController.setDataSource(
|
await plPlayerController.setDataSource(
|
||||||
DataSource(
|
DataSource(
|
||||||
videoSource: video ?? videoUrl,
|
videoSource: video ?? videoUrl,
|
||||||
@ -234,17 +252,25 @@ class VideoDetailController extends GetxController
|
|||||||
defaultValue: VideoDecodeFormats.values.last.code))!;
|
defaultValue: VideoDecodeFormats.values.last.code))!;
|
||||||
try {
|
try {
|
||||||
// 当前视频没有对应格式返回第一个
|
// 当前视频没有对应格式返回第一个
|
||||||
currentDecodeFormats =
|
currentDecodeFormats = supportDecodeFormats
|
||||||
supportDecodeFormats.contains(supportDecodeFormats)
|
.contains(currentDecodeFormats)
|
||||||
? supportDecodeFormats
|
? currentDecodeFormats
|
||||||
: supportDecodeFormats.first;
|
: VideoDecodeFormatsCode.fromString(supportDecodeFormats.first)!;
|
||||||
} catch (_) {}
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
|
||||||
/// 取出符合当前解码格式的videoItem
|
/// 取出符合当前解码格式的videoItem
|
||||||
firstVideo = videosList
|
try {
|
||||||
.firstWhere((e) => e.codecs!.startsWith(currentDecodeFormats.code));
|
firstVideo = videosList.firstWhere(
|
||||||
|
(e) => e.codecs!.startsWith(currentDecodeFormats.code));
|
||||||
|
} catch (_) {
|
||||||
|
firstVideo = videosList.first;
|
||||||
|
}
|
||||||
videoUrl = firstVideo.baseUrl!;
|
videoUrl = firstVideo.baseUrl!;
|
||||||
} catch (_) {}
|
} catch (err) {
|
||||||
|
print(err);
|
||||||
|
}
|
||||||
|
|
||||||
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
|
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
|
||||||
late AudioItem firstAudio;
|
late AudioItem firstAudio;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -51,6 +53,12 @@ class VideoIntroController extends GetxController {
|
|||||||
RxInt lastPlayCid = 0.obs;
|
RxInt lastPlayCid = 0.obs;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
|
|
||||||
|
// 同时观看
|
||||||
|
bool isShowOnlineTotal = false;
|
||||||
|
RxInt totel = 1.obs;
|
||||||
|
Timer? timer;
|
||||||
|
bool isPaused = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
@ -78,6 +86,12 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
userLogin = userInfo != null;
|
userLogin = userInfo != null;
|
||||||
lastPlayCid.value = int.parse(Get.parameters['cid']!);
|
lastPlayCid.value = int.parse(Get.parameters['cid']!);
|
||||||
|
isShowOnlineTotal =
|
||||||
|
setting.get(SettingBoxKey.enableOnlineTotal, defaultValue: false);
|
||||||
|
if (isShowOnlineTotal) {
|
||||||
|
queryOnlineTotal();
|
||||||
|
startTimer(); // 在页面加载时启动定时器
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取视频简介&分p
|
// 获取视频简介&分p
|
||||||
@ -417,4 +431,33 @@ class VideoIntroController extends GetxController {
|
|||||||
lastPlayCid.value = cid;
|
lastPlayCid.value = cid;
|
||||||
await queryVideoIntro();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
late final dynamic owner;
|
late final dynamic owner;
|
||||||
late final dynamic follower;
|
late final dynamic follower;
|
||||||
late final dynamic followStatus;
|
late final dynamic followStatus;
|
||||||
|
late int mid;
|
||||||
|
late String memberHeroTag;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -160,14 +162,15 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
// 用户主页
|
// 用户主页
|
||||||
onPushMember() {
|
onPushMember() {
|
||||||
feedBack();
|
feedBack();
|
||||||
int mid = !loadingStatus
|
mid = !loadingStatus
|
||||||
? widget.videoDetail!.owner!.mid
|
? widget.videoDetail!.owner!.mid
|
||||||
: videoItem['owner'].mid;
|
: videoItem['owner'].mid;
|
||||||
|
memberHeroTag = Utils.makeHeroTag(mid);
|
||||||
String face = !loadingStatus
|
String face = !loadingStatus
|
||||||
? widget.videoDetail!.owner!.face
|
? widget.videoDetail!.owner!.face
|
||||||
: videoItem['owner'].face;
|
: videoItem['owner'].face;
|
||||||
Get.toNamed('/member?mid=$mid',
|
Get.toNamed('/member?mid=$mid',
|
||||||
arguments: {'face': face, 'heroTag': (mid + 99).toString()});
|
arguments: {'face': face, 'heroTag': memberHeroTag});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -255,6 +258,17 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
color: t.colorScheme.outline,
|
color: t.colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
if (videoIntroController.isShowOnlineTotal)
|
||||||
|
Obx(
|
||||||
|
() => Text(
|
||||||
|
'${videoIntroController.totel.value}人在看',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: t.colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -100,7 +100,7 @@ class IntroDetail extends StatelessWidget {
|
|||||||
Text.rich(
|
Text.rich(
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
height: 1.4,
|
height: 1.4,
|
||||||
fontSize: 13,
|
// fontSize: 13,
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
|
@ -28,7 +28,25 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
episodes = widget.ugcSeason.sections!.first.episodes!;
|
|
||||||
|
/// 根据 cid 找到对应集,找到对应 episodes
|
||||||
|
/// 有多个episodes时,只显示其中一个
|
||||||
|
/// TODO 同时显示多个合集
|
||||||
|
List<SectionItem> sections = widget.ugcSeason.sections!;
|
||||||
|
for (int i = 0; i < sections.length; i++) {
|
||||||
|
List<EpisodeItem> 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);
|
currentIndex = episodes.indexWhere((e) => e.cid == widget.cid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +154,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(
|
Text(
|
||||||
'${currentIndex + 1}/${widget.ugcSeason.epCount}',
|
'${currentIndex + 1}/${episodes.length}',
|
||||||
style: Theme.of(context).textTheme.labelMedium,
|
style: Theme.of(context).textTheme.labelMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/reply.dart';
|
import 'package:pilipala/http/reply.dart';
|
||||||
import 'package:pilipala/models/common/reply_sort_type.dart';
|
import 'package:pilipala/models/common/reply_sort_type.dart';
|
||||||
import 'package:pilipala/models/common/reply_type.dart';
|
import 'package:pilipala/models/common/reply_type.dart';
|
||||||
import 'package:pilipala/models/video/reply/item.dart';
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class VideoReplyController extends GetxController {
|
class VideoReplyController extends GetxController {
|
||||||
VideoReplyController(
|
VideoReplyController(
|
||||||
@ -25,13 +28,26 @@ class VideoReplyController extends GetxController {
|
|||||||
bool isLoadingMore = false;
|
bool isLoadingMore = false;
|
||||||
RxString noMore = ''.obs;
|
RxString noMore = ''.obs;
|
||||||
int ps = 20;
|
int ps = 20;
|
||||||
|
RxInt count = 0.obs;
|
||||||
// 当前回复的回复
|
// 当前回复的回复
|
||||||
ReplyItemModel? currentReplyItem;
|
ReplyItemModel? currentReplyItem;
|
||||||
|
|
||||||
ReplySortType sortType = ReplySortType.time;
|
ReplySortType _sortType = ReplySortType.time;
|
||||||
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
||||||
RxString sortTypeLabel = ReplySortType.time.labels.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 {
|
Future queryReplyList({type = 'init'}) async {
|
||||||
isLoadingMore = true;
|
isLoadingMore = true;
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
@ -45,7 +61,7 @@ class VideoReplyController extends GetxController {
|
|||||||
pageNum: currentPage + 1,
|
pageNum: currentPage + 1,
|
||||||
ps: ps,
|
ps: ps,
|
||||||
type: ReplyType.video.index,
|
type: ReplyType.video.index,
|
||||||
sort: sortType.index,
|
sort: _sortType.index,
|
||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
List<ReplyItemModel> replies = res['data'].replies;
|
List<ReplyItemModel> replies = res['data'].replies;
|
||||||
@ -81,6 +97,7 @@ class VideoReplyController extends GetxController {
|
|||||||
replyList.addAll(replies);
|
replyList.addAll(replies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
count.value = res['data'].page.count;
|
||||||
isLoadingMore = false;
|
isLoadingMore = false;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -92,23 +109,26 @@ class VideoReplyController extends GetxController {
|
|||||||
|
|
||||||
// 排序搜索评论
|
// 排序搜索评论
|
||||||
queryBySort() {
|
queryBySort() {
|
||||||
feedBack();
|
EasyThrottle.throttle('queryBySort', const Duration(seconds: 1), () {
|
||||||
switch (sortType) {
|
feedBack();
|
||||||
case ReplySortType.time:
|
switch (_sortType) {
|
||||||
sortType = ReplySortType.like;
|
case ReplySortType.time:
|
||||||
break;
|
_sortType = ReplySortType.like;
|
||||||
case ReplySortType.like:
|
break;
|
||||||
sortType = ReplySortType.reply;
|
case ReplySortType.like:
|
||||||
break;
|
_sortType = ReplySortType.reply;
|
||||||
case ReplySortType.reply:
|
break;
|
||||||
sortType = ReplySortType.time;
|
case ReplySortType.reply:
|
||||||
break;
|
_sortType = ReplySortType.time;
|
||||||
default:
|
break;
|
||||||
}
|
default:
|
||||||
sortTypeTitle.value = sortType.titles;
|
}
|
||||||
sortTypeLabel.value = sortType.labels;
|
sortTypeTitle.value = _sortType.titles;
|
||||||
currentPage = 0;
|
sortTypeLabel.value = _sortType.labels;
|
||||||
replyList.clear();
|
currentPage = 0;
|
||||||
queryReplyList(type: 'init');
|
noMore.value = '';
|
||||||
|
replyList.clear();
|
||||||
|
queryReplyList(type: 'init');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,9 +160,9 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
scale: animation, child: child);
|
scale: animation, child: child);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
_videoReplyController.sortTypeTitle.value,
|
'共${_videoReplyController.count.value}条回复',
|
||||||
key: ValueKey<String>(
|
key: ValueKey<int>(
|
||||||
_videoReplyController.sortTypeTitle.value),
|
_videoReplyController.count.value),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -591,7 +591,8 @@ InlineSpan buildContent(
|
|||||||
if (content.jumpUrl.isNotEmpty && hasMatchMember) {
|
if (content.jumpUrl.isNotEmpty && hasMatchMember) {
|
||||||
List urlKeys = content.jumpUrl.keys.toList();
|
List urlKeys = content.jumpUrl.keys.toList();
|
||||||
matchUrl = matchMember.splitMapJoin(
|
matchUrl = matchMember.splitMapJoin(
|
||||||
RegExp("(?:${urlKeys.join("|")})"),
|
/// RegExp.escape() 转义特殊字符
|
||||||
|
RegExp(RegExp.escape(urlKeys.join("|"))),
|
||||||
onMatch: (Match match) {
|
onMatch: (Match match) {
|
||||||
String matchStr = match[0]!;
|
String matchStr = match[0]!;
|
||||||
spanChilds.add(
|
spanChilds.add(
|
||||||
|
@ -37,12 +37,15 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
PlPlayerController? plPlayerController;
|
PlPlayerController? plPlayerController;
|
||||||
final ScrollController _extendNestCtr = ScrollController();
|
final ScrollController _extendNestCtr = ScrollController();
|
||||||
late StreamController<double> appbarStream;
|
late StreamController<double> appbarStream;
|
||||||
|
final VideoIntroController videoIntroController =
|
||||||
|
Get.put(VideoIntroController(), tag: Get.arguments['heroTag']);
|
||||||
|
|
||||||
PlayerStatus playerStatus = PlayerStatus.playing;
|
PlayerStatus playerStatus = PlayerStatus.playing;
|
||||||
// bool isShowCover = true;
|
// bool isShowCover = true;
|
||||||
double doubleOffset = 0;
|
double doubleOffset = 0;
|
||||||
|
|
||||||
Box localCache = GStrorage.localCache;
|
Box localCache = GStrorage.localCache;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
late double statusBarHeight;
|
late double statusBarHeight;
|
||||||
final videoHeight = Get.size.width * 9 / 16;
|
final videoHeight = Get.size.width * 9 / 16;
|
||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
@ -95,7 +98,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
plPlayerController!.pause();
|
|
||||||
plPlayerController!.dispose();
|
plPlayerController!.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -103,7 +105,12 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
@override
|
@override
|
||||||
// 离开当前页面时
|
// 离开当前页面时
|
||||||
void didPushNext() async {
|
void didPushNext() async {
|
||||||
|
/// 开启
|
||||||
|
if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: false)) {
|
||||||
|
videoDetailController.brightness = plPlayerController!.brightness.value;
|
||||||
|
}
|
||||||
videoDetailController.defaultST = plPlayerController!.position.value;
|
videoDetailController.defaultST = plPlayerController!.position.value;
|
||||||
|
videoIntroController.isPaused = true;
|
||||||
plPlayerController!.pause();
|
plPlayerController!.pause();
|
||||||
super.didPushNext();
|
super.didPushNext();
|
||||||
}
|
}
|
||||||
@ -112,6 +119,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
// 返回当前页面时
|
// 返回当前页面时
|
||||||
void didPopNext() async {
|
void didPopNext() async {
|
||||||
videoDetailController.playerInit();
|
videoDetailController.playerInit();
|
||||||
|
videoIntroController.isPaused = false;
|
||||||
if (_extendNestCtr.position.pixels == 0) {
|
if (_extendNestCtr.position.pixels == 0) {
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
plPlayerController!.play();
|
plPlayerController!.play();
|
||||||
|
@ -387,9 +387,12 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
// 当前选中的解码格式
|
// 当前选中的解码格式
|
||||||
VideoDecodeFormats currentDecodeFormats =
|
VideoDecodeFormats currentDecodeFormats =
|
||||||
widget.videoDetailCtr!.currentDecodeFormats;
|
widget.videoDetailCtr!.currentDecodeFormats;
|
||||||
|
VideoItem firstVideo = widget.videoDetailCtr!.firstVideo;
|
||||||
// 当前视频可用的解码格式
|
// 当前视频可用的解码格式
|
||||||
List<FormatItem> videoFormat = videoInfo.supportFormats!;
|
List<FormatItem> videoFormat = videoInfo.supportFormats!;
|
||||||
List list = videoFormat.first.codecs!;
|
List list = videoFormat
|
||||||
|
.firstWhere((e) => e.quality == firstVideo.quality!.code)
|
||||||
|
.codecs!;
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
@ -483,10 +486,12 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
size: 15,
|
size: 15,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
fuc: () {
|
fuc: () async {
|
||||||
// 销毁播放器实例
|
// 销毁播放器实例
|
||||||
widget.controller!.dispose(type: 'all');
|
await widget.controller!.dispose(type: 'all');
|
||||||
Get.offAll(const MainApp());
|
if (mounted) {
|
||||||
|
Navigator.popUntil(context, (route) => route.isFirst);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
@ -506,9 +511,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () => showSetSpeedSheet(),
|
||||||
_.togglePlaybackSpeed();
|
|
||||||
},
|
|
||||||
child: Text(
|
child: Text(
|
||||||
'${_.playbackSpeed.toString()}X',
|
'${_.playbackSpeed.toString()}X',
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
|
@ -619,7 +619,7 @@ class PlPlayerController {
|
|||||||
try {
|
try {
|
||||||
brightness.value = brightnes;
|
brightness.value = brightnes;
|
||||||
ScreenBrightness().setScreenBrightness(brightnes);
|
ScreenBrightness().setScreenBrightness(brightnes);
|
||||||
setVideoBrightness();
|
// setVideoBrightness();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw 'Failed to set brightness';
|
throw 'Failed to set brightness';
|
||||||
}
|
}
|
||||||
@ -662,27 +662,24 @@ class PlPlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 缓存fit
|
/// 缓存fit
|
||||||
Future<void> setVideoFit() async {
|
// Future<void> setVideoFit() async {
|
||||||
videoStorage.put(VideoBoxKey.videoBrightness, _videoFit.value.name);
|
// videoStorage.put(VideoBoxKey.videoBrightness, _videoFit.value.name);
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// 读取fit
|
/// 读取fit
|
||||||
Future<void> getVideoFit() async {
|
// Future<void> getVideoFit() async {
|
||||||
String fitValue =
|
// String fitValue =
|
||||||
videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 'contain');
|
// videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 'contain');
|
||||||
_videoFit.value = videoFitType
|
// _videoFit.value = videoFitType
|
||||||
.firstWhere((element) => element['attr'] == fitValue)['attr'];
|
// .firstWhere((element) => element['attr'] == fitValue)['attr'];
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// 缓存亮度
|
|
||||||
Future<void> setVideoBrightness() async {}
|
|
||||||
|
|
||||||
/// 读取亮度
|
/// 读取亮度
|
||||||
Future<void> getVideoBrightness() async {
|
// Future<void> getVideoBrightness() async {
|
||||||
double brightnessValue =
|
// double brightnessValue =
|
||||||
videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 0.5);
|
// videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 0.5);
|
||||||
setBrightness(brightnessValue);
|
// setBrightness(brightnessValue);
|
||||||
}
|
// }
|
||||||
|
|
||||||
set controls(bool visible) {
|
set controls(bool visible) {
|
||||||
_showControls.value = visible;
|
_showControls.value = visible;
|
||||||
@ -760,36 +757,41 @@ class PlPlayerController {
|
|||||||
|
|
||||||
Future<void> dispose({String type = 'single'}) async {
|
Future<void> dispose({String type = 'single'}) async {
|
||||||
// 每次减1,最后销毁
|
// 每次减1,最后销毁
|
||||||
if (type == 'single') {
|
if (type == 'single' && playerCount.value > 1) {
|
||||||
_playerCount.value -= 1;
|
_playerCount.value -= 1;
|
||||||
_heartDuration = 0;
|
_heartDuration = 0;
|
||||||
if (playerCount.value > 0) {
|
pause();
|
||||||
return;
|
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();
|
// playerStatus.status.close();
|
||||||
_timerForVolume?.cancel();
|
// dataStatus.status.close();
|
||||||
_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();
|
removeListeners();
|
||||||
dataStatus.status.close();
|
await _videoPlayerController?.dispose();
|
||||||
|
_videoPlayerController = null;
|
||||||
removeListeners();
|
_instance = null;
|
||||||
await _videoPlayerController?.dispose();
|
// 关闭所有视频页面恢复亮度
|
||||||
_videoPlayerController = null;
|
resetBrightness();
|
||||||
_instance = null;
|
} catch (err) {
|
||||||
|
print(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,6 +156,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
widget.controller.brightness.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> triggerFullScreen() async {
|
Future<void> triggerFullScreen() async {
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
// ignore_for_file: must_be_immutable
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/pages/about/index.dart';
|
import 'package:pilipala/pages/about/index.dart';
|
||||||
import 'package:pilipala/pages/blacklist/index.dart';
|
import 'package:pilipala/pages/blacklist/index.dart';
|
||||||
import 'package:pilipala/pages/dynamics/deatil/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/search/index.dart';
|
||||||
import 'package:pilipala/pages/searchResult/index.dart';
|
import 'package:pilipala/pages/searchResult/index.dart';
|
||||||
import 'package:pilipala/pages/setting/extra_setting.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/play_setting.dart';
|
||||||
import 'package:pilipala/pages/setting/privacy_setting.dart';
|
import 'package:pilipala/pages/setting/privacy_setting.dart';
|
||||||
import 'package:pilipala/pages/setting/style_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/webview/index.dart';
|
||||||
import 'package:pilipala/pages/setting/index.dart';
|
import 'package:pilipala/pages/setting/index.dart';
|
||||||
import 'package:pilipala/pages/media/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 {
|
class Routes {
|
||||||
static final List<GetPage> getPages = [
|
static final List<GetPage> 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(
|
GetPage(
|
||||||
name: '/preview',
|
name: '/preview',
|
||||||
@ -43,49 +55,77 @@ class Routes {
|
|||||||
showCupertinoParallax: false,
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -82,24 +82,36 @@ class GStrorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SettingBoxKey {
|
class SettingBoxKey {
|
||||||
static const String themeMode = 'themeMode';
|
/// 播放器
|
||||||
static const String feedBackEnable = 'feedBackEnable';
|
static const String btmProgressBehavior = 'btmProgressBehavior';
|
||||||
static const String defaultFontSize = 'fontSize';
|
|
||||||
static const String defaultVideoQa = 'defaultVideoQa';
|
|
||||||
static const String defaultAudioQa = 'defaultAudioQa';
|
|
||||||
static const String defaultDecode = 'defaultDecode';
|
|
||||||
static const String defaultVideoSpeed = 'defaultVideoSpeed';
|
static const String defaultVideoSpeed = 'defaultVideoSpeed';
|
||||||
static const String autoUpgradeEnable = 'autoUpgradeEnable';
|
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 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 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 blackMidsList = 'blackMidsList';
|
||||||
|
|
||||||
|
/// 其他
|
||||||
static const String autoUpdate = 'autoUpdate';
|
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 {
|
class LocalCacheKey {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get_utils/get_utils.dart';
|
import 'package:get/get_utils/get_utils.dart';
|
||||||
@ -210,6 +211,7 @@ class Utils {
|
|||||||
bool isUpdate = Utils.needUpdate(currentInfo.version, data.tagName!);
|
bool isUpdate = Utils.needUpdate(currentInfo.version, data.tagName!);
|
||||||
if (isUpdate) {
|
if (isUpdate) {
|
||||||
SmartDialog.show(
|
SmartDialog.show(
|
||||||
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('🎉 发现新版本 '),
|
title: const Text('🎉 发现新版本 '),
|
||||||
@ -228,22 +230,27 @@ class Utils {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => SmartDialog.dismiss(),
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
child: Text(
|
child: Text(
|
||||||
'稍后',
|
'稍后',
|
||||||
style:
|
style:
|
||||||
TextStyle(color: Theme.of(context).colorScheme.outline),
|
TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await SmartDialog.dismiss();
|
await SmartDialog.dismiss();
|
||||||
launchUrl(
|
launchUrl(
|
||||||
Uri.parse(
|
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
|
||||||
'https://github.com/guozhigq/pilipala/releases'),
|
mode: LaunchMode.externalApplication,
|
||||||
mode: LaunchMode.externalApplication,
|
);
|
||||||
);
|
},
|
||||||
},
|
child: const Text('网盘下载'),
|
||||||
child: const Text('去下载')),
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => matchVersion(data),
|
||||||
|
child: const Text('Github下载'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -251,4 +258,27 @@ class Utils {
|
|||||||
}
|
}
|
||||||
return true;
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,6 +438,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.1"
|
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:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
@ -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
|
# 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
|
# 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.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.3
|
version: 1.0.5
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.19.6 <3.0.0"
|
sdk: ">=2.19.6 <3.0.0"
|
||||||
@ -117,6 +117,8 @@ dependencies:
|
|||||||
flutter_svg: ^2.0.7
|
flutter_svg: ^2.0.7
|
||||||
# 防抖节流
|
# 防抖节流
|
||||||
easy_debounce: ^2.0.3
|
easy_debounce: ^2.0.3
|
||||||
|
# 高帧率
|
||||||
|
flutter_displaymode: ^0.6.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user