Merge branch 'main' into feature-collections

This commit is contained in:
guozhigq
2024-04-13 23:02:11 +08:00
44 changed files with 642 additions and 379 deletions

View File

@ -4,7 +4,7 @@ on:
workflow_dispatch:
push:
branches:
- "main"
- "never"
paths-ignore:
- "**.md"
- "**.txt"
@ -12,6 +12,7 @@ on:
- ".idea/**"
- "!.github/workflows/**"
jobs:
update_version:
name: Read and update version

View File

@ -490,8 +490,11 @@ class Api {
/// 我的订阅
static const userSubFolder = '/x/v3/fav/folder/collected/list';
/// 我的订阅详情
static const userSubFolderDetail = '/x/space/fav/season/list';
/// 我的订阅详情 type 21
static const userSeasonList = '/x/space/fav/season/list';
/// 我的订阅详情 type 11
static const userResourceList = '/x/v3/fav/resource/list';
/// 表情
static const emojiList = '/x/emote/user/panel/web';

View File

@ -330,12 +330,12 @@ class UserHttp {
}
}
static Future userSubFolderDetail({
static Future userSeasonList({
required int seasonId,
required int pn,
required int ps,
}) async {
var res = await Request().get(Api.userSubFolderDetail, data: {
var res = await Request().get(Api.userSeasonList, data: {
'season_id': seasonId,
'ps': ps,
'pn': pn,
@ -350,6 +350,35 @@ class UserHttp {
}
}
static Future userResourceList({
required int seasonId,
required int pn,
required int ps,
}) async {
var res = await Request().get(Api.userResourceList, data: {
'media_id': seasonId,
'ps': ps,
'pn': pn,
'keyword': '',
'order': 'mtime',
'type': 0,
'tid': 0,
'platform': 'web',
});
if (res.data['code'] == 0) {
try {
return {
'status': true,
'data': SubDetailModelData.fromJson(res.data['data'])
};
} catch (err) {
return {'status': false, 'msg': err};
}
} else {
return {'status': false, 'msg': res.data['message']};
}
}
// 取消订阅
static Future cancelSub({required int seasonId}) async {
var res = await Request().post(

View File

@ -20,6 +20,7 @@ import 'package:pilipala/services/disable_battery_opt.dart';
import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/app_scheme.dart';
import 'package:pilipala/utils/data.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc.
import 'package:pilipala/utils/recommend_filter.dart';
@ -64,14 +65,8 @@ void main() async {
},
);
// 小白条、导航栏沉浸
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent,
systemNavigationBarDividerColor: Colors.transparent,
statusBarColor: Colors.transparent,
));
Data.init();
GlobalData();
PiliSchame.init();
DisableBatteryOpt();
});
@ -134,45 +129,51 @@ class MyApp extends StatelessWidget {
brightness: Brightness.dark,
);
}
final SnackBarThemeData snackBarThemeData = SnackBarThemeData(
actionTextColor: darkColorScheme.primary,
backgroundColor: darkColorScheme.secondaryContainer,
closeIconColor: darkColorScheme.secondary,
contentTextStyle: TextStyle(color: darkColorScheme.secondary),
elevation: 20,
);
ThemeData themeData = ThemeData(
// fontFamily: 'HarmonyOS',
colorScheme: currentThemeValue == ThemeType.dark
? darkColorScheme
: lightColorScheme,
snackBarTheme: snackBarThemeData,
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: ZoomPageTransitionsBuilder(
allowEnterRouteSnapshotting: false,
),
},
),
);
// 小白条、导航栏沉浸
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: GlobalData().enableMYBar
? const Color(0x00010000)
: themeData.canvasColor,
systemNavigationBarDividerColor: GlobalData().enableMYBar
? const Color(0x00010000)
: themeData.canvasColor,
systemNavigationBarIconBrightness: currentThemeValue == ThemeType.dark
? Brightness.light
: Brightness.dark,
statusBarColor: Colors.transparent,
));
// 图片缓存
// PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
return GetMaterialApp(
title: 'PiLiPaLa',
theme: ThemeData(
// fontFamily: 'HarmonyOS',
colorScheme: currentThemeValue == ThemeType.dark
? darkColorScheme
: lightColorScheme,
useMaterial3: true,
snackBarTheme: SnackBarThemeData(
actionTextColor: lightColorScheme.primary,
backgroundColor: lightColorScheme.secondaryContainer,
closeIconColor: lightColorScheme.secondary,
contentTextStyle: TextStyle(color: lightColorScheme.secondary),
elevation: 20,
),
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: ZoomPageTransitionsBuilder(
allowEnterRouteSnapshotting: false,
),
},
),
),
darkTheme: ThemeData(
// fontFamily: 'HarmonyOS',
colorScheme: currentThemeValue == ThemeType.light
? lightColorScheme
: darkColorScheme,
useMaterial3: true,
snackBarTheme: SnackBarThemeData(
actionTextColor: darkColorScheme.primary,
backgroundColor: darkColorScheme.secondaryContainer,
closeIconColor: darkColorScheme.secondary,
contentTextStyle: TextStyle(color: darkColorScheme.secondary),
elevation: 20,
),
),
theme: themeData,
darkTheme: themeData,
localizationsDelegates: const [
GlobalCupertinoLocalizations.delegate,
GlobalMaterialLocalizations.delegate,

View File

@ -1,5 +1,10 @@
import 'package:flutter/material.dart';
import '../../pages/dynamics/index.dart';
import '../../pages/home/index.dart';
import '../../pages/media/index.dart';
import '../../pages/rank/index.dart';
List defaultNavigationBars = [
{
'id': 0,
@ -13,6 +18,7 @@ List defaultNavigationBars = [
),
'label': "首页",
'count': 0,
'page': const HomePage(),
},
{
'id': 1,
@ -26,6 +32,7 @@ List defaultNavigationBars = [
),
'label': "排行榜",
'count': 0,
'page': const RankPage(),
},
{
'id': 2,
@ -39,6 +46,7 @@ List defaultNavigationBars = [
),
'label': "动态",
'count': 0,
'page': const DynamicsPage(),
},
{
'id': 3,
@ -52,5 +60,6 @@ List defaultNavigationBars = [
),
'label': "媒体库",
'count': 0,
'page': const MediaPage(),
}
];

View File

@ -2,13 +2,11 @@ import 'dart:async';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:nil/nil.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/utils/main_stream.dart';
import 'controller.dart';
import 'widgets/bangumu_card_v.dart';
@ -34,10 +32,6 @@ class _BangumiPageState extends State<BangumiPage>
void initState() {
super.initState();
scrollController = _bangumidController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
StreamController<bool> searchBarStream =
Get.find<HomeController>().searchBarStream;
_futureBuilderFuture = _bangumidController.queryBangumiListFeed();
_futureBuilderFutureFollow = _bangumidController.queryBangumiFollow();
scrollController.addListener(
@ -49,16 +43,7 @@ class _BangumiPageState extends State<BangumiPage>
_bangumidController.onLoad();
});
}
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
searchBarStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
searchBarStream.add(false);
}
handleScrollEvent(scrollController);
},
);
}

