Merge branch 'main' into design

This commit is contained in:
guozhigq
2024-04-30 15:51:37 +08:00
9 changed files with 315 additions and 271 deletions

View File

@ -45,25 +45,37 @@ class EpisodeBottomSheet {
title = '${episode.title}${episode.longTitle!}';
break;
}
return InkWell(
onTap: () {
SmartDialog.showToast('切换至「$title');
changeFucCall.call(episode, index);
},
child: Padding(
padding: const EdgeInsets.only(left: 14, right: 14, top: 8, bottom: 8),
child: isFullScreen
? Text(
title,
maxLines: 1,
return isFullScreen || episode?.cover == null || episode?.cover == ''
? ListTile(
onTap: () {
SmartDialog.showToast('切换至「$title');
changeFucCall.call(episode, index);
},
dense: false,
leading: isCurrentIndex
? Image.asset(
'assets/images/live.gif',
color: primary,
height: 12,
)
: null,
title: Text(title,
style: TextStyle(
fontSize: 14,
color: isCurrentIndex ? primary : onSurface,
),
)
: Row(
)))
: InkWell(
onTap: () {
SmartDialog.showToast('切换至「$title');
changeFucCall.call(episode, index);
},
child: Padding(
padding:
const EdgeInsets.only(left: 14, right: 14, top: 8, bottom: 8),
child: Row(
children: [
NetworkImgLayer(width: 130, height: 75, src: episode.cover),
NetworkImgLayer(
width: 130, height: 75, src: episode?.cover ?? ''),
const SizedBox(width: 10),
Expanded(
child: Text(
@ -77,8 +89,8 @@ class EpisodeBottomSheet {
),
],
),
),
);
),
);
}
Widget buildTitle() {

View File

@ -65,6 +65,20 @@ void main() async {
},
);
// 小白条、导航栏沉浸
if (Platform.isAndroid) {
List<String> versionParts = Platform.version.split('.');
int androidVersion = int.parse(versionParts[0]);
if (androidVersion >= 29) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
}
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent,
systemNavigationBarDividerColor: Colors.transparent,
statusBarColor: Colors.transparent,
));
}
Data.init();
GlobalData();
PiliSchame.init();
@ -130,26 +144,33 @@ class MyApp extends StatelessWidget {
);
}
ThemeData themeData = ThemeData(
colorScheme: currentThemeValue == ThemeType.dark
? darkColorScheme
: lightColorScheme,
);
// ThemeData themeData = ThemeData(
// colorScheme: currentThemeValue == ThemeType.dark
// ? darkColorScheme
// : lightColorScheme,
// );
// 小白条、导航栏沉浸
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,
));
// // 小白条、导航栏沉浸
// if (Platform.isAndroid) {
// List<String> versionParts = Platform.version.split('.');
// int androidVersion = int.parse(versionParts[0]);
// if (androidVersion >= 29) {
// 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;

View File

@ -412,8 +412,8 @@ class Part {
dimension = json["dimension"] == null
? null
: Dimension.fromJson(json["dimension"]);
firstFrame = json["first_frame"];
cover = json["first_frame"];
firstFrame = json["first_frame"] ?? '';
cover = json["first_frame"] ?? '';
}
Map<String, dynamic> toJson() {

View File

@ -115,20 +115,15 @@ class VideoDetailController extends GetxController
super.onInit();
final Map argMap = Get.arguments;
userInfo = userInfoCache.get('userInfoCache');
var keys = argMap.keys.toList();
if (keys.isNotEmpty) {
if (keys.contains('videoItem')) {
var args = argMap['videoItem'];
if (args.pic != null && args.pic != '') {
videoItem['pic'] = args.pic;
cover.value = args.pic;
}
}
if (keys.contains('pic')) {
videoItem['pic'] = argMap['pic'];
cover.value = argMap['pic'];
}
if (argMap.containsKey('videoItem')) {
var args = argMap['videoItem'];
updateCover(args.pic);
}
if (argMap.containsKey('pic')) {
updateCover(argMap['pic']);
}
tabCtr = TabController(length: 2, vsync: this);
autoPlay.value =
setting.get(SettingBoxKey.autoPlayEnable, defaultValue: true);
@ -550,4 +545,10 @@ class VideoDetailController extends GetxController
},
);
}
void updateCover(String? pic) {
if (pic != null && pic != '') {
cover.value = videoItem['pic'] = pic;
}
}
}

View File

