mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
Merge branch 'main' of https://github.com/guozhigq/pilipala into guozhigq-main
This commit is contained in:
@@ -224,7 +224,7 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
// 列数
|
||||
crossAxisCount: 3,
|
||||
mainAxisExtent: Get.size.width / 3 / 0.65 +
|
||||
32 * MediaQuery.of(context).textScaleFactor,
|
||||
MediaQuery.textScalerOf(context).scale(32.0),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
|
||||
@@ -8,12 +8,13 @@ import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
bool flag = false;
|
||||
late List tabs;
|
||||
late RxList tabs = [].obs;
|
||||
RxInt initialIndex = 1.obs;
|
||||
late TabController tabController;
|
||||
late List tabsCtrList;
|
||||
late List<Widget> tabsPageList;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box settingStorage = GStrorage.setting;
|
||||
RxBool userLogin = false.obs;
|
||||
RxString userFace = ''.obs;
|
||||
var userInfo;
|
||||
@@ -21,6 +22,8 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
late final StreamController<bool> searchBarStream =
|
||||
StreamController<bool>.broadcast();
|
||||
late bool hideSearchBar;
|
||||
late List defaultTabs;
|
||||
late List<String> tabbarSort;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -28,34 +31,10 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
userLogin.value = userInfo != null;
|
||||
userFace.value = userInfo != null ? userInfo.face : '';
|
||||
|
||||
// 进行tabs配置
|
||||
tabs = tabsConfig;
|
||||
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,
|
||||
);
|
||||
setTabConfig();
|
||||
hideSearchBar =
|
||||
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() {
|
||||
@@ -77,4 +56,42 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
if (val) return;
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,11 @@ class _HomePageState extends State<HomePage>
|
||||
ctr: _homeController,
|
||||
callback: showUserBottomSheet,
|
||||
),
|
||||
const CustomTabs(),
|
||||
if (_homeController.tabs.length > 1) ...[
|
||||
const CustomTabs(),
|
||||
] else ...[
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _homeController.tabController,
|
||||
@@ -250,17 +254,6 @@ class CustomTabs extends StatefulWidget {
|
||||
|
||||
class _CustomTabsState extends State<CustomTabs> {
|
||||
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) {
|
||||
feedBack();
|
||||
@@ -271,34 +264,30 @@ class _CustomTabsState extends State<CustomTabs> {
|
||||
_homeController.tabController.index = index;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_homeController.tabController.removeListener(listen);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 44,
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14.0),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: _homeController.tabs.length,
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return const SizedBox(width: 10);
|
||||
},
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
String label = _homeController.tabs[index]['label'];
|
||||
return Obx(
|
||||
() => CustomChip(
|
||||
onTap: () => onTap(index),
|
||||
label: label,
|
||||
selected: index == _homeController.initialIndex.value,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Obx(
|
||||
() => ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14.0),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: _homeController.tabs.length,
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return const SizedBox(width: 10);
|
||||
},
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
String label = _homeController.tabs[index]['label'];
|
||||
return Obx(
|
||||
() => CustomChip(
|
||||
onTap: () => onTap(index),
|
||||
label: label,
|
||||
selected: index == _homeController.initialIndex.value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -162,8 +162,9 @@ class _LivePageState extends State<LivePage>
|
||||
crossAxisCount: crossAxisCount,
|
||||
mainAxisExtent:
|
||||
Get.size.width / crossAxisCount / StyleString.aspectRatio +
|
||||
(crossAxisCount == 1 ? 48 : 68) *
|
||||
MediaQuery.of(context).textScaleFactor,
|
||||
MediaQuery.textScalerOf(context).scale(
|
||||
(crossAxisCount == 1 ? 48 : 68),
|
||||
),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/common.dart';
|
||||
import 'package:pilipala/pages/dynamics/index.dart';
|
||||
import 'package:pilipala/pages/home/view.dart';
|
||||
import 'package:pilipala/pages/media/index.dart';
|
||||
@@ -27,6 +29,7 @@ class MainController extends GetxController {
|
||||
size: 21,
|
||||
),
|
||||
'label': "首页",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
@@ -38,6 +41,7 @@ class MainController extends GetxController {
|
||||
size: 21,
|
||||
),
|
||||
'label': "动态",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
@@ -49,6 +53,7 @@ class MainController extends GetxController {
|
||||
size: 21,
|
||||
),
|
||||
'label': "媒体库",
|
||||
'count': 0,
|
||||
}
|
||||
].obs;
|
||||
final StreamController<bool> bottomBarStream =
|
||||
@@ -56,6 +61,10 @@ class MainController extends GetxController {
|
||||
Box setting = GStrorage.setting;
|
||||
DateTime? _lastPressedAt;
|
||||
late bool hideTabBar;
|
||||
late PageController pageController;
|
||||
int selectedIndex = 0;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
RxBool userLogin = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -64,17 +73,47 @@ class MainController extends GetxController {
|
||||
Utils.checkUpdata();
|
||||
}
|
||||
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 ||
|
||||
DateTime.now().difference(_lastPressedAt!) >
|
||||
const Duration(seconds: 2)) {
|
||||
// 两次点击时间间隔超过2秒,重新记录时间戳
|
||||
_lastPressedAt = DateTime.now();
|
||||
if (selectedIndex != 0) {
|
||||
pageController.jumpTo(0);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
final DynamicsController _dynamicController = Get.put(DynamicsController());
|
||||
final MediaController _mediaController = Get.put(MediaController());
|
||||
|
||||
PageController? _pageController;
|
||||
int selectedIndex = 0;
|
||||
int? _lastSelectTime; //上次点击时间
|
||||
Box setting = GStrorage.setting;
|
||||
late bool enableMYBar;
|
||||
@@ -34,13 +32,14 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||
_pageController = PageController(initialPage: selectedIndex);
|
||||
_mainController.pageController =
|
||||
PageController(initialPage: _mainController.selectedIndex);
|
||||
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
|
||||
}
|
||||
|
||||
void setIndex(int value) async {
|
||||
feedBack();
|
||||
_pageController!.jumpToPage(value);
|
||||
_mainController.pageController.jumpToPage(value);
|
||||
var currentPage = _mainController.pages[value];
|
||||
if (currentPage is HomePage) {
|
||||
if (_homeController.flag) {
|
||||
@@ -68,6 +67,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
_dynamicController.flag = true;
|
||||
_mainController.clearUnread();
|
||||
} else {
|
||||
_dynamicController.flag = false;
|
||||
}
|
||||
@@ -94,14 +94,17 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
localCache.put('sheetHeight', sheetHeight);
|
||||
localCache.put('statusBarHeight', statusBarHeight);
|
||||
return PopScope(
|
||||
onPopInvoked: (bool status) => _mainController.onBackPressed(context),
|
||||
canPop: false,
|
||||
onPopInvoked: (bool didPop) async {
|
||||
_mainController.onBackPressed(context);
|
||||
},
|
||||
child: Scaffold(
|
||||
extendBody: true,
|
||||
body: PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _pageController,
|
||||
controller: _mainController.pageController,
|
||||
onPageChanged: (index) {
|
||||
selectedIndex = index;
|
||||
_mainController.selectedIndex = index;
|
||||
setState(() {});
|
||||
},
|
||||
children: _mainController.pages,
|
||||
@@ -116,36 +119,48 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
curve: Curves.easeInOutCubicEmphasized,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
offset: Offset(0, snapshot.data ? 0 : 1),
|
||||
child: enableMYBar
|
||||
? NavigationBar(
|
||||
onDestinationSelected: (value) => setIndex(value),
|
||||
selectedIndex: selectedIndex,
|
||||
destinations: <Widget>[
|
||||
..._mainController.navigationBars.map((e) {
|
||||
return NavigationDestination(
|
||||
icon: e['icon'],
|
||||
selectedIcon: e['selectIcon'],
|
||||
label: e['label'],
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
)
|
||||
: BottomNavigationBar(
|
||||
currentIndex: selectedIndex,
|
||||
onTap: (value) => setIndex(value),
|
||||
iconSize: 16,
|
||||
selectedFontSize: 12,
|
||||
unselectedFontSize: 12,
|
||||
items: [
|
||||
..._mainController.navigationBars.map((e) {
|
||||
return BottomNavigationBarItem(
|
||||
icon: e['icon'],
|
||||
activeIcon: e['selectIcon'],
|
||||
label: e['label'],
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
child: Obx(
|
||||
() => enableMYBar
|
||||
? NavigationBar(
|
||||
onDestinationSelected: (value) => setIndex(value),
|
||||
selectedIndex: _mainController.selectedIndex,
|
||||
destinations: <Widget>[
|
||||
..._mainController.navigationBars.map((e) {
|
||||
return NavigationDestination(
|
||||
icon: Badge(
|
||||
label: Text(e['count'].toString()),
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
||||
isLabelVisible: e['count'] > 0,
|
||||
child: e['icon'],
|
||||
),
|
||||
selectedIcon: e['selectIcon'],
|
||||
label: e['label'],
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
)
|
||||
: BottomNavigationBar(
|
||||
currentIndex: _mainController.selectedIndex,
|
||||
onTap: (value) => setIndex(value),
|
||||
iconSize: 16,
|
||||
selectedFontSize: 12,
|
||||
unselectedFontSize: 12,
|
||||
items: [
|
||||
..._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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -163,7 +163,7 @@ class _MediaPageState extends State<MediaPage>
|
||||
// const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 200 * MediaQuery.of(context).textScaleFactor,
|
||||
height: MediaQuery.textScalerOf(context).scale(200),
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
|
||||
@@ -26,10 +26,9 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
|
||||
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
double width = (boxConstraints.maxWidth -
|
||||
StyleString.cardSpace *
|
||||
6 /
|
||||
MediaQuery.of(context).textScaleFactor) /
|
||||
2;
|
||||
StyleString.cardSpace *
|
||||
6 /
|
||||
MediaQuery.textScalerOf(context).scale(2.0));
|
||||
return Container(
|
||||
constraints: const BoxConstraints(minHeight: 88),
|
||||
height: width / StyleString.aspectRatio,
|
||||
|
||||
@@ -17,7 +17,7 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
|
||||
mainAxisSpacing: StyleString.cardSpace + 3,
|
||||
mainAxisExtent:
|
||||
MediaQuery.sizeOf(context).width / 2 / StyleString.aspectRatio +
|
||||
66 * MediaQuery.of(context).textScaleFactor),
|
||||
MediaQuery.textScalerOf(context).scale(66.0)),
|
||||
itemCount: list.length,
|
||||
itemBuilder: (context, index) {
|
||||
return LiveItem(liveItem: list![index]);
|
||||
|
||||
@@ -67,11 +67,11 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
||||
TextSpan(
|
||||
text: i['text'],
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
fontSize: MediaQuery.textScalerOf(context)
|
||||
.scale(Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall!
|
||||
.fontSize! *
|
||||
MediaQuery.of(context).textScaleFactor,
|
||||
.fontSize!),
|
||||
fontWeight: FontWeight.bold,
|
||||
color: i['type'] == 'em'
|
||||
? Theme.of(context).colorScheme.primary
|
||||
|
||||
90
lib/pages/setting/pages/home_tabbar_set.dart
Normal file
90
lib/pages/setting/pages/home_tabbar_set.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -254,6 +254,11 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
onTap: () => Get.toNamed('/fontSizeSetting'),
|
||||
title: Text('字体大小', style: titleStyle),
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
onTap: () => Get.toNamed('/tabbarSetting'),
|
||||
title: Text('首页tabbar', style: titleStyle),
|
||||
),
|
||||
if (Platform.isAndroid)
|
||||
ListTile(
|
||||
dense: false,
|
||||
|
||||
@@ -9,7 +9,8 @@ import './controller.dart';
|
||||
|
||||
class RelatedVideoPanel extends StatelessWidget {
|
||||
final ReleatedController _releatedController =
|
||||
Get.put(ReleatedController(), tag: Get.arguments['heroTag']);
|
||||
Get.put(ReleatedController(), tag: Get.arguments?['heroTag']);
|
||||
RelatedVideoPanel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -2,12 +2,10 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class VideoReplyNewDialog extends StatefulWidget {
|
||||
final int? oid;
|
||||
@@ -34,23 +32,16 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
final TextEditingController _replyContentController = TextEditingController();
|
||||
final FocusNode replyContentFocusNode = FocusNode();
|
||||
final GlobalKey _formKey = GlobalKey<FormState>();
|
||||
bool ableClean = false;
|
||||
Timer? timer;
|
||||
Box localCache = GStrorage.localCache;
|
||||
late double sheetHeight;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 监听输入框聚焦
|
||||
// replyContentFocusNode.addListener(_onFocus);
|
||||
_replyContentController.addListener(_printLatestValue);
|
||||
// 界面观察者 必须
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
// 自动聚焦
|
||||
_autoFocus();
|
||||
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
}
|
||||
|
||||
_autoFocus() async {
|
||||
@@ -60,12 +51,6 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
}
|
||||
}
|
||||
|
||||
_printLatestValue() {
|
||||
setState(() {
|
||||
ableClean = _replyContentController.text != '';
|
||||
});
|
||||
}
|
||||
|
||||
Future submitReplyAdd() async {
|
||||
feedBack();
|
||||
String message = _replyContentController.text;
|
||||
@@ -113,12 +98,14 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 200,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 200,
|
||||
minHeight: 120,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12, right: 15, left: 15, bottom: 10),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 6, right: 15, left: 15, bottom: 10),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
@@ -137,7 +124,9 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
@@ -152,34 +141,23 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
width: 36,
|
||||
height: 36,
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
if (keyboardHeight > 0) {
|
||||
FocusScope.of(context).unfocus();
|
||||
} else {
|
||||
FocusScope.of(context)
|
||||
.requestFocus(replyContentFocusNode);
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
keyboardHeight > 0
|
||||
? Icons.keyboard_hide
|
||||
: Icons.keyboard,
|
||||
size: 22,
|
||||
color: Theme.of(context).colorScheme.onBackground),
|
||||
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;
|
||||
}),
|
||||
)),
|
||||
onPressed: () {
|
||||
FocusScope.of(context)
|
||||
.requestFocus(replyContentFocusNode);
|
||||
},
|
||||
icon: Icon(Icons.keyboard,
|
||||
size: 22,
|
||||
color: Theme.of(context).colorScheme.onBackground),
|
||||
highlightColor:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith((states) {
|
||||
return Theme.of(context).highlightColor;
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
videoIntroController.isPaused = false;
|
||||
if (_extendNestCtr.position.pixels == 0 && autoplay) {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
plPlayerController!.seekTo(videoDetailController.defaultST);
|
||||
plPlayerController?.seekTo(videoDetailController.defaultST);
|
||||
plPlayerController?.play();
|
||||
}
|
||||
plPlayerController?.addStatusLister(playerListener);
|
||||
|
||||
Reference in New Issue
Block a user