View File

@ -92,11 +92,15 @@ class _BangumiPanelState extends State<BangumiPanel> {
// 在回调函数中获取更新后的状态
final double offset = min((currentIndex * 150) - 75,
listViewScrollCtr.position.maxScrollExtent);
listViewScrollCtr.animateTo(
offset,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
if (currentIndex.value == 0) {
listViewScrollCtr.jumpTo(0);
} else {
listViewScrollCtr.animateTo(
offset,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
});
}

111
lib/pages/dlna/index.dart Normal file
View File

@ -0,0 +1,111 @@
import 'dart:async';
import 'package:dlna_dart/dlna.dart';
import 'package:flutter/material.dart';
class LiveDlnaPage extends StatefulWidget {
final String datasource;
const LiveDlnaPage({Key? key, required this.datasource}) : super(key: key);
@override
State<LiveDlnaPage> createState() => _LiveDlnaPageState();
}
class _LiveDlnaPageState extends State<LiveDlnaPage> {
final Map<String, DLNADevice> _deviceList = {};
final DLNAManager searcher = DLNAManager();
late final Timer stopSearchTimer;
String selectDeviceKey = '';
bool isSearching = true;
DLNADevice? get device => _deviceList[selectDeviceKey];
@override
void initState() {
stopSearchTimer = Timer(const Duration(seconds: 20), () {
setState(() => isSearching = false);
searcher.stop();
});
searcher.stop();
startSearch();
super.initState();
}
@override
void dispose() {
super.dispose();
searcher.stop();
stopSearchTimer.cancel();
}
void startSearch() async {
// clear old devices
isSearching = true;
selectDeviceKey = '';
_deviceList.clear();
setState(() {});
// start search server
final m = await searcher.start();
m.devices.stream.listen((deviceList) {
deviceList.forEach((key, value) {
_deviceList[key] = value;
});
setState(() {});
});
// close the server, the closed server can be start by call searcher.start()
}
void selectDevice(String key) {
if (selectDeviceKey.isNotEmpty) device?.pause();
selectDeviceKey = key;
device?.setUrl(widget.datasource);
device?.play();
setState(() {});
}
@override
Widget build(BuildContext context) {
Widget cur;
if (isSearching && _deviceList.isEmpty) {
cur = const Center(child: CircularProgressIndicator());
} else if (_deviceList.isEmpty) {
cur = Center(
child: Text(
'没有找到设备',
style: Theme.of(context).textTheme.bodyLarge,
),
);
} else {
cur = ListView(
children: _deviceList.keys
.map<Widget>((key) => ListTile(
contentPadding: const EdgeInsets.all(2),
title: Text(_deviceList[key]!.info.friendlyName),
subtitle: Text(key),
onTap: () => selectDevice(key),
))
.toList(),
);
}
return AlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('查找设备'),
IconButton(
onPressed: startSearch,
icon: const Icon(Icons.refresh_rounded),
),
],
),
content: SizedBox(
height: 200,
width: 200,
child: cur,
),
);
}
}

View File

@ -194,7 +194,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
centerTitle: false,
titleSpacing: 0,
title: StreamBuilder(
stream: titleStreamC.stream,
stream: titleStreamC.stream.distinct(),
initialData: false,
builder: (context, AsyncSnapshot snapshot) {
return AnimatedOpacity(

View File

@ -3,15 +3,14 @@ import 'dart:async';
import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/skeleton/dynamic_card.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/no_data.dart';
import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/main_stream.dart';
import 'package:pilipala/utils/storage.dart';
import '../mine/controller.dart';
@ -44,8 +43,6 @@ class _DynamicsPageState extends State<DynamicsPage>
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
_futureBuilderFutureUp = _dynamicsController.queryFollowUp();
scrollController = _dynamicsController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
scrollController.addListener(
() async {
if (scrollController.position.pixels >=
@ -55,14 +52,7 @@ class _DynamicsPageState extends State<DynamicsPage>
_dynamicsController.queryFollowDynamic(type: 'onLoad');
});
}
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
}
handleScrollEvent(scrollController);
},
);

View File

