SubwayTooter-Android-App/app/src/main/java/jp/juggler/util/ViewUtils.kt

290 lines
9.2 KiB
Kotlin
Raw Normal View History

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.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
import android.widget.CompoundButton
2021-12-04 09:55:28 +01:00
import android.widget.TextView
2021-06-28 09:09:00 +02:00
import androidx.annotation.ColorInt
2020-09-27 12:59:54 +02:00
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.pref.PrefI
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 {
when (val imm = this.context?.getSystemService(Context.INPUT_METHOD_SERVICE)) {
is InputMethodManager ->
imm.hideSoftInputFromWindow(this.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
else -> log.e("hideKeyboard: can't get InputMethodManager")
2021-06-20 05:02:30 +02:00
}
} catch (ex: Throwable) {
2023-01-13 07:33:28 +01:00
log.e(ex, "hideKeyboard failed.")
2021-06-20 05:02:30 +02:00
}
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)
when (imm) {
is InputMethodManager ->
imm.showSoftInput(this, InputMethodManager.HIDE_NOT_ALWAYS)
else -> log.e("showKeyboard: can't get InputMethodManager")
2021-06-20 05:02:30 +02:00
}
} catch (ex: Throwable) {
2023-01-13 07:33:28 +01:00
log.e(ex, "showKeyboard failed.")
2021-06-20 05:02:30 +02:00
}
2018-12-01 00:02:18 +01:00
}
// set visibility VISIBLE or GONE
// return this or null
// レシーバがnullableなのはplatform typeによるnull例外を避ける目的
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
}
// set visibility VISIBLE or INVISIBLE
// return this or null
// レシーバがnullableなのはplatform typeによるnull例外を避ける目的
fun <T : View> T?.visibleOrInvisible(visible: Boolean): T? {
this?.visibility = if (visible) View.VISIBLE else View.INVISIBLE
return if (visible) this else null
}
fun <T : View> T.visible(): T = apply { visibility = View.VISIBLE }
fun <T : View> T.invisible(): T = apply { visibility = View.INVISIBLE }
fun <T : View> T.gone(): T = apply { visibility = View.GONE }
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
}
// 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
)
fun Context.setSwitchColor(root: View?) {
2021-06-20 05:02:30 +02:00
val colorBg = attrColor(R.attr.colorWindowBackground)
2023-01-13 07:33:28 +01:00
val colorOff = attrColor(R.attr.colorSwitchOff)
val colorOn = PrefI.ipSwitchOnColor()
2021-06-20 05:02:30 +02:00
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 (Build.VERSION.SDK_INT < 30) {
@Suppress("DEPRECATION")
clearFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
2021-06-20 05:02:30 +02:00
)
}
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
var c = when {
forceDark -> Color.BLACK
else -> PrefI.ipStatusBarColor.invoke().notZero() ?: attrColor(R.attr.colorPrimaryDark)
2021-06-20 05:02:30 +02:00
}
2021-06-28 09:09:00 +02:00
setStatusBarColorCompat(c)
c = when {
forceDark -> Color.BLACK
else -> PrefI.ipNavigationBarColor()
2021-06-28 09:09:00 +02:00
}
setNavigationBarColorCompat(c)
}
}
private fun AppCompatActivity.setStatusBarColorCompat(@ColorInt c: Int) {
window?.apply {
statusBarColor = Color.BLACK or c
2021-06-20 05:02:30 +02:00
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)
}
2022-08-11 09:51:49 +02:00
} else {
2021-06-20 05:02:30 +02:00
@Suppress("DEPRECATION")
val bit = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
@Suppress("DEPRECATION")
decorView.systemUiVisibility =
2022-08-11 09:51:49 +02:00
when {
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()
}
2021-06-20 05:02:30 +02:00
}
}
2021-06-28 09:09:00 +02:00
}
}
2021-06-20 05:02:30 +02:00
2021-06-28 09:09:00 +02:00
private fun AppCompatActivity.setNavigationBarColorCompat(@ColorInt c: Int) {
if (c == 0) {
// no way to restore to system default, need restart app.
return
}
2021-06-20 05:02:30 +02:00
2021-06-28 09:09:00 +02:00
window?.apply {
navigationBarColor = c or Color.BLACK
2021-06-20 05:02:30 +02:00
2021-06-28 09:09:00 +02:00
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)
}
2022-08-11 09:51:49 +02:00
} else {
2021-06-28 09:09:00 +02:00
@Suppress("DEPRECATION")
val bit = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
@Suppress("DEPRECATION")
decorView.systemUiVisibility = when {
2022-08-11 09:51:49 +02:00
//Dark Text to show up on your light status bar
rgbToLab(c).first >= 50f ->
2021-06-28 09:09:00 +02:00
decorView.systemUiVisibility or bit
2022-08-11 09:51:49 +02:00
//Light Text to show up on your dark status bar
else ->
2021-06-28 09:09:00 +02:00
decorView.systemUiVisibility and bit.inv()
2021-06-20 05:02:30 +02:00
}
2021-06-28 09:09:00 +02:00
}
2021-06-20 05:02:30 +02:00
}
2020-09-27 12:59:54 +02:00
}
2021-12-04 09:55:28 +01:00
var TextView.textOrGone: CharSequence?
2021-12-04 09:55:28 +01:00
get() = text
set(value) {
vg(value?.isNotEmpty() == true)?.text = value
2021-12-04 09:55:28 +01:00
}