WebView resetting UiMode and breaking dark theme - android

Our app relies on AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) to make us pick up Light and Dark theme colors from values/colors and values-night/colors
But every time we try to use the WebView, it starts by resetting the UiMode and our app gets confused which color values to pick for our themes
Some people discussed the issue in detail here and here
Anyone out there run into a similar issue?

As previous issue was closed I opened the new one: https://issuetracker.google.com/issues/170328697
And I tried to fix it in this way:
class UiModeCareWebView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : WebView(context, attrs, defStyleAttr) {
init {
fixUiModeIfNeeded()
}
private fun fixUiModeIfNeeded() {
val configuration = context.resources.configuration
val configurationNighMode = configuration.uiMode and UI_MODE_NIGHT_MASK
val appCompatNightMode = getDefaultNightMode()
val newUiModeConfiguration = when {
configurationNighMode == UI_MODE_NIGHT_NO && appCompatNightMode == MODE_NIGHT_YES -> {
UI_MODE_NIGHT_YES or (configuration.uiMode and UI_MODE_NIGHT_MASK.inv())
}
configurationNighMode == UI_MODE_NIGHT_YES && appCompatNightMode == MODE_NIGHT_NO -> {
UI_MODE_NIGHT_NO or (configuration.uiMode and UI_MODE_NIGHT_MASK.inv())
}
else -> null
}
if (newUiModeConfiguration != null) {
val fixedConfiguration = Configuration().apply {
uiMode = newUiModeConfiguration
}
#Suppress("DEPRECATION")
context.resources.updateConfiguration(
fixedConfiguration,
context.resources.displayMetrics
)
}
}
}

Answering my own question, looks like Google fixed the issue https://issuetracker.google.com/issues/37124582
with https://developer.android.com/jetpack/androidx/releases/appcompat#1.1.0-alpha03 Fixed WebView resets DayNight Resources

First of all you need to add android.webkit dependency into your project
dependencies {
implementation "androidx.webkit:webkit:1.3.0"
}
At the time of writing this post the latest stable version of webkit is 1.3.0.
It is worth noting that the Dark Theme support has been added with version 1.2.0, before this version it was impossible to add Dark Theme support to Webview.
Next step would be to check whether Webview and Android framework on user’s device supporting theming:
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
...
}
Please note that WebViewFeature.FORCE_DARK is only supported starting with Webview version 76. Unfortunately before that version there is no straightforward way to support the dark theming.
If you own the contents displayed within the Webview you might want to implement your custom CSS themes and toggle them using #JavascriptInterface from your app.
If WebViewFeature.FORCE_DARK is supported we can choose from three available options:
FORCE_DARK_OFF - Disable dark theme, the content will be rendered in default Light Theme
FORCE_DARK_ON - Enable dark theme, the content will be rendered in Dark Theme
FORCE_DARK_AUTO - Enable dark theme based on the state of parent view
Then we need to apply the setting using WebSettingsCompat
WebSettingsCompat.setForceDark(webView.settings, WebSettingsCompat.FORCE_DARK_ON)
You can read about in more detail in the below blogpost
https://androidexplained.github.io/ui/android/material-design/2020/09/24/dark-mode-webview.html

Related

Application of the system theme

Starting with Android 10, it became possible to change the device theme from settings (to dark and light), and the application by default grasps the theme that is set in the system. But I ran into a rather interesting problem. A device with Android 9 and in the settings there is also the possibility of changing the theme, but when I use a dark theme, nothing happens and my application remains in the light theme. I read on the Internet and found a way to get a theme(is light):
private fun foo(): Boolean {
return resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_NO
}
, but this function always returns that a light theme has been applied (in Android 9 when a dark theme is enabled in the settings). As far as I understood, this is because this function returns the theme that is in the application and wanted to find out how, in this case, you can get the theme of the system.
The resources associated with the Activity will reflect the configuration of that Activity. That means that resources.configuration (or any context.resources.configuration) will have the uiMode of your Activity, not of the System.
When your application is starting, Android must create the Application instance for your app. At this moment your resources are not loaded yet, so Android attaches resources and configuration of the System to your Application instance. So you can access the uiMode of the System from the application Context. Also, you can access System resources and configuration using Resources.getSystem(), which is handy when you don't have access to any Context.
val activityIsLight =
resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_NO
val systemIsLight =
applicationContext.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_NO
// Or without using any Context
val systemIsLight =
Resources.getSystem().configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_NO

