@@ -0,0 +1,240 @@
package org.dolphinemu.dolphinemu.utils

import android.app.Activity
import androidx.appcompat.app.AppCompatActivity
import org.dolphinemu.dolphinemu.R
import android.os.Build
import androidx.core.content.ContextCompat
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.WindowCompat
import org.dolphinemu.dolphinemu.ui.main.ThemeProvider
import android.content.res.Configuration
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.elevation.ElevationOverlayProvider
import com.google.android.material.color.MaterialColors
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.annotation.RequiresApi
import androidx.preference.PreferenceManager
import kotlin.math.roundToInt

object ThemeHelper {

const val CURRENT_THEME = "current_theme"
const val CURRENT_THEME_MODE = "current_theme_mode"
const val USE_BLACK_BACKGROUNDS = "use_black_backgrounds"

const val DEFAULT = 0
const val MONET = 1
const val MATERIAL_DEFAULT = 2
const val GREEN = 3
const val PINK = 4

const val NAV_BAR_ALPHA = 0.9f

@JvmStatic
fun setTheme(activity: AppCompatActivity) {
// We have to use shared preferences in addition to Dolphin's settings to guarantee that the
// requested theme id is ready before the onCreate method of any given Activity.
val preferences = PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
setThemeMode(activity)
when (preferences.getInt(CURRENT_THEME, DEFAULT)) {
DEFAULT -> activity.setTheme(R.style.Theme_Dolphin_Main)
MONET -> activity.setTheme(R.style.Theme_Dolphin_Main_MaterialYou)
MATERIAL_DEFAULT -> activity.setTheme(R.style.Theme_Dolphin_Main_Material)
GREEN -> activity.setTheme(R.style.Theme_Dolphin_Main_Green)
PINK -> activity.setTheme(R.style.Theme_Dolphin_Main_Pink)
}
if (preferences.getBoolean(USE_BLACK_BACKGROUNDS, false)) {
activity.setTheme(R.style.ThemeOverlay_Dolphin_Dark)
}

// Since the top app bar matches the color of the status bar, devices below API 23 have to get a
// black status bar since their icons do not adapt based on background color
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
activity.window.statusBarColor =
ContextCompat.getColor(activity.applicationContext, android.R.color.black)
}
}

private fun setThemeMode(activity: AppCompatActivity) {
val themeMode = PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
.getInt(CURRENT_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
activity.delegate.localNightMode = themeMode
val windowController = WindowCompat.getInsetsController(
activity.window,
activity.window.decorView
)
val systemReportedThemeMode =
activity.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
when (themeMode) {
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> when (systemReportedThemeMode) {
Configuration.UI_MODE_NIGHT_NO -> setLightModeSystemBars(windowController)
Configuration.UI_MODE_NIGHT_YES -> setDarkModeSystemBars(windowController)
}
AppCompatDelegate.MODE_NIGHT_NO -> setLightModeSystemBars(windowController)
AppCompatDelegate.MODE_NIGHT_YES -> setDarkModeSystemBars(windowController)
}
}

private fun setLightModeSystemBars(windowController: WindowInsetsControllerCompat) {
windowController.isAppearanceLightStatusBars = true
windowController.isAppearanceLightNavigationBars = true
}

private fun setDarkModeSystemBars(windowController: WindowInsetsControllerCompat) {
windowController.isAppearanceLightStatusBars = false
windowController.isAppearanceLightNavigationBars = false
}

@JvmStatic
fun saveTheme(activity: AppCompatActivity, themeValue: Int) {
PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
.edit()
.putInt(CURRENT_THEME, themeValue)
.apply()
activity.recreate()
}

@JvmStatic
fun deleteThemeKey(activity: AppCompatActivity) {
PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
.edit()
.remove(CURRENT_THEME)
.apply()
activity.recreate()
}

@JvmStatic
fun saveThemeMode(activity: AppCompatActivity, themeModeValue: Int) {
PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
.edit()
.putInt(CURRENT_THEME_MODE, themeModeValue)
.apply()
setThemeMode(activity)
}

@JvmStatic
fun deleteThemeModeKey(activity: AppCompatActivity) {
PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
.edit()
.remove(CURRENT_THEME_MODE)
.apply()
setThemeMode(activity)
}

@JvmStatic
fun saveBackgroundSetting(activity: AppCompatActivity, backgroundValue: Boolean) {
PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
.edit()
.putBoolean(USE_BLACK_BACKGROUNDS, backgroundValue)
.apply()
activity.recreate()
}

@JvmStatic
fun deleteBackgroundSetting(activity: AppCompatActivity) {
PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
.edit()
.remove(USE_BLACK_BACKGROUNDS)
.apply()
activity.recreate()
}

@JvmStatic
fun setCorrectTheme(activity: AppCompatActivity) {
val currentTheme = (activity as ThemeProvider).themeId
setTheme(activity)
if (currentTheme != (activity as ThemeProvider).themeId) {
activity.recreate()
}
}

@JvmStatic
fun setStatusBarColor(activity: AppCompatActivity, @ColorInt color: Int) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
activity.window.statusBarColor =
ContextCompat.getColor(activity.applicationContext, android.R.color.black)
} else {
activity.window.statusBarColor = color
}
}

