Merge branch 'design'

This commit is contained in:
guozhigq
2024-10-20 22:59:18 +08:00
36 changed files with 1353 additions and 640 deletions

View File

@ -1,6 +1,6 @@
import 'dart:async';
import 'package:bottom_sheet/bottom_sheet.dart';
// import 'package:bottom_sheet/bottom_sheet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@ -62,6 +62,7 @@ class VideoIntroController extends GetxController {
late ModelResult modelResult;
PersistentBottomSheetController? bottomSheetController;
late bool enableRelatedVideo;
UgcSeason? ugcSeason;
@override
void onInit() {
@ -87,6 +88,7 @@ class VideoIntroController extends GetxController {
var result = await VideoHttp.videoIntro(bvid: bvid);
if (result['status']) {
videoDetail.value = result['data']!;
ugcSeason = result['data']!.ugcSeason;
if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) {
lastPlayCid.value = videoDetail.value.pages!.first.cid!;
}
@ -531,25 +533,31 @@ class VideoIntroController extends GetxController {
}
// 设置关注分组
void setFollowGroup() {
showFlexibleBottomSheet(
bottomSheetBorderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
minHeight: 0.6,
initHeight: 0.6,
maxHeight: 1,
void setFollowGroup() async {
final mediaQueryData = MediaQuery.of(Get.context!);
final contentHeight = mediaQueryData.size.height - kToolbarHeight;
final double initialChildSize =
(contentHeight - Get.width * 9 / 16) / contentHeight;
await showModalBottomSheet(
context: Get.context!,
builder: (BuildContext context, ScrollController scrollController,
double offset) {
return GroupPanel(
mid: videoDetail.value.owner!.mid!,
scrollController: scrollController,
useSafeArea: true,
isScrollControlled: true,
builder: (BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: initialChildSize,
minChildSize: 0,
maxChildSize: 1,
snap: true,
expand: false,
snapSizes: [initialChildSize],
builder: (BuildContext context, ScrollController scrollController) {
return GroupPanel(
mid: videoDetail.value.owner!.mid!,
scrollController: scrollController,
);
},
);
},
anchors: [0.6, 1],
isSafeArea: true,
);
}
@ -602,9 +610,9 @@ class VideoIntroController extends GetxController {
episodes: episodes,
currentCid: lastPlayCid.value,
dataType: dataType,
context: Get.context!,
sheetHeight: Get.size.height,
isFullScreen: true,
ugcSeason: ugcSeason,
changeFucCall: (item, index) {
if (dataType == VideoEpidoesType.videoEpisode) {
changeSeasonOrbangu(
@ -615,7 +623,7 @@ class VideoIntroController extends GetxController {
}
SmartDialog.dismiss();
},
).buildShowContent(Get.context!),
).buildShowContent(),
);
}

View File

@ -1,4 +1,4 @@
import 'package:bottom_sheet/bottom_sheet.dart';
// import 'package:bottom_sheet/bottom_sheet.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@ -169,7 +169,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
owner = widget.videoDetail!.owner;
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
_expandableCtr = ExpandableController(initialExpanded: false);
_expandableCtr = ExpandableController(
initialExpanded: GlobalDataCache().enableAutoExpand);
}
// 收藏
@ -198,25 +199,35 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
}
}
void _showFavPanel() {
showFlexibleBottomSheet(
bottomSheetBorderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
void _showFavPanel() async {
final mediaQueryData = MediaQuery.of(context);
final contentHeight = mediaQueryData.size.height - kToolbarHeight;
final double initialChildSize =
(contentHeight - Get.width * 9 / 16) / contentHeight;
await showModalBottomSheet(
context: Get.context!,
useSafeArea: true,
isScrollControlled: true,
transitionAnimationController: AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
),
minHeight: 0.6,
initHeight: 0.6,
maxHeight: 1,
context: context,
builder: (BuildContext context, ScrollController scrollController,
double offset) {
return FavPanel(
ctr: videoIntroController,
scrollController: scrollController,
builder: (BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: initialChildSize,
minChildSize: 0,
maxChildSize: 1,
snap: true,
expand: false,
snapSizes: [initialChildSize],
builder: (BuildContext context, ScrollController scrollController) {
return FavPanel(
ctr: videoIntroController,
scrollController: scrollController,
);
},
);
},
anchors: [0.6, 1],
isSafeArea: true,
);
}

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/http_error.dart';
@ -32,8 +31,14 @@ class _FavPanelState extends State<FavPanel> {
AppBar(
centerTitle: false,
elevation: 0,
automaticallyImplyLeading: false,
leadingWidth: 0,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
leading: IconButton(
onPressed: () => Get.back(),
icon: const Icon(Icons.close_outlined)),
title: Text(
'选择收藏夹',
style: Theme.of(context)
@ -61,16 +66,16 @@ class _FavPanelState extends State<FavPanel> {
onTap: () =>
widget.ctr!.onChoose(item.favState != 1, index),
dense: true,
leading: Icon([23, 1].contains(item.attr)
leading: Icon([22, 0].contains(item.attr)
? Icons.lock_outline
: Icons.folder_outlined),
minLeadingWidth: 0,
title: Text(item.title!),
subtitle: Text(
'${item.mediaCount}个内容 - ${[
23,
1
].contains(item.attr) ? '私密' : '公开'}',
22,
0
].contains(item.attr) ? '公开' : '私密'}',
),
trailing: Transform.scale(
scale: 0.9,
@ -92,7 +97,7 @@ class _FavPanelState extends State<FavPanel> {
}
} else {
// 骨架屏
return const Text('请求中');
return const Center(child: Text('请求中'));
}
},
),

View File

@ -59,10 +59,19 @@ class _GroupPanelState extends State<GroupPanel> {
AppBar(
centerTitle: false,
elevation: 0,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
leading: IconButton(
onPressed: () => Get.back(),
icon: const Icon(Icons.close_outlined)),
title: Text('设置关注分组', style: Theme.of(context).textTheme.titleMedium),
title: Text('设置关注分组',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold)),
),
Expanded(
child: Material(
@ -115,7 +124,7 @@ class _GroupPanelState extends State<GroupPanel> {
}
} else {
// 骨架屏
return const Text('请求中');
return const Center(child: Text('请求中'));
}
},
),

View File

@ -116,7 +116,6 @@ class _PagesPanelState extends State<PagesPanel> {
changeFucCall: changeFucCall,
sheetHeight: widget.sheetHeight,
dataType: VideoEpidoesType.videoPart,
context: context,
).show(context);
},
child: Text(

View File

@ -124,7 +124,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
changeFucCall: changeFucCall,
sheetHeight: widget.sheetHeight,
dataType: VideoEpidoesType.videoEpisode,
context: context,
ugcSeason: widget.ugcSeason,
).show(context);
},
child: Padding(

View File

@ -764,14 +764,14 @@ InlineSpan buildContent(
});
} else {
Uri uri = Uri.parse(matchStr.replaceAll('/?', '?'));
SchemeEntity scheme = SchemeEntity(
Uri scheme = Uri(
scheme: uri.scheme,
host: uri.host,
port: uri.port,
path: uri.path,
query: uri.queryParameters,
source: '',
dataString: matchStr,
// query: uri.queryParameters,
// source: '',
// dataString: matchStr,
);
PiliSchame.httpsScheme(scheme);
}

View File

@ -5,13 +5,15 @@ import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/video/reply/item.dart';
class VideoReplyReplyController extends GetxController {
VideoReplyReplyController(this.aid, this.rpid, this.replyType);
VideoReplyReplyController(this.aid, this.rpid, this.replyType, this.showRoot);
final ScrollController scrollController = ScrollController();
// 视频aid 请求时使用的oid
int? aid;
// rpid 请求楼中楼回复
String? rpid;
ReplyType replyType = ReplyType.video;
bool showRoot = false;
ReplyItemModel? rootReply;
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
// 当前页
int currentPage = 0;
@ -42,6 +44,7 @@ class VideoReplyReplyController extends GetxController {
);
if (res['status']) {
final List<ReplyItemModel> replies = res['data'].replies;
ReplyItemModel? root = res['data'].root;
if (replies.isNotEmpty) {
noMore.value = '加载中...';
if (replies.length == res['data'].page.count) {
@ -60,7 +63,9 @@ class VideoReplyReplyController extends GetxController {
return;
}
replyList.addAll(replies);
// res['data'].replies.addAll(replyList);
}
if (showRoot && root != null) {
rootReply = root;
}
}
if (replyList.isNotEmpty && currentReply != null) {

View File

@ -8,7 +8,6 @@ import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
import 'package:pilipala/utils/storage.dart';
import 'controller.dart';
class VideoReplyReplyPanel extends StatefulWidget {
@ -22,6 +21,7 @@ class VideoReplyReplyPanel extends StatefulWidget {
this.sheetHeight,
this.currentReply,
this.loadMore = true,
this.showRoot = false,
super.key,
});
final int? oid;
@ -33,6 +33,7 @@ class VideoReplyReplyPanel extends StatefulWidget {
final double? sheetHeight;
final dynamic currentReply;
final bool loadMore;
final bool showRoot;
@override
State<VideoReplyReplyPanel> createState() => _VideoReplyReplyPanelState();
@ -49,7 +50,11 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
void initState() {
_videoReplyReplyController = Get.put(
VideoReplyReplyController(
widget.oid, widget.rpid.toString(), widget.replyType!),
widget.oid,
widget.rpid.toString(),
widget.replyType!,
widget.showRoot,
),
tag: widget.rpid.toString());
super.initState();
@ -80,6 +85,93 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
super.dispose();
}
Widget _buildAppBar() {
return 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),
],
);
}
Widget _buildReplyItem(ReplyItemModel? replyItem, String replyLevel) {
return ReplyItem(
replyItem: replyItem,
replyLevel: replyLevel,
showReplyRow: false,
addReply: (replyItem) {
_videoReplyReplyController.replyList.add(replyItem);
},
replyType: widget.replyType,
replyReply: (replyItem) => replyReply(replyItem),
);
}
Widget _buildSliverList() {
return Obx(
() => SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == 0) {
return _videoReplyReplyController.rootReply != null
? Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color:
Theme.of(context).dividerColor.withOpacity(0.1),
width: 6,
),
),
),
child: _buildReplyItem(
_videoReplyReplyController.rootReply, '1'),
)
: const SizedBox();
}
int adjustedIndex = index - 1;
if (adjustedIndex == _videoReplyReplyController.replyList.length) {
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom),
height: MediaQuery.of(context).padding.bottom + 100,
child: Center(
child: Obx(
() => Text(
_videoReplyReplyController.noMore.value,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.outline,
),
),
),
),
);
} else {
return _buildReplyItem(
_videoReplyReplyController.replyList[adjustedIndex], '2');
}
},
childCount: _videoReplyReplyController.replyList.length + 2,
),
),
);
}
@override
Widget build(BuildContext context) {
return Container(
@ -87,27 +179,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
if (widget.source == 'videoDetail')
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),
],
),
if (widget.source == 'videoDetail') _buildAppBar(),
Expanded(
child: RefreshIndicator(
onRefresh: () async {
@ -120,28 +192,22 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
child: CustomScrollView(
controller: _videoReplyReplyController.scrollController,
slivers: <Widget>[
if (widget.firstFloor != null) ...[
// const SliverToBoxAdapter(child: SizedBox(height: 10)),
if (widget.firstFloor != null)
SliverToBoxAdapter(
child: ReplyItem(
replyItem: widget.firstFloor,
replyLevel: '2',
showReplyRow: false,
addReply: (replyItem) {
_videoReplyReplyController.replyList.add(replyItem);
},
replyType: widget.replyType,
replyReply: (replyItem) => replyReply(replyItem),
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context)
.dividerColor
.withOpacity(0.1),
width: 6,
),
),
),
child: _buildReplyItem(widget.firstFloor, '2'),
),
),
SliverToBoxAdapter(
child: Divider(
height: 20,
color: Theme.of(context).dividerColor.withOpacity(0.1),
thickness: 6,
),
),
],
widget.loadMore
? FutureBuilder(
future: _futureBuilderFuture,
@ -150,76 +216,21 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
ConnectionState.done) {
Map? data = snapshot.data;
if (data != null && data['status']) {
// 请求成功
return Obx(
() => SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index ==
_videoReplyReplyController
.replyList.length) {
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context)
.padding
.bottom),
height: MediaQuery.of(context)
.padding
.bottom +
100,
child: Center(
child: Obx(
() => Text(
_videoReplyReplyController
.noMore.value,
style: TextStyle(
fontSize: 12,
color: Theme.of(context)
.colorScheme
.outline,
),
),
),
),
);
} else {
return ReplyItem(
replyItem:
_videoReplyReplyController
.replyList[index],
replyLevel: '2',
showReplyRow: false,
addReply: (replyItem) {
_videoReplyReplyController
.replyList
.add(replyItem);
},
replyType: widget.replyType,
replyReply: (replyItem) =>
replyReply(replyItem),
);
}
},
childCount: _videoReplyReplyController
.replyList.length +
1,
),
),
);
return _buildSliverList();
} else {
// 请求错误
return HttpError(
errMsg: data?['msg'] ?? '请求错误',
fn: () => setState(() {}),
);
}
} else {
// 骨架屏
return SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return const VideoReplySkeleton();
}, childCount: 8),
(BuildContext context, int index) {
return const VideoReplySkeleton();
},
childCount: 8,
),
);
}
},
@ -237,7 +248,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
),
),
),
)
),
],
),
),