Compare commits

...

12 Commits

Author SHA1 Message Date
dom
b897103af0 Upgrade deps
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-03 09:39:03 +08:00
dom
353664fbd4 Open offline dir on desktop
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-03 09:35:32 +08:00
dom
de3505ce07 Floating NavBar
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-03 09:35:32 +08:00
dom
cdc1720358 Reformat
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-03 09:35:25 +08:00
dom
904d210ba2 build
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-02 12:45:08 +08:00
HCha
db8dd85b63 fix: revert commit 7ee6d1e element.weight (#1879) 2026-04-02 12:44:35 +08:00
dom
8ad130567e Release 2.0.2
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 18:25:39 +08:00
dom
7eb21bc5a2 build
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 18:24:02 +08:00
dom
ea4316a847 opt ui
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 16:57:12 +08:00
dom
2bbc97a950 fix macOS build
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 11:51:22 +08:00
dom
0178d105ba add Local Network permissions for iOS & macOS
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 11:17:32 +08:00
dom
771fa75f48 upgrade deps
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 11:17:09 +08:00
69 changed files with 1016 additions and 158 deletions

View File

@@ -13,7 +13,7 @@ on:
jobs: jobs:
build-mac-app: build-mac-app:
name: Release Mac name: Release Mac
runs-on: macos-latest runs-on: macos-26
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v6

View File

@@ -57,7 +57,6 @@ jobs:
mv dist/**/*.exe PiliPlus-Win-Setup/PiliPlus_windows_${{env.version}}_x64_setup.exe mv dist/**/*.exe PiliPlus-Win-Setup/PiliPlus_windows_${{env.version}}_x64_setup.exe
- name: Compress - name: Compress
if: ${{ github.event.inputs.tag != '' }}
run: | run: |
Compress-Archive -Path "Release/PiliPlus-Win" -DestinationPath "PiliPlus_windows_${{env.version}}_x64_portable.zip" Compress-Archive -Path "Release/PiliPlus-Win" -DestinationPath "PiliPlus_windows_${{env.version}}_x64_portable.zip"
shell: pwsh shell: pwsh
@@ -75,9 +74,9 @@ jobs:
- name: Upload windows file release - name: Upload windows file release
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
archive: true archive: false
name: PiliPlus_windows_${{env.version}}_x64_portable name: Windows-file-x64-release
path: Release path: PiliPlus_windows_*.zip
- name: Upload windows setup release - name: Upload windows setup release
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7

View File

@@ -131,5 +131,13 @@
</array> </array>
<key>UIStatusBarHidden</key> <key>UIStatusBarHidden</key>
<false/> <false/>
<key>NSLocalNetworkUsageDescription</key>
<string>需要访问本地网络以发现和连接 DLNA 投屏设备</string>
<key>NSBonjourServices</key>
<array>
<string>_ssdp._udp</string>
<string>_upnp._tcp</string>
<string>_http._tcp</string>
</array>
</dict> </dict>
</plist> </plist>

View File

@@ -0,0 +1,777 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:flutter/material.dart';
const double _kMaxLabelTextScaleFactor = 1.3;
const _kNavigationHeight = 64.0;
const _kIndicatorHeight = _kNavigationHeight - 2 * _kIndicatorPaddingInt;
const _kIndicatorWidth = 86.0;
const _kIndicatorPaddingInt = 4.0;
const _kIndicatorPadding = EdgeInsets.all(_kIndicatorPaddingInt);
const _kBorderRadius = BorderRadius.all(.circular(_kNavigationHeight / 2));
const _kNavigationShape = RoundedSuperellipseBorder(
borderRadius: _kBorderRadius,
);
/// ref [NavigationBar]
class FloatingNavigationBar extends StatelessWidget {
// ignore: prefer_const_constructors_in_immutables
FloatingNavigationBar({
super.key,
this.animationDuration = const Duration(milliseconds: 500),
this.selectedIndex = 0,
required this.destinations,
this.onDestinationSelected,
this.backgroundColor,
this.elevation,
this.shadowColor,
this.surfaceTintColor,
this.indicatorColor,
this.indicatorShape,
this.labelBehavior,
this.overlayColor,
this.labelTextStyle,
this.labelPadding,
this.bottomPadding = 8.0,
}) : assert(destinations.length >= 2),
assert(0 <= selectedIndex && selectedIndex < destinations.length);
final Duration animationDuration;
final int selectedIndex;
final List<Widget> destinations;
final ValueChanged<int>? onDestinationSelected;
final Color? backgroundColor;
final double? elevation;
final Color? shadowColor;
final Color? surfaceTintColor;
final Color? indicatorColor;
final ShapeBorder? indicatorShape;
final NavigationDestinationLabelBehavior? labelBehavior;
final WidgetStateProperty<Color?>? overlayColor;
final WidgetStateProperty<TextStyle?>? labelTextStyle;
final EdgeInsetsGeometry? labelPadding;
final double bottomPadding;
VoidCallback _handleTap(int index) {
return onDestinationSelected != null
? () => onDestinationSelected!(index)
: () {};
}
@override
Widget build(BuildContext context) {
final defaults = _NavigationBarDefaultsM3(context);
final navigationBarTheme = NavigationBarTheme.of(context);
final effectiveLabelBehavior =
labelBehavior ??
navigationBarTheme.labelBehavior ??
defaults.labelBehavior!;
final padding = MediaQuery.viewPaddingOf(context);
return UnconstrainedBox(
child: Padding(
padding: .fromLTRB(
padding.left,
0,
padding.right,
bottomPadding + padding.bottom,
),
child: SizedBox(
height: _kNavigationHeight,
width: destinations.length * _kIndicatorWidth,
child: DecoratedBox(
decoration: ShapeDecoration(
color: ElevationOverlay.applySurfaceTint(
backgroundColor ??
navigationBarTheme.backgroundColor ??
defaults.backgroundColor!,
surfaceTintColor ??
navigationBarTheme.surfaceTintColor ??
defaults.surfaceTintColor,
elevation ??
navigationBarTheme.elevation ??
defaults.elevation!,
),
shape: RoundedSuperellipseBorder(
side: defaults.borderSide,
borderRadius: _kBorderRadius,
),
),
child: Padding(
padding: _kIndicatorPadding,
child: Row(
crossAxisAlignment: .stretch,
children: <Widget>[
for (int i = 0; i < destinations.length; i++)
Expanded(
child: _SelectableAnimatedBuilder(
duration: animationDuration,
isSelected: i == selectedIndex,
builder: (context, animation) {
return _NavigationDestinationInfo(
index: i,
selectedIndex: selectedIndex,
totalNumberOfDestinations: destinations.length,
selectedAnimation: animation,
labelBehavior: effectiveLabelBehavior,
indicatorColor: indicatorColor,
indicatorShape: indicatorShape,
overlayColor: overlayColor,
onTap: _handleTap(i),
labelTextStyle: labelTextStyle,
labelPadding: labelPadding,
child: destinations[i],
);
},
),
),
],
),
),
),
),
),
);
}
}
class FloatingNavigationDestination extends StatelessWidget {
const FloatingNavigationDestination({
super.key,
required this.icon,
this.selectedIcon,
required this.label,
this.tooltip,
this.enabled = true,
});
final Widget icon;
final Widget? selectedIcon;
final String label;
final String? tooltip;
final bool enabled;
@override
Widget build(BuildContext context) {
final info = _NavigationDestinationInfo.of(context);
const selectedState = <WidgetState>{WidgetState.selected};
const unselectedState = <WidgetState>{};
const disabledState = <WidgetState>{WidgetState.disabled};
final navigationBarTheme = NavigationBarTheme.of(context);
final defaults = _NavigationBarDefaultsM3(context);
final animation = info.selectedAnimation;
return Stack(
alignment: .center,
clipBehavior: .none,
children: [
NavigationIndicator(
animation: animation,
color:
info.indicatorColor ??
navigationBarTheme.indicatorColor ??
defaults.indicatorColor!,
),
_NavigationDestinationBuilder(
label: label,
tooltip: tooltip,
enabled: enabled,
buildIcon: (context) {
final IconThemeData selectedIconTheme =
navigationBarTheme.iconTheme?.resolve(selectedState) ??
defaults.iconTheme!.resolve(selectedState)!;
final IconThemeData unselectedIconTheme =
navigationBarTheme.iconTheme?.resolve(unselectedState) ??
defaults.iconTheme!.resolve(unselectedState)!;
final IconThemeData disabledIconTheme =
navigationBarTheme.iconTheme?.resolve(disabledState) ??
defaults.iconTheme!.resolve(disabledState)!;
final Widget selectedIconWidget = IconTheme.merge(
data: enabled ? selectedIconTheme : disabledIconTheme,
child: selectedIcon ?? icon,
);
final Widget unselectedIconWidget = IconTheme.merge(
data: enabled ? unselectedIconTheme : disabledIconTheme,
child: icon,
);
return _StatusTransitionWidgetBuilder(
animation: animation,
builder: (context, child) {
return animation.isForwardOrCompleted
? selectedIconWidget
: unselectedIconWidget;
},
);
},
buildLabel: (context) {
final TextStyle? effectiveSelectedLabelTextStyle =
info.labelTextStyle?.resolve(selectedState) ??
navigationBarTheme.labelTextStyle?.resolve(selectedState) ??
defaults.labelTextStyle!.resolve(selectedState);
final TextStyle? effectiveUnselectedLabelTextStyle =
info.labelTextStyle?.resolve(unselectedState) ??
navigationBarTheme.labelTextStyle?.resolve(unselectedState) ??
defaults.labelTextStyle!.resolve(unselectedState);
final TextStyle? effectiveDisabledLabelTextStyle =
info.labelTextStyle?.resolve(disabledState) ??
navigationBarTheme.labelTextStyle?.resolve(disabledState) ??
defaults.labelTextStyle!.resolve(disabledState);
final EdgeInsetsGeometry labelPadding =
info.labelPadding ??
navigationBarTheme.labelPadding ??
defaults.labelPadding!;
final textStyle = enabled
? animation.isForwardOrCompleted
? effectiveSelectedLabelTextStyle
: effectiveUnselectedLabelTextStyle
: effectiveDisabledLabelTextStyle;
return Padding(
padding: labelPadding,
child: MediaQuery.withClampedTextScaling(
maxScaleFactor: _kMaxLabelTextScaleFactor,
child: Text(label, style: textStyle),
),
);
},
),
],
);
}
}
class _NavigationDestinationBuilder extends StatefulWidget {
const _NavigationDestinationBuilder({
required this.buildIcon,
required this.buildLabel,
required this.label,
this.tooltip,
this.enabled = true,
});
final WidgetBuilder buildIcon;
final WidgetBuilder buildLabel;
final String label;
final String? tooltip;
final bool enabled;
@override
State<_NavigationDestinationBuilder> createState() =>
_NavigationDestinationBuilderState();
}
class _NavigationDestinationBuilderState
extends State<_NavigationDestinationBuilder> {
final GlobalKey iconKey = GlobalKey();
@override
Widget build(BuildContext context) {
final info = _NavigationDestinationInfo.of(context);
final child = GestureDetector(
behavior: .opaque,
onTap: widget.enabled ? info.onTap : null,
child: _NavigationBarDestinationLayout(
icon: widget.buildIcon(context),
iconKey: iconKey,
label: widget.buildLabel(context),
),
);
if (info.labelBehavior == .alwaysShow) {
return child;
}
return _NavigationBarDestinationTooltip(
message: widget.tooltip ?? widget.label,
child: child,
);
}
}
class _NavigationDestinationInfo extends InheritedWidget {
const _NavigationDestinationInfo({
required this.index,
required this.selectedIndex,
required this.totalNumberOfDestinations,
required this.selectedAnimation,
required this.labelBehavior,
required this.indicatorColor,
required this.indicatorShape,
required this.overlayColor,
required this.onTap,
this.labelTextStyle,
this.labelPadding,
required super.child,
});
final int index;
final int selectedIndex;
final int totalNumberOfDestinations;
final Animation<double> selectedAnimation;
final NavigationDestinationLabelBehavior labelBehavior;
final Color? indicatorColor;
final ShapeBorder? indicatorShape;
final WidgetStateProperty<Color?>? overlayColor;
final VoidCallback onTap;
final WidgetStateProperty<TextStyle?>? labelTextStyle;
final EdgeInsetsGeometry? labelPadding;
static _NavigationDestinationInfo of(BuildContext context) {
final _NavigationDestinationInfo? result = context
.dependOnInheritedWidgetOfExactType<_NavigationDestinationInfo>();
assert(
result != null,
'Navigation destinations need a _NavigationDestinationInfo parent, '
'which is usually provided by NavigationBar.',
);
return result!;
}
@override
bool updateShouldNotify(_NavigationDestinationInfo oldWidget) {
return index != oldWidget.index ||
totalNumberOfDestinations != oldWidget.totalNumberOfDestinations ||
selectedAnimation != oldWidget.selectedAnimation ||
labelBehavior != oldWidget.labelBehavior ||
onTap != oldWidget.onTap;
}
}
class NavigationIndicator extends StatelessWidget {
const NavigationIndicator({
super.key,
required this.animation,
this.color,
this.width = _kIndicatorWidth,
this.height = _kIndicatorHeight,
});
final Animation<double> animation;
final Color? color;
final double width;
final double height;
static final _anim = Tween<double>(
begin: .5,
end: 1.0,
).chain(CurveTween(curve: Curves.easeInOutCubicEmphasized));
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
final double scale = animation.isDismissed
? 0.0
: _anim.evaluate(animation);
return Transform(
alignment: Alignment.center,
transform: Matrix4.diagonal3Values(scale, 1.0, 1.0),
child: child,
);
},
child: _StatusTransitionWidgetBuilder(
animation: animation,
builder: (context, child) {
return _SelectableAnimatedBuilder(
isSelected: animation.isForwardOrCompleted,
duration: const Duration(milliseconds: 100),
alwaysDoFullAnimation: true,
builder: (context, fadeAnimation) {
return FadeTransition(
opacity: fadeAnimation,
child: DecoratedBox(
decoration: ShapeDecoration(
shape: _kNavigationShape,
color: color ?? Theme.of(context).colorScheme.secondary,
),
child: const SizedBox(
width: _kIndicatorWidth,
height: _kIndicatorHeight,
),
),
);
},
);
},
),
);
}
}
class _NavigationBarDestinationLayout extends StatelessWidget {
const _NavigationBarDestinationLayout({
required this.icon,
required this.iconKey,
required this.label,
});
final Widget icon;
final GlobalKey iconKey;
final Widget label;
@override
Widget build(BuildContext context) {
return _DestinationLayoutAnimationBuilder(
builder: (context, animation) {
return CustomMultiChildLayout(
delegate: _NavigationDestinationLayoutDelegate(animation: animation),
children: <Widget>[
LayoutId(
id: _NavigationDestinationLayoutDelegate.iconId,
child: KeyedSubtree(key: iconKey, child: icon),
),
LayoutId(
id: _NavigationDestinationLayoutDelegate.labelId,
child: FadeTransition(
alwaysIncludeSemantics: true,
opacity: animation,
child: label,
),
),
],
);
},
);
}
}
class _DestinationLayoutAnimationBuilder extends StatelessWidget {
const _DestinationLayoutAnimationBuilder({required this.builder});
final Widget Function(BuildContext, Animation<double>) builder;
@override
Widget build(BuildContext context) {
final info = _NavigationDestinationInfo.of(context);
switch (info.labelBehavior) {
case NavigationDestinationLabelBehavior.alwaysShow:
return builder(context, kAlwaysCompleteAnimation);
case NavigationDestinationLabelBehavior.alwaysHide:
return builder(context, kAlwaysDismissedAnimation);
case NavigationDestinationLabelBehavior.onlyShowSelected:
return _CurvedAnimationBuilder(
animation: info.selectedAnimation,
curve: Curves.easeInOutCubicEmphasized,
reverseCurve: Curves.easeInOutCubicEmphasized.flipped,
builder: builder,
);
}
}
}
class _NavigationBarDestinationTooltip extends StatelessWidget {
const _NavigationBarDestinationTooltip({
required this.message,
required this.child,
});
final String message;
final Widget child;
@override
Widget build(BuildContext context) {
return Tooltip(
message: message,
verticalOffset: 34,
excludeFromSemantics: true,
preferBelow: false,
child: child,
);
}
}
class _NavigationDestinationLayoutDelegate extends MultiChildLayoutDelegate {
_NavigationDestinationLayoutDelegate({required this.animation})
: super(relayout: animation);
final Animation<double> animation;
static const int iconId = 1;
static const int labelId = 2;
@override
void performLayout(Size size) {
double halfWidth(Size size) => size.width / 2;
double halfHeight(Size size) => size.height / 2;
final Size iconSize = layoutChild(iconId, BoxConstraints.loose(size));
final Size labelSize = layoutChild(labelId, BoxConstraints.loose(size));
final double yPositionOffset = Tween<double>(
begin: halfHeight(iconSize),
end: halfHeight(iconSize) + halfHeight(labelSize),
).transform(animation.value);
final double iconYPosition = halfHeight(size) - yPositionOffset;
positionChild(
iconId,
Offset(
halfWidth(size) - halfWidth(iconSize),
iconYPosition,
),
);
positionChild(
labelId,
Offset(
halfWidth(size) - halfWidth(labelSize),
iconYPosition + iconSize.height,
),
);
}
@override
bool shouldRelayout(_NavigationDestinationLayoutDelegate oldDelegate) {
return oldDelegate.animation != animation;
}
}
class _StatusTransitionWidgetBuilder extends StatusTransitionWidget {
const _StatusTransitionWidgetBuilder({
required super.animation,
required this.builder,
// ignore: unused_element_parameter
this.child,
});
final TransitionBuilder builder;
final Widget? child;
@override
Widget build(BuildContext context) => builder(context, child);
}
class _SelectableAnimatedBuilder extends StatefulWidget {
const _SelectableAnimatedBuilder({
required this.isSelected,
this.duration = const Duration(milliseconds: 200),
this.alwaysDoFullAnimation = false,
required this.builder,
});
final bool isSelected;
final Duration duration;
final bool alwaysDoFullAnimation;
final Widget Function(BuildContext, Animation<double>) builder;
@override
_SelectableAnimatedBuilderState createState() =>
_SelectableAnimatedBuilderState();
}
class _SelectableAnimatedBuilderState extends State<_SelectableAnimatedBuilder>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.duration = widget.duration;
_controller.value = widget.isSelected ? 1.0 : 0.0;
}
@override
void didUpdateWidget(_SelectableAnimatedBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.duration != widget.duration) {
_controller.duration = widget.duration;
}
if (oldWidget.isSelected != widget.isSelected) {
if (widget.isSelected) {
_controller.forward(from: widget.alwaysDoFullAnimation ? 0 : null);
} else {
_controller.reverse(from: widget.alwaysDoFullAnimation ? 1 : null);
}
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.builder(context, _controller);
}
}
class _CurvedAnimationBuilder extends StatefulWidget {
const _CurvedAnimationBuilder({
required this.animation,
required this.curve,
required this.reverseCurve,
required this.builder,
});
final Animation<double> animation;
final Curve curve;
final Curve reverseCurve;
final Widget Function(BuildContext, Animation<double>) builder;
@override
_CurvedAnimationBuilderState createState() => _CurvedAnimationBuilderState();
}
class _CurvedAnimationBuilderState extends State<_CurvedAnimationBuilder> {
late AnimationStatus _animationDirection;
AnimationStatus? _preservedDirection;
@override
void initState() {
super.initState();
_animationDirection = widget.animation.status;
_updateStatus(widget.animation.status);
widget.animation.addStatusListener(_updateStatus);
}
@override
void dispose() {
widget.animation.removeStatusListener(_updateStatus);
super.dispose();
}
void _updateStatus(AnimationStatus status) {
if (_animationDirection != status) {
setState(() {
_animationDirection = status;
});
}
switch (status) {
case AnimationStatus.forward || AnimationStatus.reverse
when _preservedDirection != null:
break;
case AnimationStatus.forward || AnimationStatus.reverse:
setState(() {
_preservedDirection = status;
});
case AnimationStatus.completed || AnimationStatus.dismissed:
setState(() {
_preservedDirection = null;
});
}
}
@override
Widget build(BuildContext context) {
final shouldUseForwardCurve =
(_preservedDirection ?? _animationDirection) != AnimationStatus.reverse;
final Animation<double> curvedAnimation = CurveTween(
curve: shouldUseForwardCurve ? widget.curve : widget.reverseCurve,
).animate(widget.animation);
return widget.builder(context, curvedAnimation);
}
}
const _indicatorDark = Color(0x15FFFFFF);
const _indicatorLight = Color(0x10000000);
class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
_NavigationBarDefaultsM3(this.context)
: super(
height: _kNavigationHeight,
elevation: 3.0,
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
);
final BuildContext context;
late final _colors = Theme.of(context).colorScheme;
late final _textTheme = Theme.of(context).textTheme;
BorderSide get borderSide => _colors.brightness.isDark
? const BorderSide(color: Color(0x08FFFFFF))
: const BorderSide(color: Color(0x08000000));
@override
Color? get backgroundColor => _colors.surfaceContainer;
@override
Color? get shadowColor => Colors.transparent;
@override
Color? get surfaceTintColor => Colors.transparent;
@override
WidgetStateProperty<IconThemeData?>? get iconTheme {
return WidgetStateProperty.resolveWith((Set<WidgetState> states) {
return IconThemeData(
size: 24.0,
color: states.contains(WidgetState.disabled)
? _colors.onSurfaceVariant.withValues(alpha: 0.38)
: states.contains(WidgetState.selected)
? _colors.onSecondaryContainer
: _colors.onSurfaceVariant,
);
});
}
@override
Color? get indicatorColor =>
_colors.brightness.isDark ? _indicatorDark : _indicatorLight;
@override
ShapeBorder? get indicatorShape => const StadiumBorder();
@override
WidgetStateProperty<TextStyle?>? get labelTextStyle {
return WidgetStateProperty.resolveWith((Set<WidgetState> states) {
final TextStyle style = _textTheme.labelMedium!;
return style.apply(
color: states.contains(WidgetState.disabled)
? _colors.onSurfaceVariant.withValues(alpha: 0.38)
: states.contains(WidgetState.selected)
? _colors.onSurface
: _colors.onSurfaceVariant,
);
});
}
@override
EdgeInsetsGeometry? get labelPadding => const EdgeInsets.only(top: 2);
}