@ -90,6 +90,7 @@ class VideoIntroController extends GetxController {
final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
videoDetailCtr.tabs.value = ['简介', '评论 ${result['data']?.stat?.reply}'];
videoDetailCtr.cover.value = result['data'].pic ?? '';
// 获取到粉丝数再返回
await queryUserStat();
}

View File

@ -135,7 +135,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
late double sheetHeight;
late final dynamic owner;
late final dynamic follower;
late final dynamic followStatus;
late int mid;
late String memberHeroTag;
late bool enableAi;
@ -164,7 +163,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
owner = widget.videoDetail!.owner;
follower = Utils.numFormat(videoIntroController.userStat['follower']);
followStatus = videoIntroController.followStatus;
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
_expandableCtr = ExpandableController(initialExpanded: false);
}
@ -441,48 +439,39 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
),
const Spacer(),
Obx(
() => AnimatedOpacity(
opacity:
videoIntroController.followStatus.isEmpty ? 0 : 1,
duration: const Duration(milliseconds: 50),
child: SizedBox(
height: 32,
child: Obx(
() => videoIntroController.followStatus.isNotEmpty
? TextButton(
onPressed:
videoIntroController.actionRelationMod,
style: TextButton.styleFrom(
padding: const EdgeInsets.only(
left: 8, right: 8),
foregroundColor:
followStatus['attribute'] != 0
? outline
: t.colorScheme.onPrimary,
backgroundColor:
followStatus['attribute'] != 0
? t.colorScheme.onInverseSurface
: t.colorScheme
.primary, // 设置按钮背景色
() {
final bool isFollowed =
videoIntroController.followStatus['attribute'] != 0;
return videoIntroController.followStatus.isEmpty
? const SizedBox()
: SizedBox(
height: 32,
child: TextButton(
onPressed:
videoIntroController.actionRelationMod,
style: TextButton.styleFrom(
padding: const EdgeInsets.only(
left: 8,
right: 8,
),
child: Text(
followStatus['attribute'] != 0
? '已关注'
: '关注',
style: TextStyle(
fontSize: t
.textTheme.labelMedium!.fontSize),
),
)
: ElevatedButton(
onPressed:
videoIntroController.actionRelationMod,
child: const Text('关注'),
foregroundColor: isFollowed
? outline
: t.colorScheme.onPrimary,
backgroundColor: isFollowed
? t.colorScheme.onInverseSurface
: t.colorScheme.primary, // 设置按钮背景色
),
),
),
),
),
child: Text(
isFollowed ? '已关注' : '关注',
style: TextStyle(
fontSize:
t.textTheme.labelMedium!.fontSize,
),
),
),
);
},
)
],
),
),

View File

