1.视频对讲优化--优化帧处理定时器启动逻辑和优化缓冲区动态调整
2.排查bug并优化
This commit is contained in:
parent
b97d7006b3
commit
cddf11485f
@ -16,13 +16,12 @@ import 'package:star_lock/talk/starChart/entity/star_chart_register_node_entity.
|
|||||||
import 'package:star_lock/tools/appFirstEnterHandle.dart';
|
import 'package:star_lock/tools/appFirstEnterHandle.dart';
|
||||||
import 'package:star_lock/tools/baseGetXController.dart';
|
import 'package:star_lock/tools/baseGetXController.dart';
|
||||||
import 'package:star_lock/translations/current_locale_tool.dart';
|
import 'package:star_lock/translations/current_locale_tool.dart';
|
||||||
|
import 'package:star_lock/tools/jverify_one_click_login.dart';
|
||||||
import '../../main/lockMian/lockMain/lockMain_logic.dart';
|
import '../../main/lockMian/lockMain/lockMain_logic.dart';
|
||||||
import '../../mine/mine/starLockMine_logic.dart';
|
import '../../mine/mine/starLockMine_logic.dart';
|
||||||
import '../../network/api_repository.dart';
|
import '../../network/api_repository.dart';
|
||||||
import '../../tools/dateTool.dart';
|
import '../../tools/dateTool.dart';
|
||||||
import '../../tools/eventBusEventManage.dart';
|
import '../../tools/eventBusEventManage.dart';
|
||||||
import '../../tools/jverify_one_click_login.dart';
|
|
||||||
import '../../tools/showTipView.dart';
|
import '../../tools/showTipView.dart';
|
||||||
import '../../tools/storage.dart';
|
import '../../tools/storage.dart';
|
||||||
import '../register/entity/checkIP_entity.dart';
|
import '../register/entity/checkIP_entity.dart';
|
||||||
@ -104,39 +103,75 @@ class StarLockLoginLogic extends BaseGetXController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> oneClickLoginAction(BuildContext context) async {
|
Future<void> oneClickLoginAction(BuildContext context) async {
|
||||||
|
// 检查SDK是否初始化成功
|
||||||
|
final bool isInitSuccess = await JverifyOneClickLoginManage().isInitSuccess();
|
||||||
|
if (!isInitSuccess) {
|
||||||
|
showToast('一键登录服务未初始化,请稍后重试或使用账号密码登录'.tr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 检查网络环境支持性
|
||||||
|
final bool isSupported = await JverifyOneClickLoginManage().checkVerifyEnable();
|
||||||
|
if (!isSupported) {
|
||||||
|
showToast('当前网络环境不支持一键登录,请使用账号密码登录'.tr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
await JverifyOneClickLoginManage().loginAuth(context,(e) async {
|
await JverifyOneClickLoginManage().loginAuth(context,(e) async {
|
||||||
final int? code = e.code;
|
final int? code = e.code;
|
||||||
final String? content = e.message;
|
final String? content = e.message;
|
||||||
|
// 添加完整事件信息日志
|
||||||
|
AppLog.log('JVListenerEvent完整信息: ${e.toMap()}');
|
||||||
|
AppLog.log('code:$code content:$content');
|
||||||
|
|
||||||
|
// 检查所有可能包含手机号的字段
|
||||||
|
final Map eventMap = e.toMap();
|
||||||
|
AppLog.log('所有返回字段: $eventMap');
|
||||||
// final String operator = map['operator'];
|
// final String operator = map['operator'];
|
||||||
AppLog.log('1111code:$code content:$content');
|
AppLog.log('1111code:$code content:$content');
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 6000:
|
case 8000: // SDK初始化成功但登录失败
|
||||||
final LoginEntity entity = await ApiRepository.to.oneClickLogin(
|
showToast('一键登录服务初始化中,请稍后重试'.tr);
|
||||||
loginType: '3',
|
break;
|
||||||
loginToken: content ?? '',
|
case 6000: // 成功获取token
|
||||||
deviceInfo: state.deviceInfoMap);
|
if (content == null || content.isEmpty) {
|
||||||
if (entity.errorCode!.codeIsSuccessful) {
|
showToast('获取手机号失败,请重试'.tr);
|
||||||
ApmHelper.instance.trackEvent('login_result', {
|
return;
|
||||||
'account': state.emailOrPhone.value,
|
|
||||||
'date': DateTool().getNowDateWithType(1),
|
|
||||||
'login_res': '成功',
|
|
||||||
});
|
|
||||||
|
|
||||||
Storage.saveLoginData(entity.data);
|
|
||||||
Storage.setBool(saveIsVip, entity.data!.isVip == 1);
|
|
||||||
eventBus.fire(MineInfoChangeRefreshUI());
|
|
||||||
if (Get.isRegistered<LockMainLogic>()) {
|
|
||||||
Get.find<LockMainLogic>().getStarLockInfo(isUnShowLoading: true);
|
|
||||||
}
|
|
||||||
Get.offNamedUntil(Routers.starLockMain, (Route route) => false);
|
|
||||||
BlueManage().scanDevices.clear(); //清除设备缓存
|
|
||||||
} else {
|
|
||||||
ApmHelper.instance.trackEvent('login_result', {
|
|
||||||
'account': state.emailOrPhone.value,
|
|
||||||
'date': DateTool().getNowDateWithType(1),
|
|
||||||
'login_res': '${entity.errorCode}--${entity.errorMsg}',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
state.emailOrPhone.value = content; // 将获取到的手机号设置到状态中
|
||||||
|
try {
|
||||||
|
final LoginEntity entity = await ApiRepository.to.oneClickLogin(
|
||||||
|
loginType: '3',
|
||||||
|
loginToken: content,
|
||||||
|
deviceInfo: state.deviceInfoMap
|
||||||
|
).timeout(const Duration(seconds: 10));
|
||||||
|
// 处理响应
|
||||||
|
if (entity.errorCode!.codeIsSuccessful) {
|
||||||
|
ApmHelper.instance.trackEvent('login_result', {
|
||||||
|
'account': state.emailOrPhone.value,
|
||||||
|
'date': DateTool().getNowDateWithType(1),
|
||||||
|
'login_res': '成功',
|
||||||
|
});
|
||||||
|
|
||||||
|
Storage.saveLoginData(entity.data);
|
||||||
|
Storage.setBool(saveIsVip, entity.data!.isVip == 1);
|
||||||
|
eventBus.fire(MineInfoChangeRefreshUI());
|
||||||
|
if (Get.isRegistered<LockMainLogic>()) {
|
||||||
|
Get.find<LockMainLogic>().getStarLockInfo(isUnShowLoading: true);
|
||||||
|
}
|
||||||
|
Get.offNamedUntil(Routers.starLockMain, (Route route) => false);
|
||||||
|
BlueManage().scanDevices.clear(); //清除设备缓存
|
||||||
|
} else {
|
||||||
|
ApmHelper.instance.trackEvent('login_result', {
|
||||||
|
'account': state.emailOrPhone.value,
|
||||||
|
'date': DateTool().getNowDateWithType(1),
|
||||||
|
'login_res': '${entity.errorCode}--${entity.errorMsg}',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} on TimeoutException {
|
||||||
|
showToast('请求超时,请检查网络后重试'.tr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 6001: // 获取token失败
|
||||||
|
showToast('获取登录凭证失败,请重试或使用账号密码登录'.tr);
|
||||||
break;
|
break;
|
||||||
case 6002:
|
case 6002:
|
||||||
// showToast('用户取消一键登录'.tr);
|
// showToast('用户取消一键登录'.tr);
|
||||||
@ -188,23 +223,34 @@ class StarLockLoginLogic extends BaseGetXController {
|
|||||||
late StreamSubscription _agreePrivacySubscription;
|
late StreamSubscription _agreePrivacySubscription;
|
||||||
|
|
||||||
void _initEventListen() {
|
void _initEventListen() {
|
||||||
// _agreePrivacySubscription = eventBus
|
_agreePrivacySubscription = eventBus
|
||||||
// .on<AgreePrivacyAgreement>()
|
.on<AgreePrivacyAgreement>()
|
||||||
// .listen((AgreePrivacyAgreement event) async {
|
.listen((AgreePrivacyAgreement event) async {
|
||||||
// /// 检查ip如果属于国内才进行初始化
|
/// 检查ip如果属于国内才进行初始化
|
||||||
// final CheckIPEntity entity = await ApiRepository.to.checkIpAction(ip: '');
|
final CheckIPEntity entity = await ApiRepository.to.checkIpAction(ip: '');
|
||||||
// String currentLanguage =
|
String currentLanguage =
|
||||||
// CurrentLocaleTool.getCurrentLocaleString(); // 当前选择语言
|
CurrentLocaleTool.getCurrentLocaleString(); // 当前选择语言
|
||||||
// // 判断如果ip是国内的且选的是中文才初始化一键登录
|
// 判断如果ip是国内的且选的是中文才初始化一键登录
|
||||||
// if (entity.data!.abbreviation?.toLowerCase() == 'cn' &&
|
if (entity.data!.abbreviation?.toLowerCase() == 'cn' &&
|
||||||
// currentLanguage == 'zh_CN') {
|
currentLanguage == 'zh_CN') {
|
||||||
// // 初始化一键登录服务
|
// 添加SDK初始化调用
|
||||||
// await JverifyOneClickLoginManage();
|
await JverifyOneClickLoginManage().initSDK(
|
||||||
// state.isCheckVerifyEnable.value =
|
onSuccess: () {
|
||||||
// await JverifyOneClickLoginManage().checkVerifyEnable();
|
AppLog.log('极光认证SDK初始化成功');
|
||||||
// AppLog.log('一键登录初始化认证结果:${state.isCheckVerifyEnable.value}');
|
},
|
||||||
// }
|
onFailure: () {
|
||||||
// });
|
AppLog.log('极光认证SDK初始化失败');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 初始化一键登录服务
|
||||||
|
// await JverifyOneClickLoginManage();
|
||||||
|
// 延迟检查认证可用性
|
||||||
|
await Future.delayed(Duration(seconds: 1));
|
||||||
|
state.isCheckVerifyEnable.value =
|
||||||
|
await JverifyOneClickLoginManage().checkVerifyEnable();
|
||||||
|
AppLog.log('一键登录初始化认证结果:${state.isCheckVerifyEnable.value}');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:jverify/jverify.dart';
|
||||||
import 'package:star_lock/login/login/starLock_login_state.dart';
|
import 'package:star_lock/login/login/starLock_login_state.dart';
|
||||||
import 'package:star_lock/tools/appFirstEnterHandle.dart';
|
import 'package:star_lock/tools/appFirstEnterHandle.dart';
|
||||||
import 'package:star_lock/tools/storage.dart';
|
import 'package:star_lock/tools/storage.dart';
|
||||||
@ -33,11 +34,8 @@ class _StarLockLoginPageState extends State<StarLockLoginPage> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
AppFirstEnterHandle().getAppFirstEnter(isAgreePrivacy);
|
AppFirstEnterHandle().getAppFirstEnter(isAgreePrivacy);
|
||||||
// 获取手机号
|
|
||||||
if (state.isChinaUser.value) {
|
|
||||||
// state.getPhoneNumber();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
logic.onInit();
|
||||||
// StartChartManage().init();
|
// StartChartManage().init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:flutter_easyloading/flutter_easyloading.dart';
|
|||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:star_lock/app_settings/app_settings.dart';
|
||||||
import 'package:star_lock/blue/blue_manage.dart';
|
import 'package:star_lock/blue/blue_manage.dart';
|
||||||
import 'package:star_lock/main/lockDetail/lockSet/lockSet/checkingInInfoData_entity.dart';
|
import 'package:star_lock/main/lockDetail/lockSet/lockSet/checkingInInfoData_entity.dart';
|
||||||
import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSet_state.dart';
|
import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSet_state.dart';
|
||||||
@ -36,6 +37,16 @@ class _LockSetPageState extends State<LockSetPage>
|
|||||||
|
|
||||||
Future<void> getHttpData() async {
|
Future<void> getHttpData() async {
|
||||||
logic.getLockSettingInfoData().then((LockSetInfoEntity value) {
|
logic.getLockSettingInfoData().then((LockSetInfoEntity value) {
|
||||||
|
// 打印
|
||||||
|
final faceAntiMisopenTime = value.data?.lockSettingInfo?.faceEnErrUnlock;
|
||||||
|
final int motorTorsion = state.lockSetInfoData.value.lockSettingInfo?.motorTorsion ?? 0;
|
||||||
|
final int autoLockSeconds = state.lockSetInfoData.value.lockSettingInfo?.autoLockSecond ?? 0;
|
||||||
|
debugPrint('=== 锁面容防误开时间 ===');
|
||||||
|
debugPrint('Face Anti-Misopen Time: $faceAntiMisopenTime');
|
||||||
|
debugPrint('=== 锁电机功率设置 ===');
|
||||||
|
debugPrint('Motor Torsion Setting: $motorTorsion');
|
||||||
|
debugPrint('=== 锁自动闭锁时间 ===');
|
||||||
|
debugPrint('Auto Lock Time: ${autoLockSeconds}s');
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -162,16 +162,16 @@ class StartChartManage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 优化连接建立
|
// 优化连接建立
|
||||||
Future<void> establishConnection({required String ToPeerId}) async {
|
// Future<void> establishConnection({required String ToPeerId}) async {
|
||||||
// 提前预加载必要资源
|
// // 提前预加载必要资源
|
||||||
await init(); // 确保基础服务已初始化
|
// await init(); // 确保基础服务已初始化
|
||||||
|
//
|
||||||
// 并行执行地址交换和打洞
|
// // 并行执行地址交换和打洞
|
||||||
await Future.wait([
|
// await Future.wait([
|
||||||
startSendingRbcuInfoMessages(ToPeerId: ToPeerId),
|
// startSendingRbcuInfoMessages(ToPeerId: ToPeerId),
|
||||||
startSendingRbcuProbeTMessages(),
|
// startSendingRbcuProbeTMessages(),
|
||||||
] as Iterable<Future>);
|
// ] as Iterable<Future>);
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// 客户端注册
|
/// 客户端注册
|
||||||
Future<void> _clientRegister(LoginData? loginData) async {
|
Future<void> _clientRegister(LoginData? loginData) async {
|
||||||
@ -469,7 +469,8 @@ class StartChartManage {
|
|||||||
// 启动定时器持续发送对讲请求
|
// 启动定时器持续发送对讲请求
|
||||||
talkRequestTimer ??= Timer.periodic(
|
talkRequestTimer ??= Timer.periodic(
|
||||||
Duration(
|
Duration(
|
||||||
milliseconds: 500,
|
milliseconds: _defaultIntervalTime,
|
||||||
|
// milliseconds: 500,
|
||||||
),
|
),
|
||||||
(Timer timer) async {
|
(Timer timer) async {
|
||||||
AppLog.log('发送对讲请求:${ToPeerId}');
|
AppLog.log('发送对讲请求:${ToPeerId}');
|
||||||
@ -543,6 +544,10 @@ class StartChartManage {
|
|||||||
_log(text: '心跳已经开始了. 请勿重复发送心跳包消息');
|
_log(text: '心跳已经开始了. 请勿重复发送心跳包消息');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 确保定时器不存在后再创建
|
||||||
|
// if (_heartBeatTimer != null) {
|
||||||
|
// _heartBeatTimer?.cancel();
|
||||||
|
// }
|
||||||
_heartBeatTimer ??= Timer.periodic(
|
_heartBeatTimer ??= Timer.periodic(
|
||||||
Duration(
|
Duration(
|
||||||
seconds: heartbeatIntervalTime,
|
seconds: heartbeatIntervalTime,
|
||||||
@ -731,14 +736,13 @@ class StartChartManage {
|
|||||||
FromPeerId: FromPeerId,
|
FromPeerId: FromPeerId,
|
||||||
MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true),
|
MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true),
|
||||||
);
|
);
|
||||||
try {
|
// try {
|
||||||
await _sendMessage(message: message);
|
// await _sendMessage(message: message);
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
// 记录日志但不中断程序执行
|
// // 记录日志但不中断程序执行
|
||||||
AppLog.log('发送挂断消息失败: $e');
|
// AppLog.log('发送挂断消息失败: $e');
|
||||||
// 不抛出异常,避免应用崩溃
|
// // 不抛出异常,避免应用崩溃
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送通话中挂断消息
|
// 发送通话中挂断消息
|
||||||
@ -1070,6 +1074,23 @@ class StartChartManage {
|
|||||||
void _handleUdpResultData(ScpMessage scpMessage) {
|
void _handleUdpResultData(ScpMessage scpMessage) {
|
||||||
final int payloadType = scpMessage.PayloadType ?? 0;
|
final int payloadType = scpMessage.PayloadType ?? 0;
|
||||||
final int messageType = scpMessage.MessageType ?? 0;
|
final int messageType = scpMessage.MessageType ?? 0;
|
||||||
|
// 添加开锁回应的日志打印
|
||||||
|
if (payloadType == PayloadTypeConstant.remoteUnlock) {
|
||||||
|
AppLog.log('收到蓝牙设备回应消息,${scpMessage.PayloadType}');
|
||||||
|
// 解析回应内容
|
||||||
|
if (scpMessage.Payload != null && scpMessage.Payload!.isNotEmpty) {
|
||||||
|
// 需要根据实际协议格式解析
|
||||||
|
final List<int> payload = scpMessage.Payload!;
|
||||||
|
|
||||||
|
if (payload.length > 2) {
|
||||||
|
final int errorCode = payload[2];
|
||||||
|
if (errorCode == 6) {
|
||||||
|
// 显示"网关正忙,请稍后再试"提示
|
||||||
|
AppLog.log('网关正忙,请稍后再试');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
final ScpMessageHandler handler = ScpMessageHandlerFactory.createHandler(payloadType);
|
final ScpMessageHandler handler = ScpMessageHandlerFactory.createHandler(payloadType);
|
||||||
if (messageType == MessageTypeConstant.Req) {
|
if (messageType == MessageTypeConstant.Req) {
|
||||||
|
|||||||
@ -64,14 +64,16 @@ class AppLifecycleObserver extends WidgetsBindingObserver {
|
|||||||
// 处理应用程序进入后台的逻辑
|
// 处理应用程序进入后台的逻辑
|
||||||
|
|
||||||
final status = StartChartManage().talkStatus.status;
|
final status = StartChartManage().talkStatus.status;
|
||||||
if (status == TalkStatus.passiveCallWaitingAnswer ||
|
if ((status == TalkStatus.passiveCallWaitingAnswer ||
|
||||||
status == TalkStatus.proactivelyCallWaitingAnswer ||
|
status == TalkStatus.proactivelyCallWaitingAnswer ||
|
||||||
status == TalkStatus.answeredSuccessfully ||
|
status == TalkStatus.answeredSuccessfully) &&
|
||||||
status == TalkStatus.uninitialized) {
|
Get.currentRoute != '/StarLockRegisterPage') { // 避免在注册页返回
|
||||||
// Get.back(); // 避免返回上一页的操作,避免影响注册页
|
Get.back();
|
||||||
|
}
|
||||||
|
// 避免在无通话状态时销毁对讲资源
|
||||||
|
if (status != TalkStatus.uninitialized) {
|
||||||
|
StartChartManage().destruction();
|
||||||
}
|
}
|
||||||
// 不进行对讲资源清理,避免切出去回来无法接听
|
|
||||||
// StartChartManage().destruction();
|
|
||||||
_readMessageRefreshUIEvent?.cancel();
|
_readMessageRefreshUIEvent?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -113,7 +113,7 @@ class TalkViewNativeDecodeState {
|
|||||||
|
|
||||||
// H264帧缓冲区相关
|
// H264帧缓冲区相关
|
||||||
final List<Map<String, dynamic>> h264FrameBuffer = <Map<String, dynamic>>[]; // H264帧缓冲区,存储帧数据和类型
|
final List<Map<String, dynamic>> h264FrameBuffer = <Map<String, dynamic>>[]; // H264帧缓冲区,存储帧数据和类型
|
||||||
final int maxFrameBufferSize = 25; // 最大缓冲区大小
|
int maxFrameBufferSize = 25; // 最大缓冲区大小
|
||||||
int targetFps = 25; // 目标解码帧率,只是为了快速填充native的缓冲区
|
int targetFps = 25; // 目标解码帧率,只是为了快速填充native的缓冲区
|
||||||
Timer? frameProcessTimer; // 帧处理定时器
|
Timer? frameProcessTimer; // 帧处理定时器
|
||||||
bool isProcessingFrame = false; // 是否正在处理帧
|
bool isProcessingFrame = false; // 是否正在处理帧
|
||||||
|
|||||||
@ -63,7 +63,7 @@ class JverifyOneClickLoginManage {
|
|||||||
}
|
}
|
||||||
jverify.setup(
|
jverify.setup(
|
||||||
appKey: appKey, //"你自己应用的 AppKey",
|
appKey: appKey, //"你自己应用的 AppKey",
|
||||||
channel: 'devloper');
|
channel: 'developer');
|
||||||
|
|
||||||
/// 授权页面点击时间监听
|
/// 授权页面点击时间监听
|
||||||
jverify.addAuthPageEventListener((JVAuthPageEvent event) {
|
jverify.addAuthPageEventListener((JVAuthPageEvent event) {
|
||||||
@ -100,7 +100,7 @@ class JverifyOneClickLoginManage {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final Map map = await jverify.checkVerifyEnable();
|
final Map map = await jverify.checkVerifyEnable();
|
||||||
print('一家登录 sdk 初始化结果:$map');
|
print('一键登录 sdk 初始化结果:$map');
|
||||||
final bool result = map[f_result_key];
|
final bool result = map[f_result_key];
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user