2020-05-29 18:26:36 +02:00
using System ;
2021-10-08 14:47:40 +02:00
using Bit.App.Models ;
2019-05-29 21:50:20 +02:00
using Bit.App.Styles ;
2022-02-23 18:40:17 +01:00
using Bit.Core.Abstractions ;
using Bit.Core.Utilities ;
2019-03-30 02:23:34 +01:00
using Xamarin.Forms ;
2022-01-24 21:20:48 +01:00
using System.Linq ;
using System.Threading.Tasks ;
2021-12-16 15:36:06 +01:00
#if ! FDROID
using Microsoft.AppCenter.Crashes ;
#endif
2019-03-30 02:23:34 +01:00
namespace Bit.App.Utilities
{
public static class ThemeManager
{
2019-09-30 22:52:20 +02:00
public static bool UsingLightTheme = true ;
2020-05-29 18:26:36 +02:00
public static Func < ResourceDictionary > Resources = ( ) = > null ;
2019-09-30 22:52:20 +02:00
2022-01-24 21:20:48 +01:00
public static bool IsThemeDirty = false ;
2020-05-29 18:26:36 +02:00
public static void SetThemeStyle ( string name , ResourceDictionary resources )
2019-04-22 17:32:17 +02:00
{
2021-12-16 15:36:06 +01:00
try
{
Resources = ( ) = > resources ;
2020-05-29 18:26:36 +02:00
2022-01-24 21:20:48 +01:00
var newTheme = NeedsThemeUpdate ( name , resources ) ;
if ( newTheme is null )
{
return ;
}
var currentTheme = resources . MergedDictionaries . FirstOrDefault ( md = > md is IThemeResourceDictionary ) ;
if ( currentTheme ! = null )
{
resources . MergedDictionaries . Remove ( currentTheme ) ;
resources . MergedDictionaries . Add ( newTheme ) ;
UsingLightTheme = newTheme is Light ;
IsThemeDirty = true ;
return ;
}
2021-12-16 15:36:06 +01:00
// Reset styles
resources . Clear ( ) ;
resources . MergedDictionaries . Clear ( ) ;
2019-04-22 17:32:17 +02:00
2021-12-16 15:36:06 +01:00
// Variables
resources . MergedDictionaries . Add ( new Variables ( ) ) ;
2019-04-22 20:17:33 +02:00
2022-01-24 21:20:48 +01:00
// Theme
resources . MergedDictionaries . Add ( newTheme ) ;
UsingLightTheme = newTheme is Light ;
2019-04-22 20:17:33 +02:00
2021-12-16 15:36:06 +01:00
// Base styles
resources . MergedDictionaries . Add ( new Base ( ) ) ;
2019-04-22 17:32:17 +02:00
2021-12-16 15:36:06 +01:00
// Platform styles
if ( Device . RuntimePlatform = = Device . Android )
{
resources . MergedDictionaries . Add ( new Android ( ) ) ;
}
else if ( Device . RuntimePlatform = = Device . iOS )
{
resources . MergedDictionaries . Add ( new iOS ( ) ) ;
}
}
catch ( InvalidOperationException ioex ) when ( ioex . Message ! = null & & ioex . Message . StartsWith ( "Collection was modified" ) )
2019-04-22 17:32:17 +02:00
{
2021-12-16 15:36:06 +01:00
// https://github.com/bitwarden/mobile/issues/1689 There are certain scenarios where this might cause "collection was modified; enumeration operation may not execute"
// the way I found to prevent this for now was to catch the exception here and move on.
// Because on the screens that I found it to happen, the screen is being closed while trying to apply the resources
// so we shouldn't be introducing any issues.
// TODO: Maybe something like this https://github.com/matteobortolazzo/HtmlLabelPlugin/pull/113 can be implemented to avoid this
// on html labels.
2019-04-22 17:32:17 +02:00
}
2021-12-16 15:36:06 +01:00
catch ( Exception ex )
2019-04-22 17:32:17 +02:00
{
2021-12-16 15:36:06 +01:00
#if ! FDROID
Crashes . TrackError ( ex ) ;
#endif
2019-04-22 17:32:17 +02:00
}
}
2019-05-29 21:50:20 +02:00
2022-01-24 21:20:48 +01:00
static ResourceDictionary CheckAndGetThemeForMergedDictionaries ( Type themeType , ResourceDictionary resources )
{
return resources . MergedDictionaries . Any ( rd = > rd . GetType ( ) = = themeType )
? null
: Activator . CreateInstance ( themeType ) as ResourceDictionary ;
}
static ResourceDictionary NeedsThemeUpdate ( string themeName , ResourceDictionary resources )
{
switch ( themeName )
{
case "dark" :
return CheckAndGetThemeForMergedDictionaries ( typeof ( Dark ) , resources ) ;
case "black" :
return CheckAndGetThemeForMergedDictionaries ( typeof ( Black ) , resources ) ;
case "nord" :
return CheckAndGetThemeForMergedDictionaries ( typeof ( Nord ) , resources ) ;
case "light" :
return CheckAndGetThemeForMergedDictionaries ( typeof ( Light ) , resources ) ;
default :
if ( OsDarkModeEnabled ( ) )
{
return CheckAndGetThemeForMergedDictionaries ( typeof ( Dark ) , resources ) ;
}
return CheckAndGetThemeForMergedDictionaries ( typeof ( Light ) , resources ) ;
}
}
2022-02-23 18:40:17 +01:00
public static void SetTheme ( ResourceDictionary resources )
2019-05-29 21:50:20 +02:00
{
2022-02-23 18:40:17 +01:00
SetThemeStyle ( GetTheme ( ) , resources ) ;
2019-05-29 21:50:20 +02:00
}
2022-02-23 18:40:17 +01:00
public static string GetTheme ( )
2019-05-29 21:50:20 +02:00
{
2022-02-23 18:40:17 +01:00
var stateService = ServiceContainer . Resolve < IStateService > ( "stateService" ) ;
return stateService . GetThemeAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
2019-05-29 21:50:20 +02:00
}
2020-05-29 18:26:36 +02:00
2021-10-08 14:47:40 +02:00
public static bool OsDarkModeEnabled ( )
{
if ( Application . Current = = null )
{
// called from iOS extension
var app = new App ( new AppOptions { IosExtension = true } ) ;
return app . RequestedTheme = = OSAppTheme . Dark ;
}
return Application . Current . RequestedTheme = = OSAppTheme . Dark ;
}
2020-05-29 18:26:36 +02:00
public static void ApplyResourcesToPage ( ContentPage page )
{
foreach ( var resourceDict in Resources ( ) . MergedDictionaries )
{
page . Resources . Add ( resourceDict ) ;
}
}
public static Color GetResourceColor ( string color )
{
return ( Color ) Resources ( ) [ color ] ;
}
2022-01-24 21:20:48 +01:00
public static async Task UpdateThemeOnPagesAsync ( )
{
try
{
if ( IsThemeDirty )
{
IsThemeDirty = false ;
await Application . Current . MainPage . TraverseNavigationRecursivelyAsync ( async p = >
{
if ( p is IThemeDirtablePage themeDirtablePage )
{
themeDirtablePage . IsThemeDirty = true ;
if ( p . IsVisible )
{
await themeDirtablePage . UpdateOnThemeChanged ( ) ;
}
}
} ) ;
}
}
catch ( Exception ex )
{
#if ! FDROID
Crashes . TrackError ( ex ) ;
#endif
}
}
2019-03-30 02:23:34 +01:00
}
}