mirror of
https://github.com/moparisthebest/keepass2android
synced 2025-01-30 22:50:21 -05:00
refactoring: interface IAppTask -> abstract class AppTask (allows to implement empty default behaviour in base class, removal of static helper class)
preparations for new task (select entry then close)
This commit is contained in:
parent
f080f13b20
commit
bf139d9059
@ -50,7 +50,7 @@ namespace keepass2android
|
|||||||
public const String KEY_CLOSE_AFTER_CREATE = "close_after_create";
|
public const String KEY_CLOSE_AFTER_CREATE = "close_after_create";
|
||||||
|
|
||||||
|
|
||||||
public static void Launch(Activity act, PwEntry pw, int pos, IAppTask appTask) {
|
public static void Launch(Activity act, PwEntry pw, int pos, AppTask appTask) {
|
||||||
Intent i;
|
Intent i;
|
||||||
|
|
||||||
i = new Intent(act, typeof(EntryActivity));
|
i = new Intent(act, typeof(EntryActivity));
|
||||||
@ -69,7 +69,7 @@ namespace keepass2android
|
|||||||
private bool mShowPassword;
|
private bool mShowPassword;
|
||||||
private int mPos;
|
private int mPos;
|
||||||
|
|
||||||
IAppTask mAppTask;
|
AppTask mAppTask;
|
||||||
|
|
||||||
|
|
||||||
protected void setEntryView() {
|
protected void setEntryView() {
|
||||||
|
@ -74,6 +74,8 @@ namespace keepass2android
|
|||||||
|
|
||||||
bool mCloseForReload;
|
bool mCloseForReload;
|
||||||
|
|
||||||
|
AppTask mAppTask;
|
||||||
|
|
||||||
protected override void OnCreate(Bundle savedInstanceState)
|
protected override void OnCreate(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
base.OnCreate(savedInstanceState);
|
base.OnCreate(savedInstanceState);
|
||||||
@ -86,6 +88,8 @@ namespace keepass2android
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mAppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent);
|
||||||
|
|
||||||
SetContentView(Resource.Layout.entry_edit);
|
SetContentView(Resource.Layout.entry_edit);
|
||||||
mCloseForReload = false;
|
mCloseForReload = false;
|
||||||
|
|
||||||
@ -234,9 +238,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
save.Click += (object sender, EventArgs e) =>
|
save.Click += (object sender, EventArgs e) =>
|
||||||
{
|
{
|
||||||
OnFinish onFinish = new AfterSave(new Handler(), this);
|
SaveEntry();
|
||||||
|
|
||||||
SaveEntry(onFinish);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -284,7 +286,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveEntry(OnFinish onFinish)
|
void SaveEntry()
|
||||||
{
|
{
|
||||||
Database db = App.getDB();
|
Database db = App.getDB();
|
||||||
EntryEditActivity act = this;
|
EntryEditActivity act = this;
|
||||||
@ -359,8 +361,6 @@ namespace keepass2android
|
|||||||
|
|
||||||
newEntry.MaintainBackups(db.pm);
|
newEntry.MaintainBackups(db.pm);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//if ( newEntry.Strings.ReadSafe (PwDefs.TitleField).Equals(State.mEntry.Strings.ReadSafe (PwDefs.TitleField)) ) {
|
//if ( newEntry.Strings.ReadSafe (PwDefs.TitleField).Equals(State.mEntry.Strings.ReadSafe (PwDefs.TitleField)) ) {
|
||||||
// SetResult(KeePass.EXIT_REFRESH);
|
// SetResult(KeePass.EXIT_REFRESH);
|
||||||
//} else {
|
//} else {
|
||||||
@ -368,15 +368,30 @@ namespace keepass2android
|
|||||||
SetResult(KeePass.EXIT_REFRESH_TITLE);
|
SetResult(KeePass.EXIT_REFRESH_TITLE);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
RunnableOnFinish task;
|
RunnableOnFinish runnable;
|
||||||
|
|
||||||
|
ActionOnFinish closeOrShowError = new ActionOnFinish((success, message) => {
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
Finish();
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
OnFinish.displayMessage(this, message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ActionOnFinish afterAddEntry = new ActionOnFinish((success, message) =>
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
mAppTask.AfterAddNewEntry(this);
|
||||||
|
},closeOrShowError);
|
||||||
|
|
||||||
if ( State.mIsNew ) {
|
if ( State.mIsNew ) {
|
||||||
task = AddEntry.getInstance(this, App.getDB(), newEntry, State.parentGroup, onFinish);
|
runnable = AddEntry.getInstance(this, App.getDB(), newEntry, State.parentGroup, afterAddEntry);
|
||||||
} else {
|
} else {
|
||||||
task = new UpdateEntry(this, App.getDB(), initialEntry, newEntry, onFinish);
|
runnable = new UpdateEntry(this, App.getDB(), initialEntry, newEntry, closeOrShowError);
|
||||||
}
|
}
|
||||||
ProgressTask pt = new ProgressTask(act, task, Resource.String.saving_database);
|
ProgressTask pt = new ProgressTask(act, runnable, Resource.String.saving_database);
|
||||||
pt.run();
|
pt.run();
|
||||||
|
|
||||||
|
|
||||||
@ -535,6 +550,12 @@ namespace keepass2android
|
|||||||
populateBinaries();
|
populateBinaries();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnSaveInstanceState(Bundle outState)
|
||||||
|
{
|
||||||
|
base.OnSaveInstanceState(outState);
|
||||||
|
mAppTask.ToBundle(outState);
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnBackPressed()
|
public override void OnBackPressed()
|
||||||
{
|
{
|
||||||
if (State.mEntryModified == false)
|
if (State.mEntryModified == false)
|
||||||
@ -901,22 +922,6 @@ namespace keepass2android
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AfterSave : OnFinish {
|
|
||||||
Activity act;
|
|
||||||
public AfterSave(Handler handler, Activity act): base(handler) {
|
|
||||||
this.act = act;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override void run() {
|
|
||||||
if ( mSuccess ) {
|
|
||||||
act.Finish();
|
|
||||||
} else {
|
|
||||||
displayMessage(act);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,11 +45,11 @@ namespace keepass2android
|
|||||||
|
|
||||||
private const String TAG = "Group Activity:";
|
private const String TAG = "Group Activity:";
|
||||||
|
|
||||||
public static void Launch(Activity act, IAppTask appTask) {
|
public static void Launch(Activity act, AppTask appTask) {
|
||||||
Launch(act, null, appTask);
|
Launch(act, null, appTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Launch (Activity act, PwGroup g, IAppTask appTask)
|
public static void Launch (Activity act, PwGroup g, AppTask appTask)
|
||||||
{
|
{
|
||||||
Intent i;
|
Intent i;
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
protected PwGroup mGroup;
|
protected PwGroup mGroup;
|
||||||
|
|
||||||
internal IAppTask mAppTask;
|
internal AppTask mAppTask;
|
||||||
|
|
||||||
protected override void OnResume() {
|
protected override void OnResume() {
|
||||||
base.OnResume();
|
base.OnResume();
|
||||||
|
@ -87,7 +87,7 @@ namespace keepass2android
|
|||||||
ioc.CredSaveMode = (IOCredSaveMode)i.GetIntExtra(KEY_SERVERCREDMODE, (int) IOCredSaveMode.NoSave);
|
ioc.CredSaveMode = (IOCredSaveMode)i.GetIntExtra(KEY_SERVERCREDMODE, (int) IOCredSaveMode.NoSave);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Launch(Activity act, String fileName, IAppTask appTask) {
|
public static void Launch(Activity act, String fileName, AppTask appTask) {
|
||||||
Java.IO.File dbFile = new Java.IO.File(fileName);
|
Java.IO.File dbFile = new Java.IO.File(fileName);
|
||||||
if ( ! dbFile.Exists() ) {
|
if ( ! dbFile.Exists() ) {
|
||||||
throw new Java.IO.FileNotFoundException();
|
throw new Java.IO.FileNotFoundException();
|
||||||
@ -108,7 +108,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void Launch(Activity act, IOConnectionInfo ioc, IAppTask appTask)
|
public static void Launch(Activity act, IOConnectionInfo ioc, AppTask appTask)
|
||||||
{
|
{
|
||||||
if (ioc.IsLocalFile())
|
if (ioc.IsLocalFile())
|
||||||
{
|
{
|
||||||
@ -279,7 +279,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IAppTask mAppTask;
|
internal AppTask mAppTask;
|
||||||
|
|
||||||
protected override void OnCreate(Bundle savedInstanceState)
|
protected override void OnCreate(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
|
@ -62,131 +62,48 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// interface for "tasks": this are things the user wants to do and which require several activities
|
/// base class for "tasks": this are things the user wants to do and which require several activities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IAppTask
|
public abstract class AppTask
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads the parameters of the task from the given bundle
|
/// Loads the parameters of the task from the given bundle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Setup(Bundle b);
|
public virtual void Setup(Bundle b)
|
||||||
|
{}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the parameters of the task for storage in a bundle or intent
|
/// Returns the parameters of the task for storage in a bundle or intent
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The extras.</value>
|
/// <value>The extras.</value>
|
||||||
IEnumerable<IExtra> Extras { get;}
|
public virtual IEnumerable<IExtra> Extras {
|
||||||
|
|
||||||
void AfterUnlockDatabase(PasswordActivity act);
|
|
||||||
|
|
||||||
bool CloseEntryActivityAfterCreate
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Implementation of IAppTask for "no task currently active" (Null pattern)
|
|
||||||
/// </summary>
|
|
||||||
public class NullTask: IAppTask
|
|
||||||
{
|
|
||||||
|
|
||||||
public void Setup(Bundle b)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<IExtra> Extras
|
|
||||||
{
|
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual void AfterUnlockDatabase(PasswordActivity act)
|
||||||
public void AfterUnlockDatabase(PasswordActivity act)
|
|
||||||
{
|
{
|
||||||
GroupActivity.Launch(act, this);
|
GroupActivity.Launch(act, this);
|
||||||
}
|
}
|
||||||
public bool CloseEntryActivityAfterCreate
|
|
||||||
|
public virtual void AfterAddNewEntry(EntryEditActivity entryEditActivity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool CloseEntryActivityAfterCreate
|
||||||
{
|
{
|
||||||
get { return false;}
|
get { return false;}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// User is about to search an entry for a given URL
|
|
||||||
/// </summary>
|
|
||||||
public class SearchUrlTask: IAppTask
|
|
||||||
{
|
|
||||||
public const String UrlToSearch_key = "UrlToSearch";
|
|
||||||
|
|
||||||
public string UrlToSearchFor
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Setup(Bundle b)
|
|
||||||
{
|
|
||||||
UrlToSearchFor = b.GetString(UrlToSearch_key);
|
|
||||||
}
|
|
||||||
public IEnumerable<IExtra> Extras
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
yield return new StringExtra() { Key=UrlToSearch_key, Value = UrlToSearchFor };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void AfterUnlockDatabase(PasswordActivity act)
|
|
||||||
{
|
|
||||||
ShareUrlResults.Launch(act, this);
|
|
||||||
}
|
|
||||||
public bool CloseEntryActivityAfterCreate
|
|
||||||
{
|
|
||||||
get { return true;}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// User is about to select an entry for use in another app
|
|
||||||
/// </summary>
|
|
||||||
public class SelectEntryTask: IAppTask
|
|
||||||
{
|
|
||||||
public void Setup(Bundle b)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
public IEnumerable<IExtra> Extras
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void AfterUnlockDatabase(PasswordActivity act)
|
|
||||||
{
|
|
||||||
GroupActivity.Launch(act, this);
|
|
||||||
}
|
|
||||||
public bool CloseEntryActivityAfterCreate
|
|
||||||
{
|
|
||||||
//keypoint here: close the app after selecting the entry
|
|
||||||
get { return true;}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public static class AppTask
|
|
||||||
{
|
|
||||||
public const String AppTask_key = "KP2A_APPTASK";
|
public const String AppTask_key = "KP2A_APPTASK";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should be used in OnCreate to (re)create a task
|
/// Should be used in OnCreate to (re)create a task
|
||||||
/// if savedInstanceState is not null, the task is recreated from there. Otherwise it's taken from the intent.
|
/// if savedInstanceState is not null, the task is recreated from there. Otherwise it's taken from the intent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IAppTask GetTaskInOnCreate(Bundle savedInstanceState, Intent intent)
|
public static AppTask GetTaskInOnCreate(Bundle savedInstanceState, Intent intent)
|
||||||
{
|
{
|
||||||
if (savedInstanceState != null)
|
if (savedInstanceState != null)
|
||||||
{
|
{
|
||||||
@ -198,12 +115,12 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IAppTask CreateFromIntent(Intent i)
|
public static AppTask CreateFromIntent(Intent i)
|
||||||
{
|
{
|
||||||
return CreateFromBundle(i.Extras);
|
return CreateFromBundle(i.Extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IAppTask CreateFromBundle(Bundle b)
|
public static AppTask CreateFromBundle(Bundle b)
|
||||||
{
|
{
|
||||||
if (b == null)
|
if (b == null)
|
||||||
return new NullTask();
|
return new NullTask();
|
||||||
@ -219,7 +136,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
if (taskType == type.Name)
|
if (taskType == type.Name)
|
||||||
{
|
{
|
||||||
IAppTask task = (IAppTask)Activator.CreateInstance(type);
|
AppTask task = (AppTask)Activator.CreateInstance(type);
|
||||||
task.Setup(b);
|
task.Setup(b);
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
@ -231,11 +148,11 @@ namespace keepass2android
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the extras of the task to the intent
|
/// Adds the extras of the task to the intent
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void ToIntent(this IAppTask task, Intent intent)
|
public void ToIntent(Intent intent)
|
||||||
{
|
{
|
||||||
AppTask.GetTypeExtra(task.GetType()).ToIntent(intent);
|
AppTask.GetTypeExtra(GetType()).ToIntent(intent);
|
||||||
|
|
||||||
foreach (IExtra extra in task.Extras)
|
foreach (IExtra extra in Extras)
|
||||||
{
|
{
|
||||||
extra.ToIntent(intent);
|
extra.ToIntent(intent);
|
||||||
}
|
}
|
||||||
@ -244,11 +161,11 @@ namespace keepass2android
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the extras of the task to the bundle
|
/// Adds the extras of the task to the bundle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void ToBundle(this IAppTask task, Bundle bundle)
|
public void ToBundle(Bundle bundle)
|
||||||
{
|
{
|
||||||
AppTask.GetTypeExtra(task.GetType()).ToBundle(bundle);
|
AppTask.GetTypeExtra(GetType()).ToBundle(bundle);
|
||||||
|
|
||||||
foreach (IExtra extra in task.Extras)
|
foreach (IExtra extra in Extras)
|
||||||
{
|
{
|
||||||
extra.ToBundle(bundle);
|
extra.ToBundle(bundle);
|
||||||
}
|
}
|
||||||
@ -264,5 +181,94 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementation of AppTask for "no task currently active" (Null pattern)
|
||||||
|
/// </summary>
|
||||||
|
public class NullTask: AppTask
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User is about to search an entry for a given URL
|
||||||
|
/// </summary>
|
||||||
|
public class SearchUrlTask: AppTask
|
||||||
|
{
|
||||||
|
public const String UrlToSearch_key = "UrlToSearch";
|
||||||
|
|
||||||
|
public string UrlToSearchFor
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Setup(Bundle b)
|
||||||
|
{
|
||||||
|
UrlToSearchFor = b.GetString(UrlToSearch_key);
|
||||||
|
}
|
||||||
|
public override IEnumerable<IExtra> Extras
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new StringExtra() { Key=UrlToSearch_key, Value = UrlToSearchFor };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override void AfterUnlockDatabase(PasswordActivity act)
|
||||||
|
{
|
||||||
|
ShareUrlResults.Launch(act, this);
|
||||||
|
}
|
||||||
|
public override bool CloseEntryActivityAfterCreate
|
||||||
|
{
|
||||||
|
get { return true;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User is about to select an entry for use in another app
|
||||||
|
/// </summary>
|
||||||
|
public class SelectEntryTask: AppTask
|
||||||
|
{
|
||||||
|
|
||||||
|
public override bool CloseEntryActivityAfterCreate
|
||||||
|
{
|
||||||
|
//keypoint here: close the app after selecting the entry
|
||||||
|
get { return true;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User is about to create a new entry. The task might already "know" some information about the contents.
|
||||||
|
/// </summary>
|
||||||
|
public class CreateEntryThenCloseTask: AppTask
|
||||||
|
{
|
||||||
|
public const String Url_key = "CreateEntry_Url";
|
||||||
|
|
||||||
|
public string Url
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Setup(Bundle b)
|
||||||
|
{
|
||||||
|
Url = b.GetString(Url_key);
|
||||||
|
}
|
||||||
|
public override IEnumerable<IExtra> Extras
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new StringExtra() { Key = Url_key, Value = Url };
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override bool CloseEntryActivityAfterCreate
|
||||||
|
{
|
||||||
|
//if the user selects an entry before creating the new one, we're not closing the app
|
||||||
|
get { return false;}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,11 +78,15 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void displayMessage(Context ctx) {
|
protected void displayMessage(Context ctx) {
|
||||||
if ( mMessage != null && mMessage.Length > 0 ) {
|
displayMessage(ctx, mMessage);
|
||||||
Toast.MakeText(ctx, mMessage, ToastLength.Long).Show();
|
}
|
||||||
}
|
|
||||||
}
|
public static void displayMessage(Context ctx, string message)
|
||||||
|
{
|
||||||
|
if ( !String.IsNullOrEmpty(message) ) {
|
||||||
|
Toast.MakeText(ctx, message, ToastLength.Long).Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ namespace keepass2android
|
|||||||
view.FileSelectButtons fileSelectButtons;
|
view.FileSelectButtons fileSelectButtons;
|
||||||
bool createdWithActivityResult = false;
|
bool createdWithActivityResult = false;
|
||||||
|
|
||||||
internal IAppTask mAppTask;
|
internal AppTask mAppTask;
|
||||||
|
|
||||||
IOConnectionInfo loadIoc(string defaultFileName)
|
IOConnectionInfo loadIoc(string defaultFileName)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user