[BEEEP] [PS-940] Support for dark theme selection while using Default (System) theme (#1959)

* support for dark theme selection while using Default (System) theme

* refinements
This commit is contained in:
mp-bw 2022-06-21 14:59:30 -04:00 committed by GitHub
parent c892e9fa57
commit 109aeb49e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 3710 additions and 5592 deletions

View File

@ -59,10 +59,10 @@ namespace Bit.Droid.Utilities
{ {
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled) if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
{ {
theme = "dark"; theme = ThemeManager.Dark;
} }
if (theme == "dark" || theme == "black" || theme == "nord") if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord)
{ {
LightTheme = false; LightTheme = false;
} }

View File

@ -33,6 +33,23 @@
StyleClass="box-footer-label" StyleClass="box-footer-label"
Text="{u:I18n ThemeDescription}" /> Text="{u:I18n ThemeDescription}" />
</StackLayout> </StackLayout>
<StackLayout
StyleClass="box"
IsVisible="{Binding ShowAutoDarkThemeOptions}">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label
Text="{u:I18n DefaultDarkTheme}"
StyleClass="box-label" />
<Picker
x:Name="_autoDarkThemePicker"
ItemsSource="{Binding AutoDarkThemeOptions, Mode=OneTime}"
SelectedIndex="{Binding AutoDarkThemeSelectedIndex}"
StyleClass="box-value" />
</StackLayout>
<Label
StyleClass="box-footer-label"
Text="{u:I18n DefaultDarkThemeDescription}" />
</StackLayout>
<StackLayout StyleClass="box"> <StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform"> <StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label <Label

View File

@ -19,6 +19,7 @@ namespace Bit.App.Pages
_vm = BindingContext as OptionsPageViewModel; _vm = BindingContext as OptionsPageViewModel;
_vm.Page = this; _vm.Page = this;
_themePicker.ItemDisplayBinding = new Binding("Value"); _themePicker.ItemDisplayBinding = new Binding("Value");
_autoDarkThemePicker.ItemDisplayBinding = new Binding("Value");
_uriMatchPicker.ItemDisplayBinding = new Binding("Value"); _uriMatchPicker.ItemDisplayBinding = new Binding("Value");
_clearClipboardPicker.ItemDisplayBinding = new Binding("Value"); _clearClipboardPicker.ItemDisplayBinding = new Binding("Value");
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
@ -29,6 +30,7 @@ namespace Bit.App.Pages
else else
{ {
_themePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished); _themePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_autoDarkThemePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_uriMatchPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished); _uriMatchPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_clearClipboardPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished); _clearClipboardPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
} }

View File