@ -427,7 +427,7 @@ class ReplyItemRow extends StatelessWidget {
if (extraRow == 1)
InkWell(
// 一楼点击【共xx条回复】展开评论详情
onTap: () => replyReply!(replyItem, null),
onTap: () => replyReply!(replyItem),
child: Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(8, 5, 8, 8),

View File

@ -23,6 +23,7 @@ import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:status_bar_control/status_bar_control.dart';
import '../../../plugin/pl_player/models/bottom_control_type.dart';
import '../../../services/shutdown_timer_service.dart';
@ -38,7 +39,7 @@ class VideoDetailPage extends StatefulWidget {
}
class _VideoDetailPageState extends State<VideoDetailPage>
with TickerProviderStateMixin, RouteAware {
with TickerProviderStateMixin, RouteAware, WidgetsBindingObserver {
late VideoDetailController vdCtr;
PlPlayerController? plPlayerController;
final ScrollController _extendNestCtr = ScrollController();
@ -61,10 +62,14 @@ class _VideoDetailPageState extends State<VideoDetailPage>
late bool autoPiP;
late Floating floating;
bool isShowing = true;
// 生命周期监听
late final AppLifecycleListener _lifecycleListener;
late double statusHeight;
@override
void initState() {
super.initState();
getStatusHeight();
heroTag = Get.arguments['heroTag'];
vdCtr = Get.put(VideoDetailController(), tag: heroTag);
vdCtr.sheetHeight.value = localCache.get('sheetHeight');
@ -96,6 +101,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
floating = vdCtr.floating!;
autoEnterPip();
}
WidgetsBinding.instance.addObserver(this);
lifecycleListener();
}
// 获取视频资源,初始化播放器
@ -203,6 +210,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
});
}
getStatusHeight() async {
statusHeight = await StatusBarControl.getHeight;
}
@override
void dispose() {
shutdownTimerService.handleWaitingFinished();
@ -219,6 +230,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
floating.dispose();
}
appbarStream.close();
WidgetsBinding.instance.removeObserver(this);
_lifecycleListener.dispose();
super.dispose();
}
@ -281,6 +294,166 @@ class _VideoDetailPageState extends State<VideoDetailPage>
}
}
// 生命周期监听
void lifecycleListener() {
_lifecycleListener = AppLifecycleListener(
// onResume: () => _handleTransition('resume'),
// 后台
// onInactive: () => _handleTransition('inactive'),
// 在Android和iOS端不生效
// onHide: () => _handleTransition('hide'),
onShow: () => _handleTransition('show'),
onPause: () => _handleTransition('pause'),
onRestart: () => _handleTransition('restart'),
onDetach: () => _handleTransition('detach'),
);
}
void _handleTransition(String name) {
switch (name) {
case 'show' || 'restart':
plPlayerController?.danmakuController?.clear();
break;
}
}
/// 手动播放
Widget handlePlayPanel() {
return Stack(
children: [
GestureDetector(
onTap: handlePlay,
child: Image.network(
vdCtr.videoItem['pic'],
width: Get.width,
height: videoHeight,
fit: BoxFit.cover, // 适应方式根据需要调整
),
),
buildCustomAppBar(),
Positioned(
right: 12,
bottom: 10,
child: GestureDetector(
onTap: handlePlay,
child: Image.asset(
'assets/images/play.png',
width: 60,
height: 60,
),
),
),
],
);
}
/// tabbar
Widget tabbarBuild() {
return Container(
width: double.infinity,
height: 45,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
),
),
child: Material(
child: Row(
children: [
Expanded(
child: Obx(
() => TabBar(
padding: EdgeInsets.zero,
controller: vdCtr.tabCtr,
labelStyle: const TextStyle(fontSize: 13),
labelPadding: const EdgeInsets.symmetric(horizontal: 10.0),
dividerColor: Colors.transparent,
tabs:
vdCtr.tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
Flexible(
flex: 1,
child: Center(
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(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () => vdCtr.showShootDanmakuSheet(),
child:
const Text('发弹幕', style: TextStyle(fontSize: 12)),
),
),
SizedBox(
width: 38,
height: 38,
child: Obx(
() => !vdCtr.isShowCover.value
? IconButton(
onPressed: () {
plPlayerController?.isOpenDanmu.value =
!(plPlayerController?.isOpenDanmu.value ??
false);
},
icon: !(plPlayerController?.isOpenDanmu.value ??
false)
? SvgPicture.asset(
'assets/images/video/danmu_close.svg',
// ignore: deprecated_member_use
color: Theme.of(context)
.colorScheme
.outline,
)
: SvgPicture.asset(
'assets/images/video/danmu_open.svg',
// ignore: deprecated_member_use
color: Theme.of(context)
.colorScheme
.primary,
),
)
: IconButton(
icon: SvgPicture.asset(
'assets/images/video/danmu_close.svg',
// ignore: deprecated_member_use
color: Theme.of(context).colorScheme.outline,
),
onPressed: () {},
),
),
),
const SizedBox(width: 18),
],
),
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
final sizeContext = MediaQuery.sizeOf(context);
@ -338,168 +511,13 @@ class _VideoDetailPageState extends State<VideoDetailPage>
},
);
/// tabbar
Widget tabbarBuild = Container(
width: double.infinity,
height: 45,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
),
),
child: Material(
child: Row(
children: [
Flexible(
flex: 1,
child: Obx(
() => TabBar(
padding: EdgeInsets.zero,
controller: vdCtr.tabCtr,
labelStyle: const TextStyle(fontSize: 13),
labelPadding:
const EdgeInsets.symmetric(horizontal: 10.0), // 设置每个标签的宽度
dividerColor: Colors.transparent,
tabs: vdCtr.tabs
.map(
(String name) => Tab(text: name),
)
.toList(),
),
),
),
Flexible(
flex: 1,
child: Center(
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(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () => vdCtr.showShootDanmakuSheet(),
child:
const Text('发弹幕', style: TextStyle(fontSize: 12)),
),
),
SizedBox(
width: 38,
height: 38,
child: Obx(
() => !vdCtr.isShowCover.value
? IconButton(
onPressed: () {
plPlayerController?.isOpenDanmu.value =
!(plPlayerController
?.isOpenDanmu.value ??
false);
},
icon:
!(plPlayerController?.isOpenDanmu.value ??
false)
? SvgPicture.asset(
'assets/images/video/danmu_close.svg',
// ignore: deprecated_member_use
color: Theme.of(context)
.colorScheme
.outline,
)
: SvgPicture.asset(
'assets/images/video/danmu_open.svg',
// ignore: deprecated_member_use
color: Theme.of(context)
.colorScheme
.primary,
),
)
: IconButton(
icon: SvgPicture.asset(
'assets/images/video/danmu_close.svg',
// ignore: deprecated_member_use
color:
Theme.of(context).colorScheme.outline,
),
onPressed: () {},
),
),
),
const SizedBox(width: 18),
],
),
)),
],
),
),
);
/// 手动播放
Widget handlePlayPanel() {
return Stack(
children: [
GestureDetector(
onTap: () {
handlePlay();
},
child: Obx(
() => AnimatedOpacity(
duration: const Duration(milliseconds: 100), // 渐变动画的持续时间
opacity: 1, // 设置不透明度
child: NetworkImgLayer(
type: 'emote',
src: vdCtr.cover.value,
width: Get.width,
height: videoHeight.value,
),
),
),
),
Positioned(
top: 0,
left: 0,
right: 0,
child: buildCustomAppBar(),
),
Positioned(
right: 12,
bottom: 10,
child: IconButton(
tooltip: '播放',
onPressed: () => handlePlay(),
icon: Image.asset(
'assets/images/play.png',
width: 60,
height: 60,
)),
),
],
);
}
Widget childWhenDisabled = SafeArea(
top: MediaQuery.of(context).orientation == Orientation.portrait &&
plPlayerController?.isFullScreen.value == true,
bottom: MediaQuery.of(context).orientation == Orientation.portrait &&
plPlayerController?.isFullScreen.value == true,
left: false, //plPlayerController?.isFullScreen.value != true,
right: false, //plPlayerController?.isFullScreen.value != true,
left: false,
right: false,
child: Stack(
children: [
Scaffold(
@ -517,12 +535,22 @@ class _VideoDetailPageState extends State<VideoDetailPage>
controller: _extendNestCtr,
headerSliverBuilder:
(BuildContext context2, bool innerBoxIsScrolled) {
final Orientation orientation =
MediaQuery.of(context).orientation;
final bool isFullScreen =
plPlayerController?.isFullScreen.value == true;
final double expandedHeight =
orientation == Orientation.landscape || isFullScreen
? (MediaQuery.sizeOf(context).height -
(orientation == Orientation.landscape
? 0
: MediaQuery.of(context).padding.top))
: videoHeight.value;
return <Widget>[
Obx(
() {
if (MediaQuery.of(context).orientation ==
Orientation.landscape ||
plPlayerController?.isFullScreen.value == true) {
if (orientation == Orientation.landscape ||
isFullScreen) {
enterFullScreen();
} else {
exitFullScreen();
@ -534,15 +562,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
elevation: 0,
scrolledUnderElevation: 0,
forceElevated: innerBoxIsScrolled,
expandedHeight: MediaQuery.of(context).orientation ==
Orientation.landscape ||
plPlayerController?.isFullScreen.value == true
? (MediaQuery.sizeOf(context).height -
(MediaQuery.of(context).orientation ==
Orientation.landscape
? 0
: MediaQuery.of(context).padding.top))
: videoHeight.value,
expandedHeight: expandedHeight,
backgroundColor: Colors.black,
flexibleSpace: FlexibleSpaceBar(
background: PopScope(
@ -562,13 +582,13 @@ class _VideoDetailPageState extends State<VideoDetailPage>
child: LayoutBuilder(
builder: (BuildContext context,
BoxConstraints boxConstraints) {
// final double maxWidth =
// boxConstraints.maxWidth;
// final double maxHeight =
// boxConstraints.maxHeight;
return Stack(
children: <Widget>[
if (isShowing) videoPlayerPanel,
if (isShowing)
Padding(
padding: EdgeInsets.only(top: 0),
child: videoPlayerPanel,
),
/// 关闭自动播放时 手动播放
Obx(
@ -610,7 +630,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
color: Theme.of(context).colorScheme.background,
child: Column(
children: [
tabbarBuild,
tabbarBuild(),
Expanded(
child: TabBarView(
controller: vdCtr.tabCtr,

View File

@ -117,7 +117,7 @@ class PiliSchame {
// ignore: always_specify_types
(e) => Get.toNamed<dynamic>('/video?bvid=$bvid&cid=$cid',
arguments: <String, String?>{
'pic': null,
'pic': '',
'heroTag': heroTag,
}),
);