How to test the Android Dark Mode using Espresso

What is the best way to test the different DayNight themes on Android using Espresso? (or something better?) I haven't found anything on the internet. I thought this must be something big because of everything migrating to DayNight now.
I want to know things like: "when I click this button, has my activity theme changed" or "I have this background and this text, is the contrast right".
Thanks in advance.
I have found this to work:
#get:Rule
val activityTestRule = ActivityScenarioRule(...Activity::class.java)
private fun createActivityScenarioRule(withNightMode: Boolean = false) =
activityTestRule.scenario.apply {
onActivity {
AppCompatDelegate.setDefaultNightMode(
if (withNightMode) AppCompatDelegate.MODE_NIGHT_YES
else AppCompatDelegate.MODE_NIGHT_NO
)
}
}

How can I detect programmatically if the Android Device is in Dark Mode?

I'm trying to support the Android Q Dark theme for my Android app and I can't figure out how to import different assets based on the theme I'm currently in.
Im using the official DayNight theme for making the dark/light versions and for drawables is very easy to just point to the XML and it will choose the correct value either from values or values-night depending on what is enabled.
I wanted to do something similar where depending on the theme it would load either the asset "priceTag_light.png" or "priceTag_dark.png".
val inputStream = if(darkIsEnabled) {
assets.open("priceTag_dark.png")
} else {
assets.open("priceTag_light.png")
}
Is there a way I get that flag?
Okay finally found the solution I was looking for. As #deepak-s-gavkar points out the parameter that gives us that information is on the Configuration. So, after a small search I found this article that gives this example method that has worked perfectly for what I wanted:
fun isDarkTheme(activity: Activity): Boolean {
return activity.resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
}
You first need to do this changes in manifest
<activity
android:name=".MyActivity"
android:configChanges="uiMode" />
then onConfigurationChanged of activity
val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
when (currentNightMode) {
Configuration.UI_MODE_NIGHT_NO -> {} // Night mode is not active, we're using the light theme
Configuration.UI_MODE_NIGHT_YES -> {} // Night mode is active, we're using dark theme
}
Use the following code:
boolean isDarkThemeOn = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;

Programmatically change AppBar background color in Kotlin

I'm pretty new to Android development and completely new to Kotlin. I have an app with a navigation drawer, and am trying to change the color of the AppBarLayout based on what the user selects from the navigation drawer. I've tried a few different methods, and the closest I've come has been to change the toolbar color instead of the whole AppBar. This might be acceptable, but instead of actually setting it to the color I want, it always changes it to a dark grey.
when (item.itemId) {
R.id.nav_audit -> {
txtMain.text = "Audit"
toolbar.setBackgroundColor(R.color.colorAudit)
loadAudits()
}
R.id.nav_testing -> {
txtMain.text = "Testing"
toolbar.setBackgroundColor(R.color.colorTesting)
}
R.id.nav_workflow -> {
txtMain.text = "Workflow"
toolbar.setBackgroundColor(R.color.colorWorkflow)
}
R.id.nav_other -> {
txtMain.text = "Other"
toolbar.setBackgroundColor(R.color.colorPrimary)
}
}
I've also looked at possibly changing the theme, but it looks like it may not be easy to do that after the application has already loaded. Any thoughts are appreciated.
Besides changing the color of the Toolbar that you are already doing, one way to change the status bar in Kotlin is like this:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
(activity as? AppCompatActivity)?.window?.statusBarColor =
ContextCompat.getColor(context, R.color. colorTesting)
}
You could do a method that returns the color depending on itemId

How do I change the style of the MediaRouteButton in the ActionBar?