@ -23,6 +23,7 @@ namespace Bit.App.Pages
private bool _disableAutoTotpCopy; private bool _disableAutoTotpCopy;
private int _clearClipboardSelectedIndex; private int _clearClipboardSelectedIndex;
private int _themeSelectedIndex; private int _themeSelectedIndex;
private int _autoDarkThemeSelectedIndex;
private int _uriMatchSelectedIndex; private int _uriMatchSelectedIndex;
private bool _inited; private bool _inited;
private bool _updatingAutofill; private bool _updatingAutofill;
@ -53,10 +54,16 @@ namespace Bit.App.Pages
ThemeOptions = new List<KeyValuePair<string, string>> ThemeOptions = new List<KeyValuePair<string, string>>
{ {
new KeyValuePair<string, string>(null, AppResources.ThemeDefault), new KeyValuePair<string, string>(null, AppResources.ThemeDefault),
new KeyValuePair<string, string>("light", AppResources.Light), new KeyValuePair<string, string>(ThemeManager.Light, AppResources.Light),
new KeyValuePair<string, string>("dark", AppResources.Dark), new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
new KeyValuePair<string, string>("black", AppResources.Black), new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
new KeyValuePair<string, string>("nord", "Nord"), new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
};
AutoDarkThemeOptions = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
}; };
UriMatchOptions = new List<KeyValuePair<UriMatchType?, string>> UriMatchOptions = new List<KeyValuePair<UriMatchType?, string>>
{ {
@ -71,6 +78,7 @@ namespace Bit.App.Pages
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; } public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
public List<KeyValuePair<string, string>> ThemeOptions { get; set; } public List<KeyValuePair<string, string>> ThemeOptions { get; set; }
public List<KeyValuePair<string, string>> AutoDarkThemeOptions { get; set; }
public List<KeyValuePair<UriMatchType?, string>> UriMatchOptions { get; set; } public List<KeyValuePair<UriMatchType?, string>> UriMatchOptions { get; set; }
public int ClearClipboardSelectedIndex public int ClearClipboardSelectedIndex
@ -80,7 +88,7 @@ namespace Bit.App.Pages
{ {
if (SetProperty(ref _clearClipboardSelectedIndex, value)) if (SetProperty(ref _clearClipboardSelectedIndex, value))
{ {
var task = SaveClipboardChangedAsync(); SaveClipboardChangedAsync().FireAndForget();
} }
} }
} }
@ -90,9 +98,25 @@ namespace Bit.App.Pages
get => _themeSelectedIndex; get => _themeSelectedIndex;
set set
{ {
if (SetProperty(ref _themeSelectedIndex, value)) if (SetProperty(ref _themeSelectedIndex, value,
additionalPropertyNames: new[] { nameof(ShowAutoDarkThemeOptions) })
)
{ {
var task = SaveThemeAsync(); SaveThemeAsync().FireAndForget();
}
}
}
public bool ShowAutoDarkThemeOptions => ThemeOptions[ThemeSelectedIndex].Key == null;
public int AutoDarkThemeSelectedIndex
{
get => _autoDarkThemeSelectedIndex;
set
{
if (SetProperty(ref _autoDarkThemeSelectedIndex, value))
{
SaveThemeAsync().FireAndForget();
} }
} }
} }
@ -104,7 +128,7 @@ namespace Bit.App.Pages
{ {
if (SetProperty(ref _uriMatchSelectedIndex, value)) if (SetProperty(ref _uriMatchSelectedIndex, value))
{ {
var task = SaveDefaultUriAsync(); SaveDefaultUriAsync().FireAndForget();
} }
} }
} }
@ -116,7 +140,7 @@ namespace Bit.App.Pages
{ {
if (SetProperty(ref _disableFavicon, value)) if (SetProperty(ref _disableFavicon, value))
{ {
var task = UpdateDisableFaviconAsync(); UpdateDisableFaviconAsync().FireAndForget();
} }
} }
} }
@ -128,7 +152,7 @@ namespace Bit.App.Pages
{ {
if (SetProperty(ref _disableAutoTotpCopy, value)) if (SetProperty(ref _disableAutoTotpCopy, value))
{ {
var task = UpdateAutoTotpCopyAsync(); UpdateAutoTotpCopyAsync().FireAndForget();
} }
} }
} }
@ -140,7 +164,7 @@ namespace Bit.App.Pages
{ {
if (SetProperty(ref _autofillDisableSavePrompt, value)) if (SetProperty(ref _autofillDisableSavePrompt, value))
{ {
var task = UpdateAutofillDisableSavePromptAsync(); UpdateAutofillDisableSavePromptAsync().FireAndForget();
} }
} }
} }
@ -166,6 +190,8 @@ namespace Bit.App.Pages
DisableFavicon = (await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); DisableFavicon = (await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
var theme = await _stateService.GetThemeAsync(); var theme = await _stateService.GetThemeAsync();
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme); ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
var autoDarkTheme = await _stateService.GetAutoDarkThemeAsync() ?? "dark";
AutoDarkThemeSelectedIndex = AutoDarkThemeOptions.FindIndex(k => k.Key == autoDarkTheme);
var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync(); var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync();
UriMatchSelectedIndex = defaultUriMatch == null ? 0 : UriMatchSelectedIndex = defaultUriMatch == null ? 0 :
UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch); UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch);
@ -202,8 +228,8 @@ namespace Bit.App.Pages
{ {
if (_inited && ThemeSelectedIndex > -1) if (_inited && ThemeSelectedIndex > -1)
{ {
var theme = ThemeOptions[ThemeSelectedIndex].Key; await _stateService.SetThemeAsync(ThemeOptions[ThemeSelectedIndex].Key);
await _stateService.SetThemeAsync(theme); await _stateService.SetAutoDarkThemeAsync(AutoDarkThemeOptions[AutoDarkThemeSelectedIndex].Key);
ThemeManager.SetTheme(Application.Current.Resources); ThemeManager.SetTheme(Application.Current.Resources);
_messagingService.Send("updatedTheme"); _messagingService.Send("updatedTheme");
} }

File diff suppressed because it is too large Load Diff

View File

@ -1541,6 +1541,12 @@
<data name="ThemeDefault" xml:space="preserve"> <data name="ThemeDefault" xml:space="preserve">
<value>Default (System)</value> <value>Default (System)</value>
</data> </data>
<data name="DefaultDarkTheme" xml:space="preserve">
<value>Default Dark Theme</value>
</data>
<data name="DefaultDarkThemeDescription" xml:space="preserve">
<value>Choose the dark theme to use when using Default (System) theme while your device's dark mode is enabled</value>
</data>
<data name="CopyNotes" xml:space="preserve"> <data name="CopyNotes" xml:space="preserve">
<value>Copy Note</value> <value>Copy Note</value>
</data> </data>
@ -1557,6 +1563,10 @@
<value>Black</value> <value>Black</value>
<comment>The color black</comment> <comment>The color black</comment>
</data> </data>
<data name="Nord" xml:space="preserve">
<value>Nord</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
</data>
<data name="BlacklistedUris" xml:space="preserve"> <data name="BlacklistedUris" xml:space="preserve">
<value>Blacklisted URIs</value> <value>Blacklisted URIs</value>
</data> </data>

View File

@ -17,13 +17,18 @@ namespace Bit.App.Utilities
public static bool IsThemeDirty = false; public static bool IsThemeDirty = false;
public static void SetThemeStyle(string name, ResourceDictionary resources) public const string Light = "light";
public const string Dark = "dark";
public const string Black = "black";
public const string Nord = "nord";
public static void SetThemeStyle(string name, string autoDarkName, ResourceDictionary resources)
{ {
try try
{ {
Resources = () => resources; Resources = () => resources;
var newTheme = NeedsThemeUpdate(name, resources); var newTheme = NeedsThemeUpdate(name, autoDarkName, resources);
if (newTheme is null) if (newTheme is null)
{ {
return; return;
@ -85,22 +90,30 @@ namespace Bit.App.Utilities
: Activator.CreateInstance(themeType) as ResourceDictionary; : Activator.CreateInstance(themeType) as ResourceDictionary;
} }
static ResourceDictionary NeedsThemeUpdate(string themeName, ResourceDictionary resources) static ResourceDictionary NeedsThemeUpdate(string themeName, string autoDarkThemeName, ResourceDictionary resources)
{ {
switch (themeName) switch (themeName)
{ {
case "dark": case Dark:
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources); return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
case "black": case Black:
return CheckAndGetThemeForMergedDictionaries(typeof(Black), resources); return CheckAndGetThemeForMergedDictionaries(typeof(Black), resources);
case "nord": case Nord:
return CheckAndGetThemeForMergedDictionaries(typeof(Nord), resources); return CheckAndGetThemeForMergedDictionaries(typeof(Nord), resources);
case "light": case Light:
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources); return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
default: default:
if (OsDarkModeEnabled()) if (OsDarkModeEnabled())
{ {
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources); switch (autoDarkThemeName)
{
case Black:
return CheckAndGetThemeForMergedDictionaries(typeof(Black), resources);
case Nord:
return CheckAndGetThemeForMergedDictionaries(typeof(Nord), resources);
default:
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
}
} }
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources); return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
} }
@ -108,7 +121,7 @@ namespace Bit.App.Utilities
public static void SetTheme(ResourceDictionary resources) public static void SetTheme(ResourceDictionary resources)
{ {
SetThemeStyle(GetTheme(), resources); SetThemeStyle(GetTheme(), GetAutoDarkTheme(), resources);
} }
public static string GetTheme() public static string GetTheme()
@ -117,6 +130,12 @@ namespace Bit.App.Utilities
return stateService.GetThemeAsync().GetAwaiter().GetResult(); return stateService.GetThemeAsync().GetAwaiter().GetResult();
} }
public static string GetAutoDarkTheme()
{
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
return stateService.GetAutoDarkThemeAsync().GetAwaiter().GetResult();
}
public static bool OsDarkModeEnabled() public static bool OsDarkModeEnabled()
{ {
if (Application.Current == null) if (Application.Current == null)

View File

@ -110,6 +110,8 @@ namespace Bit.Core.Abstractions
Task SetRememberedOrgIdentifierAsync(string value); Task SetRememberedOrgIdentifierAsync(string value);
Task<string> GetThemeAsync(string userId = null); Task<string> GetThemeAsync(string userId = null);
Task SetThemeAsync(string value, string userId = null); Task SetThemeAsync(string value, string userId = null);
Task<string> GetAutoDarkThemeAsync(string userId = null);
Task SetAutoDarkThemeAsync(string value, string userId = null);
Task<bool?> GetAddSitePromptShownAsync(string userId = null); Task<bool?> GetAddSitePromptShownAsync(string userId = null);
Task SetAddSitePromptShownAsync(bool? value, string userId = null); Task SetAddSitePromptShownAsync(bool? value, string userId = null);
Task<bool?> GetPushInitialPromptShownAsync(); Task<bool?> GetPushInitialPromptShownAsync();

View File

@ -73,6 +73,7 @@
public static string DisableFaviconKey(string userId) => $"disableFavicon_{userId}"; public static string DisableFaviconKey(string userId) => $"disableFavicon_{userId}";
public static string DefaultUriMatchKey(string userId) => $"defaultUriMatch_{userId}"; public static string DefaultUriMatchKey(string userId) => $"defaultUriMatch_{userId}";
public static string ThemeKey(string userId) => $"theme_{userId}"; public static string ThemeKey(string userId) => $"theme_{userId}";
public static string AutoDarkThemeKey(string userId) => $"autoDarkTheme_{userId}";
public static string DisableAutoTotpCopyKey(string userId) => $"disableAutoTotpCopy_{userId}"; public static string DisableAutoTotpCopyKey(string userId) => $"disableAutoTotpCopy_{userId}";
public static string PreviousPageKey(string userId) => $"previousPage_{userId}"; public static string PreviousPageKey(string userId) => $"previousPage_{userId}";
public static string PasswordRepromptAutofillKey(string userId) => $"passwordRepromptAutofillKey_{userId}"; public static string PasswordRepromptAutofillKey(string userId) => $"passwordRepromptAutofillKey_{userId}";

View File

@ -924,6 +924,25 @@ namespace Bit.Core.Services
SetValueGloballyAsync(Constants.ThemeKey, value, reconciledOptions).FireAndForget(); SetValueGloballyAsync(Constants.ThemeKey, value, reconciledOptions).FireAndForget();
} }
public async Task<string> GetAutoDarkThemeAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
var key = Constants.AutoDarkThemeKey(reconciledOptions.UserId);
return await GetValueAsync<string>(key, reconciledOptions);
}
public async Task SetAutoDarkThemeAsync(string value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
var key = Constants.AutoDarkThemeKey(reconciledOptions.UserId);
await SetValueAsync(key, value, reconciledOptions);
// TODO remove this to restore per-account Theme support
SetValueGloballyAsync(Constants.AutoDarkThemeKey, value, reconciledOptions).FireAndForget();
}
public async Task<bool?> GetAddSitePromptShownAsync(string userId = null) public async Task<bool?> GetAddSitePromptShownAsync(string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
@ -1414,6 +1433,7 @@ namespace Bit.Core.Services
await SetPasswordVerifiedAutofillAsync(null, userId); await SetPasswordVerifiedAutofillAsync(null, userId);
await SetSyncOnRefreshAsync(null, userId); await SetSyncOnRefreshAsync(null, userId);
await SetThemeAsync(null, userId); await SetThemeAsync(null, userId);
await SetAutoDarkThemeAsync(null, userId);
await SetAddSitePromptShownAsync(null, userId); await SetAddSitePromptShownAsync(null, userId);
await SetPasswordGenerationOptionsAsync(null, userId); await SetPasswordGenerationOptionsAsync(null, userId);
} }
@ -1423,6 +1443,7 @@ namespace Bit.Core.Services
{ {
await CheckStateAsync(); await CheckStateAsync();
var currentTheme = await GetThemeAsync(); var currentTheme = await GetThemeAsync();
var currentAutoDarkTheme = await GetAutoDarkThemeAsync();
var currentDisableFavicons = await GetDisableFaviconAsync(); var currentDisableFavicons = await GetDisableFaviconAsync();
account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync(); account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync();
@ -1452,6 +1473,7 @@ namespace Bit.Core.Services
account.Settings.VaultTimeoutAction = VaultTimeoutAction.Lock; account.Settings.VaultTimeoutAction = VaultTimeoutAction.Lock;
} }
await SetThemeAsync(currentTheme, account.Profile.UserId); await SetThemeAsync(currentTheme, account.Profile.UserId);
await SetAutoDarkThemeAsync(currentAutoDarkTheme, account.Profile.UserId);
await SetDisableFaviconAsync(currentDisableFavicons, account.Profile.UserId); await SetDisableFaviconAsync(currentDisableFavicons, account.Profile.UserId);
state.Accounts[account.Profile.UserId] = account; state.Accounts[account.Profile.UserId] = account;

View File

@ -83,10 +83,10 @@ namespace Bit.iOS.Core.Utilities
{ {
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled) if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
{ {
theme = "dark"; theme = ThemeManager.Dark;
} }
if (theme == "dark" || theme == "black" || theme == "nord") if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord)
{ {
LightTheme = false; LightTheme = false;
} }