Merge branch 'design'
This commit is contained in:
@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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('请求中'));
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@ -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('请求中'));
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@ -116,7 +116,6 @@ class _PagesPanelState extends State<PagesPanel> {
|
||||
changeFucCall: changeFucCall,
|
||||
sheetHeight: widget.sheetHeight,
|
||||
dataType: VideoEpidoesType.videoPart,
|
||||
context: context,
|
||||
).show(context);
|
||||
},
|
||||
child: Text(
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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> {
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user