Merge branch 'main' of https://github.com/guozhigq/pilipala into guozhigq-main

This commit is contained in:
orz12
2024-01-22 10:22:19 +08:00
25 changed files with 341 additions and 201 deletions

View File

@@ -69,7 +69,7 @@ class VideoCardH extends StatelessWidget {
final double width = (boxConstraints.maxWidth - final double width = (boxConstraints.maxWidth -
StyleString.cardSpace * StyleString.cardSpace *
6 / 6 /
MediaQuery.of(context).textScaleFactor) / MediaQuery.textScalerOf(context).scale(1.0)) /
2; 2;
return Container( return Container(
constraints: const BoxConstraints(minHeight: 88), constraints: const BoxConstraints(minHeight: 88),

View File

@@ -342,7 +342,8 @@ class VideoStat extends StatelessWidget {
maxLines: 1, maxLines: 1,
text: TextSpan( text: TextSpan(
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, fontSize: MediaQuery.textScalerOf(context)
.scale(Theme.of(context).textTheme.labelSmall!.fontSize),
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)), text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)),

View File

@@ -467,4 +467,7 @@ class Api {
/// page_size /// page_size
static const getSeasonDetailApi = static const getSeasonDetailApi =
'/x/polymer/web-space/seasons_archives_list'; '/x/polymer/web-space/seasons_archives_list';
/// 获取未读动态数
static const getUnreadDynamic = '/x/web-interface/dynamic/entrance';
} }

17
lib/http/common.dart Normal file
View File

@@ -0,0 +1,17 @@
import 'index.dart';
class CommonHttp {
static Future unReadDynamic() async {
var res = await Request().get(Api.getUnreadDynamic,
data: {'alltype_offset': 0, 'video_offset': '', 'article_offset': 0});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']['dyn_basic_infos']};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
}

View File

@@ -158,9 +158,8 @@ class MyApp extends StatelessWidget {
return FlutterSmartDialog( return FlutterSmartDialog(
toastBuilder: (String msg) => CustomToast(msg: msg), toastBuilder: (String msg) => CustomToast(msg: msg),
child: MediaQuery( child: MediaQuery(
data: MediaQuery.of(context).copyWith( data: MediaQuery.of(context)
textScaleFactor: .copyWith(textScaler: TextScaler.linear(textScale)),
MediaQuery.of(context).textScaleFactor * textScale),
child: child!, child: child!,
), ),
); );

View File

@@ -9,6 +9,7 @@ enum TabType { live, rcmd, hot, bangumi }
extension TabTypeDesc on TabType { extension TabTypeDesc on TabType {
String get description => ['直播', '推荐', '热门', '番剧'][index]; String get description => ['直播', '推荐', '热门', '番剧'][index];
String get id => ['live', 'rcmd', 'hot', 'bangumi'][index];
} }
List tabsConfig = [ List tabsConfig = [

View File

@@ -224,7 +224,7 @@ class _BangumiPageState extends State<BangumiPage>
// 列数 // 列数
crossAxisCount: 3, crossAxisCount: 3,
mainAxisExtent: Get.size.width / 3 / 0.65 + mainAxisExtent: Get.size.width / 3 / 0.65 +
32 * MediaQuery.of(context).textScaleFactor, MediaQuery.textScalerOf(context).scale(32.0),
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {

View File

@@ -8,12 +8,13 @@ import 'package:pilipala/utils/storage.dart';
class HomeController extends GetxController with GetTickerProviderStateMixin { class HomeController extends GetxController with GetTickerProviderStateMixin {
bool flag = false; bool flag = false;
late List tabs; late RxList tabs = [].obs;
RxInt initialIndex = 1.obs; RxInt initialIndex = 1.obs;
late TabController tabController; late TabController tabController;
late List tabsCtrList; late List tabsCtrList;
late List<Widget> tabsPageList; late List<Widget> tabsPageList;
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
Box settingStorage = GStrorage.setting;
RxBool userLogin = false.obs; RxBool userLogin = false.obs;
RxString userFace = ''.obs; RxString userFace = ''.obs;
var userInfo; var userInfo;
@@ -21,6 +22,8 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
late final StreamController<bool> searchBarStream = late final StreamController<bool> searchBarStream =
StreamController<bool>.broadcast(); StreamController<bool>.broadcast();
late bool hideSearchBar; late bool hideSearchBar;
late List defaultTabs;
late List<String> tabbarSort;
@override @override
void onInit() { void onInit() {
@@ -28,34 +31,10 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null; userLogin.value = userInfo != null;
userFace.value = userInfo != null ? userInfo.face : ''; userFace.value = userInfo != null ? userInfo.face : '';
// 进行tabs配置 // 进行tabs配置
tabs = tabsConfig; setTabConfig();
tabsCtrList = tabsConfig.map((e) => e['ctr']).toList();
tabsPageList = tabsConfig.map<Widget>((e) => e['page']).toList();
tabController = TabController(
initialIndex: initialIndex.value,
length: tabs.length,
vsync: this,
);
hideSearchBar = hideSearchBar =
setting.get(SettingBoxKey.hideSearchBar, defaultValue: true); setting.get(SettingBoxKey.hideSearchBar, defaultValue: true);
// 监听 tabController 切换
tabController.animation!.addListener(() {
if (tabController.indexIsChanging) {
if (initialIndex.value != tabController.index) {
initialIndex.value = tabController.index;
}
} else {
final int temp = tabController.animation!.value.round();
if (initialIndex.value != temp) {
initialIndex.value = temp;
tabController.index = initialIndex.value;
}
}
});
} }
void onRefresh() { void onRefresh() {
@@ -77,4 +56,42 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
if (val) return; if (val) return;
userFace.value = userInfo != null ? userInfo.face : ''; userFace.value = userInfo != null ? userInfo.face : '';
} }
void setTabConfig() async {
defaultTabs = tabsConfig;
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']);
tabs.value = defaultTabs
.where((i) => tabbarSort.contains((i['type'] as TabType).id))
.toList();
if (tabbarSort.contains(TabType.rcmd.id)) {
initialIndex.value = tabbarSort.indexOf(TabType.rcmd.id);
} else {
initialIndex.value = 0;
}
tabsCtrList = tabs.map((e) => e['ctr']).toList();
tabsPageList = tabs.map<Widget>((e) => e['page']).toList();
tabController = TabController(
initialIndex: initialIndex.value,
length: tabs.length,
vsync: this,
);
// 监听 tabController 切换
tabController.animation!.addListener(() {
if (tabController.indexIsChanging) {
if (initialIndex.value != tabController.index) {
initialIndex.value = tabController.index;
}
} else {
final int temp = tabController.animation!.value.round();
if (initialIndex.value != temp) {
initialIndex.value = temp;
tabController.index = initialIndex.value;
}
}
});
}
} }

View File

@@ -90,7 +90,11 @@ class _HomePageState extends State<HomePage>
ctr: _homeController, ctr: _homeController,
callback: showUserBottomSheet, callback: showUserBottomSheet,
), ),
const CustomTabs(), if (_homeController.tabs.length > 1) ...[
const CustomTabs(),
] else ...[
const SizedBox(height: 6),
],
Expanded( Expanded(
child: TabBarView( child: TabBarView(
controller: _homeController.tabController, controller: _homeController.tabController,
@@ -250,17 +254,6 @@ class CustomTabs extends StatefulWidget {
class _CustomTabsState extends State<CustomTabs> { class _CustomTabsState extends State<CustomTabs> {
final HomeController _homeController = Get.put(HomeController()); final HomeController _homeController = Get.put(HomeController());
int currentTabIndex = 1;
@override
void initState() {
super.initState();
_homeController.tabController.addListener(listen);
}
void listen() {
_homeController.initialIndex.value = _homeController.tabController.index;
}
void onTap(int index) { void onTap(int index) {
feedBack(); feedBack();
@@ -271,34 +264,30 @@ class _CustomTabsState extends State<CustomTabs> {
_homeController.tabController.index = index; _homeController.tabController.index = index;
} }
@override
void dispose() {
super.dispose();
_homeController.tabController.removeListener(listen);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
height: 44, height: 44,
margin: const EdgeInsets.only(top: 4), margin: const EdgeInsets.only(top: 4),
child: ListView.separated( child: Obx(
padding: const EdgeInsets.symmetric(horizontal: 14.0), () => ListView.separated(
scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 14.0),
itemCount: _homeController.tabs.length, scrollDirection: Axis.horizontal,
separatorBuilder: (BuildContext context, int index) { itemCount: _homeController.tabs.length,
return const SizedBox(width: 10); separatorBuilder: (BuildContext context, int index) {
}, return const SizedBox(width: 10);
itemBuilder: (BuildContext context, int index) { },
String label = _homeController.tabs[index]['label']; itemBuilder: (BuildContext context, int index) {
return Obx( String label = _homeController.tabs[index]['label'];
() => CustomChip( return Obx(
onTap: () => onTap(index), () => CustomChip(
label: label, onTap: () => onTap(index),
selected: index == _homeController.initialIndex.value, label: label,
), selected: index == _homeController.initialIndex.value,
); ),
}, );
},
),
), ),
); );
} }

View File

@@ -162,8 +162,9 @@ class _LivePageState extends State<LivePage>
crossAxisCount: crossAxisCount, crossAxisCount: crossAxisCount,
mainAxisExtent: mainAxisExtent:
Get.size.width / crossAxisCount / StyleString.aspectRatio + Get.size.width / crossAxisCount / StyleString.aspectRatio +
(crossAxisCount == 1 ? 48 : 68) * MediaQuery.textScalerOf(context).scale(
MediaQuery.of(context).textScaleFactor, (crossAxisCount == 1 ? 48 : 68),
),
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {

View File

@@ -1,9 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/http/common.dart';
import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/view.dart'; import 'package:pilipala/pages/home/view.dart';
import 'package:pilipala/pages/media/index.dart'; import 'package:pilipala/pages/media/index.dart';
@@ -27,6 +29,7 @@ class MainController extends GetxController {
size: 21, size: 21,
), ),
'label': "首页", 'label': "首页",
'count': 0,
}, },
{ {
'icon': const Icon( 'icon': const Icon(
@@ -38,6 +41,7 @@ class MainController extends GetxController {
size: 21, size: 21,
), ),
'label': "动态", 'label': "动态",
'count': 0,
}, },
{ {
'icon': const Icon( 'icon': const Icon(
@@ -49,6 +53,7 @@ class MainController extends GetxController {
size: 21, size: 21,
), ),
'label': "媒体库", 'label': "媒体库",
'count': 0,
} }
].obs; ].obs;
final StreamController<bool> bottomBarStream = final StreamController<bool> bottomBarStream =
@@ -56,6 +61,10 @@ class MainController extends GetxController {
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
DateTime? _lastPressedAt; DateTime? _lastPressedAt;
late bool hideTabBar; late bool hideTabBar;
late PageController pageController;
int selectedIndex = 0;
Box userInfoCache = GStrorage.userInfo;
RxBool userLogin = false.obs;
@override @override
void onInit() { void onInit() {
@@ -64,17 +73,47 @@ class MainController extends GetxController {
Utils.checkUpdata(); Utils.checkUpdata();
} }
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true); hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
var userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null;
getUnreadDynamic();
} }
Future<bool> onBackPressed(BuildContext context) { void onBackPressed(BuildContext context) {
if (_lastPressedAt == null || if (_lastPressedAt == null ||
DateTime.now().difference(_lastPressedAt!) > DateTime.now().difference(_lastPressedAt!) >
const Duration(seconds: 2)) { const Duration(seconds: 2)) {
// 两次点击时间间隔超过2秒重新记录时间戳 // 两次点击时间间隔超过2秒重新记录时间戳
_lastPressedAt = DateTime.now(); _lastPressedAt = DateTime.now();
if (selectedIndex != 0) {
pageController.jumpTo(0);
}
SmartDialog.showToast("再按一次退出Pili"); SmartDialog.showToast("再按一次退出Pili");
return Future.value(false); // 不退出应用 return; // 不退出应用
} }
return Future.value(true); // 退出应用 SystemNavigator.pop(); // 退出应用
}
void getUnreadDynamic() async {
if (!userLogin.value) {
return;
}
int dynamicItemIndex =
navigationBars.indexWhere((item) => item['label'] == "动态");
var res = await CommonHttp.unReadDynamic();
var data = res['data'];
if (dynamicItemIndex != -1) {
navigationBars[dynamicItemIndex]['count'] =
data == null ? 0 : data.length; // 修改 count 属性为新的值
}
navigationBars.refresh();
}
void clearUnread() async {
int dynamicItemIndex =
navigationBars.indexWhere((item) => item['label'] == "动态");
if (dynamicItemIndex != -1) {
navigationBars[dynamicItemIndex]['count'] = 0; // 修改 count 属性为新的值
}
navigationBars.refresh();
} }
} }

View File

@@ -24,8 +24,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
final DynamicsController _dynamicController = Get.put(DynamicsController()); final DynamicsController _dynamicController = Get.put(DynamicsController());
final MediaController _mediaController = Get.put(MediaController()); final MediaController _mediaController = Get.put(MediaController());
PageController? _pageController;
int selectedIndex = 0;
int? _lastSelectTime; //上次点击时间 int? _lastSelectTime; //上次点击时间
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
late bool enableMYBar; late bool enableMYBar;
@@ -34,13 +32,14 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
void initState() { void initState() {
super.initState(); super.initState();
_lastSelectTime = DateTime.now().millisecondsSinceEpoch; _lastSelectTime = DateTime.now().millisecondsSinceEpoch;
_pageController = PageController(initialPage: selectedIndex); _mainController.pageController =
PageController(initialPage: _mainController.selectedIndex);
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true); enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
} }
void setIndex(int value) async { void setIndex(int value) async {
feedBack(); feedBack();
_pageController!.jumpToPage(value); _mainController.pageController.jumpToPage(value);
var currentPage = _mainController.pages[value]; var currentPage = _mainController.pages[value];
if (currentPage is HomePage) { if (currentPage is HomePage) {
if (_homeController.flag) { if (_homeController.flag) {
@@ -68,6 +67,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
_lastSelectTime = DateTime.now().millisecondsSinceEpoch; _lastSelectTime = DateTime.now().millisecondsSinceEpoch;
} }
_dynamicController.flag = true; _dynamicController.flag = true;
_mainController.clearUnread();
} else { } else {
_dynamicController.flag = false; _dynamicController.flag = false;
} }
@@ -94,14 +94,17 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
localCache.put('sheetHeight', sheetHeight); localCache.put('sheetHeight', sheetHeight);
localCache.put('statusBarHeight', statusBarHeight); localCache.put('statusBarHeight', statusBarHeight);
return PopScope( return PopScope(
onPopInvoked: (bool status) => _mainController.onBackPressed(context), canPop: false,
onPopInvoked: (bool didPop) async {
_mainController.onBackPressed(context);
},
child: Scaffold( child: Scaffold(
extendBody: true, extendBody: true,
body: PageView( body: PageView(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
controller: _pageController, controller: _mainController.pageController,
onPageChanged: (index) { onPageChanged: (index) {
selectedIndex = index; _mainController.selectedIndex = index;
setState(() {}); setState(() {});
}, },
children: _mainController.pages, children: _mainController.pages,
@@ -116,36 +119,48 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
curve: Curves.easeInOutCubicEmphasized, curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
offset: Offset(0, snapshot.data ? 0 : 1), offset: Offset(0, snapshot.data ? 0 : 1),
child: enableMYBar child: Obx(
? NavigationBar( () => enableMYBar
onDestinationSelected: (value) => setIndex(value), ? NavigationBar(
selectedIndex: selectedIndex, onDestinationSelected: (value) => setIndex(value),
destinations: <Widget>[ selectedIndex: _mainController.selectedIndex,
..._mainController.navigationBars.map((e) { destinations: <Widget>[
return NavigationDestination( ..._mainController.navigationBars.map((e) {
icon: e['icon'], return NavigationDestination(
selectedIcon: e['selectIcon'], icon: Badge(
label: e['label'], label: Text(e['count'].toString()),
); padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
}).toList(), isLabelVisible: e['count'] > 0,
], child: e['icon'],
) ),
: BottomNavigationBar( selectedIcon: e['selectIcon'],
currentIndex: selectedIndex, label: e['label'],
onTap: (value) => setIndex(value), );
iconSize: 16, }).toList(),
selectedFontSize: 12, ],
unselectedFontSize: 12, )
items: [ : BottomNavigationBar(
..._mainController.navigationBars.map((e) { currentIndex: _mainController.selectedIndex,
return BottomNavigationBarItem( onTap: (value) => setIndex(value),
icon: e['icon'], iconSize: 16,
activeIcon: e['selectIcon'], selectedFontSize: 12,
label: e['label'], unselectedFontSize: 12,
); items: [
}).toList(), ..._mainController.navigationBars.map((e) {
], return BottomNavigationBarItem(
), icon: Badge(
label: Text(e['count'].toString()),
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
isLabelVisible: e['count'] > 0,
child: e['icon'],
),
activeIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
),
),
); );
}, },
), ),

View File

@@ -163,7 +163,7 @@ class _MediaPageState extends State<MediaPage>
// const SizedBox(height: 10), // const SizedBox(height: 10),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
height: 200 * MediaQuery.of(context).textScaleFactor, height: MediaQuery.textScalerOf(context).scale(200),
child: FutureBuilder( child: FutureBuilder(
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {

View File

@@ -26,10 +26,9 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
StyleString.safeSpace, 5, StyleString.safeSpace, 5), StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder(builder: (context, boxConstraints) { child: LayoutBuilder(builder: (context, boxConstraints) {
double width = (boxConstraints.maxWidth - double width = (boxConstraints.maxWidth -
StyleString.cardSpace * StyleString.cardSpace *
6 / 6 /
MediaQuery.of(context).textScaleFactor) / MediaQuery.textScalerOf(context).scale(2.0));
2;
return Container( return Container(
constraints: const BoxConstraints(minHeight: 88), constraints: const BoxConstraints(minHeight: 88),
height: width / StyleString.aspectRatio, height: width / StyleString.aspectRatio,

View File

@@ -17,7 +17,7 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
mainAxisSpacing: StyleString.cardSpace + 3, mainAxisSpacing: StyleString.cardSpace + 3,
mainAxisExtent: mainAxisExtent:
MediaQuery.sizeOf(context).width / 2 / StyleString.aspectRatio + MediaQuery.sizeOf(context).width / 2 / StyleString.aspectRatio +
66 * MediaQuery.of(context).textScaleFactor), MediaQuery.textScalerOf(context).scale(66.0)),
itemCount: list.length, itemCount: list.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return LiveItem(liveItem: list![index]); return LiveItem(liveItem: list![index]);

View File

@@ -67,11 +67,11 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
TextSpan( TextSpan(
text: i['text'], text: i['text'],
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context) fontSize: MediaQuery.textScalerOf(context)
.scale(Theme.of(context)
.textTheme .textTheme
.titleSmall! .titleSmall!
.fontSize! * .fontSize!),
MediaQuery.of(context).textScaleFactor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: i['type'] == 'em' color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary

View File

@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/common/tab_type.dart';
import 'package:pilipala/utils/storage.dart';
class TabbarSetPage extends StatefulWidget {
const TabbarSetPage({super.key});
@override
State<TabbarSetPage> createState() => _TabbarSetPageState();
}
class _TabbarSetPageState extends State<TabbarSetPage> {
Box settingStorage = GStrorage.setting;
late List defaultTabs;
late List<String> tabbarSort;
@override
void initState() {
super.initState();
defaultTabs = tabsConfig;
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']);
}
void saveEdit() {
List<String> sortedTabbar = defaultTabs
.where((i) => tabbarSort.contains((i['type'] as TabType).id))
.map<String>((i) => (i['type'] as TabType).id)
.toList();
if (sortedTabbar.isEmpty) {
SmartDialog.showToast('请至少设置一项!');
return;
}
settingStorage.put(SettingBoxKey.tabbarSort, sortedTabbar);
SmartDialog.showToast('保存成功,下次启动时生效');
}
void onReorder(int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final tabsItem = defaultTabs.removeAt(oldIndex);
defaultTabs.insert(newIndex, tabsItem);
});
}
@override
Widget build(BuildContext context) {
final listTiles = [
for (int i = 0; i < defaultTabs.length; i++) ...[
CheckboxListTile(
key: Key(defaultTabs[i]['label']),
value: tabbarSort.contains((defaultTabs[i]['type'] as TabType).id),
onChanged: (bool? newValue) {
String tabTypeId = (defaultTabs[i]['type'] as TabType).id;
if (!newValue!) {
tabbarSort.remove(tabTypeId);
} else {
tabbarSort.add(tabTypeId);
}
setState(() {});
},
title: Text(defaultTabs[i]['label']),
secondary: const Icon(Icons.drag_indicator_rounded),
)
]
];
return Scaffold(
appBar: AppBar(
title: const Text('Tabbar编辑'),
actions: [
TextButton(onPressed: () => saveEdit(), child: const Text('保存')),
const SizedBox(width: 12)
],
),
body: ReorderableListView(
onReorder: onReorder,
physics: const NeverScrollableScrollPhysics(),
footer: SizedBox(
height: MediaQuery.of(context).padding.bottom + 30,
),
children: listTiles,
),
);
}
}

View File

@@ -254,6 +254,11 @@ class _StyleSettingState extends State<StyleSetting> {
onTap: () => Get.toNamed('/fontSizeSetting'), onTap: () => Get.toNamed('/fontSizeSetting'),
title: Text('字体大小', style: titleStyle), title: Text('字体大小', style: titleStyle),
), ),
ListTile(
dense: false,
onTap: () => Get.toNamed('/tabbarSetting'),
title: Text('首页tabbar', style: titleStyle),
),
if (Platform.isAndroid) if (Platform.isAndroid)
ListTile( ListTile(
dense: false, dense: false,

View File

@@ -9,7 +9,8 @@ import './controller.dart';
class RelatedVideoPanel extends StatelessWidget { class RelatedVideoPanel extends StatelessWidget {
final ReleatedController _releatedController = final ReleatedController _releatedController =
Get.put(ReleatedController(), tag: Get.arguments['heroTag']); Get.put(ReleatedController(), tag: Get.arguments?['heroTag']);
RelatedVideoPanel({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@@ -2,12 +2,10 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/video.dart'; import 'package:pilipala/http/video.dart';
import 'package:pilipala/models/common/reply_type.dart'; import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/video/reply/item.dart'; import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
class VideoReplyNewDialog extends StatefulWidget { class VideoReplyNewDialog extends StatefulWidget {
final int? oid; final int? oid;
@@ -34,23 +32,16 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
final TextEditingController _replyContentController = TextEditingController(); final TextEditingController _replyContentController = TextEditingController();
final FocusNode replyContentFocusNode = FocusNode(); final FocusNode replyContentFocusNode = FocusNode();
final GlobalKey _formKey = GlobalKey<FormState>(); final GlobalKey _formKey = GlobalKey<FormState>();
bool ableClean = false;
Timer? timer;
Box localCache = GStrorage.localCache;
late double sheetHeight;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// 监听输入框聚焦 // 监听输入框聚焦
// replyContentFocusNode.addListener(_onFocus); // replyContentFocusNode.addListener(_onFocus);
_replyContentController.addListener(_printLatestValue);
// 界面观察者 必须 // 界面观察者 必须
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
// 自动聚焦 // 自动聚焦
_autoFocus(); _autoFocus();
sheetHeight = localCache.get('sheetHeight');
} }
_autoFocus() async { _autoFocus() async {
@@ -60,12 +51,6 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
} }
} }
_printLatestValue() {
setState(() {
ableClean = _replyContentController.text != '';
});
}
Future submitReplyAdd() async { Future submitReplyAdd() async {
feedBack(); feedBack();
String message = _replyContentController.text; String message = _replyContentController.text;
@@ -113,12 +98,14 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints( constraints: const BoxConstraints(
maxHeight: 200, maxHeight: 200,
), minHeight: 120,
),
child: Container(
padding: const EdgeInsets.only(
top: 12, right: 15, left: 15, bottom: 10),
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.only(
top: 6, right: 15, left: 15, bottom: 10),
child: Form( child: Form(
key: _formKey, key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
@@ -137,7 +124,9 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
), ),
)), ),
),
),
Divider( Divider(
height: 1, height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1), color: Theme.of(context).dividerColor.withOpacity(0.1),
@@ -152,34 +141,23 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
width: 36, width: 36,
height: 36, height: 36,
child: IconButton( child: IconButton(
onPressed: () { onPressed: () {
if (keyboardHeight > 0) { FocusScope.of(context)
FocusScope.of(context).unfocus(); .requestFocus(replyContentFocusNode);
} else { },
FocusScope.of(context) icon: Icon(Icons.keyboard,
.requestFocus(replyContentFocusNode); size: 22,
} color: Theme.of(context).colorScheme.onBackground),
}, highlightColor:
icon: Icon( Theme.of(context).colorScheme.onInverseSurface,
keyboardHeight > 0 style: ButtonStyle(
? Icons.keyboard_hide padding: MaterialStateProperty.all(EdgeInsets.zero),
: Icons.keyboard, backgroundColor:
size: 22, MaterialStateProperty.resolveWith((states) {
color: Theme.of(context).colorScheme.onBackground), return Theme.of(context).highlightColor;
highlightColor: }),
Theme.of(context).colorScheme.onInverseSurface, ),
style: ButtonStyle( ),
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith((states) {
// 如果按钮被按下,返回高亮颜色
if (states.contains(MaterialState.pressed)) {
return Theme.of(context).highlightColor;
}
// 默认状态下,返回透明颜色
return Colors.transparent;
}),
)),
), ),
const Spacer(), const Spacer(),
TextButton( TextButton(
@@ -200,22 +178,3 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
); );
} }
} }
typedef DebounceCallback = void Function();
class Debouncer {
DebounceCallback? callback;
final int? milliseconds;
Timer? _timer;
Debouncer({this.milliseconds});
run(DebounceCallback callback) {
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(Duration(milliseconds: milliseconds!), () {
callback();
});
}
}

View File

@@ -235,7 +235,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
videoIntroController.isPaused = false; videoIntroController.isPaused = false;
if (_extendNestCtr.position.pixels == 0 && autoplay) { if (_extendNestCtr.position.pixels == 0 && autoplay) {
await Future.delayed(const Duration(milliseconds: 300)); await Future.delayed(const Duration(milliseconds: 300));
plPlayerController!.seekTo(videoDetailController.defaultST); plPlayerController?.seekTo(videoDetailController.defaultST);
plPlayerController?.play(); plPlayerController?.play();
} }
plPlayerController?.addStatusLister(playerListener); plPlayerController?.addStatusLister(playerListener);

View File

@@ -621,7 +621,7 @@ class PlPlayerController {
if (duration.value.inSeconds != 0) { if (duration.value.inSeconds != 0) {
if (type != 'slider') { if (type != 'slider') {
/// 拖动进度条调节时,不等待第一帧,防止抖动 /// 拖动进度条调节时,不等待第一帧,防止抖动
await _videoPlayerController!.stream.buffer.first; await _videoPlayerController?.stream.buffer.first;
} }
await _videoPlayerController?.seek(position); await _videoPlayerController?.seek(position);
// if (playerStatus.stopped) { // if (playerStatus.stopped) {
@@ -786,7 +786,7 @@ class PlPlayerController {
volume.value = volumeNew; volume.value = volumeNew;
try { try {
FlutterVolumeController.showSystemUI = false; FlutterVolumeController.updateShowSystemUI(false);
await FlutterVolumeController.setVolume(volumeNew); await FlutterVolumeController.setVolume(volumeNew);
} catch (err) { } catch (err) {
print(err); print(err);
@@ -1086,12 +1086,13 @@ class PlPlayerController {
localCache.put(LocalCacheKey.danmakuOpacity, opacityVal); localCache.put(LocalCacheKey.danmakuOpacity, opacityVal);
localCache.put(LocalCacheKey.danmakuFontScale, fontSizeVal); localCache.put(LocalCacheKey.danmakuFontScale, fontSizeVal);
localCache.put(LocalCacheKey.danmakuDuration, danmakuDurationVal); localCache.put(LocalCacheKey.danmakuDuration, danmakuDurationVal);
if (_videoPlayerController != null) {
var pp = _videoPlayerController!.platform as NativePlayer; var pp = _videoPlayerController!.platform as NativePlayer;
await pp.setProperty('audio-files', ''); await pp.setProperty('audio-files', '');
removeListeners(); removeListeners();
await _videoPlayerController?.dispose(); await _videoPlayerController?.dispose();
_videoPlayerController = null; _videoPlayerController = null;
}
_instance = null; _instance = null;
// 关闭所有视频页面恢复亮度 // 关闭所有视频页面恢复亮度
resetBrightness(); resetBrightness();

View File

@@ -2,7 +2,6 @@ import 'dart:async';
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -130,7 +129,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
setting.get(SettingBoxKey.enableBackgroundPlay, defaultValue: false); setting.get(SettingBoxKey.enableBackgroundPlay, defaultValue: false);
Future.microtask(() async { Future.microtask(() async {
try { try {
FlutterVolumeController.showSystemUI = true; FlutterVolumeController.updateShowSystemUI(true);
_ctr.volumeValue.value = (await FlutterVolumeController.getVolume())!; _ctr.volumeValue.value = (await FlutterVolumeController.getVolume())!;
FlutterVolumeController.addListener((double value) { FlutterVolumeController.addListener((double value) {
if (mounted && !_ctr.volumeInterceptEventStream.value) { if (mounted && !_ctr.volumeInterceptEventStream.value) {
@@ -154,7 +153,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
Future<void> setVolume(double value) async { Future<void> setVolume(double value) async {
try { try {
FlutterVolumeController.showSystemUI = false; FlutterVolumeController.updateShowSystemUI(false);
await FlutterVolumeController.setVolume(value); await FlutterVolumeController.setVolume(value);
} catch (_) {} } catch (_) {}
_ctr.volumeValue.value = value; _ctr.volumeValue.value = value;
@@ -703,7 +702,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
), ),
); );
} else { } else {
return nil; return const SizedBox();
} }
}), }),

View File

@@ -36,6 +36,7 @@ import '../pages/setting/index.dart';
import '../pages/setting/pages/color_select.dart'; import '../pages/setting/pages/color_select.dart';
import '../pages/setting/pages/display_mode.dart'; import '../pages/setting/pages/display_mode.dart';
import '../pages/setting/pages/font_size_select.dart'; import '../pages/setting/pages/font_size_select.dart';
import '../pages/setting/pages/home_tabbar_set.dart';
import '../pages/setting/pages/play_speed_set.dart'; import '../pages/setting/pages/play_speed_set.dart';
import '../pages/setting/recommend_setting.dart'; import '../pages/setting/recommend_setting.dart';
import '../pages/setting/play_setting.dart'; import '../pages/setting/play_setting.dart';
@@ -114,6 +115,8 @@ class Routes {
// //
CustomGetPage(name: '/blackListPage', page: () => const BlackListPage()), CustomGetPage(name: '/blackListPage', page: () => const BlackListPage()),
CustomGetPage(name: '/colorSetting', page: () => const ColorSelectPage()), CustomGetPage(name: '/colorSetting', page: () => const ColorSelectPage()),
// 首页tabbar
CustomGetPage(name: '/tabbarSetting', page: () => const TabbarSetPage()),
CustomGetPage( CustomGetPage(
name: '/fontSizeSetting', page: () => const FontSizeSelectPage()), name: '/fontSizeSetting', page: () => const FontSizeSelectPage()),
// 屏幕帧率 // 屏幕帧率

View File

@@ -151,7 +151,8 @@ class SettingBoxKey {
customRows = 'customRows', // 自定义列 customRows = 'customRows', // 自定义列
enableMYBar = 'enableMYBar', enableMYBar = 'enableMYBar',
hideSearchBar = 'hideSearchBar', // 收起顶栏 hideSearchBar = 'hideSearchBar', // 收起顶栏
hideTabBar = 'hideTabBar'; // 收起底栏 hideTabBar = 'hideTabBar', // 收起底栏
tabbarSort = 'tabbarSort'; // 首页tabbar
} }
class LocalCacheKey { class LocalCacheKey {