mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
Compare commits
12 Commits
dev
...
b897103af0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b897103af0 | ||
|
|
353664fbd4 | ||
|
|
de3505ce07 | ||
|
|
cdc1720358 | ||
|
|
904d210ba2 | ||
|
|
db8dd85b63 | ||
|
|
8ad130567e | ||
|
|
7eb21bc5a2 | ||
|
|
ea4316a847 | ||
|
|
2bbc97a950 | ||
|
|
0178d105ba | ||
|
|
771fa75f48 |
2
.github/workflows/mac.yml
vendored
2
.github/workflows/mac.yml
vendored
@@ -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
|
||||||
|
|||||||
7
.github/workflows/win_x64.yml
vendored
7
.github/workflows/win_x64.yml
vendored
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
777
lib/common/widgets/floating_navigation_bar.dart
Normal file
777
lib/common/widgets/floating_navigation_bar.dart
Normal 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);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ enum AccountType {
|
|||||||
main('主账号'),
|
main('主账号'),
|
||||||
heartbeat('记录观看'),
|
heartbeat('记录观看'),
|
||||||
recommend('推荐'),
|
recommend('推荐'),
|
||||||
video('视频取流')
|
video('视频取流'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
enum DmBlockType {
|
enum DmBlockType {
|
||||||
keyword('关键词'),
|
keyword('关键词'),
|
||||||
regex('正则'),
|
regex('正则'),
|
||||||
uid('用户')
|
uid('用户'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
enum DynamicBadgeMode {
|
enum DynamicBadgeMode {
|
||||||
hidden('隐藏'),
|
hidden('隐藏'),
|
||||||
point('红点'),
|
point('红点'),
|
||||||
number('数字')
|
number('数字'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String desc;
|
final String desc;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ enum DynamicsTabType {
|
|||||||
video('投稿'),
|
video('投稿'),
|
||||||
pgc('番剧'),
|
pgc('番剧'),
|
||||||
article('专栏'),
|
article('专栏'),
|
||||||
up('UP')
|
up('UP'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ enum UpPanelPosition {
|
|||||||
leftFixed('左侧常驻'),
|
leftFixed('左侧常驻'),
|
||||||
rightFixed('右侧常驻'),
|
rightFixed('右侧常驻'),
|
||||||
leftDrawer('左侧抽屉'),
|
leftDrawer('左侧抽屉'),
|
||||||
rightDrawer('右侧抽屉')
|
rightDrawer('右侧抽屉'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
enum EpisodeType {
|
enum EpisodeType {
|
||||||
part('分P'),
|
part('分P'),
|
||||||
season('合集'),
|
season('合集'),
|
||||||
pgc('剧集')
|
pgc('剧集'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
enum FavOrderType {
|
enum FavOrderType {
|
||||||
mtime('最近收藏'),
|
mtime('最近收藏'),
|
||||||
view('最多播放'),
|
view('最多播放'),
|
||||||
pubtime('最近投稿')
|
pubtime('最近投稿'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
enum FollowOrderType {
|
enum FollowOrderType {
|
||||||
def('', '最近关注'),
|
def('', '最近关注'),
|
||||||
attention('attention', '最常访问')
|
attention('attention', '最常访问'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String type;
|
final String type;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ enum HomeTabType implements EnumWithLabel {
|
|||||||
hot('热门'),
|
hot('热门'),
|
||||||
rank('分区'),
|
rank('分区'),
|
||||||
bangumi('番剧'),
|
bangumi('番剧'),
|
||||||
cinema('影视')
|
cinema('影视'),
|
||||||
;
|
;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -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, '已看完'),
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ enum MemberTabType {
|
|||||||
favorite('收藏'),
|
favorite('收藏'),
|
||||||
bangumi('番剧'),
|
bangumi('番剧'),
|
||||||
cheese('课堂'),
|
cheese('课堂'),
|
||||||
shop('小店')
|
shop('小店'),
|
||||||
;
|
;
|
||||||
|
|
||||||
static bool showMemberShop = Pref.showMemberShop;
|
static bool showMemberShop = Pref.showMemberShop;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ enum MsgUnReadType {
|
|||||||
reply('回复我的'),
|
reply('回复我的'),
|
||||||
at('@我'),
|
at('@我'),
|
||||||
like('收到的赞'),
|
like('收到的赞'),
|
||||||
sysMsg('系统通知')
|
sysMsg('系统通知'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
enum ReplySortType {
|
enum ReplySortType {
|
||||||
time('最新评论', '最新'),
|
time('最新评论', '最新'),
|
||||||
hot('最热评论', '最热'),
|
hot('最热评论', '最热'),
|
||||||
select('精选评论', '精选')
|
select('精选评论', '精选'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ enum SearchType {
|
|||||||
// 用户:bili_user
|
// 用户:bili_user
|
||||||
bili_user('用户'),
|
bili_user('用户'),
|
||||||
// 专栏:article
|
// 专栏:article
|
||||||
article('专栏')
|
article('专栏'),
|
||||||
;
|
;
|
||||||
// 相簿:photo
|
// 相簿:photo
|
||||||
// photo
|
// photo
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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('最多喜欢'),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ enum SettingType {
|
|||||||
styleSetting('外观设置'),
|
styleSetting('外观设置'),
|
||||||
extraSetting('其它设置'),
|
extraSetting('其它设置'),
|
||||||
webdavSetting('WebDAV 设置'),
|
webdavSetting('WebDAV 设置'),
|
||||||
about('关于')
|
about('关于'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ enum ActionType {
|
|||||||
skip('跳过'),
|
skip('跳过'),
|
||||||
mute('静音'),
|
mute('静音'),
|
||||||
full('整个视频'),
|
full('整个视频'),
|
||||||
poi('精彩时刻')
|
poi('精彩时刻'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ enum SkipType implements EnumWithLabel {
|
|||||||
skipOnce('跳过一次'),
|
skipOnce('跳过一次'),
|
||||||
skipManually('手动跳过'),
|
skipManually('手动跳过'),
|
||||||
showOnly('仅显示'),
|
showOnly('仅显示'),
|
||||||
disable('禁用')
|
disable('禁用'),
|
||||||
;
|
;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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_bcache(Bilibili海外)', 'cn-hk-eq-bcache-01.bilivideo.com')
|
hk_bcache('hk_bcache(Bilibili海外)', 'cn-hk-eq-bcache-01.bilivideo.com'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String desc;
|
final String desc;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ enum SubtitlePrefType {
|
|||||||
off('默认不显示字幕'),
|
off('默认不显示字幕'),
|
||||||
on('优先选择非自动生成(ai)字幕'),
|
on('优先选择非自动生成(ai)字幕'),
|
||||||
withoutAi('跳过自动生成(ai)字幕,选择第一个可用字幕'),
|
withoutAi('跳过自动生成(ai)字幕,选择第一个可用字幕'),
|
||||||
auto('静音时等同第二项,非静音时等同第三项')
|
auto('静音时等同第二项,非静音时等同第三项'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String desc;
|
final String desc;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ enum WebviewMenuItem {
|
|||||||
openInBrowser('浏览器中打开'),
|
openInBrowser('浏览器中打开'),
|
||||||
clearCache('清除缓存'),
|
clearCache('清除缓存'),
|
||||||
resetCookie('重新设置Cookie'),
|
resetCookie('重新设置Cookie'),
|
||||||
goBack('返回')
|
goBack('返回'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)限制',
|
||||||
|
|||||||
@@ -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(',');
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ enum BtmProgressBehavior {
|
|||||||
alwaysShow('始终展示'),
|
alwaysShow('始终展示'),
|
||||||
alwaysHide('始终隐藏'),
|
alwaysHide('始终隐藏'),
|
||||||
onlyShowFullScreen('仅全屏时展示'),
|
onlyShowFullScreen('仅全屏时展示'),
|
||||||
onlyHideFullScreen('仅全屏时隐藏')
|
onlyHideFullScreen('仅全屏时隐藏'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String desc;
|
final String desc;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ enum FullScreenMode {
|
|||||||
// 屏幕长宽比 < kScreenRatio 或为竖屏视频时竖屏,否则横屏
|
// 屏幕长宽比 < kScreenRatio 或为竖屏视频时竖屏,否则横屏
|
||||||
ratio('屏幕长宽比<$kScreenRatio或为竖屏视频时竖屏,否则横屏'),
|
ratio('屏幕长宽比<$kScreenRatio或为竖屏视频时竖屏,否则横屏'),
|
||||||
// 强制重力转屏(仅安卓)
|
// 强制重力转屏(仅安卓)
|
||||||
gravity('忽略系统方向锁定,强制按重力转屏(仅安卓)')
|
gravity('忽略系统方向锁定,强制按重力转屏(仅安卓)'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String desc;
|
final String desc;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ enum PlayRepeat implements EnumWithLabel {
|
|||||||
listOrder('顺序播放'),
|
listOrder('顺序播放'),
|
||||||
singleCycle('单个循环'),
|
singleCycle('单个循环'),
|
||||||
listCycle('列表循环'),
|
listCycle('列表循环'),
|
||||||
autoPlayRelated('自动连播')
|
autoPlayRelated('自动连播'),
|
||||||
;
|
;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
36
pubspec.lock
36
pubspec.lock
@@ -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:
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user