mirror of
https://github.com/2dust/v2rayN.git
synced 2026-10-03 03:52:07 +03:00
@@ -134,4 +134,25 @@ public static class Extension
|
|||||||
.Replace("\r", replacement)
|
.Replace("\r", replacement)
|
||||||
.Replace("\n", replacement);
|
.Replace("\n", replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<TOutput> HandleSafe<TInput, TOutput>(
|
||||||
|
this Interaction<TInput, TOutput> interaction,
|
||||||
|
TInput input,
|
||||||
|
TOutput defaultValue = default!)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await interaction.Handle(input);
|
||||||
|
}
|
||||||
|
catch (UnhandledInteractionException<TInput, TOutput> ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog($"Unhandled interaction exception for input: {input}", ex);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog($"Exception occurred while handling interaction for input: {input}", ex);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public partial class AddServer2ViewModel : MyReactiveObject, ICloseable
|
|||||||
|
|
||||||
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
var fileName = await BrowseConfigFileInteraction.Handle(RxVoid.Default);
|
var fileName = await BrowseConfigFileInteraction.HandleSafe(RxVoid.Default);
|
||||||
if (fileName.IsNullOrEmpty())
|
if (fileName.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -85,8 +85,6 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#endregion Menu
|
#endregion Menu
|
||||||
|
|
||||||
private readonly SynchronizationContext _uiContext = SynchronizationContext.Current;
|
|
||||||
|
|
||||||
#region Init
|
#region Init
|
||||||
|
|
||||||
public MainWindowViewModel()
|
public MainWindowViewModel()
|
||||||
@@ -302,7 +300,7 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||||
.Subscribe(async blShow =>
|
.Subscribe(async blShow =>
|
||||||
{
|
{
|
||||||
await ShowHideWindowInteraction.Handle(blShow);
|
await ShowHideWindowInteraction.HandleSafe(blShow);
|
||||||
});
|
});
|
||||||
|
|
||||||
StatusBarViewModel.SetDefaultServerRequested
|
StatusBarViewModel.SetDefaultServerRequested
|
||||||
@@ -413,14 +411,25 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
private async Task RefreshServersDispatcherAsync()
|
private async Task RefreshServersDispatcherAsync()
|
||||||
{
|
{
|
||||||
//await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
|
//await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
|
||||||
_uiContext?.Post(_ => _ = RefreshServers(), null);
|
await Signal.FromAsync(async () =>
|
||||||
|
{
|
||||||
|
await RefreshServers();
|
||||||
|
return RxVoid.Default;
|
||||||
|
})
|
||||||
|
.SubscribeOn(RxSchedulers.MainThreadScheduler)
|
||||||
|
.ToTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshSubscriptions()
|
private async Task RefreshSubscriptions()
|
||||||
{
|
{
|
||||||
//await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
|
//await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
|
||||||
|
await Signal.FromAsync(async () =>
|
||||||
_uiContext?.Post(_ => _ = ProfilesViewModel.RefreshSubscriptions(), null);
|
{
|
||||||
|
await ProfilesViewModel.RefreshSubscriptions();
|
||||||
|
return RxVoid.Default;
|
||||||
|
})
|
||||||
|
.SubscribeOn(RxSchedulers.MainThreadScheduler)
|
||||||
|
.ToTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Servers && Groups
|
#endregion Servers && Groups
|
||||||
@@ -467,7 +476,7 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
var stringData = clipboardData;
|
var stringData = clipboardData;
|
||||||
if (clipboardData == null)
|
if (clipboardData == null)
|
||||||
{
|
{
|
||||||
var result = await ReadTextFromClipboardInteraction.Handle(RxVoid.Default);
|
var result = await ReadTextFromClipboardInteraction.HandleSafe(RxVoid.Default);
|
||||||
if (result.IsNullOrEmpty())
|
if (result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
@@ -490,7 +499,7 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task AddServerViaScanAsync()
|
public async Task AddServerViaScanAsync()
|
||||||
{
|
{
|
||||||
var result = await ScanScreenInteraction.Handle(RxVoid.Default);
|
var result = await ScanScreenInteraction.HandleSafe(RxVoid.Default);
|
||||||
await ScanScreenResult(result);
|
await ScanScreenResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,7 +511,7 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task AddServerViaImageAsync()
|
public async Task AddServerViaImageAsync()
|
||||||
{
|
{
|
||||||
var imageFileName = await BrowseImageFileInteraction.Handle(RxVoid.Default);
|
var imageFileName = await BrowseImageFileInteraction.HandleSafe(RxVoid.Default);
|
||||||
await AddScanResultAsync(imageFileName);
|
await AddScanResultAsync(imageFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,10 +700,12 @@ public partial class MainWindowViewModel : MyReactiveObject
|
|||||||
//{
|
//{
|
||||||
// await ClashProxiesViewModel.ProxiesReload();
|
// await ClashProxiesViewModel.ProxiesReload();
|
||||||
//}, RxSchedulers.MainThreadScheduler);
|
//}, RxSchedulers.MainThreadScheduler);
|
||||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
await Signal.FromAsync(async () =>
|
||||||
{
|
{
|
||||||
await ClashProxiesViewModel.ProxiesReload();
|
await ClashProxiesViewModel.ProxiesReload();
|
||||||
});
|
return RxVoid.Default;
|
||||||
|
}).SubscribeOn(RxSchedulers.MainThreadScheduler)
|
||||||
|
.ToTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
ReloadResult(showClashUI);
|
ReloadResult(showClashUI);
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public partial class MsgViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await DispatcherShowMsgInteraction.Handle(sb.ToString());
|
await DispatcherShowMsgInteraction.HandleSafe(sb.ToString());
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -138,13 +138,7 @@ public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
|||||||
|
|
||||||
await RefreshServers();
|
await RefreshServers();
|
||||||
|
|
||||||
try
|
await ProfilesFocusInteraction.HandleSafe(RxVoid.Default);
|
||||||
{
|
|
||||||
await ProfilesFocusInteraction.Handle(RxVoid.Default);
|
|
||||||
}
|
|
||||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ServerFilterChanged(bool c)
|
private async Task ServerFilterChanged(bool c)
|
||||||
|
|||||||
@@ -345,13 +345,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
await RefreshServers();
|
await RefreshServers();
|
||||||
|
|
||||||
try
|
await ProfilesFocusInteraction.HandleSafe(RxVoid.Default);
|
||||||
{
|
|
||||||
await ProfilesFocusInteraction.Handle(RxVoid.Default);
|
|
||||||
}
|
|
||||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ServerFilterChanged(bool c)
|
private async Task ServerFilterChanged(bool c)
|
||||||
@@ -395,13 +389,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
SelectedProfile = selected ?? lstModel.First();
|
SelectedProfile = selected ?? lstModel.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await DispatcherRefreshServersBizInteraction.HandleSafe(RxVoid.Default);
|
||||||
{
|
|
||||||
await DispatcherRefreshServersBizInteraction.Handle(RxVoid.Default);
|
|
||||||
}
|
|
||||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshSubscriptions()
|
public async Task RefreshSubscriptions()
|
||||||
@@ -419,7 +407,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task AdjustMainLvColWidth()
|
public async Task AdjustMainLvColWidth()
|
||||||
{
|
{
|
||||||
await AdjustMainLvColWidthInteraction.Handle(RxVoid.Default);
|
await AdjustMainLvColWidthInteraction.HandleSafe(RxVoid.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||||
@@ -535,7 +523,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -556,7 +544,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task RemoveDuplicateServer()
|
private async Task RemoveDuplicateServer()
|
||||||
{
|
{
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -631,7 +619,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ShareServerInteraction.Handle(url);
|
await ShareServerInteraction.HandleSafe(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GenGroupAllServer()
|
private async Task GenGroupAllServer()
|
||||||
@@ -799,13 +787,13 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle((string)result.Data);
|
await SetClipboardDataInteraction.HandleSafe((string)result.Data);
|
||||||
NoticeManager.Instance.SendMessage(ResUI.OperationSuccess);
|
NoticeManager.Instance.SendMessage(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SaveFileDialogInteraction.Handle(item);
|
await SaveFileDialogInteraction.HandleSafe(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,11 +842,11 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (blEncode)
|
if (blEncode)
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle(Utils.Base64Encode(sb.ToString()));
|
await SetClipboardDataInteraction.HandleSafe(Utils.Base64Encode(sb.ToString()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle(sb.ToString());
|
await SetClipboardDataInteraction.HandleSafe(sb.ToString());
|
||||||
}
|
}
|
||||||
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
||||||
}
|
}
|
||||||
@@ -881,7 +869,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (!result.IsNullOrEmpty())
|
if (!result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle(result);
|
await SetClipboardDataInteraction.HandleSafe(result);
|
||||||
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -925,7 +913,7 @@ public partial class ProfilesViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
});
|
});
|
||||||
ImportRulesFromFileCmd = ReactiveCommand.CreateFromTask(async () =>
|
ImportRulesFromFileCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
var fileName = await BrowseRulesFileInteraction.Handle(RxVoid.Default);
|
var fileName = await BrowseRulesFileInteraction.HandleSafe(RxVoid.Default);
|
||||||
await ImportRulesFromFileAsync(fileName);
|
await ImportRulesFromFileAsync(fileName);
|
||||||
});
|
});
|
||||||
ImportRulesFromClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
|
ImportRulesFromClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
@@ -156,7 +156,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
};
|
};
|
||||||
await SetClipboardDataInteraction.Handle(JsonUtils.Serialize(lst, options));
|
await SetClipboardDataInteraction.HandleSafe(JsonUtils.Serialize(lst, options));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
var stringData = clipboardData;
|
var stringData = clipboardData;
|
||||||
if (clipboardData == null)
|
if (clipboardData == null)
|
||||||
{
|
{
|
||||||
var result = await ReadTextFromClipboardInteraction.Handle(RxVoid.Default);
|
var result = await ReadTextFromClipboardInteraction.HandleSafe(RxVoid.Default);
|
||||||
if (result.IsNullOrEmpty())
|
if (result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
@@ -315,7 +315,7 @@ public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
private async Task<int> AddBatchRoutingRulesAsync(RoutingItem routingItem, string? clipboardData)
|
private async Task<int> AddBatchRoutingRulesAsync(RoutingItem routingItem, string? clipboardData)
|
||||||
{
|
{
|
||||||
var blReplace = false;
|
var blReplace = false;
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.AddBatchRoutingRulesYesNo) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.AddBatchRoutingRulesYesNo) == false)
|
||||||
{
|
{
|
||||||
blReplace = true;
|
blReplace = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ public partial class RoutingSettingViewModel : MyReactiveObject
|
|||||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ public partial class StatusBarViewModel : MyReactiveObject
|
|||||||
sb.AppendLine($"{cmd} HTTPS_PROXY={Global.HttpProtocol}{address}");
|
sb.AppendLine($"{cmd} HTTPS_PROXY={Global.HttpProtocol}{address}");
|
||||||
sb.AppendLine($"{cmd} ALL_PROXY={Global.Socks5Protocol}{address}");
|
sb.AppendLine($"{cmd} ALL_PROXY={Global.Socks5Protocol}{address}");
|
||||||
|
|
||||||
await SetClipboardDataInteraction.Handle(sb.ToString());
|
await SetClipboardDataInteraction.HandleSafe(sb.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddServerViaClipboard()
|
private async Task AddServerViaClipboard()
|
||||||
@@ -389,14 +389,7 @@ public partial class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (blChange)
|
if (blChange)
|
||||||
{
|
{
|
||||||
try
|
await DispatcherRefreshIconInteraction.HandleSafe(RxVoid.Default);
|
||||||
{
|
|
||||||
await DispatcherRefreshIconInteraction.Handle(RxVoid.Default);
|
|
||||||
}
|
|
||||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
|
||||||
{
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,7 +425,7 @@ public partial class StatusBarViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
||||||
ReloadRequested.Publish();
|
ReloadRequested.Publish();
|
||||||
await DispatcherRefreshIconInteraction.Handle(RxVoid.Default);
|
await DispatcherRefreshIconInteraction.HandleSafe(RxVoid.Default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,7 +462,7 @@ public partial class StatusBarViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var password = await PasswordInputInteraction.Handle(RxVoid.Default);
|
var password = await PasswordInputInteraction.HandleSafe(RxVoid.Default);
|
||||||
if (password.IsNullOrEmpty())
|
if (password.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
_config.TunModeItem.EnableTun = false;
|
_config.TunModeItem.EnableTun = false;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public partial class SubSettingViewModel : MyReactiveObject
|
|||||||
}, canEditRemove);
|
}, canEditRemove);
|
||||||
SubShareCmd = ReactiveCommand.CreateFromTask(async () =>
|
SubShareCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
await ShareSubInteraction.Handle(SelectedSource?.Url);
|
await ShareSubInteraction.HandleSafe(SelectedSource?.Url);
|
||||||
}, canEditRemove);
|
}, canEditRemove);
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
@@ -84,7 +84,7 @@ public partial class SubSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task DeleteSubAsync()
|
private async Task DeleteSubAsync()
|
||||||
{
|
{
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user