View File

@@ -2,7 +2,7 @@ enum AccountType {
main('主账号'), main('主账号'),
heartbeat('记录观看'), heartbeat('记录观看'),
recommend('推荐'), recommend('推荐'),
video('视频取流') video('视频取流'),
; ;
final String title; final String title;

View File

@@ -3,7 +3,7 @@ enum AudioNormalization {
// ref https://github.com/KRTirtho/spotube/commit/da10ab2e291d4ba4d3082b9a6ae535639fb8f1b7 // ref https://github.com/KRTirtho/spotube/commit/da10ab2e291d4ba4d3082b9a6ae535639fb8f1b7
dynaudnorm('预设 dynaudnorm', 'dynaudnorm=g=5:f=250:r=0.9:p=0.5'), dynaudnorm('预设 dynaudnorm', 'dynaudnorm=g=5:f=250:r=0.9:p=0.5'),
loudnorm('预设 loudnorm', 'loudnorm=I=-16:LRA=11:TP=-1.5'), loudnorm('预设 loudnorm', 'loudnorm=I=-16:LRA=11:TP=-1.5'),
custom('自定义参数') custom('自定义参数'),
; ;
final String title; final String title;

View File

@@ -4,7 +4,7 @@ enum BadgeType {
none(), none(),
vip('大会员'), vip('大会员'),
person('认证个人', Color(0xFFFFCC00)), person('认证个人', Color(0xFFFFCC00)),
institution('认证机构', Colors.lightBlueAccent) institution('认证机构', Colors.lightBlueAccent),
; ;
final String? desc; final String? desc;

View File

@@ -2,7 +2,7 @@ import 'package:PiliPlus/models/common/enum_with_label.dart';
enum BarHideType with EnumWithLabel { enum BarHideType with EnumWithLabel {
instant('即时'), instant('即时'),
sync('同步') sync('同步'),
; ;
@override @override

View File

@@ -1,7 +1,7 @@
enum DmBlockType { enum DmBlockType {
keyword('关键词'), keyword('关键词'),
regex('正则'), regex('正则'),
uid('用户') uid('用户'),
; ;
final String label; final String label;

View File

@@ -1,7 +1,7 @@
enum DynamicBadgeMode { enum DynamicBadgeMode {
hidden('隐藏'), hidden('隐藏'),
point('红点'), point('红点'),
number('数字') number('数字'),
; ;
final String desc; final String desc;

View File

@@ -3,7 +3,7 @@ enum DynamicsTabType {
video('投稿'), video('投稿'),
pgc('番剧'), pgc('番剧'),
article('专栏'), article('专栏'),
up('UP') up('UP'),
; ;
final String label; final String label;

View File

@@ -3,7 +3,7 @@ enum UpPanelPosition {
leftFixed('左侧常驻'), leftFixed('左侧常驻'),
rightFixed('右侧常驻'), rightFixed('右侧常驻'),
leftDrawer('左侧抽屉'), leftDrawer('左侧抽屉'),
rightDrawer('右侧抽屉') rightDrawer('右侧抽屉'),
; ;
final String label; final String label;

View File

@@ -1,7 +1,7 @@
enum EpisodeType { enum EpisodeType {
part('分P'), part('分P'),
season('合集'), season('合集'),
pgc('剧集') pgc('剧集'),
; ;
final String title; final String title;

View File

@@ -1,7 +1,7 @@
enum FavOrderType { enum FavOrderType {
mtime('最近收藏'), mtime('最近收藏'),
view('最多播放'), view('最多播放'),
pubtime('最近投稿') pubtime('最近投稿'),
; ;
final String label; final String label;

View File

@@ -13,7 +13,7 @@ enum FavTabType {
article('专栏', FavArticlePage()), article('专栏', FavArticlePage()),
note('笔记', FavNotePage()), note('笔记', FavNotePage()),
topic('话题', FavTopicPage()), topic('话题', FavTopicPage()),
cheese('课堂', FavCheesePage()) cheese('课堂', FavCheesePage()),
; ;
final String title; final String title;

View File

@@ -1,6 +1,6 @@
enum FollowOrderType { enum FollowOrderType {
def('', '最近关注'), def('', '最近关注'),
attention('attention', '最常访问') attention('attention', '最常访问'),
; ;
final String type; final String type;

View File

@@ -19,7 +19,7 @@ enum HomeTabType implements EnumWithLabel {
hot('热门'), hot('热门'),
rank('分区'), rank('分区'),
bangumi('番剧'), bangumi('番剧'),
cinema('影视') cinema('影视'),
; ;
@override @override

View File

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
enum LaterViewType { enum LaterViewType {
all(0, '全部'), all(0, '全部'),
// toView(1, '未看'), // toView(1, '未看'),
unfinished(2, '未看完') unfinished(2, '未看完'),
// viewed(3, '已看完'), // viewed(3, '已看完'),
; ;

View File

@@ -4,7 +4,7 @@ enum LiveContributionRankType {
online_rank('在线榜', 'contribution_rank'), online_rank('在线榜', 'contribution_rank'),
daily_rank('日榜', 'today_rank'), daily_rank('日榜', 'today_rank'),
weekly_rank('周榜', 'current_week_rank'), weekly_rank('周榜', 'current_week_rank'),
monthly_rank('月榜', 'current_month_rank') monthly_rank('月榜', 'current_month_rank'),
; ;
final String title; final String title;

View File

@@ -6,7 +6,7 @@ enum ContributeType {
season(Api.spaceSeason), season(Api.spaceSeason),
series(Api.spaceSeries), series(Api.spaceSeries),
bangumi(Api.spaceBangumi), bangumi(Api.spaceBangumi),
comic(Api.spaceComic) comic(Api.spaceComic),
; ;
final String api; final String api;

View File

@@ -8,7 +8,7 @@ enum MemberTabType {
favorite('收藏'), favorite('收藏'),
bangumi('番剧'), bangumi('番剧'),
cheese('课堂'), cheese('课堂'),
shop('小店') shop('小店'),
; ;
static bool showMemberShop = Pref.showMemberShop; static bool showMemberShop = Pref.showMemberShop;

View File

@@ -3,7 +3,7 @@ enum MsgUnReadType {
reply('回复我的'), reply('回复我的'),
at('@我'), at('@我'),
like('收到的赞'), like('收到的赞'),
sysMsg('系统通知') sysMsg('系统通知'),
; ;
final String title; final String title;

View File

@@ -7,8 +7,8 @@ import 'package:flutter/material.dart';
enum NavigationBarType implements EnumWithLabel { enum NavigationBarType implements EnumWithLabel {
home( home(
'首页', '首页',
Icon(Icons.home_outlined, size: 23), Icon(Icons.home_outlined, size: 24),
Icon(Icons.home, size: 21), Icon(Icons.home, size: 24),
HomePage(), HomePage(),
), ),
dynamics( dynamics(
@@ -19,10 +19,10 @@ enum NavigationBarType implements EnumWithLabel {
), ),
mine( mine(
'我的', '我的',
Icon(Icons.person_outline, size: 21), Icon(Icons.person_outline, size: 24),
Icon(Icons.person, size: 21), Icon(Icons.person, size: 24),
MinePage(), MinePage(),
) ),
; ;
@override @override

View File

@@ -2,7 +2,7 @@ import 'package:PiliPlus/http/api.dart';
enum PgcReviewType { enum PgcReviewType {
long(label: '长评', api: Api.pgcReviewL), long(label: '长评', api: Api.pgcReviewL),
short(label: '短评', api: Api.pgcReviewS) short(label: '短评', api: Api.pgcReviewS),
; ;
final String label; final String label;
@@ -15,7 +15,7 @@ enum PgcReviewType {
enum PgcReviewSortType { enum PgcReviewSortType {
def('默认', 0), def('默认', 0),
latest('最新', 1) latest('最新', 1),
; ;
final int sort; final int sort;

View File

@@ -19,7 +19,7 @@ enum RankType {
documentary('记录', seasonType: 3), documentary('记录', seasonType: 3),
movie('电影', seasonType: 2), movie('电影', seasonType: 2),
tv('剧集', seasonType: 5), tv('剧集', seasonType: 5),
variety('综艺', seasonType: 7) variety('综艺', seasonType: 7),
; ;
final String label; final String label;

View File

@@ -4,7 +4,7 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart
enum ReplyOptionType { enum ReplyOptionType {
allow('允许评论'), allow('允许评论'),
close('关闭评论'), close('关闭评论'),
choose('精选评论') choose('精选评论'),
; ;
final String title; final String title;

View File

@@ -1,7 +1,7 @@
enum ReplySortType { enum ReplySortType {
time('最新评论', '最新'), time('最新评论', '最新'),
hot('最热评论', '最热'), hot('最热评论', '最热'),
select('精选评论', '精选') select('精选评论', '精选'),
; ;
final String title; final String title;

View File

@@ -3,7 +3,7 @@ enum ArticleOrderType {
pubdate('最新发布'), pubdate('最新发布'),
click('最多点击'), click('最多点击'),
attention('最多喜欢'), attention('最多喜欢'),
scores('最多评论') scores('最多评论'),
; ;
String get order => name; String get order => name;
@@ -20,7 +20,7 @@ enum ArticleZoneType {
interest('兴趣', 29), interest('兴趣', 29),
novel('轻小说', 16), novel('轻小说', 16),
tech('科技', 17), tech('科技', 17),
note('笔记', 41) note('笔记', 41),
; ;
final String label; final String label;

View File

@@ -18,7 +18,7 @@ enum SearchType {
// 用户bili_user // 用户bili_user
bili_user('用户'), bili_user('用户'),
// 专栏article // 专栏article
article('专栏') article('专栏'),
; ;
// 相簿photo // 相簿photo
// photo // photo

View File

@@ -3,7 +3,7 @@ enum UserOrderType {
fansDesc('粉丝数由高到低', 0, 'fans'), fansDesc('粉丝数由高到低', 0, 'fans'),
fansAsc('粉丝数由低到高', 1, 'fans'), fansAsc('粉丝数由低到高', 1, 'fans'),
levelDesc('Lv等级由高到低', 0, 'level'), levelDesc('Lv等级由高到低', 0, 'level'),
levelAsc('Lv等级由低到高', 1, 'level') levelAsc('Lv等级由低到高', 1, 'level'),
; ;
final String label; final String label;
@@ -16,7 +16,7 @@ enum UserType {
all('全部用户'), all('全部用户'),
up('UP主'), up('UP主'),
common('普通用户'), common('普通用户'),
verified('认证用户') verified('认证用户'),
; ;
final String label; final String label;

View File

@@ -2,7 +2,7 @@ enum VideoPubTimeType {
all('不限'), all('不限'),
day('最近一天'), day('最近一天'),
week('最近一周'), week('最近一周'),
halfYear('最近半年') halfYear('最近半年'),
; ;
final String label; final String label;
@@ -14,7 +14,7 @@ enum VideoDurationType {
tenMins('0-10分钟'), tenMins('0-10分钟'),
halfHour('10-30分钟'), halfHour('10-30分钟'),
hour('30-60分钟'), hour('30-60分钟'),
hourPlus('60分钟+') hourPlus('60分钟+'),
; ;
final String label; final String label;
@@ -43,7 +43,7 @@ enum VideoZoneType {
cinephile('影视', tids: 181), cinephile('影视', tids: 181),
documentary('记录', tids: 177), documentary('记录', tids: 177),
movie('电影', tids: 23), movie('电影', tids: 23),
tv('电视', tids: 11) tv('电视', tids: 11),
; ;
final String label; final String label;
@@ -58,7 +58,7 @@ enum ArchiveFilterType {
pubdate('新发布'), pubdate('新发布'),
dm('弹幕多'), dm('弹幕多'),
stow('收藏多'), stow('收藏多'),
scores('评论多') scores('评论多'),
; ;
// 专栏 // 专栏
// attention('最多喜欢'), // attention('最多喜欢'),

View File

@@ -6,7 +6,7 @@ enum SettingType {
styleSetting('外观设置'), styleSetting('外观设置'),
extraSetting('其它设置'), extraSetting('其它设置'),
webdavSetting('WebDAV 设置'), webdavSetting('WebDAV 设置'),
about('关于') about('关于'),
; ;
final String title; final String title;

View File

@@ -2,7 +2,7 @@ enum ActionType {
skip('跳过'), skip('跳过'),
mute('静音'), mute('静音'),
full('整个视频'), full('整个视频'),
poi('精彩时刻') poi('精彩时刻'),
; ;
final String title; final String title;

View File

@@ -5,7 +5,7 @@ enum SkipType implements EnumWithLabel {
skipOnce('跳过一次'), skipOnce('跳过一次'),
skipManually('手动跳过'), skipManually('手动跳过'),
showOnly('仅显示'), showOnly('仅显示'),
disable('禁用') disable('禁用'),
; ;
@override @override

View File

@@ -7,7 +7,7 @@ enum StatType {
reply(Icons.comment_outlined, '评论'), reply(Icons.comment_outlined, '评论'),
follow(Icons.favorite_border, '关注'), follow(Icons.favorite_border, '关注'),
play(Icons.play_circle_outlined, '播放'), play(Icons.play_circle_outlined, '播放'),
listen(Icons.headset_outlined, '播放') listen(Icons.headset_outlined, '播放'),
; ;
final IconData iconData; final IconData iconData;

View File

@@ -3,7 +3,7 @@ import 'package:PiliPlus/models/common/enum_with_label.dart';
enum SuperResolutionType with EnumWithLabel { enum SuperResolutionType with EnumWithLabel {
disable('禁用'), disable('禁用'),
efficiency('效率'), efficiency('效率'),
quality('画质') quality('画质'),
; ;
@override @override

View File

@@ -4,7 +4,7 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart
enum ThemeType { enum ThemeType {
light('浅色'), light('浅色'),
dark('深色'), dark('深色'),
system('跟随系统') system('跟随系统'),
; ;
final String desc; final String desc;

View File

@@ -7,7 +7,7 @@ enum AudioQuality {
dolby_30255(30255, '杜比全景声'), dolby_30255(30255, '杜比全景声'),
k192(30280, '192K'), k192(30280, '192K'),
k132(30232, '132K'), k132(30232, '132K'),
k64(30216, '64K') k64(30216, '64K'),
; ;
final int code; final int code;

View File

@@ -24,7 +24,7 @@ enum CDNService {
aliov('aliov阿里云海外', 'upos-sz-mirroraliov.bilivideo.com'), aliov('aliov阿里云海外', 'upos-sz-mirroraliov.bilivideo.com'),
cosov('cosov腾讯云海外', 'upos-sz-mirrorcosov.bilivideo.com'), cosov('cosov腾讯云海外', 'upos-sz-mirrorcosov.bilivideo.com'),
hwov('hwov华为云海外', 'upos-sz-mirrorhwov.bilivideo.com'), hwov('hwov华为云海外', 'upos-sz-mirrorhwov.bilivideo.com'),
hk_bcache('hk_bcacheBilibili海外', 'cn-hk-eq-bcache-01.bilivideo.com') hk_bcache('hk_bcacheBilibili海外', 'cn-hk-eq-bcache-01.bilivideo.com'),
; ;
final String desc; final String desc;

View File

@@ -7,7 +7,7 @@ enum LiveQuality {
bluRay(400, '蓝光'), bluRay(400, '蓝光'),
superHD(250, '超清'), superHD(250, '超清'),
smooth(150, '高清'), smooth(150, '高清'),
flunt(80, '流畅') flunt(80, '流畅'),
; ;
final int code; final int code;

View File

@@ -26,7 +26,7 @@ enum SourceType {
extraId: 4, extraId: 4,
playlistSource: PlaylistSource.MEDIA_LIST, playlistSource: PlaylistSource.MEDIA_LIST,
), ),
file file,
; ;
final int? mediaType; final int? mediaType;

View File

@@ -2,7 +2,7 @@ enum SubtitlePrefType {
off('默认不显示字幕'), off('默认不显示字幕'),
on('优先选择非自动生成(ai)字幕'), on('优先选择非自动生成(ai)字幕'),
withoutAi('跳过自动生成(ai)字幕,选择第一个可用字幕'), withoutAi('跳过自动生成(ai)字幕,选择第一个可用字幕'),
auto('静音时等同第二项,非静音时等同第三项') auto('静音时等同第二项,非静音时等同第三项'),
; ;
final String desc; final String desc;

View File

@@ -4,7 +4,7 @@ enum VideoDecodeFormatType {
DVH1(['dvh1']), DVH1(['dvh1']),
AV1(['av01']), AV1(['av01']),
HEVC(['hev1', 'hvc1']), HEVC(['hev1', 'hvc1']),
AVC(['avc1']) AVC(['avc1']),
; ;
String get description => name; String get description => name;

View File

@@ -11,7 +11,7 @@ enum VideoQuality {
high720(64, '720P 准高清', '720P'), high720(64, '720P 准高清', '720P'),
clear480(32, '480P 标清', '480P'), clear480(32, '480P 标清', '480P'),
fluent360(16, '360P 流畅', '360P'), fluent360(16, '360P 流畅', '360P'),
speed240(6, '240P 极速', '240P') speed240(6, '240P 极速', '240P'),
; ;
final int code; final int code;

View File

@@ -13,7 +13,7 @@ enum VideoType {
type: 10, type: 10,
replyType: 33, replyType: 33,
api: Api.pugvUrl, api: Api.pugvUrl,
) ),
; ;
final int type; final int type;

View File

@@ -4,7 +4,7 @@ enum WebviewMenuItem {
openInBrowser('浏览器中打开'), openInBrowser('浏览器中打开'),
clearCache('清除缓存'), clearCache('清除缓存'),
resetCookie('重新设置Cookie'), resetCookie('重新设置Cookie'),
goBack('返回') goBack('返回'),
; ;
final String title; final String title;

View File

@@ -1,8 +1,12 @@
import 'dart:io' show Platform, Process;
import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models/common/video/video_type.dart';
import 'package:PiliPlus/pages/common/multi_select/base.dart' import 'package:PiliPlus/pages/common/multi_select/base.dart'
show MultiSelectData; show MultiSelectData;
import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/route_manager.dart'; import 'package:get/route_manager.dart';
class BiliDownloadEntryInfo with MultiSelectData { class BiliDownloadEntryInfo with MultiSelectData {
@@ -68,10 +72,7 @@ class BiliDownloadEntryInfo with MultiSelectData {
itemBuilder: (_) => [ itemBuilder: (_) => [
PopupMenuItem( PopupMenuItem(
height: 38, height: 38,
child: const Text( child: const Text('查看详情页', style: TextStyle(fontSize: 13)),
'查看详情页',
style: TextStyle(fontSize: 13),
),
onTap: () { onTap: () {
if (ep case final ep?) { if (ep case final ep?) {
if (ep.from == VideoType.pugv.name) { if (ep.from == VideoType.pugv.name) {
@@ -97,14 +98,34 @@ class BiliDownloadEntryInfo with MultiSelectData {
); );
}, },
), ),
if (PlatformUtils.isDesktop)
PopupMenuItem(
height: 38,
child: const Text('打开本地文件夹', style: TextStyle(fontSize: 13)),
onTap: () async {
try {
final String executable;
if (Platform.isWindows) {
executable = 'explorer';
} else if (Platform.isMacOS) {
executable = 'open';
} else if (Platform.isLinux) {
executable = 'xdg-open';
} else {
throw UnimplementedError();
}
await Process.run(executable, [entryDirPath]);
} catch (e) {
SmartDialog.showToast(e.toString());
}
},
),
if (ownerId case final mid?) if (ownerId case final mid?)
PopupMenuItem( PopupMenuItem(
height: 38, height: 38,
child: Text( child: Text(
'访问${ownerName != null ? '$ownerName' : '用户主页'}', '访问${ownerName != null ? '$ownerName' : '用户主页'}',
style: const TextStyle( style: const TextStyle(fontSize: 13),
fontSize: 13,
),
), ),
onTap: () => Get.toNamed('/member?mid=$mid'), onTap: () => Get.toNamed('/member?mid=$mid'),
), ),
@@ -395,7 +416,7 @@ enum DownloadStatus {
failDanmaku('获取弹幕失败'), failDanmaku('获取弹幕失败'),
failPlayUrl('获取播放地址失败'), failPlayUrl('获取播放地址失败'),
pause('暂停中'), pause('暂停中'),
wait('等待中') wait('等待中'),
; ;
final String message; final String message;

View File

@@ -6,6 +6,7 @@ import 'package:PiliPlus/grpc/dm.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/plugin/pl_player/models/data_source.dart'; import 'package:PiliPlus/plugin/pl_player/models/data_source.dart';
import 'package:PiliPlus/plugin/pl_player/utils/danmaku_options.dart';
import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/path_utils.dart'; import 'package:PiliPlus/utils/path_utils.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
@@ -68,6 +69,7 @@ class PlDanmakuController {
final uniques = HashMap<String, DanmakuElem>(); final uniques = HashMap<String, DanmakuElem>();
final filters = _plPlayerController.filters; final filters = _plPlayerController.filters;
final danmakuWeight = DanmakuOptions.danmakuWeight;
final shouldFilter = filters.count != 0; final shouldFilter = filters.count != 0;
for (final element in elems) { for (final element in elems) {
if (_isLogin) { if (_isLogin) {
@@ -85,7 +87,8 @@ class PlDanmakuController {
} }
} }
if (shouldFilter && filters.remove(element)) { if (element.weight < danmakuWeight ||
(shouldFilter && filters.remove(element))) {
continue; continue;
} }
} }

View File

@@ -181,7 +181,8 @@ class AuthorPanel extends StatelessWidget {
Positioned( Positioned(
top: 0, top: 0,
right: 0, right: 0,
height: height, bottom: 0,
child: Center(
child: CachedNetworkImage( child: CachedNetworkImage(
height: height, height: height,
memCacheHeight: height.cacheSize(context), memCacheHeight: height.cacheSize(context),
@@ -191,11 +192,12 @@ class AuthorPanel extends StatelessWidget {
placeholder: (_, _) => const SizedBox.shrink(), placeholder: (_, _) => const SizedBox.shrink(),
), ),
), ),
),
if (moduleAuthor.decorate!.fan?.numStr?.isNotEmpty == true) if (moduleAuthor.decorate!.fan?.numStr?.isNotEmpty == true)
Positioned( Positioned(
top: 0, top: 0,
bottom: 0,
right: height, right: height,
height: height,
child: Center( child: Center(
child: Text( child: Text(
moduleAuthor.decorate!.fan!.numStr!.toString(), moduleAuthor.decorate!.fan!.numStr!.toString(),

View File

@@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:PiliPlus/common/widgets/avatars.dart'; import 'package:PiliPlus/common/widgets/avatars.dart';
import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/badge.dart';

View File

@@ -55,6 +55,7 @@ class MainController extends GetxController
late int lastCheckUnreadAt = 0; late int lastCheckUnreadAt = 0;
final enableMYBar = Pref.enableMYBar; final enableMYBar = Pref.enableMYBar;
final floatingNavBar = Pref.floatingNavBar;
final useSideBar = Pref.useSideBar; final useSideBar = Pref.useSideBar;
final mainTabBarView = Pref.mainTabBarView; final mainTabBarView = Pref.mainTabBarView;
late final optTabletNav = Pref.optTabletNav; late final optTabletNav = Pref.optTabletNav;

View File

@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/assets.dart';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/floating_navigation_bar.dart';
import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart';
import 'package:PiliPlus/common/widgets/flutter/tabs.dart'; import 'package:PiliPlus/common/widgets/flutter/tabs.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
@@ -270,9 +271,26 @@ class _MainAppState extends PopScopeState<MainApp>
} }
Widget? get _bottomNav { Widget? get _bottomNav {
Widget? bottomNav = _mainController.navigationBars.length > 1 Widget? bottomNav;
? _mainController.enableMYBar if (_mainController.navigationBars.length > 1) {
? Obx( if (_mainController.floatingNavBar) {
bottomNav = Obx(
() => FloatingNavigationBar(
onDestinationSelected: _mainController.setIndex,
selectedIndex: _mainController.selectedIndex.value,
destinations: _mainController.navigationBars
.map(
(e) => FloatingNavigationDestination(
label: e.label,
icon: _buildIcon(type: e),
selectedIcon: _buildIcon(type: e, selected: true),
),
)
.toList(),
),
);
} else if (_mainController.enableMYBar) {
bottomNav = Obx(
() => NavigationBar( () => NavigationBar(
maintainBottomViewPadding: true, maintainBottomViewPadding: true,
onDestinationSelected: _mainController.setIndex, onDestinationSelected: _mainController.setIndex,
@@ -287,8 +305,9 @@ class _MainAppState extends PopScopeState<MainApp>
) )
.toList(), .toList(),
), ),
) );
: Obx( } else {
bottomNav = Obx(
() => BottomNavigationBar( () => BottomNavigationBar(
currentIndex: _mainController.selectedIndex.value, currentIndex: _mainController.selectedIndex.value,
onTap: _mainController.setIndex, onTap: _mainController.setIndex,
@@ -306,9 +325,10 @@ class _MainAppState extends PopScopeState<MainApp>
) )
.toList(), .toList(),
), ),
) );
: null; }
if (bottomNav != null && _mainController.hideBottomBar) {
if (_mainController.hideBottomBar) {
if (_mainController.barOffset case final barOffset?) { if (_mainController.barOffset case final barOffset?) {
return Obx( return Obx(
() => FractionalTranslation( () => FractionalTranslation(
@@ -331,6 +351,8 @@ class _MainAppState extends PopScopeState<MainApp>
); );
} }
} }
}
return bottomNav; return bottomNav;
} }

View File

@@ -106,7 +106,7 @@ List<SettingsModel> get styleSettings => [
), ),
const SwitchModel( const SwitchModel(
title: '优化平板导航栏', title: '优化平板导航栏',
leading: Icon(MdiIcons.soundbar), leading: Icon(Icons.auto_fix_high),
setKey: SettingBoxKey.optTabletNav, setKey: SettingBoxKey.optTabletNav,
defaultVal: true, defaultVal: true,
needReboot: true, needReboot: true,
@@ -119,6 +119,13 @@ List<SettingsModel> get styleSettings => [
defaultVal: true, defaultVal: true,
needReboot: true, needReboot: true,
), ),
const SwitchModel(
title: '悬浮底栏',
leading: Icon(MdiIcons.soundbar),
setKey: SettingBoxKey.floatingNavBar,
defaultVal: false,
needReboot: true,
),
NormalModel( NormalModel(
leading: const Icon(Icons.calendar_view_week_outlined), leading: const Icon(Icons.calendar_view_week_outlined),
title: '列表宽度dp限制', title: '列表宽度dp限制',

View File

@@ -3,7 +3,7 @@ import 'package:PiliPlus/models/common/enum_with_label.dart';
enum AudioOutput implements EnumWithLabel { enum AudioOutput implements EnumWithLabel {
opensles('OpenSL ES'), opensles('OpenSL ES'),
aaudio('AAudio'), aaudio('AAudio'),
audiotrack('AudioTrack') audiotrack('AudioTrack'),
; ;
static final defaultValue = values.map((e) => e.name).join(','); static final defaultValue = values.map((e) => e.name).join(',');

View File

@@ -2,7 +2,7 @@ enum BtmProgressBehavior {
alwaysShow('始终展示'), alwaysShow('始终展示'),
alwaysHide('始终隐藏'), alwaysHide('始终隐藏'),
onlyShowFullScreen('仅全屏时展示'), onlyShowFullScreen('仅全屏时展示'),
onlyHideFullScreen('仅全屏时隐藏') onlyHideFullScreen('仅全屏时隐藏'),
; ;
final String desc; final String desc;

View File

@@ -13,7 +13,7 @@ enum FullScreenMode {
// 屏幕长宽比 < kScreenRatio 或为竖屏视频时竖屏,否则横屏 // 屏幕长宽比 < kScreenRatio 或为竖屏视频时竖屏,否则横屏
ratio('屏幕长宽比<$kScreenRatio或为竖屏视频时竖屏,否则横屏'), ratio('屏幕长宽比<$kScreenRatio或为竖屏视频时竖屏,否则横屏'),
// 强制重力转屏(仅安卓) // 强制重力转屏(仅安卓)
gravity('忽略系统方向锁定,强制按重力转屏(仅安卓)') gravity('忽略系统方向锁定,强制按重力转屏(仅安卓)'),
; ;
final String desc; final String desc;

View File

@@ -31,7 +31,7 @@ enum HwDecType {
amf('amf', 'AMF (AMD独占)'), amf('amf', 'AMF (AMD独占)'),
amfCopy('amf-copy', 'AMF (AMD独占) (非直通)'), amfCopy('amf-copy', 'AMF (AMD独占) (非直通)'),
qsv('qsv', 'Quick Sync Video (Intel独占)'), qsv('qsv', 'Quick Sync Video (Intel独占)'),
qsvCopy('qsv-copy', 'Quick Sync Video (Intel独占) (非直通)') qsvCopy('qsv-copy', 'Quick Sync Video (Intel独占) (非直通)'),
; ;
final String hwdec; final String hwdec;

View File

@@ -5,7 +5,7 @@ enum PlayRepeat implements EnumWithLabel {
listOrder('顺序播放'), listOrder('顺序播放'),
singleCycle('单个循环'), singleCycle('单个循环'),
listCycle('列表循环'), listCycle('列表循环'),
autoPlayRelated('自动连播') autoPlayRelated('自动连播'),
; ;
@override @override

View File

@@ -8,7 +8,7 @@ enum PlaySpeed {
onePointSevenFive(1.75), onePointSevenFive(1.75),
two(2.0), two(2.0),
three(3.0) three(3.0),
; ;
final double value; final double value;

View File

@@ -3,7 +3,7 @@ import 'package:get/get.dart';
enum PlayerStatus { enum PlayerStatus {
completed, completed,
playing, playing,
paused paused,
; ;
bool get isCompleted => this == PlayerStatus.completed; bool get isCompleted => this == PlayerStatus.completed;

View File

@@ -10,7 +10,7 @@ enum VideoFitType {
none('原始', boxFit: BoxFit.none), none('原始', boxFit: BoxFit.none),
scaleDown('限制', boxFit: BoxFit.scaleDown), scaleDown('限制', boxFit: BoxFit.scaleDown),
ratio_4x3('4:3', aspectRatio: 4 / 3), ratio_4x3('4:3', aspectRatio: 4 / 3),
ratio_16x9('16:9', aspectRatio: Style.aspectRatio16x9) ratio_16x9('16:9', aspectRatio: Style.aspectRatio16x9),
; ;
final String desc; final String desc;

View File

@@ -150,7 +150,7 @@ enum WebpPreset {
photo('photo', '照片', '户外摄影,自然光环境'), photo('photo', '照片', '户外摄影,自然光环境'),
drawing('drawing', '绘图', '手绘或线稿,高对比度细节'), drawing('drawing', '绘图', '手绘或线稿,高对比度细节'),
icon('icon', '图标', '小型彩色图像'), icon('icon', '图标', '小型彩色图像'),
text('text', '文本', '文字类') text('text', '文本', '文字类'),
; ;
final String flag; final String flag;

View File

@@ -73,3 +73,10 @@ foreach ($patch in $patches) {
Write-Host "$patch applied" Write-Host "$patch applied"
} }
} }
# TODO: remove
if ($platform.ToLower() -eq "android") {
"69e31205362b4e59b7eb89b24797e687b4b67afe" | Set-Content -Path .\bin\internal\engine.version
Remove-Item -Path ".\bin\cache" -Recurse -Force
flutter --version
}

View File

@@ -13,7 +13,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
enum _ShutdownType with EnumWithLabel { enum _ShutdownType with EnumWithLabel {
pause('暂停视频'), pause('暂停视频'),
exit('退出APP') exit('退出APP'),
; ;
@override @override

View File

@@ -149,7 +149,8 @@ abstract final class SettingBoxKey {
followOrderType = 'followOrderType', followOrderType = 'followOrderType',
enableImgMenu = 'enableImgMenu', enableImgMenu = 'enableImgMenu',
showDynDispute = 'showDynDispute', showDynDispute = 'showDynDispute',
touchSlopH = 'touchSlopH'; touchSlopH = 'touchSlopH',
floatingNavBar = 'floatingNavBar';
static const String minimizeOnExit = 'minimizeOnExit', static const String minimizeOnExit = 'minimizeOnExit',
windowSize = 'windowSize', windowSize = 'windowSize',

View File

@@ -966,4 +966,7 @@ abstract final class Pref {
static bool get saveReply => static bool get saveReply =>
_setting.get(SettingBoxKey.saveReply, defaultValue: true); _setting.get(SettingBoxKey.saveReply, defaultValue: true);
static bool get floatingNavBar =>
_setting.get(SettingBoxKey.floatingNavBar, defaultValue: false);
} }

View File

@@ -28,5 +28,13 @@
<string>MainMenu</string> <string>MainMenu</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
<key>NSLocalNetworkUsageDescription</key>
<string>需要访问本地网络以发现和连接 DLNA 投屏设备</string>
<key>NSBonjourServices</key>
<array>
<string>_ssdp._udp</string>
<string>_upnp._tcp</string>
<string>_http._tcp</string>
</array>
</dict> </dict>
</plist> </plist>

View File

@@ -320,18 +320,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: connectivity_plus name: connectivity_plus
sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c" sha256: b8fe52979ff12432ecf8f0abf6ff70410b1bb734be1c9e4f2f86807ad7166c79
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.1.0"
connectivity_plus_platform_interface: connectivity_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: connectivity_plus_platform_interface name: connectivity_plus_platform_interface
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.1.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@@ -400,10 +400,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: device_info_plus name: device_info_plus
sha256: "4df8babf73058181227e18b08e6ea3520cf5fc5d796888d33b7cb0f33f984b7c" sha256: b4fed1b2835da9d670d7bed7db79ae2a94b0f5ad6312268158a9b5479abbacdd
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.3.0" version: "12.4.0"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -906,10 +906,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: image_cropper name: image_cropper
sha256: "2cd06f097b5bd18ff77d3f80fadef83e270ea23c82906bbf17febc3db8d68ec6" sha256: d2555be1ec4b7b12fc502ede481c846ad44578fbb0748debd4c648b25ca07cad
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.1.0" version: "12.1.1"
image_cropper_for_web: image_cropper_for_web:
dependency: transitive dependency: transitive
description: description:
@@ -938,10 +938,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image_picker_android name: image_picker_android
sha256: "9eae0cbd672549dacc18df855c2a23782afe4854ada5190b7d63b30ee0b0d3fd" sha256: "66810af8e99b2657ee98e5c6f02064f69bb63f7a70e343937f70946c5f8c6622"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.13+15" version: "0.8.13+16"
image_picker_for_web: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
@@ -1283,10 +1283,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.0" version: "9.0.1"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -1499,10 +1499,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: saver_gallery name: saver_gallery
sha256: "1d942bd7f4fedc162d9a751e156ebac592e4b81fc2e757af82de9077f3437003" sha256: "3f983d4be63aff52523c3e097a9b00ce9ab8444f9a982c878cde9d0359f4681d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "4.1.1"
screen_brightness_android: screen_brightness_android:
dependency: "direct overridden" dependency: "direct overridden"
description: description:
@@ -1579,10 +1579,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: share_plus name: share_plus
sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" sha256: "223873d106614442ea6f20db5a038685cc5b32a2fba81cdecaefbbae0523f7fa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.0.1" version: "12.0.2"
share_plus_platform_interface: share_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -1905,10 +1905,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics name: vector_graphics
sha256: "7076216a10d5c390315fbe536a30f1254c341e7543e6c4c8a815e591307772b1" sha256: "81da85e9ca8885ade47f9685b953cb098970d11be4821ac765580a6607ea4373"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.20" version: "1.1.21"
vector_graphics_codec: vector_graphics_codec:
dependency: transitive dependency: transitive
description: description:

View File

@@ -17,7 +17,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# 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.
# update when release # update when release
version: 2.0.1+1 version: 2.0.2+1
environment: environment:
sdk: ">=3.10.0" sdk: ">=3.10.0"