I realize I'm probably doing something fundamentally wrong with styles and themes but I'm still a bit of an Android newbie so please excuse my ignorance. I'm trying to change the style of my MediaRouteButton from the default dark to light since I have a light ActionBar. My MediaRouteButton is implemented in the ActionBar as follows:
<item
android:id="#+id/menu_item_cast"
android:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
android:actionViewClass="android.support.v7.app.MediaRouteButton"
android:showAsAction="always"
android:actionButtonStyle="#android:style/Theme.MediaRouter.Light"/>
However, this gives me:
android/res/menu/main.xml:24: error: Error: No resource found that matches the given name (at 'actionButtonStyle' with value '#android:style/Theme.MediaRouter.Light').
If you don't want to change the color of the icon, framework would choose the right one (dark or light) based on the theme of your actionbar, so for an actionbar with light background, it will choose a darker icon and vice versa; here is a sample app with two different themes, Theme.AppCompat.Light and Theme.AppCompat, respectively (everything else is identical):
As you can see, the appropriate one is selected automatically. If you want to use a different color based on your branding requirements, the easiest would be to add the following images to your project (with usual resolutions under mdpi, hdpi, ..):
mr_ic_media_route_disabled_holo_dark.png
mr_ic_media_route_off_holo_dark.png
mr_ic_media_route_on_0_holo_dark.png
mr_ic_media_route_on_1_holo_dark.png
mr_ic_media_route_on_2_holo_dark.png
(if you are using a light actionbar theme, replace "dark" with "light"). Take a look at the assets at Google Cast >
Sample Apps (section Cast Icons) to get a feel for what these images are and build your own ones based on those.
I ended up decompiling android-support-v7-mediarouter.jar to see what was going on. With the code available I was able to extend MediaRouteButton and set the private Drawable through reflection hacking. There has to be a better way right?
public class CustomMediaRouteButton extends MediaRouteButton {
private static final String TAG = "CustomMediaRouteButton";
public CustomMediaRouteButton(Context context){
this(context, null);
}
public CustomMediaRouteButton(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.mediaRouteButtonStyle);
}
public CustomMediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Drawable d = getResources().getDrawable(R.drawable.mr_ic_media_route_holo_light);
setRemoteIndicatorDrawable(d);
}
private void setRemoteIndicatorDrawable(Drawable d) {
try {
Field field = MediaRouteButton.class.getDeclaredField("mRemoteIndicator");
field.setAccessible(true);
Drawable remoteIndicator = (Drawable)field.get(this);
if (remoteIndicator != null) {
remoteIndicator.setCallback(null);
unscheduleDrawable(remoteIndicator);
}
field.set(this, d);
if (d != null) {
d.setCallback(this);
d.setState(getDrawableState());
d.setVisible(getVisibility() == 0, false);
}
} catch (Exception e) {
Log.e(TAG, "problem changing drawable:" + e.getMessage());
}
refreshDrawableState();
}
}
You can change it easily now with your custom drawable.
Just call this method on your cast button.
mediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
mediaRouteButton.setRemoteIndicatorDrawable(yourDrawable);
I found a way to change your color of MediaRouteButton by code, and is easy to be done, no need to touch existing code.
The MediaRouteButton will style itself following the theme of context which you passed. You can create a ContextThemeWrapper to wrap the context and then pass it to MediaRouteActionProvider.
Following is an example:
MenuItem item = menu.add(Menu.NONE, R.id.menu_cast, Menu.NONE, "Cast");
MenuItemCompat.setActionProvider(item, new MediaRouteActionProvider(new ContextThemeWrapper(this, R.style.AppTheme)));
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
Here the R.style.AppTheme is a theme which extends from Theme.AppCompat, it is a dark theme so the cast button will always show in light version. You can also pass in light theme to make cast button behave in dark version. Also you can change it dynamically, just invalidate the option menu, it should recreate the action provider using the new theme.
I am using support library 23.1.1 and have not found any problem in this way.
If you want to change the icons used (not just the style), you need to name them the exact same way they are named here. For instance, for the light theme, you need to have a set of icons for every resolution with names: ic_cast_on_light.png, ic_cast_on_0_light.png, ic_cast_on_1_light.png, ic_cast_on_2_light.png, ic_cast_disabled_light.png, ic_cast_off_light.png.
You should be able to change the style by applying the style to your activity, e.g. in AndroidManifest.xml. If you want to change the drawable, I succeeded by adding mr_ic_media_route_holo_light drawable to my project. Just add it to drawables folder and customize it as you need it. Example: https://github.com/android/platform_frameworks_support/blob/master/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml

Categories

Resources