@JvmStatic
fun setNavigationBarColor(activity: Activity, @ColorInt color: Int) {
val gestureType = InsetsHelper.getSystemGestureType(activity.applicationContext)
val orientation = activity.resources.configuration.orientation

// Use black if the Android version is too low to support changing button colors
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
activity.window.navigationBarColor =
ContextCompat.getColor(activity.applicationContext, android.R.color.black)
} else if ((gestureType == InsetsHelper.THREE_BUTTON_NAVIGATION ||
gestureType == InsetsHelper.TWO_BUTTON_NAVIGATION) &&
orientation == Configuration.ORIENTATION_LANDSCAPE
) {
activity.window.navigationBarColor = color
} else if (gestureType == InsetsHelper.THREE_BUTTON_NAVIGATION ||
gestureType == InsetsHelper.TWO_BUTTON_NAVIGATION
) {
activity.window.navigationBarColor = getColorWithOpacity(color, NAV_BAR_ALPHA)
} else {
activity.window.navigationBarColor = ContextCompat.getColor(
activity.applicationContext,
android.R.color.transparent
)
}
}

@JvmStatic
fun enableScrollTint(
activity: AppCompatActivity, toolbar: MaterialToolbar, appBarLayout: AppBarLayout
) {
appBarLayout.addOnOffsetChangedListener { layout: AppBarLayout, verticalOffset: Int ->
if (-verticalOffset >= layout.totalScrollRange / 2) {
@ColorInt val color =
ElevationOverlayProvider(appBarLayout.context).compositeOverlay(
MaterialColors.getColor(appBarLayout, R.attr.colorSurface),
activity.resources.getDimensionPixelSize(R.dimen.elevated_app_bar).toFloat()
)
toolbar.setBackgroundColor(color)
setStatusBarColor(activity, color)
} else {
@ColorInt val statusBarColor = ContextCompat.getColor(
activity.applicationContext,
android.R.color.transparent
)
@ColorInt val appBarColor = MaterialColors.getColor(toolbar, R.attr.colorSurface)
toolbar.setBackgroundColor(appBarColor)
setStatusBarColor(activity, statusBarColor)
}
}
}

@JvmStatic
fun enableStatusBarScrollTint(activity: AppCompatActivity, appBarLayout: AppBarLayout) {
appBarLayout.addOnOffsetChangedListener { _: AppBarLayout, verticalOffset: Int ->
if (-verticalOffset > 0) {
@ColorInt val color = MaterialColors.getColor(appBarLayout, R.attr.colorSurface)
setStatusBarColor(activity, color)
} else {
@ColorInt val statusBarColor = ContextCompat.getColor(
activity.applicationContext,
android.R.color.transparent
)
setStatusBarColor(activity, statusBarColor)
}
}
}

@RequiresApi(api = Build.VERSION_CODES.O_MR1)
@ColorInt
private fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int {
return Color.argb(
(alphaFactor * Color.alpha(color)).roundToInt(), Color.red(color),
Color.green(color), Color.blue(color)
)
}
}