diff --git a/src/Kp2aBusinessLogic/IKp2aApp.cs b/src/Kp2aBusinessLogic/IKp2aApp.cs index d4602a17..9a8357f8 100644 --- a/src/Kp2aBusinessLogic/IKp2aApp.cs +++ b/src/Kp2aBusinessLogic/IKp2aApp.cs @@ -29,6 +29,14 @@ namespace keepass2android } + + public enum MessageSeverity + { + Info, + Warning, + Error + } + /// /// Interface through which Activities and the logic layer can access some app specific functionalities and Application static data /// @@ -102,10 +110,13 @@ namespace keepass2android Context ctx, string messageSuffix = ""); - /// - /// Returns a Handler object which can run tasks on the UI thread - /// - Handler UiThreadHandler { get; } + void ShowMessage(Context ctx, int resourceId, MessageSeverity severity); + void ShowMessage(Context ctx, string text, MessageSeverity severity); + + /// + /// Returns a Handler object which can run tasks on the UI thread + /// + Handler UiThreadHandler { get; } IProgressDialog CreateProgressDialog(Context ctx); diff --git a/src/Kp2aBusinessLogic/SelectStorageLocationActivityBase.cs b/src/Kp2aBusinessLogic/SelectStorageLocationActivityBase.cs index df1277bb..f44b11d9 100644 --- a/src/Kp2aBusinessLogic/SelectStorageLocationActivityBase.cs +++ b/src/Kp2aBusinessLogic/SelectStorageLocationActivityBase.cs @@ -94,7 +94,7 @@ namespace keepass2android } if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE"))) { - ShowToast(data.GetStringExtra("EXTRA_ERROR_MESSAGE")); + ShowErrorToast(data.GetStringExtra("EXTRA_ERROR_MESSAGE")); } if (resultCode == Result.Ok) @@ -150,7 +150,7 @@ namespace keepass2android protected abstract void StartFileChooser(string path, int requestCode, bool isForSave); - protected abstract void ShowToast(string text); + protected abstract void ShowErrorToast(string text); protected abstract void ShowInvalidSchemeMessage(string dataString); @@ -208,7 +208,7 @@ namespace keepass2android { return () => { - ShowToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message); + ShowErrorToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message); ReturnCancel(); }; } diff --git a/src/Kp2aBusinessLogic/database/edit/OnFinish.cs b/src/Kp2aBusinessLogic/database/edit/OnFinish.cs index 455e3a53..7c3071e8 100644 --- a/src/Kp2aBusinessLogic/database/edit/OnFinish.cs +++ b/src/Kp2aBusinessLogic/database/edit/OnFinish.cs @@ -130,24 +130,24 @@ namespace keepass2android if ( !String.IsNullOrEmpty(message) ) { Kp2aLog.Log("OnFinish message: " + message); if (makeDialog && ctx != null) - { - try - { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx); - - builder.SetMessage(message) - .SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss()) - .Show(); + { + try + { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx); + + builder.SetMessage(message) + .SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss()) + .Show(); } catch (Exception) - { - Toast.MakeText(ctx, message, ToastLength.Long).Show(); - } - } + { + Toast.MakeText(ctx, message, ToastLength.Long).Show(); + } + } else Toast.MakeText(ctx ?? Application.Context, message, ToastLength.Long).Show(); - } + } } } } diff --git a/src/keepass2android-app/AboutDialog.cs b/src/keepass2android-app/AboutDialog.cs index 203a34c0..421311c3 100644 --- a/src/keepass2android-app/AboutDialog.cs +++ b/src/keepass2android-app/AboutDialog.cs @@ -59,7 +59,7 @@ namespace keepass2android } catch (ActivityNotFoundException) { - Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler, MessageSeverity.Error); } }; @@ -71,7 +71,7 @@ namespace keepass2android } catch (ActivityNotFoundException) { - Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler, MessageSeverity.Error); } }; FindViewById(Resource.Id.translate).Click += delegate @@ -82,7 +82,7 @@ namespace keepass2android } catch (ActivityNotFoundException) { - Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler, MessageSeverity.Error); } }; FindViewById(Resource.Id.donate).Click += delegate { diff --git a/src/keepass2android-app/CreateDatabaseActivity.cs b/src/keepass2android-app/CreateDatabaseActivity.cs index 60da7bea..e417ef34 100644 --- a/src/keepass2android-app/CreateDatabaseActivity.cs +++ b/src/keepass2android-app/CreateDatabaseActivity.cs @@ -183,7 +183,7 @@ namespace keepass2android // Verify that a password or keyfile is set if (password.Length == 0 && !keyfileCheckbox.Checked) { - Toast.MakeText(this, Resource.String.error_nopass, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.error_nopass, MessageSeverity.Error); return; } @@ -207,7 +207,7 @@ namespace keepass2android } catch (Exception) { - Toast.MakeText(this, Resource.String.error_adding_keyfile, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.error_adding_keyfile, MessageSeverity.Error); return; } } @@ -235,7 +235,7 @@ namespace keepass2android if (! pass.Equals(confpass)) { // Passwords do not match - Toast.MakeText(this, Resource.String.error_pass_match, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.error_pass_match, MessageSeverity.Error); return false; } return true; diff --git a/src/keepass2android-app/DisableAutofillForQueryActivity.cs b/src/keepass2android-app/DisableAutofillForQueryActivity.cs index df6e3101..3f5eac86 100644 --- a/src/keepass2android-app/DisableAutofillForQueryActivity.cs +++ b/src/keepass2android-app/DisableAutofillForQueryActivity.cs @@ -41,7 +41,7 @@ namespace keepass2android string requestedUrl = Intent.GetStringExtra(ChooseForAutofillActivityBase.ExtraQueryString); if (requestedUrl == null) { - Toast.MakeText(this, "Cannot execute query for null.", ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, "Cannot execute query for null.", MessageSeverity.Error); RestartApp(); return; } diff --git a/src/keepass2android-app/EntryActivity.cs b/src/keepass2android-app/EntryActivity.cs index 832e850a..c84e108c 100644 --- a/src/keepass2android-app/EntryActivity.cs +++ b/src/keepass2android-app/EntryActivity.cs @@ -78,7 +78,7 @@ namespace keepass2android var task = new EntryActivity.WriteBinaryTask(_activity, App.Kp2a, new ActionOnFinish(_activity, (success, message, activity) => { if (!success) - Toast.MakeText(activity, message, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error); } ), ((EntryActivity)_activity).Entry.Binaries.Get(_binaryToSave), ioc); ProgressTask pt = new ProgressTask(App.Kp2a, _activity, task); @@ -107,8 +107,8 @@ namespace keepass2android public const int requestCodeBinaryFilename = 42376; public const int requestCodeSelFileStorageForWriteAttachment = 42377; - + protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); public static void Launch(Activity act, PwEntry pw, int pos, AppTask appTask, ActivityFlags? flags = null, int historyIndex=-1) { @@ -767,9 +767,9 @@ namespace keepass2android if (parent == null || (parent.Exists() && !parent.IsDirectory)) { - Toast.MakeText(this, + App.Kp2a.ShowMessage(this, Resource.String.error_invalid_path, - ToastLength.Long).Show(); + MessageSeverity.Error); return null; } @@ -778,9 +778,9 @@ namespace keepass2android // Create parent directory if (!parent.Mkdirs()) { - Toast.MakeText(this, + App.Kp2a.ShowMessage(this, Resource.String.error_could_not_create_parent, - ToastLength.Long).Show(); + MessageSeverity.Error); return null; } @@ -794,18 +794,18 @@ namespace keepass2android } catch (Exception exWrite) { - Toast.MakeText(this, + App.Kp2a.ShowMessage(this, GetString(Resource.String.SaveAttachment_Failed, new Java.Lang.Object[] {filename}) - + exWrite.Message, ToastLength.Long).Show(); + + exWrite.Message, MessageSeverity.Error); return null; } finally { MemUtil.ZeroByteArray(pbData); } - Toast.MakeText(this, + App.Kp2a.ShowMessage(this, GetString(Resource.String.SaveAttachment_doneMessage, new Java.Lang.Object[] {filename}), - ToastLength.Short).Show(); + MessageSeverity.Info); return Uri.Parse("content://" + AttachmentContentProvider.Authority + "/" + filename); } @@ -838,7 +838,7 @@ namespace keepass2android catch (ActivityNotFoundException) { //ignore - Toast.MakeText(this, "Couldn't open file", ToastLength.Short).Show(); + App.Kp2a.ShowMessage(this, "Couldn't open file", MessageSeverity.Error); } } @@ -1558,7 +1558,7 @@ namespace keepass2android } catch (ActivityNotFoundException) { - Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.no_url_handler, MessageSeverity.Error); } return true; } diff --git a/src/keepass2android-app/EntryEditActivity.cs b/src/keepass2android-app/EntryEditActivity.cs index d6337052..709c299b 100644 --- a/src/keepass2android-app/EntryEditActivity.cs +++ b/src/keepass2android-app/EntryEditActivity.cs @@ -64,10 +64,10 @@ namespace keepass2android { [Activity(Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden, Theme = "@style/Kp2aTheme_ActionBar")] public class EntryEditActivity : LockCloseActivity { - - - public const String KeyEntry = "entry"; + protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); + + public const String KeyEntry = "entry"; public const String KeyParent = "parent"; public const String KeyTemplateUuid = "KeyTemplateUuid"; @@ -715,7 +715,7 @@ namespace keepass2android } catch(Exception exAttach) { - Toast.MakeText(this, GetString(Resource.String.AttachFailed)+" "+exAttach.Message, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, GetString(Resource.String.AttachFailed)+" "+exAttach.Message, MessageSeverity.Error); } State.EntryModified = true; PopulateBinaries(); @@ -833,7 +833,7 @@ namespace keepass2android string s = Util.GetFilenameFromInternalFileChooser(data, this); if (s == null) { - Toast.MakeText(this, "No URI retrieved.", ToastLength.Short).Show(); + App.Kp2a.ShowMessage(this, "No URI retrieved.", MessageSeverity.Error); return; } uri = Uri.Parse(s); @@ -1139,7 +1139,7 @@ namespace keepass2android } else { - Toast.MakeText(this, "did not find target field", ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, "did not find target field", MessageSeverity.Error); } @@ -1158,7 +1158,8 @@ namespace keepass2android { if (GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this) != ConnectionResult.Success) { - Toast.MakeText(this, Resource.String.qr_scanning_error_no_google_play_services, ToastLength.Long); + App.Kp2a.ShowMessage(this, Resource.String.qr_scanning_error_no_google_play_services, + MessageSeverity.Error); return; } @@ -1178,7 +1179,7 @@ namespace keepass2android } else { - Toast.MakeText(this, "Scanned code should contain an otpauth:// text.", ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, "Scanned code should contain an otpauth:// text.", MessageSeverity.Warning); } })) .AddOnFailureListener(new FailureListener((e) => @@ -1503,7 +1504,7 @@ namespace keepass2android // Require title String title = Util.GetEditText(this, Resource.Id.entry_title); if ( title.Length == 0 ) { - Toast.MakeText(this, Resource.String.error_title_required, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.error_title_required, MessageSeverity.Error); return false; } @@ -1513,7 +1514,7 @@ namespace keepass2android DateTime newExpiry = new DateTime(); if ((State.Entry.Expires) && (!DateTime.TryParse( Util.GetEditText(this,Resource.Id.entry_expires), out newExpiry))) { - Toast.MakeText(this, Resource.String.error_invalid_expiry_date, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.error_invalid_expiry_date, MessageSeverity.Error); return false; } State.Entry.ExpiryTime = newExpiry.ToUniversalTime(); @@ -1527,13 +1528,13 @@ namespace keepass2android string key = keyView.Text; if (String.IsNullOrEmpty(key)) { - Toast.MakeText(this, Resource.String.error_string_key, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.error_string_key, MessageSeverity.Error); return false; } if (allKeys.Contains(key)) { - Toast.MakeText(this, GetString(Resource.String.error_string_duplicate_key, new Object[]{key}), ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, GetString(Resource.String.error_string_duplicate_key, new Object[]{key}), MessageSeverity.Error); return false; } diff --git a/src/keepass2android-app/ExportDatabaseActivity.cs b/src/keepass2android-app/ExportDatabaseActivity.cs index 6193df0b..46898f33 100644 --- a/src/keepass2android-app/ExportDatabaseActivity.cs +++ b/src/keepass2android-app/ExportDatabaseActivity.cs @@ -29,9 +29,9 @@ namespace keepass2android var exportDb = new ExportDatabaseActivity.ExportDb(_activity, App.Kp2a, new ActionOnFinish(_activity, (success, message, activity) => { if (!success) - Toast.MakeText(activity, message, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error); else - Toast.MakeText(activity, _activity.GetString(Resource.String.export_database_successful), ToastLength.Long).Show(); + App.Kp2a.ShowMessage(activity, _activity.GetString(Resource.String.export_database_successful), MessageSeverity.Info); activity.Finish(); } ), _ffp, ioc); diff --git a/src/keepass2android-app/FileSaveProcessManager.cs b/src/keepass2android-app/FileSaveProcessManager.cs index 529d6811..802b1789 100644 --- a/src/keepass2android-app/FileSaveProcessManager.cs +++ b/src/keepass2android-app/FileSaveProcessManager.cs @@ -107,7 +107,7 @@ namespace keepass2android { if (!success) { - Toast.MakeText(activity, messageOrFilename, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(activity, messageOrFilename, MessageSeverity.Error); return; } SaveFile(new IOConnectionInfo { Path = FileSelectHelper.ConvertFilenameToIocPath(messageOrFilename) }); diff --git a/src/keepass2android-app/FileSelectHelper.cs b/src/keepass2android-app/FileSelectHelper.cs index 9e528808..db38d28f 100644 --- a/src/keepass2android-app/FileSelectHelper.cs +++ b/src/keepass2android-app/FileSelectHelper.cs @@ -115,6 +115,7 @@ namespace keepass2android string keyContent = keyContentTxt.Text; string toastMsg = null; + MessageSeverity severity = MessageSeverity.Info; if (!string.IsNullOrEmpty(keyName) && !string.IsNullOrEmpty(keyContent)) { try @@ -128,7 +129,9 @@ namespace keepass2android { toastMsg = ctx.GetString(Resource.String.private_key_save_failed, new Java.Lang.Object[] { e.Message }); - } + severity = MessageSeverity.Error; + + } } else { @@ -136,7 +139,7 @@ namespace keepass2android } if (toastMsg!= null) { - Toast.MakeText(_activity, toastMsg, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(_activity, toastMsg, severity); } UpdatePrivateKeyNames(keyNamesAdapter, fileStorage, ctx); @@ -153,7 +156,7 @@ namespace keepass2android int msgId = deleted ? Resource.String.private_key_delete : Resource.String.private_key_delete_failed; string msg = ctx.GetString(msgId, new Java.Lang.Object[] { keyName }); - Toast.MakeText(_activity, msg, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(_activity, msg, deleted ? MessageSeverity.Info :MessageSeverity.Error); UpdatePrivateKeyNames(keyNamesAdapter, fileStorage, ctx); keySpinner.SetSelection(SftpKeySpinnerCreateNewIdx); @@ -581,9 +584,9 @@ namespace keepass2android // Make sure file name exists if (filename.Length == 0) { - Toast.MakeText(_activity, + App.Kp2a.ShowMessage(_activity, Resource.String.error_filename_required, - ToastLength.Long).Show(); + MessageSeverity.Error); return false; } @@ -604,9 +607,9 @@ namespace keepass2android } catch (NoFileStorageFoundException) { - Toast.MakeText(_activity, + App.Kp2a.ShowMessage(_activity, "Unexpected scheme in "+filename, - ToastLength.Long).Show(); + MessageSeverity.Error); return false; } @@ -620,9 +623,9 @@ namespace keepass2android if (parent == null || (parent.Exists() && !parent.IsDirectory)) { - Toast.MakeText(_activity, + App.Kp2a.ShowMessage(_activity, Resource.String.error_invalid_path, - ToastLength.Long).Show(); + MessageSeverity.Error); return false; } @@ -631,9 +634,9 @@ namespace keepass2android // Create parent dircetory if (!parent.Mkdirs()) { - Toast.MakeText(_activity, + App.Kp2a.ShowMessage(_activity, Resource.String.error_could_not_create_parent, - ToastLength.Long).Show(); + MessageSeverity.Error); return false; } @@ -643,11 +646,11 @@ namespace keepass2android } catch (Java.IO.IOException ex) { - Toast.MakeText( + App.Kp2a.ShowMessage( _activity, _activity.GetText(Resource.String.error_file_not_create) + " " + ex.LocalizedMessage, - ToastLength.Long).Show(); + MessageSeverity.Error); return false; } @@ -700,7 +703,7 @@ namespace keepass2android _activity.StartActivityForResult(i, _requestCode); #else - Toast.MakeText(LocaleManager.LocalizedAppContext, "File chooser is excluded!", ToastLength.Long).Show(); + App.Kp2a.ShowMessage(LocaleManager.LocalizedAppContext, "File chooser is excluded!", MessageSeverity.Error); #endif return true; } @@ -782,7 +785,7 @@ namespace keepass2android { if (!success) { - Toast.MakeText(newActivity, messageOrFilename, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(newActivity, messageOrFilename, MessageSeverity.Error); return; } var ioc = new IOConnectionInfo { Path = ConvertFilenameToIocPath(messageOrFilename) }; diff --git a/src/keepass2android-app/FingerprintSetupActivity.cs b/src/keepass2android-app/FingerprintSetupActivity.cs index b1b06413..f45fd5c2 100644 --- a/src/keepass2android-app/FingerprintSetupActivity.cs +++ b/src/keepass2android-app/FingerprintSetupActivity.cs @@ -251,7 +251,7 @@ namespace keepass2android catch (Exception e) { CheckCurrentRadioButton(); - Toast.MakeText(this, e.ToString(), ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, e.ToString(), MessageSeverity.Error); FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Visible; FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone; } diff --git a/src/keepass2android-app/GeneratePasswordActivity.cs b/src/keepass2android-app/GeneratePasswordActivity.cs index ec1af73d..054fe80c 100644 --- a/src/keepass2android-app/GeneratePasswordActivity.cs +++ b/src/keepass2android-app/GeneratePasswordActivity.cs @@ -543,7 +543,7 @@ namespace keepass2android } catch (Exception e) { - Toast.MakeText(this, e.Message, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, e.Message, MessageSeverity.Error); } return password; diff --git a/src/keepass2android-app/GroupBaseActivity.cs b/src/keepass2android-app/GroupBaseActivity.cs index 7525e610..8f43e58d 100644 --- a/src/keepass2android-app/GroupBaseActivity.cs +++ b/src/keepass2android-app/GroupBaseActivity.cs @@ -56,6 +56,8 @@ namespace keepass2android public const int RequestCodeActivateRealSearch = 12366; + protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); + static readonly Dictionary bottomBarElementsPriority = new Dictionary() { { Resource.Id.cancel_insert_element, 20 }, @@ -927,7 +929,7 @@ namespace keepass2android { ((GroupBaseActivity)activity)?.StopMovingElements(); if (!String.IsNullOrEmpty(message)) - Toast.MakeText(activity, message, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error); })); var progressTask = new ProgressTask(App.Kp2a, this, moveElement); progressTask.Run(); @@ -1328,7 +1330,7 @@ namespace keepass2android { Handler.Post(() => { - Toast.MakeText(ActiveActivity ?? LocaleManager.LocalizedAppContext, "Unrecoverable error: " + Message, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(ActiveActivity ?? LocaleManager.LocalizedAppContext, "Unrecoverable error: " + Message, MessageSeverity.Error); }); App.Kp2a.Lock(false); diff --git a/src/keepass2android-app/GroupEditActivity.cs b/src/keepass2android-app/GroupEditActivity.cs index 92a8a4be..7bab9c03 100644 --- a/src/keepass2android-app/GroupEditActivity.cs +++ b/src/keepass2android-app/GroupEditActivity.cs @@ -111,9 +111,10 @@ namespace keepass2android SetResult (Result.Ok, intent); Finish (); - } else { - Toast.MakeText (this, Resource.String.error_no_name, ToastLength.Long).Show (); - } + } else + { + App.Kp2a.ShowMessage(this, Resource.String.error_no_name, MessageSeverity.Error); + } }; if (Intent.HasExtra(KeyGroupUuid)) diff --git a/src/keepass2android-app/KeeAutoExec.cs b/src/keepass2android-app/KeeAutoExec.cs index 257f4356..4f223e2a 100644 --- a/src/keepass2android-app/KeeAutoExec.cs +++ b/src/keepass2android-app/KeeAutoExec.cs @@ -316,7 +316,7 @@ namespace keepass2android try { ck.AddUserKey(new KcpKeyFile(strAbs)); } catch (InvalidOperationException) { - Toast.MakeText(LocaleManager.LocalizedAppContext,Resource.String.error_adding_keyfile,ToastLength.Long).Show(); + App.Kp2a.ShowMessage(LocaleManager.LocalizedAppContext,Resource.String.error_adding_keyfile, MessageSeverity.Error); return false; } catch (Exception) { throw; } diff --git a/src/keepass2android-app/LockingActivity.cs b/src/keepass2android-app/LockingActivity.cs index 2df074ed..1c9dd9d2 100644 --- a/src/keepass2android-app/LockingActivity.cs +++ b/src/keepass2android-app/LockingActivity.cs @@ -21,8 +21,10 @@ using Android.App; using Android.Content; using Android.Content.PM; using Android.Runtime; +using Android.Views; using Google.Android.Material.Dialog; using keepass2android; +using keepass2android.Utils; namespace keepass2android { @@ -76,19 +78,34 @@ namespace keepass2android base.OnPause(); TimeoutHelper.Pause(this); - } + App.Kp2a.MessagePresenter = new NonePresenter(); + } protected override void OnDestroy() { base.OnDestroy(); GC.Collect(); } - - protected override void OnResume() { - base.OnResume(); - - TimeoutHelper.Resume(this); - } + + protected override void OnResume() + { + base.OnResume(); + + TimeoutHelper.Resume(this); + var snackbarAnchorView = SnackbarAnchorView; + if (snackbarAnchorView != null) + { + App.Kp2a.MessagePresenter = new ChainedSnackbarPresenter(snackbarAnchorView); + } + else + { + App.Kp2a.MessagePresenter = new ToastPresenter(); + } + } + + + protected virtual View? SnackbarAnchorView => null; + public const int RequestCodeChallengeYubikey = 793; diff --git a/src/keepass2android-app/NfcOtpActivity.cs b/src/keepass2android-app/NfcOtpActivity.cs index ceafc5dd..d09d3db4 100644 --- a/src/keepass2android-app/NfcOtpActivity.cs +++ b/src/keepass2android-app/NfcOtpActivity.cs @@ -145,7 +145,7 @@ namespace keepass2android catch (Exception e) { Kp2aLog.LogUnexpectedError(e); - Toast.MakeText(this, "No Yubikey OTP found!", ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, "No Yubikey OTP found!", MessageSeverity.Error); Finish(); return; } diff --git a/src/keepass2android-app/PasswordActivity.cs b/src/keepass2android-app/PasswordActivity.cs index ae50b99a..68b2f7cf 100644 --- a/src/keepass2android-app/PasswordActivity.cs +++ b/src/keepass2android-app/PasswordActivity.cs @@ -66,6 +66,7 @@ using Exception = System.Exception; using String = System.String; using Toolbar = AndroidX.AppCompat.Widget.Toolbar; using AndroidX.Core.Content; +using Google.Android.Material.Snackbar; namespace keepass2android { @@ -309,7 +310,7 @@ namespace keepass2android catch (Exception e) { Kp2aLog.Log(e.ToString()); - Toast.MakeText(this, "Error: " + e.Message, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, "Error: " + e.Message, MessageSeverity.Error); return; } @@ -328,8 +329,7 @@ namespace keepass2android ChallengeInfo temp = _challengeProv.Encrypt(_challengeSecret); if (!temp.Save(_otpAuxIoc)) { - Toast.MakeText(this, Resource.String.ErrorUpdatingChalAuxFile, ToastLength.Long) - .Show(); + App.Kp2a.ShowMessage(this, Resource.String.ErrorUpdatingChalAuxFile, MessageSeverity.Error); return false; } @@ -348,7 +348,7 @@ namespace keepass2android } else { - Toast.MakeText(this, Resource.String.bad_resp, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.bad_resp, MessageSeverity.Error); } } } @@ -458,7 +458,7 @@ namespace keepass2android } else { - Toast.MakeText(Activity,GetErrorMessage(), ToastLength.Long).Show(); + App.Kp2a.ShowMessage(Activity,GetErrorMessage(), MessageSeverity.Error); } return; @@ -957,7 +957,7 @@ namespace keepass2android { btn.SetImageResource(Resource.Drawable.baseline_fingerprint_24); }, 1300); - Toast.MakeText(this, message, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, message, MessageSeverity.Error); } public void OnBiometricAttemptFailed(string message) @@ -1036,7 +1036,7 @@ namespace keepass2android if (_appnameclickCount == 6) { Kp2aLog.LogUnexpectedError(new Exception("some blabla")); - Toast.MakeText(this, "Once again and the app will crash.", ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, "Once again and the app will crash.", MessageSeverity.Warning); } if (_appnameclickCount == 7) @@ -1123,7 +1123,7 @@ namespace keepass2android //For security reasons: discard the OTP (otherwise the user might not select a database now and forget //about the OTP, but it would still be stored in the Intents and later be passed to PasswordActivity again. - Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_no_db), ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_because_no_db), MessageSeverity.Warning); GoToFileSelectActivity(); return false; } @@ -1400,7 +1400,7 @@ namespace keepass2android string errorMessage; if (!CreateCompositeKey(out compositeKey, out errorMessage)) return (() => { - Toast.MakeText(this, errorMessage, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, errorMessage, MessageSeverity.Warning); _performingLoad = false; }); return () => { PerformLoadDatabaseWithCompositeKey(compositeKey); }; @@ -1668,7 +1668,7 @@ namespace keepass2android //did we find a field? if (!foundEmptyField) { - Toast.MakeText(this, GetString(Resource.String.otp_discarded_no_space), ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_no_space), MessageSeverity.Error); } } @@ -1729,80 +1729,128 @@ namespace keepass2android UsedFingerprintUnlock = false; } + protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); + protected override void OnResume() - { - base.OnResume(); - _activityDesign.ReapplyTheme(); + { + base.OnResume(); + + _activityDesign.ReapplyTheme(); - Kp2aLog.Log("starting: " + _starting + ", Finishing: " + IsFinishing + ", _performingLoad: " + _performingLoad); + Kp2aLog.Log("starting: " + _starting + ", Finishing: " + IsFinishing + ", _performingLoad: " + + _performingLoad); - CheckBox cbOfflineMode = (CheckBox)FindViewById(Resource.Id.work_offline); - App.Kp2a.OfflineMode = cbOfflineMode.Checked = App.Kp2a.OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings - LinearLayout offlineModeContainer = FindViewById(Resource.Id.work_offline_container); - var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage; - if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection)) - { - offlineModeContainer.Visibility = ViewStates.Visible; - } - else - { - offlineModeContainer.Visibility = ViewStates.Gone; - App.Kp2a.OfflineMode = false; - } - + CheckBox cbOfflineMode = (CheckBox)FindViewById(Resource.Id.work_offline); + App.Kp2a.OfflineMode = + cbOfflineMode.Checked = + App.Kp2a + .OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings + LinearLayout offlineModeContainer = FindViewById(Resource.Id.work_offline_container); + var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage; + if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection)) + { + offlineModeContainer.Visibility = ViewStates.Visible; + } + else + { + offlineModeContainer.Visibility = ViewStates.Gone; + App.Kp2a.OfflineMode = false; + } - - View killButton = FindViewById(Resource.Id.kill_app); - if (PreferenceManager.GetDefaultSharedPreferences(this) - .GetBoolean(GetString(Resource.String.show_kill_app_key), false)) - { - killButton.Click += (sender, args) => - { - _killOnDestroy = true; + + + View killButton = FindViewById(Resource.Id.kill_app); + if (PreferenceManager.GetDefaultSharedPreferences(this) + .GetBoolean(GetString(Resource.String.show_kill_app_key), false)) + { + killButton.Click += (sender, args) => + { + _killOnDestroy = true; SetResult(Result.Canceled); - Finish(); + Finish(); - }; - killButton.Visibility = ViewStates.Visible; + }; + killButton.Visibility = ViewStates.Visible; - } - else - { - killButton.Visibility = ViewStates.Gone; - } + } + else + { + killButton.Visibility = ViewStates.Gone; + } - TryGetOtpFromClipboard(); + TryGetOtpFromClipboard(); - if (!_keepPasswordInOnResume) - { - if ( - _lastOnPauseTime < DateTime.Now - TimeSpan.FromSeconds(5) //only clear when user left the app for more than 5 seconds (allows to use Yubiclip, also allows to switch shortly to another app) - && - PreferenceManager.GetDefaultSharedPreferences(this) - .GetBoolean(GetString(Resource.String.ClearPasswordOnLeave_key), true)) - { - ClearEnteredPassword(); - } + if (!_keepPasswordInOnResume) + { + if ( + _lastOnPauseTime < + DateTime.Now - + TimeSpan.FromSeconds( + 5) //only clear when user left the app for more than 5 seconds (allows to use Yubiclip, also allows to switch shortly to another app) + && + PreferenceManager.GetDefaultSharedPreferences(this) + .GetBoolean(GetString(Resource.String.ClearPasswordOnLeave_key), true)) + { + ClearEnteredPassword(); + } - } + } - _keepPasswordInOnResume = false; + _keepPasswordInOnResume = false; - MakePasswordMaskedOrVisible(); + MakePasswordMaskedOrVisible(); - UpdateOkButtonState(); + UpdateOkButtonState(); - if (KeyProviderTypes.Contains(KeyProviders.Challenge)) - { - FindViewById(Resource.Id.otpInitView).Visibility = _challengeSecret == null ? ViewStates.Visible : ViewStates.Gone; - } + if (KeyProviderTypes.Contains(KeyProviders.Challenge)) + { + FindViewById(Resource.Id.otpInitView).Visibility = + _challengeSecret == null ? ViewStates.Visible : ViewStates.Gone; + } + /* + Snackbar snackbar = Snackbar + .Make(FindViewById(Resource.Id.main_content), + "snack snack snack snack snack snack snack snack snack snack snack snack snack snack snacksnack snack snacksnack snack snacksnack snack snack snack snack snack snack snack snack snack snack snack snack snack snack snacksnack snack snacksnack snack snacksnack snack snack snack snack snacksnack snack snack ", + Snackbar.LengthLong); + snackbar.SetTextMaxLines(5); + snackbar.SetBackgroundTint(GetColor(Resource.Color.md_theme_secondaryContainer)); + snackbar.SetTextColor(GetColor(Resource.Color.md_theme_onSecondaryContainer)); + snackbar.SetAction("dismiss", + view => snackbar.SetBackgroundTint(GetColor(Resource.Color.md_theme_surfaceContainer))); - //use !IsFinishing to make sure we're not starting another activity when we're already finishing (e.g. due to TaskComplete in OnActivityResult) - //use !performingLoad to make sure we're not already loading the database (after ActivityResult from File-Prepare-Activity; this would cause _loadDbFileTask to exist when we reload later!) - if ( !IsFinishing && !_performingLoad) + snackbar.Show(); + + new Handler().PostDelayed(() => + { + + Snackbar snackbar2 = Snackbar + .Make(FindViewById(Resource.Id.main_content), "snack snack snack ", + Snackbar.LengthLong); + snackbar2.SetTextMaxLines(5); + snackbar2.SetBackgroundTint(GetColor(Resource.Color.md_theme_errorContainer)); + snackbar2.SetTextColor(GetColor(Resource.Color.md_theme_onErrorContainer)); + snackbar2.Show(); + }, 1500); + + + new Handler().PostDelayed(() => + { + + Snackbar snackbar2 = Snackbar + .Make(FindViewById(Resource.Id.main_content), "snack snack warn ", + Snackbar.LengthLong); + snackbar2.SetTextMaxLines(5); + snackbar2.SetBackgroundTint(GetColor(Resource.Color.md_theme_inverseSurface)); + snackbar2.SetTextColor(GetColor(Resource.Color.md_theme_inverseOnSurface)); + snackbar2.Show(); + }, 2500);*/ + + //use !IsFinishing to make sure we're not starting another activity when we're already finishing (e.g. due to TaskComplete in OnActivityResult) + //use !performingLoad to make sure we're not already loading the database (after ActivityResult from File-Prepare-Activity; this would cause _loadDbFileTask to exist when we reload later!) + if ( !IsFinishing && !_performingLoad) { @@ -1954,7 +2002,7 @@ namespace keepass2android btn.Tag = error; - Toast.MakeText(this, Resource.String.fingerprint_reenable2, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.fingerprint_reenable2, MessageSeverity.Error); _biometricDec = null; return false; @@ -2024,7 +2072,7 @@ namespace keepass2android /* private void errorMessage(CharSequence text) { - Toast.MakeText(this, text, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, text, MessageSeverity.Error); } */ @@ -2084,7 +2132,9 @@ namespace keepass2android _act.LoadingErrorCount++; } - if ((Exception != null) && (Exception.Message == KeePassLib.Resources.KLRes.FileCorrupted)) + + + if ((Exception != null) && (Exception.Message == KeePassLib.Resources.KLRes.FileCorrupted)) { Message = _act.GetString(Resource.String.CorruptDatabaseHelp); } @@ -2150,7 +2200,8 @@ namespace keepass2android } else { - DisplayMessage(_act); + MessageSeverity severity = Success ? MessageSeverity.Info : MessageSeverity.Error; + App.Kp2a.ShowMessage(_act, Message, severity); if (Success) { _act.LaunchNextActivity(); @@ -2234,7 +2285,7 @@ namespace keepass2android private void ShowError(string message) { - App.Kp2a.ShowToast(message); + App.Kp2a.ShowToast(message, MessageSeverity.Error); } } private class PasswordActivityBroadcastReceiver : BroadcastReceiver diff --git a/src/keepass2android-app/QueryCredentialsActivity.cs b/src/keepass2android-app/QueryCredentialsActivity.cs index 43948a4e..6c86e965 100644 --- a/src/keepass2android-app/QueryCredentialsActivity.cs +++ b/src/keepass2android-app/QueryCredentialsActivity.cs @@ -128,11 +128,11 @@ namespace keepass2android Kp2aLog.LogUnexpectedError(e); } if (String.IsNullOrEmpty(_requestedUrl)) - Toast.MakeText(this, GetString(Resource.String.query_credentials, new Java.Lang.Object[] {pluginDisplayName}), ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, GetString(Resource.String.query_credentials, new Java.Lang.Object[] {pluginDisplayName}), MessageSeverity.Info); else - Toast.MakeText(this, + App.Kp2a.ShowMessage(this, GetString(Resource.String.query_credentials_for_url, - new Java.Lang.Object[] { pluginDisplayName, _requestedUrl }), ToastLength.Long).Show(); ; + new Java.Lang.Object[] { pluginDisplayName, _requestedUrl }), MessageSeverity.Info); ; } private void StartQuery() diff --git a/src/keepass2android-app/QuickUnlock.cs b/src/keepass2android-app/QuickUnlock.cs index 71cea014..4dd2176a 100644 --- a/src/keepass2android-app/QuickUnlock.cs +++ b/src/keepass2android-app/QuickUnlock.cs @@ -35,6 +35,7 @@ using KeePassLib; using KeePassLib.Serialization; using Toolbar = AndroidX.AppCompat.Widget.Toolbar; using AndroidX.Core.Content; +using keepass2android.Utils; namespace keepass2android { @@ -203,7 +204,7 @@ namespace keepass2android btn.SetImageResource(Resource.Drawable.baseline_fingerprint_24); }, 1300); - Toast.MakeText(this, message, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, message, MessageSeverity.Error); } @@ -325,7 +326,7 @@ namespace keepass2android { Kp2aLog.Log("QuickUnlock not successful!"); App.Kp2a.Lock(false); - Toast.MakeText(this, GetString(Resource.String.QuickUnlock_fail), ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, GetString(Resource.String.QuickUnlock_fail), MessageSeverity.Error); Finish(); } @@ -383,8 +384,9 @@ namespace keepass2android { base.OnResume(); _design.ReapplyTheme(); - - CheckIfUnloaded(); + App.Kp2a.MessagePresenter = new ChainedSnackbarPresenter(FindViewById(Resource.Id.main_content)); + + CheckIfUnloaded(); InitFingerprintUnlock(); @@ -449,7 +451,8 @@ namespace keepass2android protected override void OnPause() { - if (_biometryIdentifier != null) + App.Kp2a.MessagePresenter = new NonePresenter(); + if (_biometryIdentifier != null) { Kp2aLog.Log("FP: Stop listening"); _biometryIdentifier.StopListening(); diff --git a/src/keepass2android-app/Resources/layout/entry_edit.xml b/src/keepass2android-app/Resources/layout/entry_edit.xml index 02a0066b..2f8ba8ee 100644 --- a/src/keepass2android-app/Resources/layout/entry_edit.xml +++ b/src/keepass2android-app/Resources/layout/entry_edit.xml @@ -1,5 +1,11 @@ - + + - \ No newline at end of file + + \ No newline at end of file diff --git a/src/keepass2android-app/SelectCurrentDbActivity.cs b/src/keepass2android-app/SelectCurrentDbActivity.cs index 0430dc8b..6be80475 100644 --- a/src/keepass2android-app/SelectCurrentDbActivity.cs +++ b/src/keepass2android-app/SelectCurrentDbActivity.cs @@ -392,7 +392,7 @@ namespace keepass2android if (ioc.Path.Length == 0) { // No file name - Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.FileNotFound, MessageSeverity.Error); return false; } @@ -400,7 +400,7 @@ namespace keepass2android if (!dbFile.Exists()) { // File does not exist - Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.FileNotFound, MessageSeverity.Error); return false; } } @@ -408,7 +408,7 @@ namespace keepass2android { if (!ioc.Path.StartsWith("content://")) { - Toast.MakeText(this, Resource.String.error_can_not_handle_uri, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resource.String.error_can_not_handle_uri, MessageSeverity.Error); return false; } IoUtil.TryTakePersistablePermissions(this.ContentResolver, intent.Data); @@ -468,7 +468,7 @@ namespace keepass2android } catch (Exception e) { - Toast.MakeText(this, "Failed to open child databases",ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, "Failed to open child databases", MessageSeverity.Error); Kp2aLog.LogUnexpectedError(e); } diff --git a/src/keepass2android-app/SelectStorageLocationActivity.cs b/src/keepass2android-app/SelectStorageLocationActivity.cs index a7b6d122..ecfa3005 100644 --- a/src/keepass2android-app/SelectStorageLocationActivity.cs +++ b/src/keepass2android-app/SelectStorageLocationActivity.cs @@ -70,15 +70,15 @@ namespace keepass2android protected Bundle State { get; set; } - protected override void ShowToast(string text) + protected override void ShowErrorToast(string text) { - Toast.MakeText(this, text, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, text, MessageSeverity.Error); } protected override void ShowInvalidSchemeMessage(string dataString) { - Toast.MakeText(this, Resources.GetString(Resource.String.unknown_uri_scheme, new Java.Lang.Object[] { dataString }), - ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, Resources.GetString(Resource.String.unknown_uri_scheme, new Java.Lang.Object[] { dataString }), + MessageSeverity.Error); } protected override string IntentToFilename(Intent data) @@ -194,7 +194,7 @@ namespace keepass2android StartActivityForResult(intent, requestCode); #else - Toast.MakeText(this, "File chooser is excluded!", ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, "File chooser is excluded!", MessageSeverity.Error); #endif } diff --git a/src/keepass2android-app/SetPasswordDialog.cs b/src/keepass2android-app/SetPasswordDialog.cs index cf581570..61c410f8 100644 --- a/src/keepass2android-app/SetPasswordDialog.cs +++ b/src/keepass2android-app/SetPasswordDialog.cs @@ -57,7 +57,7 @@ namespace keepass2android // Verify that passwords match if ( ! pass.Equals(confpass) ) { // Passwords do not match - Toast.MakeText(Context, Resource.String.error_pass_match, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(Context, Resource.String.error_pass_match, MessageSeverity.Error); return; } @@ -67,7 +67,7 @@ namespace keepass2android // Verify that a password or keyfile is set if ( pass.Length == 0 && keyfile.Length == 0 ) { - Toast.MakeText(Context, Resource.String.error_nopass, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(Context, Resource.String.error_nopass, MessageSeverity.Error); return; } @@ -114,7 +114,7 @@ namespace keepass2android edit.PutString(App.Kp2a.CurrentDb.CurrentFingerprintModePrefKey, FingerprintUnlockMode.Disabled.ToString()); edit.Commit(); - Toast.MakeText(_dlg.Context, Resource.String.fingerprint_reenable, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(_dlg.Context, Resource.String.fingerprint_reenable, MessageSeverity.Warning); _dlg.Context.StartActivity(typeof(BiometricSetupActivity)); } diff --git a/src/keepass2android-app/ShareUrlResults.cs b/src/keepass2android-app/ShareUrlResults.cs index 0eb4e09b..ff3d6887 100644 --- a/src/keepass2android-app/ShareUrlResults.cs +++ b/src/keepass2android-app/ShareUrlResults.cs @@ -121,7 +121,7 @@ namespace keepass2android } catch (Exception e) { - Toast.MakeText(this, e.Message, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, e.Message, MessageSeverity.Error); SetResult(Result.Canceled); Finish(); return; @@ -184,7 +184,7 @@ namespace keepass2android createUrlEntry.Click += (sender, e) => { GroupActivity.Launch(this, new CreateEntryThenCloseTask { Url = searchUrl, ShowUserNotifications = (AppTask as SelectEntryTask)?.ShowUserNotifications ?? ActivationCondition.Always }, new ActivityLaunchModeRequestCode(0)); - Toast.MakeText(this, GetString(Resource.String.select_group_then_add, new Java.Lang.Object[] { GetString(Resource.String.add_entry) }), ToastLength.Long).Show(); + App.Kp2a.ShowMessage(this, GetString(Resource.String.select_group_then_add, new Java.Lang.Object[] { GetString(Resource.String.add_entry) }), MessageSeverity.Info); }; } else diff --git a/src/keepass2android-app/SyncUtil.cs b/src/keepass2android-app/SyncUtil.cs index 19ba57e4..16701ea3 100644 --- a/src/keepass2android-app/SyncUtil.cs +++ b/src/keepass2android-app/SyncUtil.cs @@ -56,7 +56,7 @@ namespace keepass2android OnFinish onFinish = new ActionOnFinish(_activity, (success, message, activity) => { if (!String.IsNullOrEmpty(message)) - Toast.MakeText(activity, message, ToastLength.Long).Show(); + App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error); // Tell the adapter to refresh it's list BaseAdapter adapter = (activity as GroupBaseActivity)?.ListAdapter; diff --git a/src/keepass2android-app/Totp/TrayTotpPluginAdapter.cs b/src/keepass2android-app/Totp/TrayTotpPluginAdapter.cs index 4f1a2e36..c4b343cf 100644 --- a/src/keepass2android-app/Totp/TrayTotpPluginAdapter.cs +++ b/src/keepass2android-app/Totp/TrayTotpPluginAdapter.cs @@ -161,7 +161,7 @@ namespace PluginTOTP return; try { - _uiThreadHandler.Post(() => Toast.MakeText(_ctx, warning, ToastLength.Short).Show()); + _uiThreadHandler.Post(() => App.Kp2a.ShowMessage(_ctx, warning, MessageSeverity.Warning)); } catch (Exception e) { diff --git a/src/keepass2android-app/Utils/MessagePresenter.cs b/src/keepass2android-app/Utils/MessagePresenter.cs new file mode 100644 index 00000000..249bee63 --- /dev/null +++ b/src/keepass2android-app/Utils/MessagePresenter.cs @@ -0,0 +1,162 @@ +using Android.OS; +using Android.Views; +using Google.Android.Material.Snackbar; + +namespace keepass2android.Utils +{ + public struct Message + { + public Message() + { + Text = null; + Severity = MessageSeverity.Info; + } + + public string Text { get; set; } + public MessageSeverity Severity { get; set; } + public bool ShowOnSubsequentScreens { get; set; } = true; + } + public interface IMessagePresenter + { + void ShowMessage(Message message); + + List PendingMessages + { + get; + } + + } + + internal class NonePresenter : IMessagePresenter + { + public void ShowMessage(Message message) + { + PendingMessages.Add(message); + } + + public List PendingMessages + { + get; + set; + } = new List(); + } + + + internal class ToastPresenter : IMessagePresenter + { + public void ShowMessage(Message message) + { + Toast.MakeText(App.Context, message.Text, ToastLength.Long).Show(); + } + + public List PendingMessages => new(); + } + + internal class ChainedSnackbarPresenter: IMessagePresenter + { + internal ChainedSnackbarPresenter(View anchorView) + { + this.AnchorView = anchorView; + + } + + private DateTime nextSnackbarShowTime = DateTime.Now; + private List queuedMessages = new List(); + + public View AnchorView { get; set; } + + private TimeSpan chainingTime = TimeSpan.FromSeconds(1.5); + private Snackbar snackbar; + private Message lastMessage; + + public void ShowMessage(Message message) + { + if (DateTime.Now <= nextSnackbarShowTime) + { + var waitDuration = nextSnackbarShowTime - DateTime.Now; + nextSnackbarShowTime = nextSnackbarShowTime.Add(chainingTime); + + if (!queuedMessages.Any()) + { + if (Looper.MainLooper != null) + { + new Handler(Looper.MainLooper).PostDelayed(ShowNextSnackbar, + (long)waitDuration.TotalMilliseconds); + } + else + { + Kp2aLog.Log("Currently cannot show message"); + } + } + + queuedMessages.Add(message); + + return; + } + ShowSnackbarNow(message); + nextSnackbarShowTime = DateTime.Now.Add(chainingTime); + + } + + public List PendingMessages + { + get + { + List pendingMessages = new List(); + if (snackbar?.IsShown == true) + { + pendingMessages.Add(lastMessage); + } + + pendingMessages.AddRange(queuedMessages); + return pendingMessages; + } + } + + private void ShowNextSnackbar() + { + if (!queuedMessages.Any()) + { + return; + } + + ShowSnackbarNow(queuedMessages.First()); + queuedMessages.RemoveAt(0); + + if (!queuedMessages.Any()) + { + new Handler().PostDelayed(() => { ShowNextSnackbar(); }, (long)chainingTime.TotalMilliseconds); + } + } + + private void ShowSnackbarNow(Message message) + { + snackbar = Snackbar + .Make(AnchorView, message.Text, + Snackbar.LengthLong); + snackbar.SetTextMaxLines(10); + if ((int)Build.VERSION.SdkInt >= 23) + { + if (message.Severity == MessageSeverity.Error) + { + snackbar.SetBackgroundTint(App.Context.GetColor(Resource.Color.md_theme_errorContainer)); + snackbar.SetTextColor(App.Context.GetColor(Resource.Color.md_theme_onErrorContainer)); + } + else if (message.Severity == MessageSeverity.Warning) + { + snackbar.SetBackgroundTint(App.Context.GetColor(Resource.Color.md_theme_inverseSurface)); + snackbar.SetTextColor(App.Context.GetColor(Resource.Color.md_theme_inverseOnSurface)); + } + else + { + snackbar.SetBackgroundTint(App.Context.GetColor(Resource.Color.md_theme_secondaryContainer)); + snackbar.SetTextColor(App.Context.GetColor(Resource.Color.md_theme_onSecondaryContainer)); + } + } + + snackbar.Show(); + lastMessage = message; + + } + } +} diff --git a/src/keepass2android-app/Utils/Util.cs b/src/keepass2android-app/Utils/Util.cs index 67389100..a9bf3d8b 100644 --- a/src/keepass2android-app/Utils/Util.cs +++ b/src/keepass2android-app/Utils/Util.cs @@ -172,289 +172,311 @@ namespace keepass2android } - public class Util { - - - public const String KeyFilename = "fileName"; - public const String KeyServerusername = "serverCredUser"; - public const String KeyServerpassword = "serverCredPwd"; - public const String KeyServercredmode = "serverCredRememberMode"; + public class Util + { - public static void PutIoConnectionToIntent(IOConnectionInfo ioc, Intent i, string prefix="") - { - i.PutExtra(prefix+KeyFilename, ioc.Path); - i.PutExtra(prefix + KeyServerusername, ioc.UserName); - i.PutExtra(prefix + KeyServerpassword, ioc.Password); - i.PutExtra(prefix + KeyServercredmode, (int)ioc.CredSaveMode); - } + public const String KeyFilename = "fileName"; + public const String KeyServerusername = "serverCredUser"; + public const String KeyServerpassword = "serverCredPwd"; + public const String KeyServercredmode = "serverCredRememberMode"; - public static void SetIoConnectionFromIntent(IOConnectionInfo ioc, Intent i, string prefix="") - { - ioc.Path = i.GetStringExtra(prefix + KeyFilename); - ioc.UserName = i.GetStringExtra(prefix + KeyServerusername) ?? ""; - ioc.Password = i.GetStringExtra(prefix + KeyServerpassword) ?? ""; - ioc.CredSaveMode = (IOCredSaveMode)i.GetIntExtra(prefix + KeyServercredmode, (int)IOCredSaveMode.NoSave); - } + + public static void PutIoConnectionToIntent(IOConnectionInfo ioc, Intent i, string prefix = "") + { + i.PutExtra(prefix + KeyFilename, ioc.Path); + i.PutExtra(prefix + KeyServerusername, ioc.UserName); + i.PutExtra(prefix + KeyServerpassword, ioc.Password); + i.PutExtra(prefix + KeyServercredmode, (int)ioc.CredSaveMode); + } + + public static void SetIoConnectionFromIntent(IOConnectionInfo ioc, Intent i, string prefix = "") + { + ioc.Path = i.GetStringExtra(prefix + KeyFilename); + ioc.UserName = i.GetStringExtra(prefix + KeyServerusername) ?? ""; + ioc.Password = i.GetStringExtra(prefix + KeyServerpassword) ?? ""; + ioc.CredSaveMode = (IOCredSaveMode)i.GetIntExtra(prefix + KeyServercredmode, (int)IOCredSaveMode.NoSave); + } public static Bitmap DrawableToBitmap(Drawable drawable) - { - Bitmap bitmap = null; + { + Bitmap bitmap = null; - if (drawable is BitmapDrawable) - { - BitmapDrawable bitmapDrawable = (BitmapDrawable)drawable; - if (bitmapDrawable.Bitmap != null) - { - return bitmapDrawable.Bitmap; - } - } + if (drawable is BitmapDrawable) + { + BitmapDrawable bitmapDrawable = (BitmapDrawable)drawable; + if (bitmapDrawable.Bitmap != null) + { + return bitmapDrawable.Bitmap; + } + } - if (drawable.IntrinsicWidth <= 0 || drawable.IntrinsicHeight <= 0) - { - bitmap = Bitmap.CreateBitmap(1, 1, Bitmap.Config.Argb8888); // Single color bitmap will be created of 1x1 pixel - } - else - { - bitmap = Bitmap.CreateBitmap(drawable.IntrinsicWidth, drawable.IntrinsicHeight, Bitmap.Config.Argb8888); - } + if (drawable.IntrinsicWidth <= 0 || drawable.IntrinsicHeight <= 0) + { + bitmap = Bitmap.CreateBitmap(1, 1, + Bitmap.Config.Argb8888); // Single color bitmap will be created of 1x1 pixel + } + else + { + bitmap = Bitmap.CreateBitmap(drawable.IntrinsicWidth, drawable.IntrinsicHeight, Bitmap.Config.Argb8888); + } - Canvas canvas = new Canvas(bitmap); - drawable.SetBounds(0, 0, canvas.Width, canvas.Height); + Canvas canvas = new Canvas(bitmap); + drawable.SetBounds(0, 0, canvas.Width, canvas.Height); drawable.Draw(canvas); - + return bitmap; - } + } - public static Bitmap ChangeImageColor(Bitmap sourceBitmap, Color color) - { - Bitmap temp = Bitmap.CreateBitmap(sourceBitmap, 0, 0, - sourceBitmap.Width, sourceBitmap.Height); - Bitmap resultBitmap = temp.Copy(Bitmap.Config.Argb8888, true); + public static Bitmap ChangeImageColor(Bitmap sourceBitmap, Color color) + { + Bitmap temp = Bitmap.CreateBitmap(sourceBitmap, 0, 0, + sourceBitmap.Width, sourceBitmap.Height); + Bitmap resultBitmap = temp.Copy(Bitmap.Config.Argb8888, true); Paint p = new Paint(); - ColorFilter filter = new LightingColorFilter(color.ToArgb(), 0); - p.SetColorFilter(filter); + ColorFilter filter = new LightingColorFilter(color.ToArgb(), 0); + p.SetColorFilter(filter); - Canvas canvas = new Canvas(resultBitmap); - canvas.DrawBitmap(resultBitmap, 0, 0, p); - return resultBitmap; - } + Canvas canvas = new Canvas(resultBitmap); + canvas.DrawBitmap(resultBitmap, 0, 0, p); + return resultBitmap; + } public static float convertDpToPixel(float dp, Context context) - { - Resources resources = context.Resources; - DisplayMetrics metrics = resources.DisplayMetrics; - float px = dp * metrics.Density; - return px; - } - public static String GetClipboard(Context context) { - Android.Content.ClipboardManager clipboardManager = (ClipboardManager)context.GetSystemService(Context.ClipboardService); + Resources resources = context.Resources; + DisplayMetrics metrics = resources.DisplayMetrics; + float px = dp * metrics.Density; + return px; + } + + public static String GetClipboard(Context context) + { + Android.Content.ClipboardManager clipboardManager = + (ClipboardManager)context.GetSystemService(Context.ClipboardService); var clip = clipboardManager.PrimaryClip; if (clip != null && clip.ItemCount > 0) { return clip.GetItemAt(0).CoerceToText(context); } + return ""; } - - public static void CopyToClipboard(Context context, String text, bool isProtected) { - Android.Content.ClipboardManager clipboardManager = (ClipboardManager)context.GetSystemService(Context.ClipboardService); - ClipData clipData = Android.Content.ClipData.NewPlainText("KP2A", text); - if (isProtected) - { - //ClipDescription.Extras is only available since 24 - if ((int) Build.VERSION.SdkInt >= 24) - { - var extras = clipData.Description.Extras ?? new Android.OS.PersistableBundle(); - extras.PutBoolean("android.content.extra.IS_SENSITIVE", true); - clipData.Description.Extras = extras; - } - } - clipboardManager.PrimaryClip = clipData; - if (text == "") - { - //on some devices, adding empty text does not seem to work. Try again with some garbage. - clipData = Android.Content.ClipData.NewPlainText("KP2A", "***"); - clipboardManager.PrimaryClip = clipData; - //seems to work better on some devices: - try - { - clipboardManager.Text = text; - } - catch (Exception exception) - { - Kp2aLog.LogUnexpectedError(exception); - } - + public static void CopyToClipboard(Context context, String text, bool isProtected) + { + Android.Content.ClipboardManager clipboardManager = + (ClipboardManager)context.GetSystemService(Context.ClipboardService); + ClipData clipData = Android.Content.ClipData.NewPlainText("KP2A", text); + if (isProtected) + { + //ClipDescription.Extras is only available since 24 + if ((int)Build.VERSION.SdkInt >= 24) + { + var extras = clipData.Description.Extras ?? new Android.OS.PersistableBundle(); + extras.PutBoolean("android.content.extra.IS_SENSITIVE", true); + clipData.Description.Extras = extras; + } + } + + clipboardManager.PrimaryClip = clipData; + if (text == "") + { + //on some devices, adding empty text does not seem to work. Try again with some garbage. + clipData = Android.Content.ClipData.NewPlainText("KP2A", "***"); + clipboardManager.PrimaryClip = clipData; + //seems to work better on some devices: + try + { + clipboardManager.Text = text; + } + catch (Exception exception) + { + Kp2aLog.LogUnexpectedError(exception); + + } + } } - private static readonly Regex ARC_DEVICE_PATTERN = new Regex(".+_cheets|cheets_.+"); + private static readonly Regex ARC_DEVICE_PATTERN = new Regex(".+_cheets|cheets_.+"); - public static bool IsChromeOS(Context context) - { - return - context.PackageManager.HasSystemFeature( - "org.chromium.arc.device_management") // https://stackoverflow.com/a/39843396/292233 - || (Build.Device != null && ARC_DEVICE_PATTERN.IsMatch(Build.Device)) + public static bool IsChromeOS(Context context) + { + return + context.PackageManager.HasSystemFeature( + "org.chromium.arc.device_management") // https://stackoverflow.com/a/39843396/292233 + || (Build.Device != null && ARC_DEVICE_PATTERN.IsMatch(Build.Device)) ; - } + } - public static void GotoUrl(Context context, String url) { - if ( !string.IsNullOrEmpty(url) ) { + public static void GotoUrl(Context context, String url) + { + if (!string.IsNullOrEmpty(url)) + { - if (url.StartsWith("androidapp://")) - { - string packageName = url.Substring("androidapp://".Length); - Intent startKp2aIntent = context.PackageManager.GetLaunchIntentForPackage(packageName); - if (startKp2aIntent != null) - { - startKp2aIntent.AddCategory(Intent.CategoryLauncher); - startKp2aIntent.AddFlags(ActivityFlags.NewTask); - context.StartActivity(startKp2aIntent); - } - } - else - { - Uri uri = Uri.Parse(url); - context.StartActivity(new Intent( - url.StartsWith("tel:") ? Intent.ActionDial : Intent.ActionView, - uri)); - } - } - } - - public static void GotoUrl(Context context, int resId) { - GotoUrl(context, context.GetString(resId)); - } + if (url.StartsWith("androidapp://")) + { + string packageName = url.Substring("androidapp://".Length); + Intent startKp2aIntent = context.PackageManager.GetLaunchIntentForPackage(packageName); + if (startKp2aIntent != null) + { + startKp2aIntent.AddCategory(Intent.CategoryLauncher); + startKp2aIntent.AddFlags(ActivityFlags.NewTask); + context.StartActivity(startKp2aIntent); + } + } + else + { + Uri uri = Uri.Parse(url); + context.StartActivity(new Intent( + url.StartsWith("tel:") ? Intent.ActionDial : Intent.ActionView, + uri)); + } + } + } - public static void GotoMarket(Context context) - { - GotoUrl(context, context.GetString(Resource.String.MarketURL)+context.PackageName); - } + public static void GotoUrl(Context context, int resId) + { + GotoUrl(context, context.GetString(resId)); + } - public static bool GotoDonateUrl(Context context) - { - string donateUrl = context.GetString(Resource.String.donate_url, - new Java.Lang.Object[]{context.Resources.Configuration.Locale.Language, - context.PackageName - }); - try - { - GotoUrl(context, donateUrl); - return true; - } - catch (ActivityNotFoundException) - { - Toast.MakeText(context, Resource.String.error_failed_to_launch_link, ToastLength.Long).Show(); - return false; - } - - } - - public static String GetEditText(Activity act, int resId) { - TextView te = (TextView) act.FindViewById(resId); - System.Diagnostics.Debug.Assert(te != null); - - if (te != null) { - return te.Text; - } else { - return ""; - } - } - - public static void SetEditText(Activity act, int resId, String str) { - TextView te = (TextView) act.FindViewById(resId); - System.Diagnostics.Debug.Assert(te != null); - - if (te != null) { - te.Text = str; - } - } + public static void GotoMarket(Context context) + { + GotoUrl(context, context.GetString(Resource.String.MarketURL) + context.PackageName); + } - /** - * Indicates whether the specified action can be used as an intent. This - * method queries the package manager for installed packages that can - * respond to an intent with the specified action. If no suitable package is - * found, this method returns false. - * - * @param context The application's environment. - * @param action The Intent action to check for availability. - * - * @return True if an Intent with the specified action can be sent and - * responded to, false otherwise. - */ - static bool IsIntentAvailable(Context context, String action, String type, List categories ) - { - PackageManager packageManager = context.PackageManager; - Intent intent = new Intent(action); - if (type != null) - intent.SetType(type); - if (categories != null) - categories.ForEach(c => intent.AddCategory(c)); - IList list = - packageManager.QueryIntentActivities(intent, - PackageInfoFlags.MatchDefaultOnly); - foreach (ResolveInfo i in list) - Kp2aLog.Log(i.ActivityInfo.ApplicationInfo.PackageName); - return list.Count > 0; - } + public static bool GotoDonateUrl(Context context) + { + string donateUrl = context.GetString(Resource.String.donate_url, + new Java.Lang.Object[] + { + context.Resources.Configuration.Locale.Language, + context.PackageName + }); + try + { + GotoUrl(context, donateUrl); + return true; + } + catch (ActivityNotFoundException) + { + App.Kp2a.ShowMessage(context, Resource.String.error_failed_to_launch_link, MessageSeverity.Error); + return false; + } - /// - /// Opens a browse dialog for selecting a file. - /// - /// context activity - /// requestCode for onActivityResult - /// if true, the file location is meant for saving - /// if true, the caller prefers a location that can be used permanently - /// This means that ActionOpenDocument should be used instead of ActionGetContent (for not saving), as ActionGetContent - /// is more for one-time access, but therefore allows possibly more available sources. - public static void ShowBrowseDialog(Activity activity, int requestCodeBrowse, bool forSaving, bool tryGetPermanentAccess) - { - //even though GetContent is not well supported (since Android 7, see https://commonsware.com/Android/previews/appendix-b-android-70) - //we still offer it. - var loadAction = (tryGetPermanentAccess && IsKitKatOrLater) ? - Intent.ActionOpenDocument : Intent.ActionGetContent; - if ((!forSaving) && (IsIntentAvailable(activity, loadAction, "*/*", new List { Intent.CategoryOpenable}))) - { - Intent i = new Intent(loadAction); - i.SetType("*/*"); - i.AddCategory(Intent.CategoryOpenable); + } - activity.StartActivityForResult(i, requestCodeBrowse); - } - else - { - if ((forSaving) && (IsKitKatOrLater)) - { - Intent i = new Intent(Intent.ActionCreateDocument); - i.SetType("*/*"); - i.AddCategory(Intent.CategoryOpenable); + public static String GetEditText(Activity act, int resId) + { + TextView te = (TextView)act.FindViewById(resId); + System.Diagnostics.Debug.Assert(te != null); - activity.StartActivityForResult(i, requestCodeBrowse); - } - else - { - string defaultPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath; + if (te != null) + { + return te.Text; + } + else + { + return ""; + } + } - ShowInternalLocalFileChooser(activity, requestCodeBrowse, forSaving, defaultPath); - } - - } - } + public static void SetEditText(Activity act, int resId, String str) + { + TextView te = (TextView)act.FindViewById(resId); + System.Diagnostics.Debug.Assert(te != null); - public static bool IsKitKatOrLater - { - get { return (int)Build.VERSION.SdkInt >= 19; } - } + if (te != null) + { + te.Text = str; + } + } + + /** + * Indicates whether the specified action can be used as an intent. This + * method queries the package manager for installed packages that can + * respond to an intent with the specified action. If no suitable package is + * found, this method returns false. + * + * @param context The application's environment. + * @param action The Intent action to check for availability. + * + * @return True if an Intent with the specified action can be sent and + * responded to, false otherwise. + */ + static bool IsIntentAvailable(Context context, String action, String type, List categories) + { + PackageManager packageManager = context.PackageManager; + Intent intent = new Intent(action); + if (type != null) + intent.SetType(type); + if (categories != null) + categories.ForEach(c => intent.AddCategory(c)); + IList list = + packageManager.QueryIntentActivities(intent, + PackageInfoFlags.MatchDefaultOnly); + foreach (ResolveInfo i in list) + Kp2aLog.Log(i.ActivityInfo.ApplicationInfo.PackageName); + return list.Count > 0; + } + + /// + /// Opens a browse dialog for selecting a file. + /// + /// context activity + /// requestCode for onActivityResult + /// if true, the file location is meant for saving + /// if true, the caller prefers a location that can be used permanently + /// This means that ActionOpenDocument should be used instead of ActionGetContent (for not saving), as ActionGetContent + /// is more for one-time access, but therefore allows possibly more available sources. + public static void ShowBrowseDialog(Activity activity, int requestCodeBrowse, bool forSaving, + bool tryGetPermanentAccess) + { + //even though GetContent is not well supported (since Android 7, see https://commonsware.com/Android/previews/appendix-b-android-70) + //we still offer it. + var loadAction = (tryGetPermanentAccess && IsKitKatOrLater) + ? Intent.ActionOpenDocument + : Intent.ActionGetContent; + if ((!forSaving) && + (IsIntentAvailable(activity, loadAction, "*/*", new List { Intent.CategoryOpenable }))) + { + Intent i = new Intent(loadAction); + i.SetType("*/*"); + i.AddCategory(Intent.CategoryOpenable); + + activity.StartActivityForResult(i, requestCodeBrowse); + } + else + { + if ((forSaving) && (IsKitKatOrLater)) + { + Intent i = new Intent(Intent.ActionCreateDocument); + i.SetType("*/*"); + i.AddCategory(Intent.CategoryOpenable); + + activity.StartActivityForResult(i, requestCodeBrowse); + } + else + { + string defaultPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath; + + ShowInternalLocalFileChooser(activity, requestCodeBrowse, forSaving, defaultPath); + } + + } + } + + public static bool IsKitKatOrLater + { + get { return (int)Build.VERSION.SdkInt >= 19; } + } public static PendingIntentFlags AddMutabilityFlag(PendingIntentFlags flags, PendingIntentFlags mutability) @@ -464,219 +486,227 @@ namespace keepass2android else return flags; } - private static void ShowInternalLocalFileChooser(Activity act, int requestCodeBrowse, bool forSaving, string defaultPath) - { - + private static void ShowInternalLocalFileChooser(Activity act, int requestCodeBrowse, bool forSaving, + string defaultPath) + { + #if !EXCLUDE_FILECHOOSER - string fileProviderAuthority = act.PackageName+".android-filechooser.localfile"; + string fileProviderAuthority = act.PackageName + ".android-filechooser.localfile"; - Intent i = Keepass2android.Kp2afilechooser.Kp2aFileChooserBridge.GetLaunchFileChooserIntent(act, - fileProviderAuthority, - defaultPath); - if (forSaving) - i.PutExtra("group.pals.android.lib.ui.filechooser.FileChooserActivity.save_dialog", true); + Intent i = Keepass2android.Kp2afilechooser.Kp2aFileChooserBridge.GetLaunchFileChooserIntent(act, + fileProviderAuthority, + defaultPath); + if (forSaving) + i.PutExtra("group.pals.android.lib.ui.filechooser.FileChooserActivity.save_dialog", true); - act.StartActivityForResult(i, requestCodeBrowse); + act.StartActivityForResult(i, requestCodeBrowse); #else - Toast.MakeText(act, "File Chooser excluded!",ToastLength.Long).Show(); + App.Kp2a.ShowMessage(act, "File Chooser excluded!", MessageSeverity.Error); #endif - } + } - /// - /// Tries to extract the filename from the intent. Returns that filename or null if no success - /// (e.g. on content-URIs in Android KitKat+). - /// Guarantees that the file exists. - /// - public static string IntentToFilename(Intent data, Context ctx) - { - string s = GetFilenameFromInternalFileChooser(data, ctx); - if (!String.IsNullOrEmpty(s)) - return s; + /// + /// Tries to extract the filename from the intent. Returns that filename or null if no success + /// (e.g. on content-URIs in Android KitKat+). + /// Guarantees that the file exists. + /// + public static string IntentToFilename(Intent data, Context ctx) + { + string s = GetFilenameFromInternalFileChooser(data, ctx); + if (!String.IsNullOrEmpty(s)) + return s; - try - { - Uri uri = data.Data; - if ((uri != null) && (uri.Scheme == "content")) - { - String[] col = new String[] {MediaStore.MediaColumns.Data}; - - ICursor c1 = ctx.ContentResolver.Query(uri, col, null, null, null); - c1.MoveToFirst(); + try + { + Uri uri = data.Data; + if ((uri != null) && (uri.Scheme == "content")) + { + String[] col = new String[] { MediaStore.MediaColumns.Data }; - var possibleFilename = c1.GetString(0); - if (File.Exists(possibleFilename)) - return possibleFilename; - } - } - catch (Exception e) - { - Kp2aLog.LogUnexpectedError(e); - } + ICursor c1 = ctx.ContentResolver.Query(uri, col, null, null, null); + c1.MoveToFirst(); - String filename = data.Data.Path; - if ((String.IsNullOrEmpty(filename) || (!File.Exists(filename)))) - filename = data.DataString; - if (File.Exists(filename)) - return filename; - //found no valid file - return null; - } + var possibleFilename = c1.GetString(0); + if (File.Exists(possibleFilename)) + return possibleFilename; + } + } + catch (Exception e) + { + Kp2aLog.LogUnexpectedError(e); + } - public static string GetFilenameFromInternalFileChooser(Intent data, Context ctx) - { + String filename = data.Data.Path; + if ((String.IsNullOrEmpty(filename) || (!File.Exists(filename)))) + filename = data.DataString; + if (File.Exists(filename)) + return filename; + //found no valid file + return null; + } + + public static string GetFilenameFromInternalFileChooser(Intent data, Context ctx) + { #if !EXCLUDE_FILECHOOSER - string EXTRA_RESULTS = "group.pals.android.lib.ui.filechooser.FileChooserActivity.results"; - if (data.HasExtra(EXTRA_RESULTS)) - { - IList uris = data.GetParcelableArrayListExtra(EXTRA_RESULTS); - Uri uri = (Uri) uris[0]; - { - return Group.Pals.Android.Lib.UI.Filechooser.Providers.BaseFileProviderUtils.GetRealUri(ctx, uri).ToString(); - } - } + string EXTRA_RESULTS = "group.pals.android.lib.ui.filechooser.FileChooserActivity.results"; + if (data.HasExtra(EXTRA_RESULTS)) + { + IList uris = data.GetParcelableArrayListExtra(EXTRA_RESULTS); + Uri uri = (Uri)uris[0]; + { + return Group.Pals.Android.Lib.UI.Filechooser.Providers.BaseFileProviderUtils.GetRealUri(ctx, uri) + .ToString(); + } + } #endif - return null; - } + return null; + } - public static bool HasActionBar(Activity activity) - { - //Actionbar is available since 11, but the layout has its own "pseudo actionbar" until 13 - return ((int)Android.OS.Build.VERSION.SdkInt >= 14) && (activity.ActionBar != null); - } + public static bool HasActionBar(Activity activity) + { + //Actionbar is available since 11, but the layout has its own "pseudo actionbar" until 13 + return ((int)Android.OS.Build.VERSION.SdkInt >= 14) && (activity.ActionBar != null); + } - public delegate bool FileSelectedHandler(string filename); - - - - public class DismissListener: Java.Lang.Object, IDialogInterfaceOnDismissListener - { - private readonly Action _onDismiss; - - public DismissListener(Action onDismiss) - { - _onDismiss = onDismiss; - } - - public void OnDismiss(IDialogInterface dialog) - { - _onDismiss(); - } - } + public delegate bool FileSelectedHandler(string filename); - class CancelListener: Java.Lang.Object, IDialogInterfaceOnCancelListener - { - private readonly Action _onCancel; - public CancelListener(Action onCancel) - { - _onCancel = onCancel; - } + public class DismissListener : Java.Lang.Object, IDialogInterfaceOnDismissListener + { + private readonly Action _onDismiss; - public void OnCancel(IDialogInterface dialog) - { - _onCancel(); - } - } + public DismissListener(Action onDismiss) + { + _onDismiss = onDismiss; + } - public static void ShowFilenameDialog(Activity activity, Func onOpen, Func onCreate, Action onCancel, bool showBrowseButton, string defaultFilename, string detailsText, int requestCodeBrowse) - { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); - builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.file_selection_filename, null)); - - if (onCancel != null) - builder.SetOnCancelListener(new CancelListener(onCancel)); - Dialog dialog = builder.Create(); - dialog.Show(); - - Button openButton = (Button) dialog.FindViewById(Resource.Id.open); - Button createButton = (Button) dialog.FindViewById(Resource.Id.create); - - TextView enterFilenameDetails = (TextView) dialog.FindViewById(Resource.Id.label_open_by_filename_details); - openButton.Visibility = onOpen != null ? ViewStates.Visible : ViewStates.Gone; - createButton.Visibility = onCreate != null? ViewStates.Visible : ViewStates.Gone; - // Set the initial value of the filename - EditText editFilename = (EditText) dialog.FindViewById(Resource.Id.file_filename); - editFilename.Text = defaultFilename; - enterFilenameDetails.Text = detailsText; - enterFilenameDetails.Visibility = enterFilenameDetails.Text == "" ? ViewStates.Gone : ViewStates.Visible; - - // Open button - if (onOpen != null) - openButton.Click += (sender, args) => - { - String fileName = ((EditText) dialog.FindViewById(Resource.Id.file_filename)).Text; - if (onOpen(fileName, dialog)) - dialog.Dismiss(); - }; - - // Create button - if (onCreate != null) - createButton.Click += (sender, args) => - { - String fileName = ((EditText)dialog.FindViewById(Resource.Id.file_filename)).Text; - if (onCreate(fileName, dialog)) - dialog.Dismiss(); - }; - - Button cancelButton = (Button) dialog.FindViewById(Resource.Id.fnv_cancel); - cancelButton.Click += delegate - { - dialog.Dismiss(); - if (onCancel != null) - onCancel(); - }; - - ImageButton browseButton = (ImageButton) dialog.FindViewById(Resource.Id.browse_button); - if (!showBrowseButton) - { - browseButton.Visibility = ViewStates.Invisible; - } - browseButton.Click += (sender, evt) => - { - string filename = ((EditText) dialog.FindViewById(Resource.Id.file_filename)).Text; - - Util.ShowBrowseDialog(activity, requestCodeBrowse, onCreate != null, /*TODO should we prefer ActionOpenDocument here?*/ false); - - }; - - } - - public static void QueryCredentials(IOConnectionInfo ioc, Action afterQueryCredentials, Activity activity) - { - //Build dialog to query credentials: - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); - builder.SetTitle(activity.GetString(Resource.String.credentials_dialog_title)); - builder.SetPositiveButton(activity.GetString(Android.Resource.String.Ok), (dlgSender, dlgEvt) => - { - Dialog dlg = (Dialog)dlgSender; - string username = ((EditText)dlg.FindViewById(Resource.Id.cred_username)).Text; - string password = ((EditText)dlg.FindViewById(Resource.Id.cred_password)).Text; - int credentialRememberMode = ((Spinner)dlg.FindViewById(Resource.Id.cred_remember_mode)).SelectedItemPosition; - ioc.UserName = username; - ioc.Password = password; - ioc.CredSaveMode = (IOCredSaveMode)credentialRememberMode; - afterQueryCredentials(ioc); - }); - builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.url_credentials, null)); - builder.SetNeutralButton(activity.GetString(Android.Resource.String.Cancel), - (dlgSender, dlgEvt) => { }); - Dialog dialog = builder.Create(); - dialog.Show(); - ((EditText)dialog.FindViewById(Resource.Id.cred_username)).Text = ioc.UserName; - ((EditText)dialog.FindViewById(Resource.Id.cred_password)).Text = ioc.Password; - ((Spinner)dialog.FindViewById(Resource.Id.cred_remember_mode)).SetSelection((int)ioc.CredSaveMode); - } + public void OnDismiss(IDialogInterface dialog) + { + _onDismiss(); + } + } - public static void FinishAndForward(Activity activity, Intent i) - { - i.SetFlags(ActivityFlags.ForwardResult); - activity.StartActivity(i); - activity.Finish(); - } + class CancelListener : Java.Lang.Object, IDialogInterfaceOnCancelListener + { + private readonly Action _onCancel; + + public CancelListener(Action onCancel) + { + _onCancel = onCancel; + } + + public void OnCancel(IDialogInterface dialog) + { + _onCancel(); + } + } + + public static void ShowFilenameDialog(Activity activity, Func onOpen, + Func onCreate, Action onCancel, bool showBrowseButton, string defaultFilename, + string detailsText, int requestCodeBrowse) + { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); + builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.file_selection_filename, null)); + + if (onCancel != null) + builder.SetOnCancelListener(new CancelListener(onCancel)); + Dialog dialog = builder.Create(); + dialog.Show(); + + Button openButton = (Button)dialog.FindViewById(Resource.Id.open); + Button createButton = (Button)dialog.FindViewById(Resource.Id.create); + + TextView enterFilenameDetails = (TextView)dialog.FindViewById(Resource.Id.label_open_by_filename_details); + openButton.Visibility = onOpen != null ? ViewStates.Visible : ViewStates.Gone; + createButton.Visibility = onCreate != null ? ViewStates.Visible : ViewStates.Gone; + // Set the initial value of the filename + EditText editFilename = (EditText)dialog.FindViewById(Resource.Id.file_filename); + editFilename.Text = defaultFilename; + enterFilenameDetails.Text = detailsText; + enterFilenameDetails.Visibility = enterFilenameDetails.Text == "" ? ViewStates.Gone : ViewStates.Visible; + + // Open button + if (onOpen != null) + openButton.Click += (sender, args) => + { + String fileName = ((EditText)dialog.FindViewById(Resource.Id.file_filename)).Text; + if (onOpen(fileName, dialog)) + dialog.Dismiss(); + }; + + // Create button + if (onCreate != null) + createButton.Click += (sender, args) => + { + String fileName = ((EditText)dialog.FindViewById(Resource.Id.file_filename)).Text; + if (onCreate(fileName, dialog)) + dialog.Dismiss(); + }; + + Button cancelButton = (Button)dialog.FindViewById(Resource.Id.fnv_cancel); + cancelButton.Click += delegate + { + dialog.Dismiss(); + if (onCancel != null) + onCancel(); + }; + + ImageButton browseButton = (ImageButton)dialog.FindViewById(Resource.Id.browse_button); + if (!showBrowseButton) + { + browseButton.Visibility = ViewStates.Invisible; + } + + browseButton.Click += (sender, evt) => + { + string filename = ((EditText)dialog.FindViewById(Resource.Id.file_filename)).Text; + + Util.ShowBrowseDialog(activity, requestCodeBrowse, + onCreate != null, /*TODO should we prefer ActionOpenDocument here?*/ false); + + }; + + } + + public static void QueryCredentials(IOConnectionInfo ioc, Action afterQueryCredentials, + Activity activity) + { + //Build dialog to query credentials: + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); + builder.SetTitle(activity.GetString(Resource.String.credentials_dialog_title)); + builder.SetPositiveButton(activity.GetString(Android.Resource.String.Ok), (dlgSender, dlgEvt) => + { + Dialog dlg = (Dialog)dlgSender; + string username = ((EditText)dlg.FindViewById(Resource.Id.cred_username)).Text; + string password = ((EditText)dlg.FindViewById(Resource.Id.cred_password)).Text; + int credentialRememberMode = + ((Spinner)dlg.FindViewById(Resource.Id.cred_remember_mode)).SelectedItemPosition; + ioc.UserName = username; + ioc.Password = password; + ioc.CredSaveMode = (IOCredSaveMode)credentialRememberMode; + afterQueryCredentials(ioc); + }); + builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.url_credentials, null)); + builder.SetNeutralButton(activity.GetString(Android.Resource.String.Cancel), + (dlgSender, dlgEvt) => { }); + Dialog dialog = builder.Create(); + dialog.Show(); + ((EditText)dialog.FindViewById(Resource.Id.cred_username)).Text = ioc.UserName; + ((EditText)dialog.FindViewById(Resource.Id.cred_password)).Text = ioc.Password; + ((Spinner)dialog.FindViewById(Resource.Id.cred_remember_mode)).SetSelection((int)ioc.CredSaveMode); + } + + + public static void FinishAndForward(Activity activity, Intent i) + { + i.SetFlags(ActivityFlags.ForwardResult); + activity.StartActivity(i); + activity.Finish(); + } public static void PrepareDonateOptionMenu(IMenu menu, Context ctx) { @@ -685,12 +715,12 @@ namespace keepass2android { donateItem.SetVisible( !PreferenceManager.GetDefaultSharedPreferences(ctx) - .GetBoolean(ctx.GetString(Resource.String.NoDonateOption_key), false) - ); + .GetBoolean(ctx.GetString(Resource.String.NoDonateOption_key), false) + ); } } - + public static bool GetCloseDatabaseAfterFailedBiometricQuickUnlock(Context ctx) { return (PreferenceManager.GetDefaultSharedPreferences(ctx).GetBoolean( @@ -699,102 +729,110 @@ namespace keepass2android } - + public static void MoveBottomBarButtons(int btn1Id, int btn2Id, int bottomBarId, Activity context) - { - var btn1 = context.FindViewById