@ -67,7 +67,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
pinned: true,
titleSpacing: 0,
title: StreamBuilder(
stream: titleStreamC.stream,
stream: titleStreamC.stream.distinct(),
initialData: false,
builder: (context, AsyncSnapshot snapshot) {
return AnimatedOpacity(

View File

@ -35,7 +35,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
userLogin.value = userInfo != null;
userFace.value = userInfo != null ? userInfo.face : '';
hideSearchBar =
setting.get(SettingBoxKey.hideSearchBar, defaultValue: true);
setting.get(SettingBoxKey.hideSearchBar, defaultValue: false);
if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
searchDefault();
}

View File

@ -171,7 +171,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: stream,
stream: stream!.distinct(),
initialData: true,
builder: (BuildContext context, AsyncSnapshot snapshot) {
final RxBool isUserLoggedIn = ctr!.userLogin;

View File

@ -1,7 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/animated_dialog.dart';
@ -9,9 +8,8 @@ import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/common/skeleton/video_card_h.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/video_card_h.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/hot/controller.dart';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/utils/main_stream.dart';
class HotPage extends StatefulWidget {
const HotPage({Key? key}) : super(key: key);
@ -34,10 +32,6 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
super.initState();
_futureBuilderFuture = _hotController.queryHotFeed('init');
scrollController = _hotController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
StreamController<bool> searchBarStream =
Get.find<HomeController>().searchBarStream;
scrollController.addListener(
() {
if (scrollController.position.pixels >=
@ -47,16 +41,7 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
_hotController.onLoad();
}
}
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
searchBarStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
searchBarStream.add(false);
}
handleScrollEvent(scrollController);
},
);
}

View File

@ -2,15 +2,13 @@ import 'dart:async';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/skeleton/video_card_v.dart';
import 'package:pilipala/common/widgets/animated_dialog.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/utils/main_stream.dart';
import 'controller.dart';
import 'widgets/live_item.dart';
@ -36,10 +34,6 @@ class _LivePageState extends State<LivePage>
super.initState();
_futureBuilderFuture = _liveController.queryLiveList('init');
scrollController = _liveController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
StreamController<bool> searchBarStream =
Get.find<HomeController>().searchBarStream;
scrollController.addListener(
() {
if (scrollController.position.pixels >=
@ -49,16 +43,7 @@ class _LivePageState extends State<LivePage>
_liveController.onLoad();
});
}
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
searchBarStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
searchBarStream.add(false);
}
handleScrollEvent(scrollController);
},
);
}

View File

@ -62,6 +62,11 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
controller: plPlayerController,
liveRoomCtr: _liveRoomController,
floating: floating,
onRefresh: () {
setState(() {
_futureBuilderFuture = _liveRoomController.queryLiveInfo();
});
},
),
);
} else {

View File

@ -14,10 +14,12 @@ class BottomControl extends StatefulWidget implements PreferredSizeWidget {
final PlPlayerController? controller;
final LiveRoomController? liveRoomCtr;
final Floating? floating;
final Function? onRefresh;
const BottomControl({
this.controller,
this.liveRoomCtr,
this.floating,
this.onRefresh,
Key? key,
}) : super(key: key);
@ -61,6 +63,14 @@ class _BottomControlState extends State<BottomControl> {
// ),
// fuc: () => Get.back(),
// ),
ComBtn(
icon: const Icon(
Icons.refresh_outlined,
size: 18,
color: Colors.white,
),
fuc: widget.onRefresh,
),
const Spacer(),
// ComBtn(
// icon: const Icon(
@ -150,21 +160,3 @@ class _BottomControlState extends State<BottomControl> {
);
}
}
class MSliderTrackShape extends RoundedRectSliderTrackShape {
@override
Rect getPreferredRect({
required RenderBox parentBox,
Offset offset = Offset.zero,
SliderThemeData? sliderTheme,
bool isEnabled = false,
bool isDiscrete = false,
}) {
const double trackHeight = 3;
final double trackLeft = offset.dx;
final double trackTop =
offset.dy + (parentBox.size.height - trackHeight) / 2 + 4;
final double trackWidth = parentBox.size.width;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
}

View File

@ -6,23 +6,16 @@ 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';
import 'package:pilipala/pages/rank/index.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
import '../../models/common/dynamic_badge_mode.dart';
import '../../models/common/nav_bar_config.dart';
class MainController extends GetxController {
List<Widget> pages = <Widget>[
const HomePage(),
const RankPage(),
const DynamicsPage(),
const MediaPage(),
];
RxList navigationBars = defaultNavigationBars.obs;
List<Widget> pages = <Widget>[];
RxList navigationBars = [].obs;
late List defaultNavTabs;
late List<int> navBarSort;
final StreamController<bool> bottomBarStream =
StreamController<bool>.broadcast();
Box setting = GStrorage.setting;
@ -40,11 +33,8 @@ class MainController extends GetxController {
if (setting.get(SettingBoxKey.autoUpdate, defaultValue: false)) {
Utils.checkUpdata();
}
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
int defaultHomePage =
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int;
selectedIndex = defaultNavigationBars
.indexWhere((item) => item['id'] == defaultHomePage);
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: false);
var userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null;
dynamicBadgeType.value = DynamicBadgeMode.values[setting.get(
@ -53,6 +43,7 @@ class MainController extends GetxController {
if (dynamicBadgeType.value != DynamicBadgeMode.hidden) {
getUnreadDynamic();
}
setNavBarConfig();
}
void onBackPressed(BuildContext context) {
@ -93,4 +84,21 @@ class MainController extends GetxController {
}
navigationBars.refresh();
}
void setNavBarConfig() async {
defaultNavTabs = [...defaultNavigationBars];
navBarSort =
setting.get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2, 3]);
defaultNavTabs.retainWhere((item) => navBarSort.contains(item['id']));
defaultNavTabs.sort((a, b) =>
navBarSort.indexOf(a['id']).compareTo(navBarSort.indexOf(b['id'])));
navigationBars.value = defaultNavTabs;
int defaultHomePage =
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int;
int defaultIndex =
navigationBars.indexWhere((item) => item['id'] == defaultHomePage);
// 如果找不到匹配项默认索引设置为0或其他合适的值
selectedIndex = defaultIndex != -1 ? defaultIndex : 0;
pages = navigationBars.map<Widget>((e) => e['page']).toList();
}
}

View File

@ -10,6 +10,7 @@ import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/pages/rank/index.dart';
import 'package:pilipala/utils/event_bus.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/storage.dart';
import './controller.dart';
@ -29,7 +30,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
int? _lastSelectTime; //上次点击时间
Box setting = GStrorage.setting;
late bool enableMYBar;
@override
void initState() {
@ -37,7 +37,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
_mainController.pageController =
PageController(initialPage: _mainController.selectedIndex);
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
}
void setIndex(int value) async {
@ -127,81 +126,82 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
},
children: _mainController.pages,
),
bottomNavigationBar: StreamBuilder(
stream: _mainController.hideTabBar
? _mainController.bottomBarStream.stream
: StreamController<bool>.broadcast().stream,
initialData: true,
builder: (context, AsyncSnapshot snapshot) {
return AnimatedSlide(
curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500),
offset: Offset(0, snapshot.data ? 0 : 1),
child: Obx(
() => enableMYBar
? NavigationBar(
onDestinationSelected: (value) => setIndex(value),
selectedIndex: _mainController.selectedIndex,
destinations: <Widget>[
..._mainController.navigationBars.map((e) {
return NavigationDestination(
icon: Obx(
() => Badge(
label:
_mainController.dynamicBadgeType.value ==
bottomNavigationBar: _mainController.navigationBars.length > 1
? StreamBuilder(
stream: _mainController.hideTabBar
? _mainController.bottomBarStream.stream.distinct()
: StreamController<bool>.broadcast().stream,
initialData: true,
builder: (context, AsyncSnapshot snapshot) {
return AnimatedSlide(
curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500),
offset: Offset(0, snapshot.data ? 0 : 1),
child: GlobalData().enableMYBar
? NavigationBar(
onDestinationSelected: (value) => setIndex(value),
selectedIndex: _mainController.selectedIndex,
destinations: <Widget>[
..._mainController.navigationBars.map((e) {
return NavigationDestination(
icon: Obx(
() => Badge(
label: _mainController
.dynamicBadgeType.value ==
DynamicBadgeMode.number
? Text(e['count'].toString())
: null,
padding:
const EdgeInsets.fromLTRB(6, 0, 6, 0),
isLabelVisible:
_mainController.dynamicBadgeType.value !=
padding:
const EdgeInsets.fromLTRB(6, 0, 6, 0),
isLabelVisible: _mainController
.dynamicBadgeType.value !=
DynamicBadgeMode.hidden &&
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: Obx(
() => Badge(
label:
_mainController.dynamicBadgeType.value ==
child: e['icon'],
),
),
selectedIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
)
: BottomNavigationBar(
currentIndex: _mainController.selectedIndex,
type: BottomNavigationBarType.fixed,
onTap: (value) => setIndex(value),
iconSize: 16,
selectedFontSize: 12,
unselectedFontSize: 12,
items: [
..._mainController.navigationBars.map((e) {
return BottomNavigationBarItem(
icon: Obx(
() => Badge(
label: _mainController
.dynamicBadgeType.value ==
DynamicBadgeMode.number
? Text(e['count'].toString())
: null,
padding:
const EdgeInsets.fromLTRB(6, 0, 6, 0),
isLabelVisible:
_mainController.dynamicBadgeType.value !=
padding:
const EdgeInsets.fromLTRB(6, 0, 6, 0),
isLabelVisible: _mainController
.dynamicBadgeType.value !=
DynamicBadgeMode.hidden &&
e['count'] > 0,
child: e['icon'],
),
),
activeIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
),
),
);
},
),
child: e['icon'],
),
),
activeIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
),
);
},
)
: null,
),
);
}

View File

@ -1,13 +1,11 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/utils/main_stream.dart';
import 'package:pilipala/utils/utils.dart';
class MediaPage extends StatefulWidget {
@ -30,26 +28,11 @@ class _MediaPageState extends State<MediaPage>
super.initState();
mediaController = Get.put(MediaController());
_futureBuilderFuture = mediaController.queryFavFolder();
ScrollController scrollController = mediaController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
mediaController.userLogin.listen((status) {
setState(() {
_futureBuilderFuture = mediaController.queryFavFolder();
});
});
scrollController.addListener(
() {
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
}
},
);
}
@override

View File

@ -65,7 +65,7 @@ class _MemberPageState extends State<MemberPage>
children: [
AppBar(
title: StreamBuilder(
stream: appbarStream.stream,
stream: appbarStream.stream.distinct(),
initialData: false,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return AnimatedOpacity(

View File

@ -59,6 +59,7 @@ class MemberCoinsItem extends StatelessWidget {
padding: const EdgeInsets.fromLTRB(5, 6, 0, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
coinItem.title!,

View File

@ -25,7 +25,7 @@ class MemberSeasonsItem extends StatelessWidget {
child: InkWell(
onTap: () async {
int cid =
await SearchHttp.ab2c(aid: seasonItem.aid, bvid: seasonItem.bvid);
await SearchHttp.ab2c(aid: seasonItem.aid, bvid: seasonItem.bvid);
Get.toNamed('/video?bvid=${seasonItem.bvid}&cid=$cid',
arguments: {'videoItem': seasonItem, 'heroTag': heroTag});
},
@ -51,8 +51,7 @@ class MemberSeasonsItem extends StatelessWidget {
bottom: 6,
right: 6,
type: 'gray',
text: Utils.CustomStamp_str(
timestamp: seasonItem.pubdate, date: 'YY-MM-DD'),
text: Utils.timeFormat(seasonItem.duration),
)
],
);

View File

@ -1,7 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/animated_dialog.dart';
@ -9,9 +8,8 @@ import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/common/skeleton/video_card_h.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/video_card_h.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/pages/rank/zone/index.dart';
import 'package:pilipala/utils/main_stream.dart';
class ZonePage extends StatefulWidget {
const ZonePage({Key? key, required this.rid}) : super(key: key);
@ -38,10 +36,6 @@ class _ZonePageState extends State<ZonePage>
_zoneController = Get.put(ZoneController(), tag: widget.rid.toString());
_futureBuilderFuture = _zoneController.queryRankFeed('init', widget.rid);
scrollController = _zoneController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
StreamController<bool> searchBarStream =
Get.find<HomeController>().searchBarStream;
scrollController.addListener(
() {
if (scrollController.position.pixels >=
@ -51,16 +45,7 @@ class _ZonePageState extends State<ZonePage>
_zoneController.onLoad();
}
}
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
searchBarStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
searchBarStream.add(false);
}
handleScrollEvent(scrollController);
},
);
}

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/skeleton/video_card_v.dart';
@ -10,8 +9,7 @@ import 'package:pilipala/common/widgets/animated_dialog.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/common/widgets/video_card_v.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/utils/main_stream.dart';
import 'controller.dart';
@ -35,10 +33,6 @@ class _RcmdPageState extends State<RcmdPage>
super.initState();
_futureBuilderFuture = _rcmdController.queryRcmdFeed('init');
ScrollController scrollController = _rcmdController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
StreamController<bool> searchBarStream =
Get.find<HomeController>().searchBarStream;
scrollController.addListener(
() {
if (scrollController.position.pixels >=
@ -49,15 +43,7 @@ class _RcmdPageState extends State<RcmdPage>
_rcmdController.onLoad();
});
}
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
searchBarStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
searchBarStream.add(false);
}
handleScrollEvent(scrollController);
},
);
}

View File

@ -0,0 +1,100 @@
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';
import '../../../models/common/nav_bar_config.dart';
class NavigationBarSetPage extends StatefulWidget {
const NavigationBarSetPage({super.key});
@override
State<NavigationBarSetPage> createState() => _NavigationbarSetPageState();
}
class _NavigationbarSetPageState extends State<NavigationBarSetPage> {
Box settingStorage = GStrorage.setting;
late List defaultNavTabs;
late List<int> navBarSort;
@override
void initState() {
super.initState();
defaultNavTabs = defaultNavigationBars;
navBarSort = settingStorage
.get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2, 3]);
// 对 tabData 进行排序
defaultNavTabs.sort((a, b) {
int indexA = navBarSort.indexOf(a['id']);
int indexB = navBarSort.indexOf(b['id']);
// 如果类型在 sortOrder 中不存在,则放在末尾
if (indexA == -1) indexA = navBarSort.length;
if (indexB == -1) indexB = navBarSort.length;
return indexA.compareTo(indexB);
});
}
void saveEdit() {
List<int> sortedTabbar = defaultNavTabs
.where((i) => navBarSort.contains(i['id']))
.map<int>((i) => i['id'])
.toList();
settingStorage.put(SettingBoxKey.navBarSort, sortedTabbar);
SmartDialog.showToast('保存成功,下次启动时生效');
}
void onReorder(int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final tabsItem = defaultNavTabs.removeAt(oldIndex);
defaultNavTabs.insert(newIndex, tabsItem);
});
}
@override
Widget build(BuildContext context) {
final listTiles = [
for (int i = 0; i < defaultNavTabs.length; i++) ...[
CheckboxListTile(
key: Key(defaultNavTabs[i]['label']),
value: navBarSort.contains(defaultNavTabs[i]['id']),
onChanged: (bool? newValue) {
int tabTypeId = defaultNavTabs[i]['id'];
if (!newValue!) {
navBarSort.remove(tabTypeId);
} else {
navBarSort.add(tabTypeId);
}
setState(() {});
},
title: Text(defaultNavTabs[i]['label']),
secondary: const Icon(Icons.drag_indicator_rounded),
enabled: defaultNavTabs[i]['id'] != 0,
)
]
];
return Scaffold(
appBar: AppBar(
title: const Text('Navbar编辑'),
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

@ -284,12 +284,17 @@ class _StyleSettingState extends State<StyleSetting> {
onTap: () => Get.toNamed('/tabbarSetting'),
title: Text('首页tabbar', style: titleStyle),
),
ListTile(
dense: false,
onTap: () => Get.toNamed('/navbarSetting'),
title: Text('底部导航栏设置', style: titleStyle),
),
if (Platform.isAndroid)
ListTile(
dense: false,
onTap: () => Get.toNamed('/displayModeSetting'),
title: Text('屏幕帧率', style: titleStyle),
)
),
],
),
);

View File

@ -6,7 +6,6 @@ import '../../models/user/sub_folder.dart';
class SubDetailController extends GetxController {
late SubFolderItemData item;
late int seasonId;
late String heroTag;
int currentPage = 1;
@ -26,17 +25,23 @@ class SubDetailController extends GetxController {
super.onInit();
}
Future<dynamic> queryUserSubFolderDetail({type = 'init'}) async {
Future<dynamic> queryUserSeasonList({type = 'init'}) async {
if (type == 'onLoad' && subList.length >= mediaCount) {
loadingText.value = '没有更多了';
return;
}
isLoadingMore = true;
var res = await UserHttp.userSubFolderDetail(
seasonId: seasonId,
ps: 20,
pn: currentPage,
);
var res = type == 21
? await UserHttp.userSeasonList(
seasonId: seasonId,
ps: 20,
pn: currentPage,
)
: await UserHttp.userResourceList(
seasonId: seasonId,
ps: 20,
pn: currentPage,
);
if (res['status']) {
subInfo.value = res['data'].info;
if (currentPage == 1 && type == 'init') {
@ -55,6 +60,6 @@ class SubDetailController extends GetxController {
}
onLoad() {
queryUserSubFolderDetail(type: 'onLoad');
queryUserSeasonList(type: 'onLoad');
}
}

View File

@ -26,13 +26,11 @@ class _SubDetailPageState extends State<SubDetailPage> {
Get.put(SubDetailController());
late StreamController<bool> titleStreamC; // a
late Future _futureBuilderFuture;
late String seasonId;
@override
void initState() {
super.initState();
seasonId = Get.parameters['seasonId']!;
_futureBuilderFuture = _subDetailController.queryUserSubFolderDetail();
_futureBuilderFuture = _subDetailController.queryUserSeasonList();
titleStreamC = StreamController<bool>();
_controller.addListener(
() {
@ -69,7 +67,7 @@ class _SubDetailPageState extends State<SubDetailPage> {
pinned: true,
titleSpacing: 0,
title: StreamBuilder(
stream: titleStreamC.stream,
stream: titleStreamC.stream.distinct(),
initialData: false,
builder: (context, AsyncSnapshot snapshot) {
return AnimatedOpacity(
@ -161,15 +159,18 @@ class _SubDetailPageState extends State<SubDetailPage> {
),
),
const SizedBox(height: 4),
Text(
'${Utils.numFormat(_subDetailController.item.viewCount)}次播放',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize,
color: Theme.of(context).colorScheme.outline),
),
Obx(
() => Text(
'${Utils.numFormat(_subDetailController.subInfo.value.cntInfo?['play'])}次播放',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize,
color:
Theme.of(context).colorScheme.outline),
),
)
],
),
),
@ -182,14 +183,12 @@ class _SubDetailPageState extends State<SubDetailPage> {
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
child: Obx(
() => Text(
'${_subDetailController.subList.length}条视频',
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
letterSpacing: 1),
child: Text(
'${_subDetailController.item.mediaCount}条视频',
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
letterSpacing: 1,
),
),
),

View File

@ -106,6 +106,7 @@ class VideoDetailController extends GetxController
BottomControlType.fit,
BottomControlType.fullscreen,
].obs;
RxDouble sheetHeight = 0.0.obs;
@override
void onInit() {
@ -172,6 +173,7 @@ class VideoDetailController extends GetxController
firstFloor: firstFloor,
replyType: ReplyType.video,
source: 'videoDetail',
sheetHeight: sheetHeight.value,
);
});
replyReplyBottomSheetCtr?.closed.then((value) {

View File

@ -372,7 +372,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
),
/// 点赞收藏转发
actionGrid(context, videoIntroController),
Material(child: actionGrid(context, videoIntroController)),
// 合集 videoPart 简洁
if (widget.videoDetail!.ugcSeason != null) ...[
Obx(
@ -381,7 +381,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
cid: videoIntroController.lastPlayCid.value != 0
? videoIntroController.lastPlayCid.value
: widget.videoDetail!.pages!.first.cid,
sheetHeight: sheetHeight,
sheetHeight: videoDetailCtr.sheetHeight.value,
changeFuc: (bvid, cid, aid) =>
videoIntroController.changeSeasonOrbangu(
bvid,
@ -399,7 +399,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
() => PagesPanel(
pages: widget.videoDetail!.pages!,
cid: videoIntroController.lastPlayCid.value,
sheetHeight: sheetHeight,
sheetHeight: videoDetailCtr.sheetHeight.value,
changeFuc: (cid) => videoIntroController.changeSeasonOrbangu(
videoIntroController.bvid,
cid,

View File

@ -5,7 +5,6 @@ import 'package:get/get.dart';
import 'package:pilipala/models/video_detail_res.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/pages/video/detail/introduction/index.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../../../../../common/pages_bottom_sheet.dart';
import '../../../../../models/common/video_episode_type.dart';
@ -35,7 +34,6 @@ class _PagesPanelState extends State<PagesPanel> {
final String heroTag = Get.arguments['heroTag'];
late VideoDetailController _videoDetailController;
final ScrollController listViewScrollCtr = ScrollController();
final ItemScrollController itemScrollController = ItemScrollController();
late PersistentBottomSheetController? _bottomSheetController;
@override
@ -60,7 +58,6 @@ class _PagesPanelState extends State<PagesPanel> {
}
void changeFucCall(item, i) async {
print('pages changeFucCall');
widget.changeFuc?.call(item.cid);
currentIndex.value = i;
_bottomSheetController?.close();
@ -72,11 +69,15 @@ class _PagesPanelState extends State<PagesPanel> {
// 在回调函数中获取更新后的状态
final double offset = min((currentIndex * 150) - 75,
listViewScrollCtr.position.maxScrollExtent);
listViewScrollCtr.animateTo(
offset,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
if (currentIndex.value == 0) {
listViewScrollCtr.jumpTo(0);
} else {
listViewScrollCtr.animateTo(
offset,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
});
}

View File

@ -4,7 +4,6 @@ import 'package:pilipala/common/pages_bottom_sheet.dart';
import 'package:pilipala/models/video_detail_res.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/utils/id_utils.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../../../../../models/common/video_episode_type.dart';
import '../controller.dart';
@ -33,7 +32,6 @@ class _SeasonPanelState extends State<SeasonPanel> {
late RxInt currentIndex = (-1).obs;
final String heroTag = Get.arguments['heroTag'];
late VideoDetailController _videoDetailController;
final ItemScrollController itemScrollController = ItemScrollController();
late PersistentBottomSheetController? _bottomSheetController;
@override

View File

@ -19,6 +19,7 @@ class VideoReplyReplyPanel extends StatefulWidget {
this.firstFloor,
this.source,
this.replyType,
this.sheetHeight,
super.key,
});
final int? oid;
@ -27,6 +28,7 @@ class VideoReplyReplyPanel extends StatefulWidget {
final ReplyItemModel? firstFloor;
final String? source;
final ReplyType? replyType;
final double? sheetHeight;
@override
State<VideoReplyReplyPanel> createState() => _VideoReplyReplyPanelState();
@ -36,7 +38,6 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
late VideoReplyReplyController _videoReplyReplyController;
late AnimationController replyAnimationCtl;
final Box<dynamic> localCache = GStrorage.localCache;
late double sheetHeight;
Future? _futureBuilderFuture;
late ScrollController scrollController;
@ -62,7 +63,6 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
},
);
sheetHeight = localCache.get('sheetHeight');
_futureBuilderFuture = _videoReplyReplyController.queryReplyList();
}
@ -77,33 +77,31 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
@override
Widget build(BuildContext context) {
return Container(
height: widget.source == 'videoDetail' ? sheetHeight : null,
height: widget.source == 'videoDetail' ? widget.sheetHeight : null,
color: Theme.of(context).colorScheme.background,
child: Column(
children: [
if (widget.source == 'videoDetail')
Container(
height: 45,
padding: const EdgeInsets.only(left: 12, right: 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text('评论详情'),
IconButton(
icon: const Icon(Icons.close, size: 20),
onPressed: () {
_videoReplyReplyController.currentPage = 0;
widget.closePanel?.call;
Navigator.pop(context);
},
),
],
AppBar(
toolbarHeight: 45,
automaticallyImplyLeading: false,
centerTitle: false,
title: Text(
'评论详情',
style: Theme.of(context).textTheme.titleSmall,
),
actions: [
IconButton(
icon: const Icon(Icons.close, size: 20),
onPressed: () {
_videoReplyReplyController.currentPage = 0;
widget.closePanel?.call;
Navigator.pop(context);
},
),
const SizedBox(width: 14),
],
),
Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
Expanded(
child: RefreshIndicator(
onRefresh: () async {
@ -140,8 +138,8 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
future: _futureBuilderFuture,
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
final Map data = snapshot.data as Map;
if (data['status']) {
Map? data = snapshot.data;
if (data != null && data['status']) {
// 请求成功
return Obx(
() => SliverList(
@ -199,7 +197,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
} else {
// 请求错误
return HttpError(
errMsg: data['msg'],
errMsg: data?['msg'] ?? '请求错误',
fn: () => setState(() {}),
);
}

View File

@ -47,7 +47,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
late BangumiIntroController bangumiIntroController;
late String heroTag;
PlayerStatus playerStatus = PlayerStatus.playing;
Rx<PlayerStatus> playerStatus = PlayerStatus.playing.obs;
double doubleOffset = 0;
final Box<dynamic> localCache = GStrorage.localCache;
@ -67,6 +67,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
super.initState();
heroTag = Get.arguments['heroTag'];
vdCtr = Get.put(VideoDetailController(), tag: heroTag);
vdCtr.sheetHeight.value = localCache.get('sheetHeight');
videoIntroController = Get.put(
VideoIntroController(bvid: Get.parameters['bvid']!),
tag: heroTag);
@ -108,10 +109,12 @@ class _VideoDetailPageState extends State<VideoDetailPage>
// 流
appbarStreamListen() {
appbarStream = StreamController<double>();
appbarStream = StreamController<double>.broadcast();
_extendNestCtr.addListener(
() {
final double offset = _extendNestCtr.position.pixels;
vdCtr.sheetHeight.value =
Get.size.height - videoHeight - statusBarHeight + offset;
appbarStream.add(offset);
},
);
@ -119,7 +122,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
// 播放器状态监听
void playerListener(PlayerStatus? status) async {
playerStatus = status!;
playerStatus.value = status!;
if (status == PlayerStatus.completed) {
// 结束播放退出全屏
if (autoExitFullcreen) {
@ -271,7 +274,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
@override
Widget build(BuildContext context) {
// final double videoHeight = MediaQuery.sizeOf(context).width * 9 / 16;
final sizeContext = MediaQuery.sizeOf(context);
final _context = MediaQuery.of(context);
late double defaultVideoHeight = sizeContext.width * 9 / 16;
@ -366,6 +368,18 @@ class _VideoDetailPageState extends State<VideoDetailPage>
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Obx(() => AnimatedOpacity(
opacity: playerStatus.value != PlayerStatus.playing
? 1
: 0,
duration: const Duration(milliseconds: 100),
child: const Icon(
Icons.drag_handle_rounded,
size: 20,
color: Colors.grey,
),
)),
const SizedBox(width: 8),
SizedBox(
height: 32,
child: TextButton(
@ -404,7 +418,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
),
),
),
const SizedBox(width: 14),
const SizedBox(width: 18),
],
),
)),
@ -557,7 +571,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
Orientation.landscape ||
plPlayerController?.isFullScreen.value == true
? MediaQuery.sizeOf(context).height
: playerStatus != PlayerStatus.playing
: playerStatus.value != PlayerStatus.playing
? kToolbarHeight
: pinnedHeaderHeight;
},
@ -618,13 +632,13 @@ class _VideoDetailPageState extends State<VideoDetailPage>
/// 重新进入会刷新
// 播放完成/暂停播放
StreamBuilder(
stream: appbarStream.stream,
stream: appbarStream.stream.distinct(),
initialData: 0,
builder: ((context, snapshot) {
return ScrollAppBar(
snapshot.data!.toDouble(),
() => continuePlay(),
playerStatus,
playerStatus.value,
null,
);
}),

View File

@ -11,6 +11,7 @@ import 'package:ns_danmaku/ns_danmaku.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/video/play/quality.dart';
import 'package:pilipala/models/video/play/url.dart';
import 'package:pilipala/pages/dlna/index.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
@ -82,7 +83,6 @@ class _HeaderControlState extends State<HeaderControl> {
/// 设置面板
void showSettingSheet() {
// Scaffold.of(context).openDrawer();
showModalBottomSheet(
elevation: 0,
context: context,
@ -731,9 +731,12 @@ class _HeaderControlState extends State<HeaderControl> {
margin: const EdgeInsets.all(12),
child: Column(
children: [
SizedBox(
height: 45,
child: Center(child: Text('选择解码格式', style: titleStyle))),
const SizedBox(
height: 45,
child: Center(
child: Text('选择解码格式', style: titleStyle),
),
),
Expanded(
child: Material(
child: ListView(
@ -1078,9 +1081,12 @@ class _HeaderControlState extends State<HeaderControl> {
margin: const EdgeInsets.all(12),
child: Column(
children: [
SizedBox(
height: 45,
child: Center(child: Text('选择播放顺序', style: titleStyle))),
const SizedBox(
height: 45,
child: Center(
child: Text('选择播放顺序', style: titleStyle),
),
),
Expanded(
child: Material(
child: ListView(
@ -1165,11 +1171,13 @@ class _HeaderControlState extends State<HeaderControl> {
children: [
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200),
child: Text(
videoIntroController.videoDetail.value.title ?? '',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
child: Obx(
() => Text(
videoIntroController.videoDetail.value.title ?? '',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
),
),
),
),
@ -1209,6 +1217,22 @@ class _HeaderControlState extends State<HeaderControl> {
// ),
// fuc: () => _.screenshot(),
// ),
ComBtn(
icon: const Icon(
Icons.cast,
size: 19,
color: Colors.white,
),
fuc: () async {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return LiveDlnaPage(
datasource: widget.videoDetailCtr!.videoUrl);
},
);
},
),
if (isFullScreen.value) ...[
SizedBox(
width: 56,

View File

@ -101,7 +101,7 @@ class PlPlayerController {
bool _isFirstTime = true;
Timer? _timer;
Timer? _timerForSeek;
late Timer? _timerForSeek;
Timer? _timerForVolume;
Timer? _timerForShowingVolume;
Timer? _timerForGettingVolume;
@ -646,9 +646,6 @@ class PlPlayerController {
/// 跳转至指定位置
Future<void> seekTo(Duration position, {type = 'seek'}) async {
// if (position >= duration.value) {
// position = duration.value - const Duration(milliseconds: 100);
// }
if (position < Duration.zero) {
position = Duration.zero;
}
@ -661,21 +658,13 @@ class PlPlayerController {
await _videoPlayerController?.stream.buffer.first;
}
await _videoPlayerController?.seek(position);
// if (playerStatus.stopped) {
// play();
// }
} else {
print('seek duration else');
_timerForSeek?.cancel();
_timerForSeek =
_timerForSeek ??=
Timer.periodic(const Duration(milliseconds: 200), (Timer t) async {
//_timerForSeek = null;
if (duration.value.inSeconds != 0) {
await _videoPlayerController!.stream.buffer.first;
await _videoPlayerController?.seek(position);
// if (playerStatus.status.value == PlayerStatus.paused) {
// play();
// }
t.cancel();
_timerForSeek = null;
}

View File

@ -39,6 +39,7 @@ import '../pages/setting/pages/color_select.dart';
import '../pages/setting/pages/display_mode.dart';
import '../pages/setting/pages/font_size_select.dart';
import '../pages/setting/pages/home_tabbar_set.dart';
import '../pages/setting/pages/navigation_bar_set.dart';
import '../pages/setting/pages/play_gesture_set.dart';
import '../pages/setting/pages/play_speed_set.dart';
import '../pages/setting/recommend_setting.dart';
@ -170,6 +171,9 @@ class Routes {
// 播放器手势
CustomGetPage(
name: '/playerGestureSet', page: () => const PlayGesturePage()),
// navigation bar
CustomGetPage(
name: '/navbarSetting', page: () => const NavigationBarSetPage()),
];
}

View File

@ -1,5 +1,7 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@ -70,9 +72,20 @@ class DownloadUtils {
static Future<bool> downloadImg(String imgUrl,
{String imgType = 'cover'}) async {
try {
if (!await requestPhotoPer()) {
if (!Platform.isAndroid || !await requestPhotoPer()) {
return false;
}
final androidInfo = await DeviceInfoPlugin().androidInfo;
if (androidInfo.version.sdkInt <= 32) {
if (!await requestStoragePer()) {
return false;
}
} else {
if (!await requestPhotoPer()) {
return false;
}
}
SmartDialog.showLoading(msg: '保存中');
var response = await Dio()
.get(imgUrl, options: Options(responseType: ResponseType.bytes));

View File

@ -1,10 +1,16 @@
import 'package:hive/hive.dart';
import 'package:pilipala/utils/storage.dart';
import '../models/common/index.dart';
Box setting = GStrorage.setting;
class GlobalData {
int imgQuality = 10;
FullScreenGestureMode fullScreenGestureMode =
FullScreenGestureMode.values.last;
bool enablePlayerControlAnimation = true;
final bool enableMYBar =
setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
// 私有构造函数
GlobalData._();

View File

@ -0,0 +1,32 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import '../pages/home/index.dart';
import '../pages/main/index.dart';
void handleScrollEvent(ScrollController scrollController) {
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
StreamController<bool> searchBarStream =
Get.find<HomeController>().searchBarStream;
EasyThrottle.throttle(
'stream-throttler',
const Duration(milliseconds: 300),
() {
try {
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
searchBarStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
searchBarStream.add(false);
}
} catch (_) {}
},
);
}

View File

@ -148,7 +148,8 @@ class SettingBoxKey {
hideTabBar = 'hideTabBar', // 收起底栏
tabbarSort = 'tabbarSort', // 首页tabbar
dynamicBadgeMode = 'dynamicBadgeMode',
enableGradientBg = 'enableGradientBg';
enableGradientBg = 'enableGradientBg',
navBarSort = 'navBarSort';
}
class LocalCacheKey {

View File

@ -409,6 +409,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
dlna_dart:
dependency: "direct main"
description:
name: dlna_dart
sha256: ae07c1c53077bbf58756fa589f936968719b0085441981d33e74f82f89d1d281
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.8"
dynamic_color:
dependency: "direct main"
description:

View File

@ -144,6 +144,8 @@ dependencies:
disable_battery_optimization: ^1.1.1
# 展开/收起
expandable: ^5.0.1
# 投屏
dlna_dart: ^0.0.8
dev_dependencies:
flutter_test: