2018-12-01 00:02:18 +01:00
|
|
|
package jp.juggler.util
|
|
|
|
|
|
|
|
import android.app.Activity
|
|
|
|
import android.content.Context
|
|
|
|
import android.content.ContextWrapper
|
2020-09-27 12:59:54 +02:00
|
|
|
import android.content.SharedPreferences
|
|
|
|
import android.content.res.ColorStateList
|
|
|
|
import android.graphics.Color
|
|
|
|
import android.os.Build
|
2018-12-01 00:02:18 +01:00
|
|
|
import android.view.View
|
|
|
|
import android.view.ViewGroup
|
2020-09-27 12:59:54 +02:00
|
|
|
import android.view.WindowInsetsController
|
|
|
|
import android.view.WindowManager
|
2018-12-01 00:02:18 +01:00
|
|
|
import android.view.inputmethod.InputMethodManager
|
2019-10-05 14:28:04 +02:00
|
|
|
import android.widget.CompoundButton
|
2020-09-27 12:59:54 +02:00
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
|
|
import androidx.appcompat.widget.SwitchCompat
|
|
|
|
import jp.juggler.subwaytooter.App1
|
|
|
|
import jp.juggler.subwaytooter.Pref
|
2019-10-05 14:28:04 +02:00
|
|
|
import jp.juggler.subwaytooter.R
|
|
|
|
import org.xmlpull.v1.XmlPullParser
|
2020-09-27 12:59:54 +02:00
|
|
|
import kotlin.math.pow
|
2018-12-01 00:02:18 +01:00
|
|
|
|
2018-12-04 10:59:01 +01:00
|
|
|
private val log = LogCategory("ViewUtils")
|
2018-12-01 00:02:18 +01:00
|
|
|
|
2021-06-20 05:02:30 +02:00
|
|
|
fun View?.scan(callback: (view: View) -> Unit) {
|
|
|
|
this ?: return
|
|
|
|
callback(this)
|
|
|
|
if (this is ViewGroup) {
|
|
|
|
for (i in 0 until this.childCount) {
|
|
|
|
this.getChildAt(i)?.scan(callback)
|
|
|
|
}
|
|
|
|
}
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|
|
|
|
|
2021-06-20 05:02:30 +02:00
|
|
|
val View?.activity: Activity?
|
|
|
|
get() {
|
|
|
|
var context = this?.context
|
|
|
|
while (context is ContextWrapper) {
|
|
|
|
if (context is Activity) return context
|
|
|
|
context = context.baseContext
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
2018-12-01 00:02:18 +01:00
|
|
|
|
|
|
|
fun View.hideKeyboard() {
|
2021-06-20 05:02:30 +02:00
|
|
|
try {
|
|
|
|
val imm = this.context?.getSystemService(Context.INPUT_METHOD_SERVICE)
|
|
|
|
if (imm is InputMethodManager) {
|
|
|
|
imm.hideSoftInputFromWindow(this.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
|
|
|
|
} else {
|
|
|
|
log.e("hideKeyboard: can't get InputMethodManager")
|
|
|
|
}
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fun View.showKeyboard() {
|
2021-06-20 05:02:30 +02:00
|
|
|
try {
|
|
|
|
val imm = this.context?.getSystemService(Context.INPUT_METHOD_SERVICE)
|
|
|
|
if (imm is InputMethodManager) {
|
|
|
|
imm.showSoftInput(this, InputMethodManager.HIDE_NOT_ALWAYS)
|
|
|
|
} else {
|
|
|
|
log.e("showKeyboard: can't get InputMethodManager")
|
|
|
|
}
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|
|
|
|
|
2019-02-03 15:12:03 +01:00
|
|
|
// set visibility VISIBLE or GONE
|
|
|
|
// return true if visible
|
2021-06-20 05:02:30 +02:00
|
|
|
fun <T : View> T?.vg(visible: Boolean): T? {
|
|
|
|
this?.visibility = if (visible) View.VISIBLE else View.GONE
|
|
|
|
return if (visible) this else null
|
2018-12-01 00:02:18 +01:00
|
|
|
}
|
2019-10-05 14:28:04 +02:00
|
|
|
|
2021-06-20 05:02:30 +02:00
|
|
|
fun ViewGroup.generateLayoutParamsEx(): ViewGroup.LayoutParams? =
|
|
|
|
try {
|
|
|
|
val parser = resources.getLayout(R.layout.generate_params)
|
|
|
|
// Skip everything until the view tag.
|
|
|
|
while (true) {
|
|
|
|
val token = parser.nextToken()
|
|
|
|
if (token == XmlPullParser.START_TAG) break
|
|
|
|
}
|
|
|
|
generateLayoutParams(parser)
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
log.e(ex, "generateLayoutParamsEx failed")
|
|
|
|
null
|
|
|
|
}
|
2019-10-05 14:28:04 +02:00
|
|
|
|
|
|
|
// isChecked with skipping animation
|
2021-06-20 05:02:30 +02:00
|
|
|
var CompoundButton.isCheckedNoAnime: Boolean
|
|
|
|
get() = isChecked
|
|
|
|
set(value) {
|
|
|
|
isChecked = value
|
|
|
|
jumpDrawablesToCurrentState()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun mixColor(col1: Int, col2: Int): Int = Color.rgb(
|
|
|
|
(Color.red(col1) + Color.red(col2)) ushr 1,
|
|
|
|
(Color.green(col1) + Color.green(col2)) ushr 1,
|
|
|
|
(Color.blue(col1) + Color.blue(col2)) ushr 1
|
2020-09-27 12:59:54 +02:00
|
|
|
)
|
|
|
|
|
2020-09-29 19:44:56 +02:00
|
|
|
fun Context.setSwitchColor(
|
2021-06-20 05:02:30 +02:00
|
|
|
pref: SharedPreferences,
|
|
|
|
root: View?
|
2020-09-27 12:59:54 +02:00
|
|
|
) {
|
2021-06-20 05:02:30 +02:00
|
|
|
val colorBg = attrColor(R.attr.colorWindowBackground)
|
|
|
|
val colorOn = Pref.ipSwitchOnColor(pref)
|
|
|
|
val colorOff = /* Pref.ipSwitchOffColor(pref).notZero() ?: */
|
|
|
|
attrColor(android.R.attr.colorPrimary)
|
|
|
|
|
|
|
|
val colorDisabled = mixColor(colorBg, colorOff)
|
|
|
|
|
|
|
|
val colorTrackDisabled = mixColor(colorBg, colorDisabled)
|
|
|
|
val colorTrackOn = mixColor(colorBg, colorOn)
|
|
|
|
val colorTrackOff = mixColor(colorBg, colorOff)
|
|
|
|
|
|
|
|
// https://stackoverflow.com/a/25635526/9134243
|
|
|
|
val thumbStates = ColorStateList(
|
|
|
|
arrayOf(
|
|
|
|
intArrayOf(-android.R.attr.state_enabled),
|
|
|
|
intArrayOf(android.R.attr.state_checked),
|
|
|
|
intArrayOf()
|
|
|
|
),
|
|
|
|
intArrayOf(
|
|
|
|
colorDisabled,
|
|
|
|
colorOn,
|
|
|
|
colorOff
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
val trackStates = ColorStateList(
|
|
|
|
arrayOf(
|
|
|
|
intArrayOf(-android.R.attr.state_enabled),
|
|
|
|
intArrayOf(android.R.attr.state_checked),
|
|
|
|
intArrayOf()
|
|
|
|
),
|
|
|
|
intArrayOf(
|
|
|
|
colorTrackDisabled,
|
|
|
|
colorTrackOn,
|
|
|
|
colorTrackOff
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
root?.scan {
|
|
|
|
(it as? SwitchCompat)?.apply {
|
|
|
|
thumbTintList = thumbStates
|
|
|
|
trackTintList = trackStates
|
|
|
|
}
|
|
|
|
}
|
2020-09-27 12:59:54 +02:00
|
|
|
}
|
|
|
|
|
2021-06-20 05:02:30 +02:00
|
|
|
private fun rgbToLab(rgb: Int): Triple<Float, Float, Float> {
|
|
|
|
|
|
|
|
fun Int.revGamma(): Float {
|
|
|
|
val v = toFloat() / 255f
|
|
|
|
return when {
|
|
|
|
v > 0.04045f -> ((v + 0.055f) / 1.055f).pow(2.4f)
|
|
|
|
else -> v / 12.92f
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val r = Color.red(rgb).revGamma()
|
|
|
|
val g = Color.green(rgb).revGamma()
|
|
|
|
val b = Color.blue(rgb).revGamma()
|
|
|
|
|
|
|
|
//https://en.wikipedia.org/wiki/Lab_color_space#CIELAB-CIEXYZ_conversions
|
|
|
|
|
|
|
|
fun f(src: Float, k: Float): Float {
|
|
|
|
val v = src * k
|
|
|
|
return when {
|
|
|
|
v > 0.008856f -> v.pow(1f / 3f)
|
|
|
|
else -> (7.787f * v) + (4f / 29f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val x = f(r * 0.4124f + g * 0.3576f + b * 0.1805f, 100f / 95.047f)
|
|
|
|
val y = f(r * 0.2126f + g * 0.7152f + b * 0.0722f, 100f / 100f)
|
|
|
|
val z = f(r * 0.0193f + g * 0.1192f + b * 0.9505f, 100f / 108.883f)
|
|
|
|
|
|
|
|
return Triple(
|
|
|
|
(116 * y) - 16, // L
|
|
|
|
500 * (x - y), // a
|
|
|
|
200 * (y - z) //b
|
|
|
|
)
|
2020-09-27 12:59:54 +02:00
|
|
|
}
|
|
|
|
|
2021-06-20 05:02:30 +02:00
|
|
|
fun AppCompatActivity.setStatusBarColor(forceDark: Boolean = false) {
|
|
|
|
|
|
|
|
window?.apply {
|
|
|
|
|
|
|
|
// 古い端末ではナビゲーションバーのアイコン色を設定できないため
|
|
|
|
// メディアビューア画面ではステータスバーやナビゲーションバーの色を設定しない…
|
|
|
|
if (forceDark && Build.VERSION.SDK_INT < 26) return
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT < 30) {
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
clearFlags(
|
|
|
|
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or
|
|
|
|
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
|
|
|
|
|
|
|
var c = when {
|
|
|
|
forceDark -> Color.BLACK
|
|
|
|
else -> Pref.ipStatusBarColor(App1.pref).notZero()
|
|
|
|
?: attrColor(R.attr.colorPrimaryDark)
|
|
|
|
}
|
|
|
|
statusBarColor = c or Color.BLACK
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= 30) {
|
|
|
|
decorView.windowInsetsController?.run {
|
|
|
|
val bit = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
|
|
|
|
setSystemBarsAppearance(if (rgbToLab(c).first >= 50f) bit else 0, bit)
|
|
|
|
}
|
|
|
|
} else if (Build.VERSION.SDK_INT >= 23) {
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
val bit = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
decorView.systemUiVisibility =
|
|
|
|
if (rgbToLab(c).first >= 50f) {
|
|
|
|
//Dark Text to show up on your light status bar
|
|
|
|
decorView.systemUiVisibility or bit
|
|
|
|
} else {
|
|
|
|
//Light Text to show up on your dark status bar
|
|
|
|
decorView.systemUiVisibility and bit.inv()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c = when {
|
|
|
|
forceDark -> Color.BLACK
|
|
|
|
else -> Pref.ipNavigationBarColor(App1.pref)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c != 0) {
|
|
|
|
navigationBarColor = c or Color.BLACK
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= 30) {
|
|
|
|
decorView.windowInsetsController?.run {
|
|
|
|
val bit = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
|
|
|
|
setSystemBarsAppearance(if (rgbToLab(c).first >= 50f) bit else 0, bit)
|
|
|
|
}
|
|
|
|
} else if (Build.VERSION.SDK_INT >= 26) {
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
val bit = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
|
|
|
@Suppress("DEPRECATION")
|
2021-06-20 15:46:07 +02:00
|
|
|
decorView.systemUiVisibility = when {
|
|
|
|
rgbToLab(c).first >= 50f -> {
|
2021-06-20 05:02:30 +02:00
|
|
|
//Dark Text to show up on your light status bar
|
|
|
|
decorView.systemUiVisibility or bit
|
2021-06-20 15:46:07 +02:00
|
|
|
}
|
|
|
|
else -> {
|
2021-06-20 05:02:30 +02:00
|
|
|
//Light Text to show up on your dark status bar
|
|
|
|
decorView.systemUiVisibility and bit.inv()
|
|
|
|
}
|
2021-06-20 15:46:07 +02:00
|
|
|
}
|
2021-06-20 05:02:30 +02:00
|
|
|
}
|
|
|
|
} // else: need restart app.
|
|
|
|
}
|
2020-09-27 12:59:54 +02:00
|
|
|
}
|