Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-01-22 15:51:22 +08:00
parent 92e5fae29c
commit b9b54ce4f7
34 changed files with 627 additions and 665 deletions

View File

@@ -59,10 +59,12 @@ class _AboutPageState extends State<AboutPage> {
super.dispose();
}
Future<void> getCacheSize() async {
cacheSize.value = CacheManager.formatSize(
await CacheManager.loadApplicationCache(),
);
void getCacheSize() {
CacheManager.loadApplicationCache().then((res) {
if (mounted) {
cacheSize.value = CacheManager.formatSize(res);
}
});
}
void _showDialog() => showDialog(
@@ -103,20 +105,18 @@ class _AboutPageState extends State<AboutPage> {
children: [
GestureDetector(
onTap: () {
_pressCount++;
if (_pressCount == 5) {
if (++_pressCount == 5) {
_pressCount = 0;
_showDialog();
}
},
onSecondaryTap: PlatformUtils.isDesktop ? _showDialog : null,
child: ExcludeSemantics(
child: Image.asset(
width: 150,
height: 150,
cacheWidth: 150.cacheSize(context),
'assets/images/logo/logo.png',
),
child: Image.asset(
width: 150,
height: 150,
excludeFromSemantics: true,
cacheWidth: 150.cacheSize(context),
'assets/images/logo/logo.png',
),
),
ListTile(

View File

@@ -57,11 +57,16 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
int get limit => widget.imageLengthLimit ?? 9;
@override
late final RichTextEditingController editController =
RichTextEditingController(
items: widget.items,
onMention: onMention,
);
late final RichTextEditingController editController;
@override
void initState() {
super.initState();
editController = RichTextEditingController(
items: widget.items,
onMention: onMention,
);
}
@override
void initPubState() {

View File

@@ -127,8 +127,10 @@ class _FavDetailPageState extends State<FavDetailPage> with GridMixin {
Widget _buildHeader(bool enableMultiSelect, ThemeData theme) {
return SliverAppBar.medium(
leadingWidth: enableMultiSelect ? 125 : null,
leading: enableMultiSelect
? Row(
mainAxisSize: .min,
children: [
IconButton(
tooltip: '取消',

View File

@@ -1,8 +1,6 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart';
import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/pages/home/controller.dart';
import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
@@ -77,79 +75,18 @@ class _HomePageState extends State<HomePage>
);
}
Widget searchBarAndUser(ThemeData theme) {
return Row(
Widget customAppBar(ThemeData theme) {
const height = 52.0;
const padding = EdgeInsets.fromLTRB(14, 6, 14, 0);
final child = Row(
children: [
searchBar(theme),
const SizedBox(width: 4),
Obx(
() => _homeController.accountService.isLogin.value
? msgBadge(_mainController)
: const SizedBox.shrink(),
),
msgBadge(_mainController),
const SizedBox(width: 8),
Semantics(
label: "我的",
child: Obx(
() => _homeController.accountService.isLogin.value
? Stack(
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
type: ImageType.avatar,
width: 34,
height: 34,
src: _homeController.accountService.face.value,
),
Positioned.fill(
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: _mainController.toMinePage,
splashColor: theme.colorScheme.primaryContainer
.withValues(alpha: 0.3),
customBorder: const CircleBorder(),
),
),
),
Positioned(
right: -6,
bottom: -6,
child: Obx(
() => MineController.anonymity.value
? IgnorePointer(
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color:
theme.colorScheme.secondaryContainer,
shape: BoxShape.circle,
),
child: Icon(
size: 16,
MdiIcons.incognito,
color: theme
.colorScheme
.onSecondaryContainer,
),
),
)
: const SizedBox.shrink(),
),
),
],
)
: defaultUser(
theme: theme,
onPressed: _mainController.toMinePage,
),
),
),
userAvatar(theme: theme, mainController: _mainController),
],
);
}
Widget customAppBar(ThemeData theme) {
if (_homeController.searchBar case final searchBar?) {
return Obx(() {
final showSearchBar = searchBar.value;
@@ -159,39 +96,39 @@ class _HomePageState extends State<HomePage>
child: AnimatedContainer(
curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500),
height: showSearchBar ? 52 : 0,
padding: const EdgeInsets.fromLTRB(14, 6, 14, 0),
child: searchBarAndUser(theme),
height: showSearchBar ? height : 0,
padding: padding,
child: child,
),
);
});
} else {
return Container(
height: 52,
padding: const EdgeInsets.fromLTRB(14, 6, 14, 0),
child: searchBarAndUser(theme),
height: height,
padding: padding,
child: child,
);
}
}
Widget searchBar(ThemeData theme) {
const borderRadius = BorderRadius.all(Radius.circular(25));
return Expanded(
child: SizedBox(
height: 44,
child: Material(
borderRadius: const BorderRadius.all(Radius.circular(25)),
borderRadius: borderRadius,
color: theme.colorScheme.onSecondaryContainer.withValues(alpha: 0.05),
child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(25)),
borderRadius: borderRadius,
splashColor: theme.colorScheme.primaryContainer.withValues(
alpha: 0.3,
),
onTap: () => Get.toNamed(
'/search',
parameters: {
if (_homeController.enableSearchWord)
'hintText': _homeController.defaultSearch.value,
},
parameters: _homeController.enableSearchWord
? {'hintText': _homeController.defaultSearch.value}
: null,
),
child: Row(
children: [
@@ -222,60 +159,109 @@ class _HomePageState extends State<HomePage>
}
}
Widget defaultUser({
Widget userAvatar({
required ThemeData theme,
required VoidCallback onPressed,
required MainController mainController,
}) {
return SizedBox(
width: 38,
height: 38,
child: IconButton(
tooltip: '点击登录',
style: ButtonStyle(
padding: const WidgetStatePropertyAll(EdgeInsets.zero),
backgroundColor: WidgetStatePropertyAll(
theme.colorScheme.onInverseSurface,
),
),
onPressed: onPressed,
icon: Icon(
Icons.person_rounded,
size: 22,
color: theme.colorScheme.primary,
),
return Semantics(
label: "我的",
child: Obx(
() {
if (mainController.accountService.isLogin.value) {
return Stack(
clipBehavior: .none,
children: [
NetworkImgLayer(
type: .avatar,
width: 34,
height: 34,
src: mainController.accountService.face.value,
),
Positioned.fill(
child: Material(
type: .transparency,
child: InkWell(
onTap: mainController.toMinePage,
splashColor: theme.colorScheme.primaryContainer.withValues(
alpha: 0.3,
),
customBorder: const CircleBorder(),
),
),
),
Positioned(
right: -4,
bottom: -4,
child: Obx(
() => MineController.anonymity.value
? IgnorePointer(
child: Container(
padding: const .all(2),
decoration: BoxDecoration(
shape: .circle,
color: theme.colorScheme.secondaryContainer,
),
child: Icon(
size: 14,
MdiIcons.incognito,
color: theme.colorScheme.onSecondaryContainer,
),
),
)
: const SizedBox.shrink(),
),
),
],
);
}
return SizedBox(
width: 38,
height: 38,
child: IconButton(
tooltip: '点击登录',
style: IconButton.styleFrom(
padding: .zero,
backgroundColor: theme.colorScheme.onInverseSurface,
),
onPressed: mainController.toMinePage,
icon: Icon(
Icons.person_rounded,
size: 22,
color: theme.colorScheme.primary,
),
),
);
},
),
);
}
Widget msgBadge(MainController mainController) {
void toWhisper() {
mainController.msgUnReadCount.value = '';
mainController.lastCheckUnreadAt = DateTime.now().millisecondsSinceEpoch;
Get.toNamed('/whisper');
}
final msgUnReadCount = mainController.msgUnReadCount.value;
return GestureDetector(
onTap: toWhisper,
child: Badge(
isLabelVisible:
mainController.msgBadgeMode != DynamicBadgeMode.hidden &&
msgUnReadCount.isNotEmpty,
alignment: mainController.msgBadgeMode == DynamicBadgeMode.number
? const Alignment(0, -0.5)
: const Alignment(0.5, -0.5),
label:
mainController.msgBadgeMode == DynamicBadgeMode.number &&
msgUnReadCount.isNotEmpty
? Text(msgUnReadCount)
: null,
child: IconButton(
tooltip: '消息',
onPressed: toWhisper,
icon: const Icon(
Icons.notifications_none,
),
),
),
return Obx(
() {
if (mainController.accountService.isLogin.value) {
final count = mainController.msgUnReadCount.value;
final isNumBadge = mainController.msgBadgeMode == .number;
return IconButton(
tooltip: '消息',
onPressed: () {
mainController
..msgUnReadCount.value = ''
..lastCheckUnreadAt = DateTime.now().millisecondsSinceEpoch;
Get.toNamed('/whisper');
},
icon: Badge(
isLabelVisible:
mainController.msgBadgeMode != .hidden && count.isNotEmpty,
alignment: isNumBadge
? const Alignment(0.0, -0.85)
: const Alignment(1.0, -0.85),
label: isNumBadge && count.isNotEmpty ? Text(count) : null,
child: const Icon(Icons.notifications_none),
),
);
}
return const SizedBox.shrink();
},
);
}

View File

@@ -288,9 +288,14 @@ class LiveRoomController extends GetxController {
),
),
TextButton(
onPressed: () => Get
..back()
..back(),
onPressed: () {
if (plPlayerController.isDesktopPip) {
plPlayerController.exitDesktopPip();
}
Get
..back()
..back();
},
child: const Text('退出'),
),
],

View File

@@ -1,14 +1,12 @@
import 'dart:io';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart';
import 'package:PiliPlus/common/widgets/flutter/tabs.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart';
import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/models/common/nav_bar_config.dart';
import 'package:PiliPlus/pages/home/view.dart';
import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/plugin/pl_player/models/play_status.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
@@ -23,7 +21,6 @@ import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart';
@@ -34,7 +31,7 @@ class MainApp extends StatefulWidget {
State<MainApp> createState() => _MainAppState();
}
class _MainAppState extends State<MainApp>
class _MainAppState extends PopScopeState<MainApp>
with RouteAware, WidgetsBindingObserver, WindowListener, TrayListener {
final _mainController = Get.put(MainController());
late final _setting = GStorage.setting;
@@ -234,7 +231,7 @@ class _MainAppState extends State<MainApp>
await trayManager.setContextMenu(trayMenu);
}
void onBack() {
static void _onBack() {
if (Platform.isAndroid) {
Utils.channel.invokeMethod('back');
} else {
@@ -244,13 +241,8 @@ class _MainAppState extends State<MainApp>
late bool useBottomNav;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final padding = MediaQuery.viewPaddingOf(context);
useBottomNav =
!_mainController.useSideBar && MediaQuery.sizeOf(context).isPortrait;
Widget? bottomNav = useBottomNav
Widget? get _bottomNav {
return useBottomNav
? _mainController.navigationBars.length > 1
? _mainController.enableMYBar
? Obx(
@@ -279,7 +271,7 @@ class _MainAppState extends State<MainApp>
iconSize: 16,
selectedFontSize: 12,
unselectedFontSize: 12,
type: BottomNavigationBarType.fixed,
type: .fixed,
items: _mainController.navigationBars
.map(
(e) => BottomNavigationBarItem(
@@ -296,151 +288,151 @@ class _MainAppState extends State<MainApp>
)
: null
: null;
return PopScope(
canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) {
if (_mainController.directExitOnBack) {
onBack();
} else {
if (_mainController.selectedIndex.value != 0) {
_mainController
..setIndex(0)
..bottomBar?.value = true
..setSearchBar();
} else {
onBack();
}
}
},
child: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness: theme.brightness.reverse,
),
child: Scaffold(
extendBody: true,
resizeToAvoidBottomInset: false,
appBar: AppBar(toolbarHeight: 0),
body: Padding(
padding: EdgeInsets.only(
left: useBottomNav ? padding.left : 0.0,
right: padding.right,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (!useBottomNav) ...[
_mainController.navigationBars.length > 1
? context.isTablet && _mainController.optTabletNav
? Column(
children: [
const SizedBox(height: 25),
userAndSearchVertical(theme),
const Spacer(flex: 2),
Expanded(
flex: 5,
child: SizedBox(
width: 130,
child: Obx(
() => NavigationDrawer(
backgroundColor: Colors.transparent,
tilePadding:
const EdgeInsets.symmetric(
vertical: 5,
horizontal: 12,
),
indicatorShape:
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(16),
),
),
onDestinationSelected:
_mainController.setIndex,
selectedIndex: _mainController
.selectedIndex
.value,
children: _mainController
.navigationBars
.map(
(e) =>
NavigationDrawerDestination(
label: Text(e.label),
icon: _buildIcon(
type: e,
),
selectedIcon: _buildIcon(
type: e,
selected: true,
),
),
)
.toList(),
}
@override
void onPopInvokedWithResult(bool didPop, Object? result) {
if (_mainController.directExitOnBack) {
_onBack();
} else {
if (_mainController.selectedIndex.value != 0) {
_mainController
..setIndex(0)
..bottomBar?.value = true
..setSearchBar();
} else {
_onBack();
}
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final padding = MediaQuery.viewPaddingOf(context);
useBottomNav =
!_mainController.useSideBar && MediaQuery.sizeOf(context).isPortrait;
final bottomNav = _bottomNav;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness: theme.brightness.reverse,
),
child: Scaffold(
extendBody: true,
resizeToAvoidBottomInset: false,
appBar: AppBar(toolbarHeight: 0),
body: Padding(
padding: EdgeInsets.only(
left: useBottomNav ? padding.left : 0.0,
right: padding.right,
),
child: Row(
mainAxisAlignment: .center,
children: [
if (!useBottomNav) ...[
_mainController.navigationBars.length > 1
? context.isTablet && _mainController.optTabletNav
? Column(
children: [
const SizedBox(height: 25),
userAndSearchVertical(theme),
const Spacer(flex: 2),
Expanded(
flex: 5,
child: SizedBox(
width: 130,
child: Obx(
() => NavigationDrawer(
backgroundColor: Colors.transparent,
tilePadding: const .symmetric(
vertical: 5,
horizontal: 12,
),
indicatorShape:
const RoundedRectangleBorder(
borderRadius: .all(
.circular(16),
),
),
onDestinationSelected:
_mainController.setIndex,
selectedIndex:
_mainController.selectedIndex.value,
children: _mainController.navigationBars
.map(
(e) =>
NavigationDrawerDestination(
label: Text(e.label),
icon: _buildIcon(type: e),
selectedIcon: _buildIcon(
type: e,
selected: true,
),
),
)
.toList(),
),
),
),
],
)
: Obx(
() => NavigationRail(
groupAlignment: 0.5,
selectedIndex:
_mainController.selectedIndex.value,
onDestinationSelected:
_mainController.setIndex,
labelType: NavigationRailLabelType.selected,
leading: userAndSearchVertical(theme),
destinations: _mainController.navigationBars
.map(
(e) => NavigationRailDestination(
label: Text(e.label),
icon: _buildIcon(type: e),
selectedIcon: _buildIcon(
type: e,
selected: true,
),
),
)
.toList(),
),
)
: Container(
padding: const EdgeInsets.only(top: 10),
width: 80,
child: userAndSearchVertical(theme),
),
VerticalDivider(
width: 1,
endIndent: padding.bottom,
color: theme.colorScheme.outline.withValues(alpha: 0.06),
),
],
Expanded(
child: _mainController.mainTabBarView
? CustomTabBarView(
scrollDirection: useBottomNav
? Axis.horizontal
: Axis.vertical,
physics: const NeverScrollableScrollPhysics(),
controller: _mainController.controller,
children: _mainController.navigationBars
.map((i) => i.page)
.toList(),
)
: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _mainController.controller,
children: _mainController.navigationBars
.map((i) => i.page)
.toList(),
),
],
)
: Obx(
() => NavigationRail(
groupAlignment: 0.5,
selectedIndex:
_mainController.selectedIndex.value,
onDestinationSelected: _mainController.setIndex,
labelType: .selected,
leading: userAndSearchVertical(theme),
destinations: _mainController.navigationBars
.map(
(e) => NavigationRailDestination(
label: Text(e.label),
icon: _buildIcon(type: e),
selectedIcon: _buildIcon(
type: e,
selected: true,
),
),
)
.toList(),
),
)
: Container(
width: 80,
padding: const .only(top: 10),
child: userAndSearchVertical(theme),
),
VerticalDivider(
width: 1,
endIndent: padding.bottom,
color: theme.colorScheme.outline.withValues(alpha: 0.06),
),
],
),
Expanded(
child: _mainController.mainTabBarView
? CustomTabBarView(
scrollDirection: useBottomNav ? .horizontal : .vertical,
physics: const NeverScrollableScrollPhysics(),
controller: _mainController.controller,
children: _mainController.navigationBars
.map((i) => i.page)
.toList(),
)
: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _mainController.controller,
children: _mainController.navigationBars
.map((i) => i.page)
.toList(),
),
),
],
),
bottomNavigationBar: _buildBottom(bottomNav),
),
bottomNavigationBar: _buildBottom(bottomNav),
),
);
}
@@ -461,22 +453,18 @@ class _MainAppState extends State<MainApp>
return bottomNav;
}
Widget _buildIcon({
required NavigationBarType type,
bool selected = false,
}) {
Widget _buildIcon({required NavigationBarType type, bool selected = false}) {
final icon = selected ? type.selectIcon : type.icon;
return type == NavigationBarType.dynamics
return type == .dynamics
? Obx(
() {
final dynCount = _mainController.dynCount.value;
return Badge(
isLabelVisible: dynCount > 0,
label:
_mainController.dynamicBadgeMode == DynamicBadgeMode.number
label: _mainController.dynamicBadgeMode == .number
? Text(dynCount.toString())
: null,
padding: const EdgeInsets.symmetric(horizontal: 6),
padding: const .symmetric(horizontal: 6),
child: icon,
);
},
@@ -487,69 +475,9 @@ class _MainAppState extends State<MainApp>
Widget userAndSearchVertical(ThemeData theme) {
return Column(
children: [
Semantics(
label: "我的",
child: Obx(
() => _mainController.accountService.isLogin.value
? Stack(
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
type: ImageType.avatar,
width: 34,
height: 34,
src: _mainController.accountService.face.value,
),
Positioned.fill(
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: _mainController.toMinePage,
splashColor: theme.colorScheme.primaryContainer
.withValues(alpha: 0.3),
customBorder: const CircleBorder(),
),
),
),
Positioned(
right: -6,
bottom: -6,
child: Obx(
() => MineController.anonymity.value
? IgnorePointer(
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color:
theme.colorScheme.secondaryContainer,
shape: BoxShape.circle,
),
child: Icon(
size: 16,
MdiIcons.incognito,
color: theme
.colorScheme
.onSecondaryContainer,
),
),
)
: const SizedBox.shrink(),
),
),
],
)
: defaultUser(
theme: theme,
onPressed: _mainController.toMinePage,
),
),
),
userAvatar(theme: theme, mainController: _mainController),
const SizedBox(height: 8),
Obx(
() => _mainController.accountService.isLogin.value
? msgBadge(_mainController)
: const SizedBox.shrink(),
),
msgBadge(_mainController),
IconButton(
tooltip: '搜索',
icon: const Icon(

View File

@@ -5,9 +5,7 @@ import 'package:PiliPlus/common/widgets/flutter/list_tile.dart';
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/models/common/nav_bar_config.dart';
import 'package:PiliPlus/models/user/info.dart';
import 'package:PiliPlus/models_new/fav/fav_folder/list.dart';
import 'package:PiliPlus/pages/common/common_page.dart';
import 'package:PiliPlus/pages/home/view.dart';
@@ -70,16 +68,17 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
return onBuild(
Column(
children: [
const SizedBox(height: 10),
_buildHeaderActions,
const SizedBox(height: 10),
Padding(
padding: const .symmetric(vertical: 10),
child: _buildHeaderActions,
),
Expanded(
child: Material(
type: MaterialType.transparency,
type: .transparency,
child: refreshIndicator(
onRefresh: controller.onRefresh,
child: ListView(
padding: const EdgeInsets.only(bottom: 100),
padding: const .only(bottom: 100),
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
children: [
@@ -102,7 +101,7 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
Widget _buildActions(Color primary) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisAlignment: .spaceEvenly,
children: controller.list
.map(
(e) => Flexible(
@@ -115,14 +114,10 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
aspectRatio: 1,
child: Column(
spacing: 6,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: .min,
mainAxisAlignment: .center,
children: [
Icon(
size: e.size,
e.icon,
color: primary,
),
Icon(size: e.size, e.icon, color: primary),
Text(
e.title,
style: const TextStyle(fontSize: 13),
@@ -139,9 +134,12 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
}
Widget get _buildHeaderActions {
const iconSize = 22.0;
const padding = EdgeInsets.all(8);
const style = ButtonStyle(tapTargetSize: .shrinkWrap);
return Row(
spacing: 5,
mainAxisAlignment: MainAxisAlignment.end,
mainAxisAlignment: .end,
children: [
if (widget.showBackBtn)
const Expanded(
@@ -155,11 +153,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
),
if (!_mainController.hasHome) ...[
IconButton(
iconSize: 22,
padding: const EdgeInsets.all(8),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
iconSize: iconSize,
padding: padding,
style: style,
tooltip: '搜索',
onPressed: () => Get.toNamed('/search'),
icon: const Icon(Icons.search),
@@ -170,11 +166,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
() {
final anonymity = MineController.anonymity.value;
return IconButton(
iconSize: 22,
padding: const EdgeInsets.all(8),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
iconSize: iconSize,
padding: padding,
style: style,
tooltip: "${anonymity ? '退出' : '进入'}无痕模式",
onPressed: MineController.onChangeAnonymity,
icon: anonymity
@@ -184,11 +178,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
},
),
IconButton(
iconSize: 22,
padding: const EdgeInsets.all(8),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
iconSize: iconSize,
padding: padding,
style: style,
tooltip: '设置账号模式',
onPressed: () => LoginPageController.switchAccountDialog(context),
icon: const Icon(Icons.switch_account_outlined),
@@ -196,11 +188,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
Obx(
() {
return IconButton(
iconSize: 22,
padding: const EdgeInsets.all(8),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
iconSize: iconSize,
padding: padding,
style: style,
tooltip: '切换至${controller.nextThemeType.desc}主题',
onPressed: controller.onChangeTheme,
icon: controller.themeType.value.icon,
@@ -208,11 +198,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
},
),
IconButton(
iconSize: 22,
padding: const EdgeInsets.all(8),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
iconSize: iconSize,
padding: padding,
style: style,
tooltip: '设置',
onPressed: () => Get.toNamed('/setting', preventDuplicates: false),
icon: const Icon(Icons.settings_outlined),
@@ -240,8 +228,8 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
color: secondary,
);
return Obx(() {
final UserInfoData userInfo = controller.userInfo.value;
final LevelInfo? levelInfo = userInfo.levelInfo;
final userInfo = controller.userInfo.value;
final levelInfo = userInfo.levelInfo;
final hasLevel = levelInfo != null;
final isVip = userInfo.vipStatus != null && userInfo.vipStatus! > 0;
final userStat = controller.userStat.value;
@@ -249,7 +237,7 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
behavior: .opaque,
onTap: controller.onLogin,
onLongPress: () {
Feedback.forLongPress(context);
@@ -259,16 +247,16 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
? null
: () => controller.onLogin(true),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
const SizedBox(width: 20),
userInfo.face != null
? Stack(
clipBehavior: Clip.none,
clipBehavior: .none,
children: [
NetworkImgLayer(
src: userInfo.face,
type: ImageType.avatar,
type: .avatar,
width: 55,
height: 55,
),
@@ -297,9 +285,9 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
const SizedBox(width: 16),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
mainAxisAlignment: .center,
crossAxisAlignment: .start,
children: [
Row(
spacing: 6,
@@ -313,6 +301,8 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
? theme.colorScheme.vipColor
: null,
),
maxLines: 1,
overflow: .ellipsis,
),
),
Image.asset(
@@ -376,7 +366,7 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisAlignment: .spaceEvenly,
children: [
_btn(
count: userStat.dynamicCount,
@@ -422,14 +412,14 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
child: AspectRatio(
aspectRatio: 1,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
mainAxisSize: .min,
mainAxisAlignment: .center,
children: [
Text(
count?.toString() ?? '-',
style: countStyle,
),
const SizedBox(height: 4),
Text(
name,
style: labelStyle,
@@ -466,7 +456,7 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
text: '我的收藏 ',
style: TextStyle(
fontSize: theme.textTheme.titleMedium!.fontSize,
fontWeight: FontWeight.bold,
fontWeight: .bold,
),
),
if (controller.favFolderCount != null)
@@ -516,19 +506,17 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
return SizedBox(
height: 200,
child: ListView.separated(
padding: const EdgeInsets.only(left: 20, top: 12, right: 20),
padding: const .only(left: 20, top: 10, right: 20),
itemCount: response.list.length + (flag ? 1 : 0),
itemBuilder: (context, index) {
if (flag && index == favFolderList.length) {
return Padding(
padding: const EdgeInsets.only(bottom: 35),
padding: const .only(bottom: 35),
child: Center(
child: IconButton(
tooltip: '查看更多',
style: ButtonStyle(
padding: const WidgetStatePropertyAll(
EdgeInsets.zero,
),
padding: const WidgetStatePropertyAll(.zero),
backgroundColor: WidgetStatePropertyAll(
theme.colorScheme.secondaryContainer.withValues(
alpha: 0.5,
@@ -560,8 +548,8 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
);
}
},
scrollDirection: Axis.horizontal,
separatorBuilder: (context, index) => const SizedBox(width: 14),
scrollDirection: .horizontal,
separatorBuilder: (_, _) => const SizedBox(width: 14),
),
);
},
@@ -571,7 +559,7 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
child: Center(
child: Text(
errMsg ?? '',
textAlign: TextAlign.center,
textAlign: .center,
),
),
),

View File

@@ -42,7 +42,7 @@ class FavFolderItem extends StatelessWidget {
color: theme.colorScheme.onInverseSurface.withValues(
alpha: 0.4,
),
offset: const Offset(4, -12),
offset: const Offset(6, -8),
blurRadius: 0.0,
spreadRadius: 0.0,
),

View File

@@ -31,8 +31,8 @@ class _RcmdPageState extends CommonPageState<RcmdPage, RcmdController>
super.build(context);
return onBuild(
Container(
clipBehavior: Clip.hardEdge,
margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
clipBehavior: .hardEdge,
margin: const .symmetric(horizontal: StyleString.safeSpace),
decoration: const BoxDecoration(borderRadius: StyleString.mdRadius),
child: refreshIndicator(
onRefresh: controller.onRefresh,
@@ -41,10 +41,7 @@ class _RcmdPageState extends CommonPageState<RcmdPage, RcmdController>
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: const EdgeInsets.only(
top: StyleString.cardSpace,
bottom: 100,
),
padding: const .only(top: StyleString.cardSpace, bottom: 100),
sliver: Obx(() => _buildBody(controller.loadingState.value)),
),
],
@@ -87,7 +84,7 @@ class _RcmdPageState extends CommonPageState<RcmdPage, RcmdController>
),
child: Text(
'上次看到这里\n点击刷新',
textAlign: TextAlign.center,
textAlign: .center,
style: TextStyle(
color: Theme.of(
context,

View File

@@ -44,7 +44,7 @@ class _SettingPageState extends State<SettingPage> {
final RxBool _noAccount = Accounts.account.isEmpty.obs;
late bool _isPortrait;
final List<_SettingsModel> _items = const [
static const List<_SettingsModel> _items = [
_SettingsModel(
type: SettingType.privacySetting,
subtitle: '黑名单、无痕模式',

View File

@@ -91,6 +91,17 @@ class _CdnSelectDialogState extends State<CdnSelectDialog> {
void initState() {
_cdnSpeedTest = Pref.cdnSpeedTest;
if (_cdnSpeedTest) {
_dio =
Dio(
BaseOptions(
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 15),
),
)
..options.headers = {
'user-agent': UaType.pc.ua,
'referer': HttpString.baseUrl,
};
final length = CDNService.values.length;
_cdnResList = List.generate(
length,
@@ -156,11 +167,7 @@ class _CdnSelectDialogState extends State<CdnSelectDialog> {
}
}
late final _dio = Dio()
..options.headers = {
'user-agent': UaType.pc.ua,
'referer': HttpString.baseUrl,
};
late final Dio _dio;
Future<void> _measureDownloadSpeed(String url, int index) async {
const maxSize = 8 * 1024 * 1024;

View File

@@ -1456,7 +1456,7 @@ class VideoDetailController extends GetxController
}
RxList<Subtitle> subtitles = RxList<Subtitle>();
late final Map<int, String> vttSubtitles = {};
final Map<int, ({bool isData, String id})> vttSubtitles = {};
late final RxInt vttSubtitlesIndex = (-1).obs;
late final RxBool showVP = true.obs;
late final RxList<ViewPointSegment> viewPointList = <ViewPointSegment>[].obs;
@@ -1471,19 +1471,21 @@ class VideoDetailController extends GetxController
return;
}
Future<void> setSub(String subtitle) async {
Future<void> setSub(({bool isData, String id}) subtitle) async {
final sub = subtitles[index - 1];
await plPlayerController.videoPlayerController?.setSubtitleTrack(
SubtitleTrack.data(
subtitle,
title: sub.lanDoc,
language: sub.lan,
SubtitleTrack(
subtitle.id,
sub.lanDoc,
sub.lan,
uri: !subtitle.isData,
data: subtitle.isData,
),
);
vttSubtitlesIndex.value = index;
}
String? subtitle = vttSubtitles[index - 1];
({bool isData, String id})? subtitle = vttSubtitles[index - 1];
if (subtitle != null) {
await setSub(subtitle);
} else {
@@ -1491,8 +1493,9 @@ class VideoDetailController extends GetxController
subtitles[index - 1].subtitleUrl!,
);
if (!isClosed && result != null) {
vttSubtitles[index - 1] = result;
await setSub(result);
final subtitle = (isData: true, id: result);
vttSubtitles[index - 1] = subtitle;
await setSub(subtitle);
}
}
}
@@ -1665,6 +1668,8 @@ class VideoDetailController extends GetxController
?..removeListener(scrollListener)
..dispose();
animController?.dispose();
subtitles.clear();
vttSubtitles.clear();
super.onClose();
}

View File

@@ -324,7 +324,6 @@ class _DownloadPanelState extends State<DownloadPanel> {
required ugc.BaseEpisodeItem episode,
}) {
late String title;
String? cover;
num? duration;
int? pubdate;
int? view;
@@ -332,6 +331,11 @@ class _DownloadPanelState extends State<DownloadPanel> {
bool? isCharging;
int? cid;
String? cover;
int? width;
int? height;
bool cacheWidth = false;
switch (episode) {
case Part part:
cid = part.cid;
@@ -339,15 +343,27 @@ class _DownloadPanelState extends State<DownloadPanel> {
title = part.part ?? widget.videoDetail!.title!;
duration = part.duration;
pubdate = part.ctime;
if (part.dimension case final dimension?) {
width = dimension.width;
height = dimension.height;
}
break;
case ugc.EpisodeItem item:
cid = item.cid;
title = item.title!;
cover = item.arc?.pic;
duration = item.arc?.duration;
pubdate = item.arc?.pubdate;
view = item.arc?.stat?.view;
danmaku = item.arc?.stat?.danmaku;
if (item.arc case final arc?) {
cover = arc.pic;
duration = arc.duration;
pubdate = arc.pubdate;
if (arc.stat case final stat?) {
view = stat.view;
danmaku = stat.danmaku;
}
if (arc.dimension case final dimension?) {
width = dimension.width;
height = dimension.height;
}
}
if (item.attribute == 8) {
isCharging = true;
}
@@ -363,8 +379,15 @@ class _DownloadPanelState extends State<DownloadPanel> {
duration = item.duration == null ? null : item.duration! ~/ 1000;
}
pubdate = item.pubTime;
if (item.dimension case final dimension?) {
width = dimension.width;
height = dimension.height;
}
break;
}
if (width != null && height != null) {
cacheWidth = width <= height;
}
late final primary = theme.colorScheme.primary;
return Padding(
@@ -401,6 +424,7 @@ class _DownloadPanelState extends State<DownloadPanel> {
src: cover,
width: 140.8,
height: 88,
cacheWidth: cacheWidth,
),
if (duration != null && duration > 0)
PBadge(

View File

@@ -1,5 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:convert' show jsonDecode, utf8;
import 'dart:io';
import 'dart:math';
@@ -717,34 +717,41 @@ class HeaderControlState extends State<HeaderControl>
final first = file.files.first;
final path = first.path;
if (path != null) {
final file = File(path);
final stream = file.openRead().transform(
utf8.decoder,
);
final buffer = StringBuffer();
await for (final chunk in stream) {
if (!mounted) return;
buffer.write(chunk);
}
if (!mounted) return;
String sub = buffer.toString();
final name = first.name;
final length = videoDetailCtr.subtitles.length;
if (name.endsWith('.json')) {
final file = File(path);
final stream = file.openRead().transform(
utf8.decoder,
);
final buffer = StringBuffer();
await for (final chunk in stream) {
if (!mounted) return;
buffer.write(chunk);
}
if (!mounted) return;
String sub = buffer.toString();
sub = await compute<List, String>(
VideoHttp.processList,
jsonDecode(sub)['body'],
);
if (!mounted) return;
videoDetailCtr.vttSubtitles[length] = (
isData: true,
id: sub,
);
} else {
videoDetailCtr.vttSubtitles[length] = (
isData: false,
id: path,
);
}
final length = videoDetailCtr.subtitles.length;
videoDetailCtr
..subtitles.add(
Subtitle(
lan: '',
lanDoc: name.split('.').firstOrNull ?? name,
),
)
..vttSubtitles[length] = sub;
videoDetailCtr.subtitles.add(
Subtitle(
lan: '',
lanDoc: name.split('.').firstOrNull ?? name,
),
);
await videoDetailCtr.setSubtitle(length + 1);
}
}