add support for yes/no/cancel question during background sync
This commit is contained in:
		| @@ -45,6 +45,8 @@ namespace keepass2android | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public ProgressUiAsStatusLoggerAdapter StatusLogger => _statusLogger; | ||||
|  | ||||
|         private BackgroundOperationRunner() | ||||
|         { | ||||
|             //private constructor | ||||
| @@ -77,7 +79,6 @@ namespace keepass2android | ||||
|                                 if (!_taskQueue.Any()) | ||||
|                                 { | ||||
|                                     _thread = null; | ||||
|                                     _currentlyRunningTask = null; | ||||
|                                     _statusLogger.EndLogging(); | ||||
|                                     break; | ||||
|                                 } | ||||
| @@ -86,7 +87,18 @@ namespace keepass2android | ||||
|                                     _currentlyRunningTask = _taskQueue.Dequeue(); | ||||
|                                 } | ||||
|                             } | ||||
|  | ||||
|                             var originalFinishedHandler = _currentlyRunningTask.operationFinishedHandler; | ||||
|                             _currentlyRunningTask.operationFinishedHandler = new ActionOnOperationFinished(app, ( | ||||
|                                 (success, message, context) => | ||||
|                                 { | ||||
|                                     _currentlyRunningTask = null; | ||||
|                                 }), originalFinishedHandler); | ||||
|                             _currentlyRunningTask.Run(); | ||||
|                             while (_currentlyRunningTask != null) | ||||
|                             { | ||||
|                                 Thread.Sleep(100); | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                     }); | ||||
| @@ -248,13 +260,7 @@ namespace keepass2android | ||||
| 	        get { return _activeActivity; } | ||||
| 	        private set | ||||
| 	        { | ||||
|                 if (_activeActivity != null && _activeActivity != _previouslyActiveContext) | ||||
|                 { | ||||
|                     _previouslyActiveContext = _activeActivity; | ||||
|  | ||||
|  | ||||
|                 } | ||||
| 	            _activeActivity = value; | ||||
|                 _activeActivity = value; | ||||
| 	             | ||||
| 	            if (_activeActivity != null) | ||||
| 	            { | ||||
| @@ -264,18 +270,12 @@ namespace keepass2android | ||||
| 	        } | ||||
| 	    } | ||||
|  | ||||
|         public Context PreviouslyActiveContext | ||||
|         { | ||||
|             get { return _previouslyActiveContext; } | ||||
|             | ||||
|         } | ||||
|  | ||||
| 		private readonly Handler _handler; | ||||
|         private readonly Handler _handler; | ||||
| 		private readonly OperationWithFinishHandler _task; | ||||
| 		private IProgressDialog _progressDialog; | ||||
|         private readonly IKp2aApp _app; | ||||
|         private Java.Lang.Thread _thread; | ||||
| 	    private Context _activeActivity, _previouslyActiveContext; | ||||
| 	    private Context _activeActivity; | ||||
| 	    private ProgressDialogStatusLogger _progressDialogStatusLogger; | ||||
|  | ||||
| 	    public BlockingOperationRunner(IKp2aApp app, OperationWithFinishHandler task) | ||||
|   | ||||
| @@ -82,6 +82,8 @@ namespace keepass2android | ||||
| 								} | ||||
| 								_saveDb = null; | ||||
| 							}), _app.CurrentDb, false, remoteData); | ||||
|                         _saveDb.SetStatusLogger(StatusLogger); | ||||
|                         _saveDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message | ||||
|                         _saveDb.SyncInBackground = false; | ||||
| 						_saveDb.Run(); | ||||
|  | ||||
|   | ||||
| @@ -38,9 +38,6 @@ namespace keepass2android | ||||
| 			_actionToPerform = actionToPerform; | ||||
| 		} | ||||
|  | ||||
| 		//if set to true, the previously active active will be passed to ActionToPerformOnFinish instead null if no activity is on foreground | ||||
|         public bool AllowInactiveActivity { get; set; } | ||||
|  | ||||
|         public override void Run() | ||||
| 		{ | ||||
| 			if (Message == null) | ||||
|   | ||||
| @@ -41,6 +41,8 @@ namespace keepass2android | ||||
| 	    private readonly bool _dontSave; | ||||
| 		private bool requiresSubsequentSync = false; //if true, we need to sync the file after saving. | ||||
|  | ||||
|         public bool DoNotSetStatusLoggerMessage = false; | ||||
|  | ||||
|         /// <summary> | ||||
| 		/// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync | ||||
| 		/// </summary> | ||||
| @@ -104,9 +106,12 @@ namespace keepass2android | ||||
| 				    if (ShowDatabaseIocInStatus) | ||||
| 				        message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")"; | ||||
|  | ||||
|                     StatusLogger.UpdateMessage(message); | ||||
|                      | ||||
| 					IOConnectionInfo ioc = _db.Ioc; | ||||
|                     if (!DoNotSetStatusLoggerMessage) | ||||
|                     { | ||||
|                         StatusLogger.UpdateMessage(message); | ||||
|                     } | ||||
|  | ||||
|                     IOConnectionInfo ioc = _db.Ioc; | ||||
| 					IFileStorage fileStorage = _app.GetFileStorage(ioc); | ||||
|  | ||||
|                     if (SyncInBackground && fileStorage is IOfflineSwitchable offlineSwitchable) | ||||
|   | ||||
| @@ -1305,7 +1305,7 @@ namespace keepass2android | ||||
|             { | ||||
|                 if (Success) | ||||
|                 { | ||||
|                     ((GroupBaseActivity)ActiveContext)?.RefreshIfDirty(); | ||||
|                     (ActiveContext as GroupBaseActivity)?.RefreshIfDirty(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
| @@ -1325,7 +1325,7 @@ namespace keepass2android | ||||
|             { | ||||
|                 if (Success) | ||||
|                 { | ||||
|                     ((GroupBaseActivity)ActiveContext)?.RefreshIfDirty(); | ||||
|                     (ActiveContext as GroupBaseActivity)?.RefreshIfDirty(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|   | ||||
| @@ -1255,5 +1255,5 @@ | ||||
|   <string name="switch_keyboard_for_totp_enabled">Note: You have enabled App - Password access - Autofill-Service - Autofill for TOTP entries. This can cause this window to show when you open an entry with a TOTP.</string> | ||||
|   <string name="switch_keyboard_inside_kp2a_enabled">Note: You have enabled App - Security - Use built-in keyboard inside Keepass2Android. This can cause this window to show when you open the app or edit an entry.</string> | ||||
|   <string name="switch_keyboard_on_search_enabled">Note: You have enabled App - Security - Password access - Keyboard switching - Switch keyboard. This can cause this window to show when you search for an entry from the browser.</string> | ||||
|    | ||||
|   <string name="user_interaction_required">User interaction required. Please open the app.</string> | ||||
| </resources> | ||||
| @@ -17,6 +17,7 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file | ||||
|  | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.Design; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Net.Security; | ||||
| @@ -46,6 +47,7 @@ using keepass2android; | ||||
| using keepass2android.Utils; | ||||
| using KeePassLib.Interfaces; | ||||
| using KeePassLib.Utility; | ||||
| using AlertDialog = AndroidX.AppCompat.App.AlertDialog; | ||||
| using Message = keepass2android.Utils.Message; | ||||
| #if !NoNet | ||||
| #if !EXCLUDE_JAVAFILESTORAGE | ||||
| @@ -480,6 +482,8 @@ namespace keepass2android | ||||
| 		// Whether the app is currently showing a dialog that requires user input, like a yesNoCancel dialog | ||||
| 		private bool _isShowingUserInputDialog = false; | ||||
|         private IMessagePresenter? _messagePresenter; | ||||
|         private YesNoCancelQuestion? _currentlyPendingYesNoCancelQuestion = null; | ||||
|         private Context _activeContext; | ||||
|  | ||||
|         private void AskForReload(Activity activity, Action<bool> actionOnResult) | ||||
| 		{ | ||||
| @@ -591,92 +595,142 @@ namespace keepass2android | ||||
| 			AskYesNoCancel(titleKey, messageKey, yesString, noString, yesHandler, noHandler, cancelHandler, null, messageSuffix); | ||||
| 		} | ||||
|  | ||||
| 		public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey, | ||||
|         class YesNoCancelQuestion | ||||
|         { | ||||
|             private AlertDialog? _dialog; | ||||
|             public UiStringKey TitleKey { get; set; } | ||||
|             public UiStringKey MessageKey { get; set; } | ||||
|             public UiStringKey YesString { get; set; } | ||||
|             public UiStringKey NoString { get; set; } | ||||
|             public EventHandler<DialogClickEventArgs>? YesHandler { get; set; } | ||||
|             public EventHandler<DialogClickEventArgs>? NoHandler { get; set; } | ||||
|             public EventHandler<DialogClickEventArgs>? CancelHandler { get; set; } | ||||
|             public EventHandler DismissHandler { get; set; } | ||||
|             public string MessageSuffix { get; set; } | ||||
|  | ||||
|             public bool TryShow(IKp2aApp app, Action onUserInputDialogClose, Action onUserInputDialogShow) | ||||
|             { | ||||
|                 if (app.ActiveContext is Activity activity) | ||||
|                 { | ||||
|                     Handler handler = new Handler(Looper.MainLooper); | ||||
|                     handler.Post(() => | ||||
|                     { | ||||
|                         if (_dialog is { IsShowing: true }) | ||||
|                         { | ||||
| 							_dialog.Dismiss(); | ||||
|                             _dialog = null; | ||||
|                         } | ||||
|  | ||||
|                         MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); | ||||
|                         builder.SetTitle(app.GetResourceString(TitleKey)); | ||||
|  | ||||
|                         builder.SetMessage(app.GetResourceString(MessageKey) + | ||||
|                                            (MessageSuffix != "" ? " " + MessageSuffix : "")); | ||||
|  | ||||
|                         // _____handlerWithShow are wrappers around given handlers to update _isSHowingYesNoCancelDialog | ||||
|                         // and to show progress dialog after yesNoCancel dialog is closed | ||||
|                         EventHandler<DialogClickEventArgs> yesHandlerWithShow = (sender, args) => | ||||
|                         { | ||||
|                             onUserInputDialogClose(); | ||||
|                             YesHandler.Invoke(sender, args); | ||||
|                         }; | ||||
|                         string yesText = app.GetResourceString(YesString); | ||||
|                         builder.SetPositiveButton(yesText, yesHandlerWithShow); | ||||
|                         string noText = ""; | ||||
|                         if (NoHandler != null) | ||||
|                         { | ||||
|                             EventHandler<DialogClickEventArgs> noHandlerWithShow = (sender, args) => | ||||
|                             { | ||||
|                                 onUserInputDialogClose(); | ||||
|                                 NoHandler.Invoke(sender, args); | ||||
|                             }; | ||||
|  | ||||
|                             noText = app.GetResourceString(NoString); | ||||
|                             builder.SetNegativeButton(noText, noHandlerWithShow); | ||||
|                         } | ||||
|  | ||||
|                         string cancelText = ""; | ||||
|                         if (CancelHandler != null) | ||||
|                         { | ||||
|                             EventHandler<DialogClickEventArgs> cancelHandlerWithShow = (sender, args) => | ||||
|                             { | ||||
|                                 onUserInputDialogClose(); | ||||
|                                 CancelHandler.Invoke(sender, args); | ||||
|                             }; | ||||
|  | ||||
|                             cancelText = App.Context.GetString(Android.Resource.String.Cancel); | ||||
|                             builder.SetNeutralButton(cancelText, | ||||
|                                 cancelHandlerWithShow); | ||||
|                         } | ||||
|  | ||||
|                         _dialog = builder.Create(); | ||||
|                         if (DismissHandler != null) | ||||
|                         { | ||||
|                             _dialog.SetOnDismissListener(new Util.DismissListener(() => | ||||
|                             { | ||||
|                                 onUserInputDialogClose(); | ||||
|                                 DismissHandler(_dialog, EventArgs.Empty); | ||||
|                             })); | ||||
|                         } | ||||
|  | ||||
|                         onUserInputDialogShow(); | ||||
|                         try | ||||
|                         { | ||||
|                             _dialog.Show(); | ||||
|                         } | ||||
|                         catch (Exception e) | ||||
|                         { | ||||
|                             Kp2aLog.LogUnexpectedError(e); | ||||
|                         } | ||||
|  | ||||
|  | ||||
|                         if (yesText.Length + noText.Length + cancelText.Length >= 20) | ||||
|                         { | ||||
|                             try | ||||
|                             { | ||||
|                                 Button button = _dialog.GetButton((int)DialogButtonType.Positive); | ||||
|                                 LinearLayout linearLayout = (LinearLayout)button.Parent; | ||||
|                                 linearLayout.Orientation = Orientation.Vertical; | ||||
|                             } | ||||
|                             catch (Exception e) | ||||
|                             { | ||||
|                                 Kp2aLog.LogUnexpectedError(e); | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                     }); | ||||
|                     return true; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
| 					BackgroundOperationRunner.Instance.StatusLogger?.UpdateSubMessage(App.Context.GetString(Resource.String.user_interaction_required)); | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey, | ||||
| 			UiStringKey yesString, UiStringKey noString, | ||||
| 			EventHandler<DialogClickEventArgs> yesHandler, | ||||
|             EventHandler<DialogClickEventArgs> noHandler, | ||||
|             EventHandler<DialogClickEventArgs> cancelHandler, | ||||
| 			EventHandler dismissHandler,string messageSuffix = "") | ||||
|         { | ||||
| 			Handler handler = new Handler(Looper.MainLooper); | ||||
| 			handler.Post(() => | ||||
| 				{ | ||||
| 					MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ActiveContext); | ||||
| 					builder.SetTitle(GetResourceString(titleKey)); | ||||
|  | ||||
| 					builder.SetMessage(GetResourceString(messageKey) + (messageSuffix != "" ? " " + messageSuffix : "")); | ||||
|  | ||||
| 					// _____handlerWithShow are wrappers around given handlers to update _isSHowingYesNoCancelDialog | ||||
| 					// and to show progress dialog after yesNoCancel dialog is closed | ||||
| 					EventHandler<DialogClickEventArgs> yesHandlerWithShow = (sender, args) => | ||||
| 					{ | ||||
| 						OnUserInputDialogClose(); | ||||
| 						yesHandler.Invoke(sender, args); | ||||
| 					}; | ||||
|                     string yesText = GetResourceString(yesString); | ||||
| 					builder.SetPositiveButton(yesText, yesHandlerWithShow); | ||||
| 					string noText = ""; | ||||
| 					if (noHandler != null) | ||||
| 					{ | ||||
|                         EventHandler<DialogClickEventArgs> noHandlerWithShow = (sender, args) => | ||||
|                         { | ||||
| 							OnUserInputDialogClose(); | ||||
| 							noHandler.Invoke(sender, args); | ||||
| 						}; | ||||
|  | ||||
| 						noText = GetResourceString(noString); | ||||
| 						builder.SetNegativeButton(noText, noHandlerWithShow); | ||||
| 					} | ||||
| 					string cancelText = ""; | ||||
| 					if (cancelHandler != null) | ||||
| 					{ | ||||
| 						EventHandler<DialogClickEventArgs> cancelHandlerWithShow = (sender, args) => | ||||
| 						{ | ||||
| 							OnUserInputDialogClose(); | ||||
| 							cancelHandler.Invoke(sender, args); | ||||
| 						}; | ||||
|  | ||||
| 						cancelText = App.Context.GetString(Android.Resource.String.Cancel); | ||||
| 						builder.SetNeutralButton(cancelText, | ||||
| 												 cancelHandlerWithShow); | ||||
| 					} | ||||
|  | ||||
| 					var dialog = builder.Create(); | ||||
| 					if (dismissHandler != null) | ||||
| 					{ | ||||
| 						dialog.SetOnDismissListener(new Util.DismissListener(() => { | ||||
| 							OnUserInputDialogClose(); | ||||
| 							dismissHandler(dialog, EventArgs.Empty); | ||||
| 						})); | ||||
| 					} | ||||
|  | ||||
| 					OnUserInputDialogShow(); | ||||
|                     try | ||||
|                     { | ||||
|                         dialog.Show(); | ||||
|                     } | ||||
| 					catch (Exception e) | ||||
|                     { | ||||
|                         Kp2aLog.LogUnexpectedError(e); | ||||
|                     } | ||||
|  | ||||
|  | ||||
|                     if (yesText.Length + noText.Length + cancelText.Length >= 20) | ||||
| 					{ | ||||
| 						try | ||||
| 						{ | ||||
| 							Button button = dialog.GetButton((int)DialogButtonType.Positive); | ||||
| 							LinearLayout linearLayout = (LinearLayout)button.Parent; | ||||
| 							linearLayout.Orientation = Orientation.Vertical; | ||||
| 						} | ||||
| 						catch (Exception e) | ||||
| 						{ | ||||
| 							Kp2aLog.LogUnexpectedError(e); | ||||
| 						} | ||||
| 	 | ||||
| 					} | ||||
| 				} | ||||
| 			); | ||||
|             _currentlyPendingYesNoCancelQuestion = new YesNoCancelQuestion() | ||||
|             { | ||||
|                 TitleKey = titleKey, | ||||
|                 MessageKey = messageKey, | ||||
|                 YesString = yesString, | ||||
|                 NoString = noString, | ||||
|                 YesHandler = yesHandler, | ||||
|                 NoHandler = noHandler, | ||||
|                 CancelHandler = cancelHandler, | ||||
|                 DismissHandler = dismissHandler, | ||||
|                 MessageSuffix = messageSuffix | ||||
|             }; | ||||
|             | ||||
|             _currentlyPendingYesNoCancelQuestion.TryShow(this, OnUserInputDialogClose, OnUserInputDialogShow); | ||||
|                      | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| @@ -718,7 +772,9 @@ namespace keepass2android | ||||
| 		private void OnUserInputDialogClose() | ||||
| 		{ | ||||
| 			_isShowingUserInputDialog = false; | ||||
| 			ShowAllActiveProgressDialogs(); | ||||
|             _currentlyPendingYesNoCancelQuestion = null; | ||||
|  | ||||
|             ShowAllActiveProgressDialogs(); | ||||
| 		} | ||||
|  | ||||
|         public Handler UiThreadHandler  | ||||
| @@ -1369,7 +1425,15 @@ namespace keepass2android | ||||
|              | ||||
|         } | ||||
|  | ||||
|         public Context ActiveContext { get; set; } | ||||
|         public Context ActiveContext | ||||
|         { | ||||
|             get => _activeContext; | ||||
|             set | ||||
|             { | ||||
|                 _activeContext = value;  | ||||
|                 _currentlyPendingYesNoCancelQuestion?.TryShow(this, OnUserInputDialogClose, OnUserInputDialogClose); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll