feat: 增加账号未初始化提示登录重新初始化
This commit is contained in:
parent
c87d5d4d38
commit
33b9cd34f1
@ -208,7 +208,10 @@ class VPNManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func disconnect() {
|
func disconnect() {
|
||||||
guard state == .connected else { return }
|
if state != .disconnected && state != .invalid {
|
||||||
manager.connection.stopVPNTunnel()
|
manager.connection.stopVPNTunnel()
|
||||||
|
} else {
|
||||||
|
alert = VPNManagerAlert(alert: nil, message: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import '../../../utils/kr_update_util.dart';
|
|||||||
import '../../../widgets/dialogs/kr_dialog.dart';
|
import '../../../widgets/dialogs/kr_dialog.dart';
|
||||||
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
|
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
|
||||||
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
||||||
|
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||||||
|
|
||||||
import '../models/kr_home_views_status.dart';
|
import '../models/kr_home_views_status.dart';
|
||||||
|
|
||||||
@ -224,14 +225,17 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
await _kr_prepareCountrySelectionBeforeStart();
|
await _kr_prepareCountrySelectionBeforeStart();
|
||||||
final selectedAfter =
|
final selectedAfter =
|
||||||
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
|
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
|
||||||
KRLogUtil.kr_i('准备后 SELECTED_NODE_TAG_autoConnect: ${selectedAfter ?? ''}',
|
KRLogUtil.kr_i(
|
||||||
|
'准备后 SELECTED_NODE_TAG_autoConnect: ${selectedAfter ?? ''}',
|
||||||
tag: 'HomeController');
|
tag: 'HomeController');
|
||||||
KRLogUtil.kr_i('准备后 kr_currentNodeName_autoConnect: ${kr_currentNodeName.value}',
|
KRLogUtil.kr_i(
|
||||||
|
'准备后 kr_currentNodeName_autoConnect: ${kr_currentNodeName.value}',
|
||||||
tag: 'HomeController');
|
tag: 'HomeController');
|
||||||
KRLogUtil.kr_i('准备后 kr_cutTag_autoConnect: ${kr_cutTag.value}',
|
KRLogUtil.kr_i('准备后 kr_cutTag_autoConnect: ${kr_cutTag.value}',
|
||||||
tag: 'HomeController');
|
tag: 'HomeController');
|
||||||
KRLogUtil.kr_i('准备后 kr_cutSeletedTag_autoConnect: ${kr_cutSeletedTag.value}',
|
KRLogUtil.kr_i(
|
||||||
|
'准备后 kr_cutSeletedTag_autoConnect: ${kr_cutSeletedTag.value}',
|
||||||
tag: 'HomeController');
|
tag: 'HomeController');
|
||||||
await kr_performNodeSwitch(selectedAfter!);
|
await kr_performNodeSwitch(selectedAfter!);
|
||||||
await KRSingBoxImp.instance.kr_start();
|
await KRSingBoxImp.instance.kr_start();
|
||||||
@ -267,6 +271,25 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
void onInit() async {
|
void onInit() async {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
Future.delayed(const Duration(milliseconds: 300), () async {
|
||||||
|
final rawAccount = KRAppRunData.getInstance().kr_account.value;
|
||||||
|
final account = rawAccount?.trim();
|
||||||
|
if (account == null ||
|
||||||
|
account.isEmpty ||
|
||||||
|
account.toLowerCase() == 'null') {
|
||||||
|
await HIDialog.show(
|
||||||
|
message: '未检测到账号信息,请重试初始化',
|
||||||
|
confirmText: '重试',
|
||||||
|
preventBackDismiss: true,
|
||||||
|
onConfirm: () {
|
||||||
|
Get.offAllNamed(Routes.KR_SPLASH);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// 🔧 紧急诊断:直接写文件验证 onInit 是否被调用
|
// 🔧 紧急诊断:直接写文件验证 onInit 是否被调用
|
||||||
try {
|
try {
|
||||||
final dir = await getApplicationDocumentsDirectory();
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
@ -892,16 +915,18 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) {
|
if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
if (_lastStatusChangeTime != null &&
|
if (_lastStatusChangeTime != null &&
|
||||||
now.difference(_lastStatusChangeTime!) > const Duration(seconds: 10)) {
|
now.difference(_lastStatusChangeTime!) >
|
||||||
|
const Duration(seconds: 10)) {
|
||||||
// 状态卡住,强制重置
|
// 状态卡住,强制重置
|
||||||
KRLogUtil.kr_w('⚠️ 检测到状态卡住超过10秒 (当前: $currentStatus),执行强制重置', tag: 'HomeController');
|
KRLogUtil.kr_w('⚠️ 检测到状态卡住超过10秒 (当前: $currentStatus),执行强制重置',
|
||||||
|
tag: 'HomeController');
|
||||||
await _forceResetState();
|
await _forceResetState();
|
||||||
// 重置后重新尝试操作
|
// 重置后重新尝试操作
|
||||||
KRLogUtil.kr_i('🔄 状态已重置,继续执行切换操作', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 状态已重置,继续执行切换操作', tag: 'HomeController');
|
||||||
} else {
|
} else {
|
||||||
KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)',
|
||||||
if (kDebugMode) {
|
tag: 'HomeController');
|
||||||
}
|
if (kDebugMode) {}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -962,18 +987,15 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
/// 🔧 等待状态达到预期值
|
/// 🔧 等待状态达到预期值
|
||||||
/// 🔧 保守修复: 返回 bool 表示是否成功达到预期状态
|
/// 🔧 保守修复: 返回 bool 表示是否成功达到预期状态
|
||||||
Future<bool> _waitForStatus(Type expectedType, {int maxSeconds = 3}) async {
|
Future<bool> _waitForStatus(Type expectedType, {int maxSeconds = 3}) async {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {}
|
||||||
}
|
|
||||||
final startTime = DateTime.now();
|
final startTime = DateTime.now();
|
||||||
|
|
||||||
while (DateTime.now().difference(startTime).inSeconds < maxSeconds) {
|
while (DateTime.now().difference(startTime).inSeconds < maxSeconds) {
|
||||||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {}
|
||||||
}
|
|
||||||
|
|
||||||
if (currentStatus.runtimeType == expectedType) {
|
if (currentStatus.runtimeType == expectedType) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {}
|
||||||
}
|
|
||||||
// 强制同步确保 kr_isConnected 正确
|
// 强制同步确保 kr_isConnected 正确
|
||||||
kr_forceSyncConnectionStatus();
|
kr_forceSyncConnectionStatus();
|
||||||
// 🔧 更新状态变化时间
|
// 🔧 更新状态变化时间
|
||||||
@ -990,8 +1012,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
'⏱️ 等待状态超时: 期望=${expectedType.toString()}, 实际=${finalStatus.runtimeType}, 耗时=${maxSeconds}秒',
|
'⏱️ 等待状态超时: 期望=${expectedType.toString()}, 实际=${finalStatus.runtimeType}, 耗时=${maxSeconds}秒',
|
||||||
tag: 'HomeController',
|
tag: 'HomeController',
|
||||||
);
|
);
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {}
|
||||||
}
|
|
||||||
kr_forceSyncConnectionStatus();
|
kr_forceSyncConnectionStatus();
|
||||||
return false; // 超时失败
|
return false; // 超时失败
|
||||||
}
|
}
|
||||||
@ -1183,7 +1204,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
/// 处理手动模式
|
/// 处理手动模式
|
||||||
void _kr_handleManualMode(dynamic element) {
|
void _kr_handleManualMode(dynamic element) {
|
||||||
try {
|
try {
|
||||||
KRLogUtil.kr_d('处理手动模式 - 内核选择: ${element.selected}, 用户选择: ${kr_cutTag.value}', tag: 'HomeController');
|
KRLogUtil.kr_d(
|
||||||
|
'处理手动模式 - 内核选择: ${element.selected}, 用户选择: ${kr_cutTag.value}',
|
||||||
|
tag: 'HomeController');
|
||||||
|
|
||||||
// 🔧 关键修复:不要用内核返回的 selected 覆盖用户手动选择
|
// 🔧 关键修复:不要用内核返回的 selected 覆盖用户手动选择
|
||||||
// 只有当内核返回的节点与用户选择一致时,才更新相关状态
|
// 只有当内核返回的节点与用户选择一致时,才更新相关状态
|
||||||
@ -1195,12 +1218,15 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
// 只有当内核确认切换成功时,才更新 UI 状态
|
// 只有当内核确认切换成功时,才更新 UI 状态
|
||||||
if (element.selected == kr_cutTag.value) {
|
if (element.selected == kr_cutTag.value) {
|
||||||
kr_cutSeletedTag.value = element.selected;
|
kr_cutSeletedTag.value = element.selected;
|
||||||
kr_currentNodeName.value = kr_truncateText(element.selected, maxLength: 25);
|
kr_currentNodeName.value =
|
||||||
|
kr_truncateText(element.selected, maxLength: 25);
|
||||||
// kr_moveToSelectedNode();
|
// kr_moveToSelectedNode();
|
||||||
KRLogUtil.kr_d('✅ 内核确认节点切换成功: ${element.selected}', tag: 'HomeController');
|
KRLogUtil.kr_d('✅ 内核确认节点切换成功: ${element.selected}',
|
||||||
|
tag: 'HomeController');
|
||||||
} else {
|
} else {
|
||||||
// 内核返回的节点与用户选择不一致,保持用户选择的显示
|
// 内核返回的节点与用户选择不一致,保持用户选择的显示
|
||||||
KRLogUtil.kr_d('⏳ 等待内核切换到用户选择的节点: ${kr_cutTag.value}', tag: 'HomeController');
|
KRLogUtil.kr_d('⏳ 等待内核切换到用户选择的节点: ${kr_cutTag.value}',
|
||||||
|
tag: 'HomeController');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
KRLogUtil.kr_e('处理手动模式出错: $e', tag: 'HomeController');
|
KRLogUtil.kr_e('处理手动模式出错: $e', tag: 'HomeController');
|
||||||
@ -1399,9 +1425,12 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
// 🔒 节流控制:2秒内的重复切换请求直接忽略
|
// 🔒 节流控制:2秒内的重复切换请求直接忽略
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
if (_lastSwitchTime != null && now.difference(_lastSwitchTime!) < _switchThrottleDuration) {
|
if (_lastSwitchTime != null &&
|
||||||
final remainingTime = _switchThrottleDuration.inMilliseconds - now.difference(_lastSwitchTime!).inMilliseconds;
|
now.difference(_lastSwitchTime!) < _switchThrottleDuration) {
|
||||||
KRLogUtil.kr_w('⚠️ 切换过于频繁,请等待 ${remainingTime}ms', tag: 'HomeController');
|
final remainingTime = _switchThrottleDuration.inMilliseconds -
|
||||||
|
now.difference(_lastSwitchTime!).inMilliseconds;
|
||||||
|
KRLogUtil.kr_w('⚠️ 切换过于频繁,请等待 ${remainingTime}ms',
|
||||||
|
tag: 'HomeController');
|
||||||
KRCommonUtil.kr_showToast('切换过于频繁,请稍后再试');
|
KRCommonUtil.kr_showToast('切换过于频繁,请稍后再试');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1428,7 +1457,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
// 🔧 修复:保存节点选择以便VPN启动时应用
|
// 🔧 修复:保存节点选择以便VPN启动时应用
|
||||||
KRLogUtil.kr_i('💾 保存节点选择以便稍后应用: $tag', tag: 'HomeController');
|
KRLogUtil.kr_i('💾 保存节点选择以便稍后应用: $tag', tag: 'HomeController');
|
||||||
KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag).then((_) {
|
KRSecureStorage()
|
||||||
|
.kr_saveData(key: 'SELECTED_NODE_TAG', value: tag)
|
||||||
|
.then((_) {
|
||||||
KRLogUtil.kr_i('✅ 节点选择已保存: $tag', tag: 'HomeController');
|
KRLogUtil.kr_i('✅ 节点选择已保存: $tag', tag: 'HomeController');
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
KRLogUtil.kr_e('❌ 保存节点选择失败: $e', tag: 'HomeController');
|
KRLogUtil.kr_e('❌ 保存节点选择失败: $e', tag: 'HomeController');
|
||||||
@ -1447,11 +1478,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
// 🔧 保存新节点选择
|
// 🔧 保存新节点选择
|
||||||
KRLogUtil.kr_i('💾 保存新节点选择: $tag', tag: 'HomeController');
|
KRLogUtil.kr_i('💾 保存新节点选择: $tag', tag: 'HomeController');
|
||||||
await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag);
|
await KRSecureStorage()
|
||||||
|
.kr_saveData(key: 'SELECTED_NODE_TAG', value: tag);
|
||||||
|
|
||||||
// 🚀 核心改进:使用 selectOutbound 进行热切换(参考 hiddify-app)
|
// 🚀 核心改进:使用 selectOutbound 进行热切换(参考 hiddify-app)
|
||||||
// 优势:不重启VPN,保持连接状态,切换瞬间完成,VPN开关不闪烁
|
// 优势:不重启VPN,保持连接状态,切换瞬间完成,VPN开关不闪烁
|
||||||
KRLogUtil.kr_i('🔄 [热切换] 调用 selectOutbound 切换节点...', tag: 'HomeController');
|
KRLogUtil.kr_i('🔄 [热切换] 调用 selectOutbound 切换节点...',
|
||||||
|
tag: 'HomeController');
|
||||||
|
|
||||||
// 🔧 关键修复:确定正确的 selector 组 tag
|
// 🔧 关键修复:确定正确的 selector 组 tag
|
||||||
// selectOutbound(groupTag, outboundTag) - 第一个参数是组的tag,不是节点的tag
|
// selectOutbound(groupTag, outboundTag) - 第一个参数是组的tag,不是节点的tag
|
||||||
@ -1462,12 +1495,14 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
for (var group in activeGroups) {
|
for (var group in activeGroups) {
|
||||||
if (group.type == ProxyType.selector) {
|
if (group.type == ProxyType.selector) {
|
||||||
selectorGroupTag = group.tag;
|
selectorGroupTag = group.tag;
|
||||||
KRLogUtil.kr_i('🔍 找到 selector 组: $selectorGroupTag', tag: 'HomeController');
|
KRLogUtil.kr_i('🔍 找到 selector 组: $selectorGroupTag',
|
||||||
|
tag: 'HomeController');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KRLogUtil.kr_i('📡 调用 selectOutbound("$selectorGroupTag", "$tag")', tag: 'HomeController');
|
KRLogUtil.kr_i('📡 调用 selectOutbound("$selectorGroupTag", "$tag")',
|
||||||
|
tag: 'HomeController');
|
||||||
|
|
||||||
// 调用 sing-box 的 selectOutbound API
|
// 调用 sing-box 的 selectOutbound API
|
||||||
final result = await KRSingBoxImp.instance.kr_singBox
|
final result = await KRSingBoxImp.instance.kr_singBox
|
||||||
@ -1476,12 +1511,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
// 处理切换结果
|
// 处理切换结果
|
||||||
result.fold(
|
result.fold(
|
||||||
(error) {
|
(error) {
|
||||||
// 切换失败
|
// 切换失败
|
||||||
KRLogUtil.kr_e('❌ selectOutbound 调用失败: $error', tag: 'HomeController');
|
KRLogUtil.kr_e('❌ selectOutbound 调用失败: $error',
|
||||||
|
tag: 'HomeController');
|
||||||
throw Exception('节点切换失败: $error');
|
throw Exception('节点切换失败: $error');
|
||||||
},
|
},
|
||||||
(_) {
|
(_) {
|
||||||
// 切换成功
|
// 切换成功
|
||||||
KRLogUtil.kr_i('✅ selectOutbound 调用成功', tag: 'HomeController');
|
KRLogUtil.kr_i('✅ selectOutbound 调用成功', tag: 'HomeController');
|
||||||
},
|
},
|
||||||
@ -1501,15 +1537,18 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
try {
|
try {
|
||||||
final updatedGroups = KRSingBoxImp.instance.kr_activeGroups;
|
final updatedGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||||||
final selectGroup = updatedGroups.firstWhere(
|
final selectGroup = updatedGroups.firstWhere(
|
||||||
(group) => group.type == ProxyType.selector,
|
(group) => group.type == ProxyType.selector,
|
||||||
orElse: () => throw Exception('未找到 selector 组'),
|
orElse: () => throw Exception('未找到 selector 组'),
|
||||||
);
|
);
|
||||||
|
|
||||||
KRLogUtil.kr_i('📊 [验证] ${selectGroup.tag}组当前选中: ${selectGroup.selected}', tag: 'HomeController');
|
KRLogUtil.kr_i(
|
||||||
|
'📊 [验证] ${selectGroup.tag}组当前选中: ${selectGroup.selected}',
|
||||||
|
tag: 'HomeController');
|
||||||
KRLogUtil.kr_i('📊 [验证] 目标节点: $tag', tag: 'HomeController');
|
KRLogUtil.kr_i('📊 [验证] 目标节点: $tag', tag: 'HomeController');
|
||||||
|
|
||||||
if (selectGroup.selected != tag) {
|
if (selectGroup.selected != tag) {
|
||||||
KRLogUtil.kr_w('⚠️ [验证] 节点选择验证失败,实际选中: ${selectGroup.selected}', tag: 'HomeController');
|
KRLogUtil.kr_w('⚠️ [验证] 节点选择验证失败,实际选中: ${selectGroup.selected}',
|
||||||
|
tag: 'HomeController');
|
||||||
// 不抛出异常,但记录警告
|
// 不抛出异常,但记录警告
|
||||||
} else {
|
} else {
|
||||||
KRLogUtil.kr_i('✅ [验证] 节点选择验证成功!', tag: 'HomeController');
|
KRLogUtil.kr_i('✅ [验证] 节点选择验证成功!', tag: 'HomeController');
|
||||||
@ -1523,7 +1562,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
KRLogUtil.kr_i('✅ 节点热切换成功,VPN保持连接: $tag', tag: 'HomeController');
|
KRLogUtil.kr_i('✅ 节点热切换成功,VPN保持连接: $tag', tag: 'HomeController');
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (switchError) {
|
} catch (switchError) {
|
||||||
// 后台切换失败,恢复到原节点
|
// 后台切换失败,恢复到原节点
|
||||||
KRLogUtil.kr_e('❌ 后台节点切换失败: $switchError', tag: 'HomeController');
|
KRLogUtil.kr_e('❌ 后台节点切换失败: $switchError', tag: 'HomeController');
|
||||||
@ -1535,7 +1573,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
// 恢复原节点选择
|
// 恢复原节点选择
|
||||||
try {
|
try {
|
||||||
await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: originalTag);
|
await KRSecureStorage()
|
||||||
|
.kr_saveData(key: 'SELECTED_NODE_TAG', value: originalTag);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
KRLogUtil.kr_e('❌ 恢复节点选择失败: $e', tag: 'HomeController');
|
KRLogUtil.kr_e('❌ 恢复节点选择失败: $e', tag: 'HomeController');
|
||||||
}
|
}
|
||||||
@ -1848,7 +1887,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
super.onReady();
|
super.onReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
@ -2525,7 +2563,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
|||||||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||||||
|
|
||||||
// 只检测中间状态(Starting/Stopping)
|
// 只检测中间状态(Starting/Stopping)
|
||||||
if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) {
|
if (currentStatus is SingboxStarting ||
|
||||||
|
currentStatus is SingboxStopping) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
if (_lastStatusChangeTime != null) {
|
if (_lastStatusChangeTime != null) {
|
||||||
final duration = now.difference(_lastStatusChangeTime!);
|
final duration = now.difference(_lastStatusChangeTime!);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user