diff --git a/src/Core/Services/Logging/ClipLogger.cs b/src/Core/Services/Logging/ClipLogger.cs index 8d28a3eba..28a7f02d1 100644 --- a/src/Core/Services/Logging/ClipLogger.cs +++ b/src/Core/Services/Logging/ClipLogger.cs @@ -1,66 +1,66 @@ -//using System.Runtime.CompilerServices; -//using System.Text; -//using Bit.Core.Abstractions; +using System.Runtime.CompilerServices; +using System.Text; +using Bit.Core.Abstractions; -//#if IOS -//using UIKit; -//#endif +#if IOS +using UIKit; +#endif -//namespace Bit.Core.Services -//{ -// /// -// /// This logger can be used to help debug iOS extensions where we cannot use the .NET debugger yet -// /// so we can use this that copies the logs to the clipboard so one -// /// can paste them and analyze its output. -// /// -// public class ClipLogger : ILogger -// { -// private static readonly StringBuilder _currentBreadcrumbs = new StringBuilder(); +namespace Bit.Core.Services +{ + /// + /// This logger can be used to help debug iOS extensions where we cannot use the .NET debugger yet + /// so we can use this that copies the logs to the clipboard so one + /// can paste them and analyze its output. + /// + public class ClipLogger : ILogger + { + private static readonly StringBuilder _currentBreadcrumbs = new StringBuilder(); -// static ILogger _instance; -// public static ILogger Instance -// { -// get -// { -// if (_instance is null) -// { -// _instance = new ClipLogger(); -// } -// return _instance; -// } -// } + static ILogger _instance; + public static ILogger Instance + { + get + { + if (_instance is null) + { + _instance = new ClipLogger(); + } + return _instance; + } + } -// protected ClipLogger() -// { -// } + protected ClipLogger() + { + } -// public static void Log(string breadcrumb) -// { -// _currentBreadcrumbs.AppendLine($"{DateTime.Now.ToShortTimeString()}: {breadcrumb}"); -//#if IOS -// UIPasteboard.General.String = _currentBreadcrumbs.ToString(); -//#endif -// } + public static void Log(string breadcrumb) + { + _currentBreadcrumbs.AppendLine($"{DateTime.Now.ToShortTimeString()}: {breadcrumb}"); +#if IOS + MainThread.BeginInvokeOnMainThread(() => UIPasteboard.General.String = _currentBreadcrumbs.ToString()); +#endif + } -// public void Error(string message, IDictionary extraData = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) -// { -// var classAndMethod = $"{Path.GetFileNameWithoutExtension(sourceFilePath)}.{memberName}"; -// var filePathAndLineNumber = $"{Path.GetFileName(sourceFilePath)}:{sourceLineNumber}"; -// var properties = new Dictionary -// { -// ["File"] = filePathAndLineNumber, -// ["Method"] = memberName -// }; + public void Error(string message, IDictionary extraData = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) + { + var classAndMethod = $"{Path.GetFileNameWithoutExtension(sourceFilePath)}.{memberName}"; + var filePathAndLineNumber = $"{Path.GetFileName(sourceFilePath)}:{sourceLineNumber}"; + var properties = new Dictionary + { + ["File"] = filePathAndLineNumber, + ["Method"] = memberName + }; -// Log(message ?? $"Error found in: {classAndMethod}, {filePathAndLineNumber}"); -// } + Log(message ?? $"Error found in: {classAndMethod}, {filePathAndLineNumber}"); + } -// public void Exception(Exception ex) => Log(ex?.ToString()); + public void Exception(Exception ex) => Log(ex?.ToString()); -// public Task InitAsync() => Task.CompletedTask; + public Task InitAsync() => Task.CompletedTask; -// public Task IsEnabled() => Task.FromResult(true); + public Task IsEnabled() => Task.FromResult(true); -// public Task SetEnabled(bool value) => Task.CompletedTask; -// } -//} + public Task SetEnabled(bool value) => Task.CompletedTask; + } +} diff --git a/src/Core/Services/Logging/LoggerHelper.cs b/src/Core/Services/Logging/LoggerHelper.cs index d1c815d48..f3e8c419b 100644 --- a/src/Core/Services/Logging/LoggerHelper.cs +++ b/src/Core/Services/Logging/LoggerHelper.cs @@ -21,7 +21,8 @@ namespace Bit.Core.Services #if !FDROID // just in case the caller throws the exception in a moment where the logger can't be resolved // we need to track the error as well - Microsoft.AppCenter.Crashes.Crashes.TrackError(ex); + //Microsoft.AppCenter.Crashes.Crashes.TrackError(ex); + ClipLogger.Log(ex?.ToString()); #endif } } diff --git a/src/Core/Utilities/ThemeManager.cs b/src/Core/Utilities/ThemeManager.cs index 6ff895eb9..ff028d6df 100644 --- a/src/Core/Utilities/ThemeManager.cs +++ b/src/Core/Utilities/ThemeManager.cs @@ -154,7 +154,11 @@ namespace Bit.App.Utilities // Currently on iOS when resuming the app after showing a System "Share/Sheet" (or other similar UI) // MAUI reports the incorrect Theme. To avoid this we are fetching the current OS Theme directly on iOS from the iOS API. // MAUI Issue: https://github.com/dotnet/maui/issues/19614 +#if IOS + public static bool OsDarkModeEnabled(UITraitCollection? traitCollection = null) +#else public static bool OsDarkModeEnabled() +#endif { #if UT return false; @@ -167,8 +171,19 @@ namespace Bit.App.Utilities if (!OperatingSystem.IsIOSVersionAtLeast(13, 0)) return false; - var traits = InvokeOnMainThread(() => WindowStateManager.Default.GetCurrentUIViewController()?.TraitCollection) ?? UITraitCollection.CurrentTraitCollection; - var uiStyle = traits.UserInterfaceStyle; + ClipLogger.Log($"TC, UIStyle: {traitCollection?.UserInterfaceStyle}"); + var uiStyle = traitCollection?.UserInterfaceStyle; + if (traitCollection is null) + { + ClipLogger.Log($"TC null getting trait collection from wsm"); + var traits = InvokeOnMainThread(() => + { + return WindowStateManager.Default.GetCurrentUIViewController()?.TraitCollection; + }) ?? UITraitCollection.CurrentTraitCollection; + uiStyle = traits.UserInterfaceStyle; + } + + ClipLogger.Log($"UIStyle: {uiStyle}"); requestedTheme = uiStyle switch { diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs index 2bcd37f5c..eccb64a6d 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using AuthenticationServices; using Bit.App.Abstractions; @@ -47,6 +48,10 @@ namespace Bit.iOS.Autofill { try { + ClipLogger.Log($"[ViewDidLoad]"); + + ClipLogger.Log($"ViewDidLoad: {TraitCollection?.UserInterfaceStyle}"); + InitAppIfNeededAsync().FireAndForget(ex => OnProvidingCredentialException(ex)); base.ViewDidLoad(); @@ -149,18 +154,6 @@ namespace Bit.iOS.Autofill } } - //public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity) - //{ - // try - // { - // await ProvideCredentialWithoutUserInteractionAsync(credentialIdentity); - // } - // catch (Exception ex) - // { - // OnProvidingCredentialException(ex); - // } - //} - [Export("prepareInterfaceToProvideCredentialForRequest:")] public override async void PrepareInterfaceToProvideCredential(IASCredentialRequest credentialRequest) { @@ -194,18 +187,6 @@ namespace Bit.iOS.Autofill } } - //public override async void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity) - //{ - // try - // { - // await PrepareInterfaceToProvideCredentialAsync(c => c.PasswordCredentialIdentity = credentialIdentity); - // } - // catch (Exception ex) - // { - // OnProvidingCredentialException(ex); - // } - //} - public override async void PrepareInterfaceForExtensionConfiguration() { try @@ -546,7 +527,45 @@ namespace Bit.iOS.Autofill _nfcSession, out _nfcDelegate, out _accountsManager); } - private async Task InitAppIfNeededAsync() + public override void ViewIsAppearing(bool animated) + { + base.ViewIsAppearing(animated); + + if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0)) + { + return; + } + + ClipLogger.Log($"ViewIsAppearing: {TraitCollection?.UserInterfaceStyle}"); + + var appOptions = new AppOptions { IosExtension = true }; + var app = new App.App(appOptions); + ThemeManager.SetTheme(app.Resources); + + iOSCoreHelpers.AppearanceAdjustments(); + + View.BackgroundColor = UIColor.SystemBackground; + Logo.Image = UIImage.FromFile("splash_logo"); + View.SetNeedsLayout(); + + ClipLogger.Log($"Window null: {View.Window is null}"); + + //View.Window?.UpdateTraitsIfNeeded(); + + ServiceContainer.Resolve().Send("update_traits"); + + + //((IUITraitChangeObservable)this).RegisterForTraitChanges((env, traits) => + //{ + // MainThread.BeginInvokeOnMainThread(() => + // { + // View.BackgroundColor = UIColor.SystemBackground; + // Logo.Image = UIImage.FromFile("splash_logo"); + // }); + //}); + } + + private async Task InitAppIfNeededAsync([CallerMemberName] string memberName = "") { if (ServiceContainer.RegisteredServices == null || ServiceContainer.RegisteredServices.Count == 0) { @@ -554,6 +573,13 @@ namespace Bit.iOS.Autofill } await _stateService.Value.ReloadStateAsync(); + ClipLogger.Log($"InitAppIfNeeded {memberName}"); + //if (_viewIsAppeared) + //{ + // var app = new App.App(new AppOptions { IosExtension = true }); + // ThemeManager.SetTheme(app.Resources); + // iOSCoreHelpers.AppearanceAdjustments(); + //} } private void LaunchHomePage() diff --git a/src/iOS.Autofill/ListItems/HeaderItemView.cs b/src/iOS.Autofill/ListItems/HeaderItemView.cs index 6f3451556..636f6238f 100644 --- a/src/iOS.Autofill/ListItems/HeaderItemView.cs +++ b/src/iOS.Autofill/ListItems/HeaderItemView.cs @@ -1,6 +1,7 @@ using Bit.Core.Services; using Bit.iOS.Core.Utilities; using Foundation; +using Microsoft.Maui.ApplicationModel; using ObjCRuntime; using UIKit; @@ -50,6 +51,22 @@ namespace Bit.iOS.Autofill.ListItems _separator.TrailingAnchor.ConstraintEqualTo(ContentView.TrailingAnchor, -7), _separator.BottomAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.BottomAnchor, 2) }); + + if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0)) + { + return; + } + + ((IUITraitChangeObservable)this).RegisterForTraitChanges((env, traits) => + { + ClipLogger.Log($"[HeaderItemView] TraitCollection: {traits.UserInterfaceStyle}"); + MainThread.BeginInvokeOnMainThread(() => + { + ContentView.BackgroundColor = UIColor.SystemBackground; + _header.TextColor = ThemeHelpers.TextColor; + _separator.BackgroundColor = ThemeHelpers.SeparatorColor; + }); + }); } catch (System.Exception ex) { diff --git a/src/iOS.Autofill/LockPasswordViewController.cs b/src/iOS.Autofill/LockPasswordViewController.cs index 9455754a1..54a371d69 100644 --- a/src/iOS.Autofill/LockPasswordViewController.cs +++ b/src/iOS.Autofill/LockPasswordViewController.cs @@ -3,6 +3,7 @@ using Bit.App.Controls; using Bit.Core.Services; using Bit.Core.Utilities; using Bit.iOS.Core.Utilities; +using Microsoft.Maui.ApplicationModel; using UIKit; namespace Bit.iOS.Autofill diff --git a/src/iOS.Autofill/LoginListViewController.cs b/src/iOS.Autofill/LoginListViewController.cs index 37867dc11..cbdfe8638 100644 --- a/src/iOS.Autofill/LoginListViewController.cs +++ b/src/iOS.Autofill/LoginListViewController.cs @@ -161,6 +161,18 @@ namespace Bit.iOS.Autofill } } + protected override void OnUpdateTraitAppearance() + { + base.OnUpdateTraitAppearance(); + + _searchBar.BackgroundColor = _searchBar.BarTintColor = ThemeHelpers.ListHeaderBackgroundColor; + _searchBar.UpdateThemeIfNeeded(); + + TableView.BackgroundColor = ThemeHelpers.BackgroundColor; + + _emptyViewButton.Layer.BorderColor = UIColor.FromName(ColorConstants.LIGHT_TEXT_MUTED).CGColor; + } + public async Task DoFido2GetAssertionAsync(IFido2GetAssertionUserInterface fido2GetAssertionUserInterface) { if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0)) @@ -436,6 +448,20 @@ namespace Bit.iOS.Autofill } }); } + if (message.Command == "update_traits" && UIDevice.CurrentDevice.CheckSystemVersion(17,0)) + { + MainThread.InvokeOnMainThreadAsync(() => + { + ClipLogger.Log($"[{nameof(LoginListViewController)}] updating traits"); + ClipLogger.Log($"[{nameof(LoginListViewController)}] style: {TraitCollection?.UserInterfaceStyle}"); + + UpdateTraitsIfNeeded(); + + OnUpdateTraitAppearance(); + + View.SetNeedsLayout(); + }); + } }); } @@ -535,6 +561,17 @@ namespace Bit.iOS.Autofill } } + //public override void ViewIsAppearing(bool animated) + //{ + // base.ViewIsAppearing(animated); + + // ClipLogger.Log($"[{nameof(LoginListViewController)}] TraitCollection: {TraitCollection?.UserInterfaceStyle}"); + // ((IUITraitChangeObservable)this).RegisterForTraitChanges((env, traits) => + // { + // ClipLogger.Log($"[LoginListViewController] changed TraitCollection: {traits.UserInterfaceStyle}"); + // }); + //} + public void ReloadTableViewData() => TableView.ReloadData(); public class TableSource : BaseLoginListTableSource diff --git a/src/iOS.Autofill/MainInterface.storyboard b/src/iOS.Autofill/MainInterface.storyboard index 0d4109f5d..0b3a2ebdb 100644 --- a/src/iOS.Autofill/MainInterface.storyboard +++ b/src/iOS.Autofill/MainInterface.storyboard @@ -420,6 +420,8 @@ + + @@ -650,6 +652,9 @@ + + + diff --git a/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs b/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs index e8cffc7da..871b573d0 100644 --- a/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs @@ -221,6 +221,29 @@ namespace Bit.iOS.Core.Controllers } } + //public override void ViewIsAppearing(bool animated) + //{ + // base.ViewIsAppearing(animated); + + // if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0)) + // { + // return; + // } + + // ClipLogger.Log($"baselock ViewIsAppearing: {TraitCollection?.UserInterfaceStyle}"); + + // ((IUITraitChangeObservable)this).RegisterForTraitChanges((env, traits) => + // { + // ClipLogger.Log($"[BaseLockPass] TraitCollection: {traits.UserInterfaceStyle}"); + // MainThread.BeginInvokeOnMainThread(() => + // { + // View.BackgroundColor = UIColor.SystemBackground; + // TableView.BackgroundColor = ThemeHelpers.BackgroundColor; + // TableView.SeparatorColor = ThemeHelpers.SeparatorColor; + // }); + // }); + //} + public override void ViewDidAppear(bool animated) { try diff --git a/src/iOS.Core/Controllers/ExtendedUITableViewController.cs b/src/iOS.Core/Controllers/ExtendedUITableViewController.cs index c11ea4c3f..1a4cbbc2c 100644 --- a/src/iOS.Core/Controllers/ExtendedUITableViewController.cs +++ b/src/iOS.Core/Controllers/ExtendedUITableViewController.cs @@ -1,5 +1,4 @@ using Bit.iOS.Core.Utilities; -using System; using UIKit; namespace Bit.iOS.Core.Controllers @@ -24,6 +23,35 @@ namespace Bit.iOS.Core.Controllers public override void ViewDidLoad() { base.ViewDidLoad(); + + if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0)) + { + OnUpdateTraitAppearance(); + } + } + + public override void ViewIsAppearing(bool animated) + { + base.ViewIsAppearing(animated); + + if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0)) + { + return; + } + + OnUpdateTraitAppearance(); + + ((IUITraitChangeObservable)this).RegisterForTraitChanges((env, traits) => + { + MainThread.BeginInvokeOnMainThread(() => + { + OnUpdateTraitAppearance(); + }); + }); + } + + protected virtual void OnUpdateTraitAppearance() + { if (View != null) { View.BackgroundColor = ThemeHelpers.BackgroundColor; diff --git a/src/iOS.Core/Controllers/ExtendedUIViewController.cs b/src/iOS.Core/Controllers/ExtendedUIViewController.cs index b599ac4e2..7205d875d 100644 --- a/src/iOS.Core/Controllers/ExtendedUIViewController.cs +++ b/src/iOS.Core/Controllers/ExtendedUIViewController.cs @@ -1,5 +1,5 @@ +using Bit.Core.Services; using Bit.iOS.Core.Utilities; -using System; using UIKit; namespace Bit.iOS.Core.Controllers @@ -28,9 +28,39 @@ namespace Bit.iOS.Core.Controllers public override void ViewDidLoad() { base.ViewDidLoad(); + + if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0)) + { + OnUpdateTraitAppearance(); + } + } + + public override void ViewIsAppearing(bool animated) + { + base.ViewIsAppearing(animated); + + if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0)) + { + return; + } + + OnUpdateTraitAppearance(); + + ((IUITraitChangeObservable)this).RegisterForTraitChanges((env, traits) => + { + MainThread.BeginInvokeOnMainThread(() => + { + OnUpdateTraitAppearance(); + }); + }); + } + + protected virtual void OnUpdateTraitAppearance() + { if (View != null) { View.BackgroundColor = ThemeHelpers.BackgroundColor; + ClipLogger.Log($"[{GetType().FullName}] back color: {View.BackgroundColor}"); } UpdateNavigationBarTheme(); } diff --git a/src/iOS.Core/Handlers/CustomTabbedHandler.cs b/src/iOS.Core/Handlers/CustomTabbedHandler.cs index 694720534..ac69b3b27 100644 --- a/src/iOS.Core/Handlers/CustomTabbedHandler.cs +++ b/src/iOS.Core/Handlers/CustomTabbedHandler.cs @@ -1,6 +1,7 @@ using Bit.App.Pages; using Bit.App.Utilities; using Bit.Core.Abstractions; +using Bit.Core.Services; using Bit.Core.Utilities; using Bit.iOS.Core.Utilities; using Microsoft.Maui.Controls.Handlers.Compatibility; @@ -21,6 +22,7 @@ namespace Bit.iOS.Core.Handlers { if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY) { + ClipLogger.Log($"CustomTabbedHandler ThemeManager.UPDATED_THEME_MESSAGE_KEY"); MainThread.BeginInvokeOnMainThread(() => { iOSCoreHelpers.AppearanceAdjustments(); diff --git a/src/iOS.Core/Utilities/ThemeHelpers.cs b/src/iOS.Core/Utilities/ThemeHelpers.cs index 4c389b931..6eab19abd 100644 --- a/src/iOS.Core/Utilities/ThemeHelpers.cs +++ b/src/iOS.Core/Utilities/ThemeHelpers.cs @@ -1,4 +1,5 @@ using Bit.App.Utilities; +using Bit.Core.Services; using Microsoft.Maui.Platform; using UIKit; @@ -80,6 +81,8 @@ namespace Bit.iOS.Core.Utilities private static void SetThemeVariables(string theme, bool osDarkModeEnabled) { + ClipLogger.Log($"[SetThemeVariables] {theme}, {osDarkModeEnabled}"); + if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled) { theme = ThemeManager.Dark; diff --git a/src/iOS.Core/Utilities/iOSCoreHelpers.cs b/src/iOS.Core/Utilities/iOSCoreHelpers.cs index 47ec9fc4a..97f10740a 100644 --- a/src/iOS.Core/Utilities/iOSCoreHelpers.cs +++ b/src/iOS.Core/Utilities/iOSCoreHelpers.cs @@ -124,7 +124,7 @@ namespace Bit.iOS.Core.Utilities else { #if DEBUG - logger = DebugLogger.Instance; + logger = ClipLogger.Instance; #else logger = Logger.Instance; #endif @@ -248,9 +248,9 @@ namespace Bit.iOS.Core.Utilities var bootstrapTask = BootstrapAsync(postBootstrapFunc); } - public static void AppearanceAdjustments() + public static void AppearanceAdjustments(UITraitCollection? traitCollection = null) { - ThemeHelpers.SetAppearance(ThemeManager.GetTheme(), ThemeManager.OsDarkModeEnabled()); + ThemeHelpers.SetAppearance(ThemeManager.GetTheme(), ThemeManager.OsDarkModeEnabled(traitCollection)); UIApplication.SharedApplication.StatusBarHidden = false; UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.LightContent; } @@ -292,6 +292,21 @@ namespace Bit.iOS.Core.Utilities { ListenYubiKey(listen, deviceActionService, nfcSession, nfcDelegate); } + else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY) + { + MainThread.BeginInvokeOnMainThread(() => + { + try + { + ClipLogger.Log($"message updated theme message key"); + AppearanceAdjustments(); + } + catch (Exception ex) + { + LoggerHelper.LogEvenIfCantBeResolved(ex); + } + }); + } }); } diff --git a/src/iOS.Core/Views/CipherLoginTableViewCell.cs b/src/iOS.Core/Views/CipherLoginTableViewCell.cs index c0c0fd6e9..3bb48c859 100644 --- a/src/iOS.Core/Views/CipherLoginTableViewCell.cs +++ b/src/iOS.Core/Views/CipherLoginTableViewCell.cs @@ -93,6 +93,21 @@ namespace Bit.iOS.Core.Views _separator.TrailingAnchor.ConstraintEqualTo(ContentView.TrailingAnchor, -7), _separator.BottomAnchor.ConstraintEqualTo(ContentView.BottomAnchor, 0) }); + + ((IUITraitChangeObservable)this).RegisterForTraitChanges((env, traits) => + { + ClipLogger.Log($"[CipherLoginTableViewCell] TraitCollection: {traits.UserInterfaceStyle}"); + MainThread.BeginInvokeOnMainThread(() => + { + ContentView.BackgroundColor = UIColor.SystemBackground; + _title.TextColor = ThemeHelpers.TextColor; + _subtitle.TextColor = ThemeHelpers.MutedColor; + _mainIcon.TextColor = ThemeHelpers.PrimaryColor; + _orgIcon.TextColor = ThemeHelpers.MutedColor; + _separator.BackgroundColor = ThemeHelpers.SeparatorColor; + _mainStackView.BackgroundColor = UIColor.SystemCyan; + }); + }); } catch (System.Exception ex) {