Files
Mealient/app/src/main/java/gq/kirmanak/mealient/extensions/ViewExtensions.kt
2022-11-17 20:56:07 +01:00

117 lines
4.0 KiB
Kotlin

package gq.kirmanak.mealient.extensions
import android.content.Context
import android.content.SharedPreferences
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.core.content.getSystemService
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.textfield.TextInputLayout
import gq.kirmanak.mealient.logging.Logger
import kotlinx.coroutines.channels.ChannelResult
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.onClosed
import kotlinx.coroutines.channels.onFailure
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
fun SwipeRefreshLayout.refreshRequestFlow(logger: Logger): Flow<Unit> = callbackFlow {
logger.v { "refreshRequestFlow() called" }
val listener = SwipeRefreshLayout.OnRefreshListener {
logger.v { "refreshRequestFlow: listener called" }
trySend(Unit).logErrors("refreshesFlow", logger)
}
setOnRefreshListener(listener)
awaitClose {
logger.v { "Removing refresh request listener" }
setOnRefreshListener(null)
}
}
fun TextView.textChangesFlow(logger: Logger): Flow<CharSequence?> = callbackFlow {
logger.v { "textChangesFlow() called" }
val textWatcher = doAfterTextChanged {
trySend(it).logErrors("textChangesFlow", logger)
}
awaitClose {
logger.d { "textChangesFlow: flow is closing" }
removeTextChangedListener(textWatcher)
}
}
fun <T> ChannelResult<T>.logErrors(methodName: String, logger: Logger): ChannelResult<T> {
onFailure { logger.e(it) { "$methodName: can't send event" } }
onClosed { logger.e(it) { "$methodName: flow has been closed" } }
return this
}
fun EditText.checkIfInputIsEmpty(
inputLayout: TextInputLayout,
lifecycleOwner: LifecycleOwner,
@StringRes stringId: Int,
trim: Boolean = true,
logger: Logger,
): String? {
val input = if (trim) text?.trim() else text
val text = input?.toString().orEmpty()
logger.d { "Input text is \"$text\"" }
return text.ifEmpty {
inputLayout.error = resources.getString(stringId)
lifecycleOwner.lifecycleScope.launch {
waitUntilNotEmpty(logger)
inputLayout.error = null
}
null
}
}
suspend fun EditText.waitUntilNotEmpty(logger: Logger) {
textChangesFlow(logger).filterNotNull().first { it.isNotEmpty() }
logger.v { "waitUntilNotEmpty() returned" }
}
fun <T> SharedPreferences.prefsChangeFlow(
logger: Logger,
valueReader: SharedPreferences.() -> T,
): Flow<T> = callbackFlow {
fun sendValue() = trySend(valueReader()).logErrors("prefsChangeFlow", logger)
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> sendValue() }
sendValue()
registerOnSharedPreferenceChangeListener(listener)
awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
}
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(value: T) {
removeObserver(this)
observer.onChanged(value)
}
})
}
fun Context.showLongToast(text: String) = showToast(text, Toast.LENGTH_LONG)
fun Context.showLongToast(@StringRes text: Int) = showLongToast(getString(text))
private fun Context.showToast(text: String, length: Int) {
Toast.makeText(this, text, length).show()
}
fun View.hideKeyboard() {
val imm = context.getSystemService<InputMethodManager>()
imm?.hideSoftInputFromWindow(windowToken, 0)
}