feat: 倍速、画质、音质调节
This commit is contained in:
@ -5,6 +5,7 @@ import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/constants.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/models/video/play/quality.dart';
|
||||
import 'package:pilipala/models/video/play/url.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
||||
@ -21,6 +22,11 @@ class VideoDetailController extends GetxController
|
||||
// 视频aid
|
||||
String bvid = Get.parameters['bvid']!;
|
||||
int cid = int.parse(Get.parameters['cid']!);
|
||||
late PlayUrlModel data;
|
||||
// 当前画质
|
||||
late VideoQuality currentVideoQa;
|
||||
// 当前音质
|
||||
late AudioQuality currentAudioQa;
|
||||
|
||||
// 是否预渲染 骨架屏
|
||||
bool preRender = false;
|
||||
@ -86,6 +92,29 @@ class VideoDetailController extends GetxController
|
||||
});
|
||||
}
|
||||
|
||||
/// 更新画质、音质
|
||||
/// TODO 继续进度播放
|
||||
updatePlayer() {
|
||||
Duration position = plPlayerController.position.value;
|
||||
plPlayerController.removeListeners();
|
||||
plPlayerController.isBuffering.value = false;
|
||||
plPlayerController.buffered.value = Duration.zero;
|
||||
|
||||
/// 暂不匹配解码规则
|
||||
|
||||
/// 根据currentVideoQa 重新设置videoUrl
|
||||
VideoItem firstVideo =
|
||||
data.dash!.video!.firstWhere((i) => i.id == currentVideoQa.code);
|
||||
String videoUrl = firstVideo.baseUrl!;
|
||||
|
||||
/// 根据currentAudioQa 重新设置audioUrl
|
||||
AudioItem firstAudio =
|
||||
data.dash!.audio!.firstWhere((i) => i.id == currentAudioQa.code);
|
||||
String audioUrl = firstAudio.baseUrl ?? '';
|
||||
|
||||
playerInit(videoUrl, audioUrl, defaultST: position);
|
||||
}
|
||||
|
||||
playerInit(source, audioSource,
|
||||
{Duration defaultST = Duration.zero, int duration = 0}) async {
|
||||
plPlayerController.setDataSource(
|
||||
@ -111,14 +140,27 @@ class VideoDetailController extends GetxController
|
||||
queryVideoUrl() async {
|
||||
var result = await VideoHttp.videoUrl(cid: cid, bvid: bvid);
|
||||
if (result['status']) {
|
||||
PlayUrlModel data = result['data'];
|
||||
// 指定质量的视频 -> 最高质量的视频
|
||||
String videoUrl = data.dash!.video!.first.baseUrl!;
|
||||
String audioUrl =
|
||||
data.dash!.audio!.isNotEmpty ? data.dash!.audio!.first.baseUrl! : '';
|
||||
playerInit(videoUrl, audioUrl,
|
||||
defaultST: Duration(milliseconds: data.lastPlayTime!),
|
||||
duration: data.timeLength ?? 0);
|
||||
data = result['data'];
|
||||
|
||||
/// 优先顺序 省流模式 -> 设置中指定质量 -> 当前可选的最高质量
|
||||
VideoItem firstVideo = data.dash!.video!.first;
|
||||
String videoUrl = firstVideo.baseUrl!;
|
||||
//
|
||||
currentVideoQa = VideoQualityCode.fromCode(firstVideo.id!)!;
|
||||
|
||||
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
|
||||
AudioItem firstAudio =
|
||||
data.dash!.audio!.isNotEmpty ? data.dash!.audio!.first : AudioItem();
|
||||
String audioUrl = firstAudio.baseUrl ?? '';
|
||||
//
|
||||
currentAudioQa = AudioQualityCode.fromCode(firstAudio.id!)!;
|
||||
|
||||
playerInit(
|
||||
videoUrl,
|
||||
audioUrl,
|
||||
defaultST: Duration(milliseconds: data.lastPlayTime!),
|
||||
duration: data.timeLength ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import 'package:pilipala/pages/video/detail/related/index.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
|
||||
import 'widgets/app_bar.dart';
|
||||
import 'widgets/header_control.dart';
|
||||
|
||||
class VideoDetailPage extends StatefulWidget {
|
||||
const VideoDetailPage({Key? key}) : super(key: key);
|
||||
@ -158,7 +159,12 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
.videoPlayerController !=
|
||||
null)
|
||||
PLVideoPlayer(
|
||||
controller: plPlayerController!),
|
||||
controller: plPlayerController!,
|
||||
headerControl: HeaderControl(
|
||||
controller: plPlayerController,
|
||||
videoDetailCtr: videoDetailController,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: isShowCover,
|
||||
child: Positioned(
|
||||
@ -194,37 +200,41 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color:
|
||||
Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
width: 280,
|
||||
margin: const EdgeInsets.only(left: 20),
|
||||
child: Obx(
|
||||
() => TabBar(
|
||||
controller: videoDetailController.tabCtr,
|
||||
dividerColor: Colors.transparent,
|
||||
indicatorColor:
|
||||
Theme.of(context).colorScheme.background,
|
||||
tabs: videoDetailController.tabs
|
||||
.map((String name) => Tab(text: name))
|
||||
.toList(),
|
||||
),
|
||||
Opacity(
|
||||
opacity: 0,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.dividerColor
|
||||
.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
width: 280,
|
||||
margin: const EdgeInsets.only(left: 20),
|
||||
child: Obx(
|
||||
() => TabBar(
|
||||
controller: videoDetailController.tabCtr,
|
||||
dividerColor: Colors.transparent,
|
||||
indicatorColor:
|
||||
Theme.of(context).colorScheme.background,
|
||||
tabs: videoDetailController.tabs
|
||||
.map((String name) => Tab(text: name))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
||||
470
lib/pages/video/detail/widgets/header_control.dart
Normal file
470
lib/pages/video/detail/widgets/header_control.dart
Normal file
@ -0,0 +1,470 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/models/video/play/quality.dart';
|
||||
import 'package:pilipala/models/video/play/url.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
|
||||
class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
|
||||
final PlPlayerController? controller;
|
||||
final VideoDetailController? videoDetailCtr;
|
||||
const HeaderControl({
|
||||
this.controller,
|
||||
this.videoDetailCtr,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<HeaderControl> createState() => _HeaderControlState();
|
||||
|
||||
@override
|
||||
Size get preferredSize => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class _HeaderControlState extends State<HeaderControl> {
|
||||
late PlayUrlModel videoInfo;
|
||||
List<PlaySpeed> playSpeed = PlaySpeed.values;
|
||||
|
||||
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
videoInfo = widget.videoDetailCtr!.data;
|
||||
}
|
||||
|
||||
/// 设置面板
|
||||
void showSettingSheet() {
|
||||
TextStyle subTitleStyle =
|
||||
TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.outline);
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (_) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 420,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
bottom: MediaQuery.of(context).padding.bottom + 23,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 35,
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
.withOpacity(0.5),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(3))),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Material(
|
||||
child: ListView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
ListTile(
|
||||
onTap: () {},
|
||||
dense: true,
|
||||
enabled: false,
|
||||
leading:
|
||||
const Icon(Icons.network_cell_outlined, size: 20),
|
||||
title: const Text('省流模式'),
|
||||
subtitle: Text('低画质 | 减少视频缓存', style: subTitleStyle),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.75,
|
||||
child: Switch(
|
||||
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.isNotEmpty &&
|
||||
states.first == MaterialState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null; // All other states will use the default thumbIcon.
|
||||
}),
|
||||
value: false,
|
||||
onChanged: (value) => {},
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => ListTile(
|
||||
onTap: () => {Get.back(), showSetSpeedSheet()},
|
||||
dense: true,
|
||||
leading: const Icon(Icons.speed_outlined, size: 20),
|
||||
title: const Text('播放速度'),
|
||||
subtitle: Text(
|
||||
'当前倍速 x${widget.controller!.playbackSpeed}',
|
||||
style: subTitleStyle),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => {Get.back(), showSetVideoQa()},
|
||||
dense: true,
|
||||
leading: const Icon(Icons.play_circle_outline, size: 20),
|
||||
title: const Text('选择画质'),
|
||||
subtitle: Text(
|
||||
'当前画质 ${widget.videoDetailCtr!.currentVideoQa.description}',
|
||||
style: subTitleStyle),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => {Get.back(), showSetAudioQa()},
|
||||
dense: true,
|
||||
leading: const Icon(Icons.album_outlined, size: 20),
|
||||
title: const Text('选择音质'),
|
||||
subtitle: Text(
|
||||
'当前音质 ${widget.videoDetailCtr!.currentAudioQa.description}',
|
||||
style: subTitleStyle),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {},
|
||||
dense: true,
|
||||
enabled: false,
|
||||
leading: const Icon(Icons.play_circle_outline, size: 20),
|
||||
title: const Text('播放设置'),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {},
|
||||
dense: true,
|
||||
enabled: false,
|
||||
leading: const Icon(Icons.subtitles_outlined, size: 20),
|
||||
title: const Text('弹幕设置'),
|
||||
),
|
||||
],
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
clipBehavior: Clip.hardEdge,
|
||||
isScrollControlled: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// 选择倍速
|
||||
void showSetSpeedSheet() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 450,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
bottom: MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: Material(
|
||||
child: ListView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 45,
|
||||
child: Center(
|
||||
child: Text('播放速度'),
|
||||
),
|
||||
),
|
||||
for (var i in playSpeed) ...[
|
||||
ListTile(
|
||||
onTap: () {
|
||||
widget.controller!.setPlaybackSpeed(i.value);
|
||||
Get.back(result: {'playbackSpeed': i.value});
|
||||
},
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.only(left: 20, right: 20),
|
||||
title: Text(i.description),
|
||||
trailing: i.value == widget.controller!.playbackSpeed
|
||||
? Icon(
|
||||
Icons.done,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 选择画质
|
||||
void showSetVideoQa() {
|
||||
List<FormatItem> videoFormat = videoInfo.supportFormats!;
|
||||
VideoQuality currentVideoQa = widget.videoDetailCtr!.currentVideoQa;
|
||||
|
||||
/// 总质量分类
|
||||
int totalQaSam = videoFormat.length;
|
||||
|
||||
/// 可用的质量分类
|
||||
int userfulQaSam = 0;
|
||||
List<VideoItem> video = videoInfo.dash!.video!;
|
||||
Set<int> idSet = {};
|
||||
for (var item in video) {
|
||||
int id = item.id!;
|
||||
if (!idSet.contains(id)) {
|
||||
idSet.add(id);
|
||||
userfulQaSam++;
|
||||
}
|
||||
}
|
||||
|
||||
TextStyle subTitleStyle =
|
||||
TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.outline);
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 310,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
bottom: MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 45,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
SmartDialog.showToast('标灰画质可能需要bilibili会员');
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('选择画质'),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Material(
|
||||
child: Scrollbar(
|
||||
thumbVisibility: true,
|
||||
child: ListView(
|
||||
children: [
|
||||
for (var i = 0; i < totalQaSam; i++) ...[
|
||||
ListTile(
|
||||
onTap: () {
|
||||
final int quality = videoFormat[i].quality!;
|
||||
widget.videoDetailCtr!.currentVideoQa =
|
||||
VideoQualityCode.fromCode(quality)!;
|
||||
widget.videoDetailCtr!.updatePlayer();
|
||||
Get.back();
|
||||
},
|
||||
dense: true,
|
||||
// 可能包含会员解锁画质
|
||||
enabled: i >= totalQaSam - userfulQaSam,
|
||||
contentPadding:
|
||||
const EdgeInsets.only(left: 20, right: 20),
|
||||
title: Text(videoFormat[i].newDesc!),
|
||||
subtitle: Text(
|
||||
videoFormat[i].format!,
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: currentVideoQa.code ==
|
||||
videoFormat[i].quality
|
||||
? Icon(
|
||||
Icons.done,
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 选择音质
|
||||
void showSetAudioQa() {
|
||||
List<FormatItem> videoFormat = videoInfo.supportFormats!;
|
||||
AudioQuality currentAudioQa = widget.videoDetailCtr!.currentAudioQa;
|
||||
|
||||
List<AudioItem> audio = videoInfo.dash!.audio!;
|
||||
TextStyle subTitleStyle =
|
||||
TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.outline);
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 250,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
bottom: MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 45, child: Center(child: Text('选择音质'))),
|
||||
Expanded(
|
||||
child: Material(
|
||||
child: ListView(
|
||||
children: [
|
||||
for (var i in audio) ...[
|
||||
ListTile(
|
||||
onTap: () {
|
||||
final int quality = i.id!;
|
||||
widget.videoDetailCtr!.currentAudioQa =
|
||||
AudioQualityCode.fromCode(quality)!;
|
||||
widget.videoDetailCtr!.updatePlayer();
|
||||
Get.back();
|
||||
},
|
||||
dense: true,
|
||||
contentPadding:
|
||||
const EdgeInsets.only(left: 20, right: 20),
|
||||
title: Text(i.quality!),
|
||||
subtitle: Text(
|
||||
i.codecs!,
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: currentAudioQa.code == i.id
|
||||
? Icon(
|
||||
Icons.done,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _ = widget.controller!;
|
||||
const textStyle = TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
);
|
||||
return AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
primary: false,
|
||||
centerTitle: false,
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 14,
|
||||
title: Row(
|
||||
children: [
|
||||
ComBtn(
|
||||
icon: const Icon(
|
||||
FontAwesomeIcons.arrowLeft,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
fuc: () => Get.back(),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
ComBtn(
|
||||
icon: const Icon(
|
||||
FontAwesomeIcons.house,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
fuc: () => Get.offAll(const HomePage()),
|
||||
),
|
||||
const Spacer(),
|
||||
// ComBtn(
|
||||
// icon: const Icon(
|
||||
// FontAwesomeIcons.cropSimple,
|
||||
// size: 15,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// fuc: () => _.screenshot(),
|
||||
// ),
|
||||
Obx(
|
||||
() => SizedBox(
|
||||
width: 45,
|
||||
height: 34,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {
|
||||
_.togglePlaybackSpeed();
|
||||
},
|
||||
child: Text(
|
||||
'${_.playbackSpeed.toString()}X',
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
ComBtn(
|
||||
icon: const Icon(
|
||||
FontAwesomeIcons.sliders,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
fuc: () => showSettingSheet(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user