szkolny/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt

1027 lines
34 KiB
Kotlin
Raw Normal View History

package pl.szczodrzynski.edziennik
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.content.res.Resources
import android.database.Cursor
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
2019-11-12 16:35:47 -06:00
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.text.*
import android.text.style.ForegroundColorSpan
import android.text.style.StrikethroughSpan
2019-11-12 16:35:47 -06:00
import android.text.style.StyleSpan
import android.util.*
import android.util.Base64
import android.util.Base64.NO_WRAP
import android.util.Base64.encodeToString
import android.view.View
import android.widget.CheckBox
import android.widget.CompoundButton
import android.widget.TextView
import androidx.annotation.*
import androidx.core.app.ActivityCompat
import androidx.core.database.getIntOrNull
import androidx.core.database.getLongOrNull
import androidx.core.database.getStringOrNull
import androidx.core.util.forEach
2019-11-12 16:35:47 -06:00
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.google.android.gms.security.ProviderInstaller
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import im.wangchao.mhttp.Response
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.ConnectionSpec
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.TlsVersion
import okio.Buffer
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.network.TLSSocketFactory
2019-11-24 14:09:49 -06:00
import pl.szczodrzynski.edziennik.utils.models.Time
2019-12-26 16:44:24 -06:00
import java.io.PrintWriter
import java.io.StringWriter
import java.math.BigInteger
import java.nio.charset.Charset
import java.security.KeyStore
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.*
import java.util.zip.CRC32
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import kotlin.Pair
fun List<Teacher>.byId(id: Long) = firstOrNull { it.id == id }
fun List<Teacher>.byNameFirstLast(nameFirstLast: String) = firstOrNull { it.name + " " + it.surname == nameFirstLast }
fun List<Teacher>.byNameLastFirst(nameLastFirst: String) = firstOrNull { it.surname + " " + it.name == nameLastFirst }
fun List<Teacher>.byNameFDotLast(nameFDotLast: String) = firstOrNull { it.name + "." + it.surname == nameFDotLast }
fun List<Teacher>.byNameFDotSpaceLast(nameFDotSpaceLast: String) = firstOrNull { it.name + ". " + it.surname == nameFDotSpaceLast }
fun JsonObject?.get(key: String): JsonElement? = this?.get(key)
fun JsonObject?.getBoolean(key: String): Boolean? = get(key)?.let { if (it.isJsonNull) null else it.asBoolean }
fun JsonObject?.getString(key: String): String? = get(key)?.let { if (it.isJsonNull) null else it.asString }
fun JsonObject?.getInt(key: String): Int? = get(key)?.let { if (it.isJsonNull) null else it.asInt }
fun JsonObject?.getLong(key: String): Long? = get(key)?.let { if (it.isJsonNull) null else it.asLong }
2019-10-20 07:07:34 -05:00
fun JsonObject?.getFloat(key: String): Float? = get(key)?.let { if(it.isJsonNull) null else it.asFloat }
fun JsonObject?.getChar(key: String): Char? = get(key)?.let { if(it.isJsonNull) null else it.asCharacter }
fun JsonObject?.getJsonObject(key: String): JsonObject? = get(key)?.let { if (it.isJsonNull) null else it.asJsonObject }
fun JsonObject?.getJsonArray(key: String): JsonArray? = get(key)?.let { if (it.isJsonNull) null else it.asJsonArray }
fun JsonObject?.getBoolean(key: String, defaultValue: Boolean): Boolean = get(key)?.let { if (it.isJsonNull) defaultValue else it.asBoolean } ?: defaultValue
fun JsonObject?.getString(key: String, defaultValue: String): String = get(key)?.let { if (it.isJsonNull) defaultValue else it.asString } ?: defaultValue
fun JsonObject?.getInt(key: String, defaultValue: Int): Int = get(key)?.let { if (it.isJsonNull) defaultValue else it.asInt } ?: defaultValue
fun JsonObject?.getLong(key: String, defaultValue: Long): Long = get(key)?.let { if (it.isJsonNull) defaultValue else it.asLong } ?: defaultValue
2019-10-20 07:07:34 -05:00
fun JsonObject?.getFloat(key: String, defaultValue: Float): Float = get(key)?.let { if(it.isJsonNull) defaultValue else it.asFloat } ?: defaultValue
fun JsonObject?.getChar(key: String, defaultValue: Char): Char = get(key)?.let { if(it.isJsonNull) defaultValue else it.asCharacter } ?: defaultValue
fun JsonObject?.getJsonObject(key: String, defaultValue: JsonObject): JsonObject = get(key)?.let { if (it.isJsonNull) defaultValue else it.asJsonObject } ?: defaultValue
fun JsonObject?.getJsonArray(key: String, defaultValue: JsonArray): JsonArray = get(key)?.let { if (it.isJsonNull) defaultValue else it.asJsonArray } ?: defaultValue
operator fun JsonObject.set(key: String, value: JsonElement) = this.add(key, value)
operator fun JsonObject.set(key: String, value: Boolean) = this.addProperty(key, value)
operator fun JsonObject.set(key: String, value: String?) = this.addProperty(key, value)
operator fun JsonObject.set(key: String, value: Number) = this.addProperty(key, value)
operator fun JsonObject.set(key: String, value: Char) = this.addProperty(key, value)
operator fun Profile.set(key: String, value: JsonElement) = this.studentData.add(key, value)
operator fun Profile.set(key: String, value: Boolean) = this.studentData.addProperty(key, value)
operator fun Profile.set(key: String, value: String?) = this.studentData.addProperty(key, value)
operator fun Profile.set(key: String, value: Number) = this.studentData.addProperty(key, value)
operator fun Profile.set(key: String, value: Char) = this.studentData.addProperty(key, value)
fun JsonArray.asJsonObjectList() = this.mapNotNull { it.asJsonObject }
fun CharSequence?.isNotNullNorEmpty(): Boolean {
return this != null && this.isNotEmpty()
}
fun CharSequence?.isNotNullNorBlank(): Boolean {
return this != null && this.isNotBlank()
}
fun currentTimeUnix() = System.currentTimeMillis() / 1000
fun Bundle?.getInt(key: String, defaultValue: Int): Int {
return this?.getInt(key, defaultValue) ?: defaultValue
}
fun Bundle?.getLong(key: String, defaultValue: Long): Long {
return this?.getLong(key, defaultValue) ?: defaultValue
}
fun Bundle?.getFloat(key: String, defaultValue: Float): Float {
return this?.getFloat(key, defaultValue) ?: defaultValue
}
fun Bundle?.getString(key: String, defaultValue: String): String {
return this?.getString(key, defaultValue) ?: defaultValue
}
/**
* ` The quick BROWN_fox Jumps OveR THE LAZy-DOG. `
*
* converts to
*
* `The Quick Brown_fox Jumps Over The Lazy-Dog.`
*/
fun String?.fixName(): String {
return this?.fixWhiteSpaces()?.toProperCase() ?: ""
}
/**
* `The quick BROWN_fox Jumps OveR THE LAZy-DOG.`
*
* converts to
*
* `The Quick Brown_fox Jumps Over The Lazy-Dog.`
*/
fun String.toProperCase(): String = changeStringCase(this)
/**
* `John Smith` -> `Smith John`
*
* `JOHN SMith` -> `SMith JOHN`
*/
fun String.swapFirstLastName(): String {
return this.split(" ").let {
if (it.size > 1)
it[1]+" "+it[0]
else
it[0]
}
}
fun String.splitName(): Pair<String, String>? {
return this.split(" ").let {
if (it.size >= 2) Pair(it[0], it[1])
else null
}
}
fun changeStringCase(s: String): String {
val delimiters = " '-/"
val sb = StringBuilder()
var capNext = true
for (ch in s.toCharArray()) {
var c = ch
c = if (capNext)
Character.toUpperCase(c)
else
Character.toLowerCase(c)
sb.append(c)
capNext = delimiters.indexOf(c) >= 0
}
return sb.toString()
}
fun buildFullName(firstName: String?, lastName: String?): String {
return "$firstName $lastName".fixName()
}
fun String.getShortName(): String {
return split(" ").let {
if (it.size > 1)
"${it[0]} ${it[1][0]}."
else
it[0]
}
}
/**
* "John Smith" -> "JS"
*
* "JOHN SMith" -> "JS"
*
* "John" -> "J"
*
* "John " -> "J"
*
* "John Smith " -> "JS"
*
* " " -> ""
*
* " " -> ""
*/
fun String?.getNameInitials(): String {
if (this.isNullOrBlank()) return ""
return this.toUpperCase().fixWhiteSpaces().split(" ").take(2).map { it[0] }.joinToString("")
}
fun List<String>.join(delimiter: String): String {
return concat(delimiter).toString()
}
fun colorFromName(name: String?): Int {
val i = (name ?: "").crc32()
return when ((i / 10 % 16 + 1).toInt()) {
13 -> 0xffF44336
4 -> 0xffF50057
2 -> 0xffD500F9
9 -> 0xff6200EA
5 -> 0xffFFAB00
1 -> 0xff304FFE
6 -> 0xff40C4FF
14 -> 0xff26A69A
15 -> 0xff00C853
7 -> 0xffFFD600
3 -> 0xffFF3D00
8 -> 0xffDD2C00
10 -> 0xff795548
12 -> 0xff2979FF
11 -> 0xffFF6D00
else -> 0xff64DD17
}.toInt()
}
fun colorFromCssName(name: String): Int {
return when (name) {
"red" -> 0xffff0000
"green" -> 0xff008000
"blue" -> 0xff0000ff
"violet" -> 0xffee82ee
"brown" -> 0xffa52a2a
"orange" -> 0xffffa500
"black" -> 0xff000000
"white" -> 0xffffffff
else -> -1
}.toInt()
}
fun List<Profile>.filterOutArchived() = this.filter { !it.archived }
fun Activity.isStoragePermissionGranted(): Boolean {
return if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
true
} else {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
false
}
} else {
true
}
}
fun Response?.getUnixDate(): Long {
val rfcDate = this?.headers()?.get("date") ?: return currentTimeUnix()
val pattern = "EEE, dd MMM yyyy HH:mm:ss Z"
val format = SimpleDateFormat(pattern, Locale.ENGLISH)
return format.parse(rfcDate).time / 1000
}
const val MINUTE = 60L
const val HOUR = 60L*MINUTE
const val DAY = 24L*HOUR
const val WEEK = 7L*DAY
const val MONTH = 30L*DAY
const val YEAR = 365L*DAY
const val MS = 1000L
fun <T> LongSparseArray<T>.values(): List<T> {
val result = mutableListOf<T>()
forEach { _, value ->
result += value
}
return result
}
fun SparseArray<*>.keys(): List<Int> {
val result = mutableListOf<Int>()
forEach { key, _ ->
result += key
}
return result
}
fun <T> SparseArray<T>.values(): List<T> {
val result = mutableListOf<T>()
forEach { _, value ->
result += value
}
return result
}
fun SparseIntArray.keys(): List<Int> {
val result = mutableListOf<Int>()
forEach { key, _ ->
result += key
}
return result
}
fun SparseIntArray.values(): List<Int> {
val result = mutableListOf<Int>()
forEach { _, value ->
result += value
}
return result
}
fun <K, V> List<Pair<K, V>>.keys(): List<K> {
val result = mutableListOf<K>()
forEach { pair ->
result += pair.first
}
return result
}
fun <K, V> List<Pair<K, V>>.values(): List<V> {
val result = mutableListOf<V>()
forEach { pair ->
result += pair.second
}
return result
}
fun <T> List<T>.toSparseArray(destination: SparseArray<T>, key: (T) -> Int) {
forEach {
destination.put(key(it), it)
}
}
fun <T> List<T>.toSparseArray(destination: LongSparseArray<T>, key: (T) -> Long) {
forEach {
destination.put(key(it), it)
}
}
fun <T> List<T>.toSparseArray(key: (T) -> Int): SparseArray<T> {
val result = SparseArray<T>()
toSparseArray(result, key)
return result
}
fun <T> List<T>.toSparseArray(key: (T) -> Long): LongSparseArray<T> {
val result = LongSparseArray<T>()
toSparseArray(result, key)
return result
}
fun <T> SparseArray<T>.singleOrNull(predicate: (T) -> Boolean): T? {
forEach { _, value ->
if (predicate(value))
return value
}
return null
}
fun <T> LongSparseArray<T>.singleOrNull(predicate: (T) -> Boolean): T? {
forEach { _, value ->
if (predicate(value))
return value
}
return null
}
fun String.fixWhiteSpaces() = buildString(length) {
var wasWhiteSpace = true
for (c in this@fixWhiteSpaces) {
if (c.isWhitespace()) {
if (!wasWhiteSpace) {
append(c)
wasWhiteSpace = true
}
} else {
append(c)
wasWhiteSpace = false
}
}
}.trimEnd()
fun List<Team>.getById(id: Long): Team? {
return singleOrNull { it.id == id }
}
fun LongSparseArray<Team>.getById(id: Long): Team? {
forEach { _, value ->
if (value.id == id)
return value
}
return null
}
operator fun MatchResult.get(group: Int): String {
if (group >= groupValues.size)
return ""
return groupValues[group]
2019-10-20 07:07:34 -05:00
}
fun Activity.setLanguage(language: String) {
val locale = Locale(language.toLowerCase(Locale.ROOT))
val configuration = resources.configuration
Locale.setDefault(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLocale(locale)
}
configuration.locale = locale
resources.updateConfiguration(configuration, resources.displayMetrics)
baseContext.resources.updateConfiguration(configuration, baseContext.resources.displayMetrics)
}
/*
Code copied from android-28/java.util.Locale.initDefault()
*/
fun initDefaultLocale() {
run {
// user.locale gets priority
/*val languageTag: String? = System.getProperty("user.locale", "")
if (languageTag.isNotNullNorEmpty()) {
return@run Locale(languageTag)
}*/
// user.locale is empty
val language: String? = System.getProperty("user.language", "pl")
val region: String? = System.getProperty("user.region")
val country: String?
val variant: String?
// for compatibility, check for old user.region property
if (region != null) {
// region can be of form country, country_variant, or _variant
val i = region.indexOf('_')
if (i >= 0) {
country = region.substring(0, i)
variant = region.substring(i + 1)
} else {
country = region
variant = ""
}
} else {
country = System.getProperty("user.country", "")
variant = System.getProperty("user.variant", "")
}
return@run Locale(language)
}.let {
Locale.setDefault(it)
}
}
fun String.crc16(): Int {
var crc = 0xFFFF
for (aBuffer in this) {
crc = crc.ushr(8) or (crc shl 8) and 0xffff
crc = crc xor (aBuffer.toInt() and 0xff) // byte to int, trunc sign
crc = crc xor (crc and 0xff shr 4)
crc = crc xor (crc shl 12 and 0xffff)
crc = crc xor (crc and 0xFF shl 5 and 0xffff)
}
crc = crc and 0xffff
return crc + 32768
}
fun String.crc32(): Long {
val crc = CRC32()
crc.update(toByteArray())
return crc.value
}
fun String.hmacSHA1(password: String): String {
val key = SecretKeySpec(password.toByteArray(), "HmacSHA1")
val mac = Mac.getInstance("HmacSHA1").apply {
init(key)
update(this@hmacSHA1.toByteArray())
}
return encodeToString(mac.doFinal(), NO_WRAP)
}
fun String.md5(): String {
val md = MessageDigest.getInstance("MD5")
return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0')
}
fun String.sha256(): ByteArray {
val md = MessageDigest.getInstance("SHA-256")
md.update(toByteArray())
return md.digest()
}
fun RequestBody.bodyToString(): String {
val buffer = Buffer()
writeTo(buffer)
return buffer.readUtf8()
}
fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this)
fun CharSequence?.asColoredSpannable(colorInt: Int): Spannable {
val spannable = SpannableString(this)
spannable.setSpan(ForegroundColorSpan(colorInt), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
fun CharSequence?.asStrikethroughSpannable(): Spannable {
val spannable = SpannableString(this)
spannable.setSpan(StrikethroughSpan(), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
2019-11-12 16:35:47 -06:00
fun CharSequence?.asItalicSpannable(): Spannable {
val spannable = SpannableString(this)
spannable.setSpan(StyleSpan(Typeface.ITALIC), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
fun CharSequence?.asBoldSpannable(): Spannable {
val spannable = SpannableString(this)
spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
fun CharSequence.asSpannable(vararg spans: Any, substring: String? = null, ignoreCase: Boolean = false): Spannable {
val spannable = SpannableString(this)
if (substring == null) {
spans.forEach {
spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
else if (substring.isNotEmpty()) {
var index = indexOf(substring, ignoreCase = ignoreCase)
while (index >= 0) {
spans.forEach {
spannable.setSpan(it, index, index + substring.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
index = indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
}
}
return spannable
}
/**
* Returns a new read-only list only of those given elements, that are not empty.
* Applies for CharSequence and descendants.
*/
fun <T : CharSequence> listOfNotEmpty(vararg elements: T): List<T> = elements.filterNot { it.isEmpty() }
fun List<CharSequence?>.concat(delimiter: CharSequence? = null): CharSequence {
if (this.isEmpty()) {
return ""
}
if (this.size == 1) {
2019-11-25 15:15:36 -06:00
return this[0] ?: ""
}
var spanned = delimiter is Spanned
if (!spanned) {
for (piece in this) {
if (piece is Spanned) {
spanned = true
break
}
}
}
var first = true
if (spanned) {
val ssb = SpannableStringBuilder()
for (piece in this) {
2019-11-25 15:15:36 -06:00
if (piece == null)
continue
if (!first && delimiter != null)
ssb.append(delimiter)
first = false
ssb.append(piece)
}
return SpannedString(ssb)
} else {
val sb = StringBuilder()
for (piece in this) {
2019-11-25 15:15:36 -06:00
if (piece == null)
continue
if (!first && delimiter != null)
sb.append(delimiter)
first = false
sb.append(piece)
}
return sb.toString()
}
}
fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) {
text = context.getString(resid, *formatArgs)
}
2019-11-24 05:08:14 -06:00
fun JsonObject(vararg properties: Pair<String, Any?>): JsonObject {
return JsonObject().apply {
for (property in properties) {
when (property.second) {
2019-11-24 05:08:14 -06:00
is JsonElement -> add(property.first, property.second as JsonElement?)
is String -> addProperty(property.first, property.second as String?)
is Char -> addProperty(property.first, property.second as Char?)
is Number -> addProperty(property.first, property.second as Number?)
is Boolean -> addProperty(property.first, property.second as Boolean?)
}
}
}
}
fun JsonArray(vararg properties: Any?): JsonArray {
return JsonArray().apply {
for (property in properties) {
when (property) {
is JsonElement -> add(property as JsonElement?)
is String -> add(property as String?)
is Char -> add(property as Char?)
is Number -> add(property as Number?)
is Boolean -> add(property as Boolean?)
}
}
}
}
fun Bundle(vararg properties: Pair<String, Any?>): Bundle {
return Bundle().apply {
for (property in properties) {
when (property.second) {
is String -> putString(property.first, property.second as String?)
is Char -> putChar(property.first, property.second as Char)
is Int -> putInt(property.first, property.second as Int)
is Long -> putLong(property.first, property.second as Long)
is Float -> putFloat(property.first, property.second as Float)
is Short -> putShort(property.first, property.second as Short)
is Double -> putDouble(property.first, property.second as Double)
is Boolean -> putBoolean(property.first, property.second as Boolean)
}
}
}
}
fun Intent(action: String? = null, vararg properties: Pair<String, Any?>): Intent {
return Intent(action).putExtras(Bundle(*properties))
}
fun Intent(packageContext: Context, cls: Class<*>, vararg properties: Pair<String, Any?>): Intent {
return Intent(packageContext, cls).putExtras(Bundle(*properties))
}
fun Bundle.toJsonObject(): JsonObject {
val json = JsonObject()
keySet()?.forEach { key ->
get(key)?.let {
when (it) {
is String -> json.addProperty(key, it)
is Char -> json.addProperty(key, it)
is Int -> json.addProperty(key, it)
is Long -> json.addProperty(key, it)
is Float -> json.addProperty(key, it)
is Short -> json.addProperty(key, it)
is Double -> json.addProperty(key, it)
is Boolean -> json.addProperty(key, it)
is Bundle -> json.add(key, it.toJsonObject())
else -> json.addProperty(key, it.toString())
}
}
}
return json
}
fun Intent.toJsonObject() = extras?.toJsonObject()
fun JsonArray?.isNullOrEmpty(): Boolean = (this?.size() ?: 0) == 0
fun JsonArray.isEmpty(): Boolean = this.size() == 0
operator fun JsonArray.plusAssign(o: JsonElement) = this.add(o)
operator fun JsonArray.plusAssign(o: String) = this.add(o)
operator fun JsonArray.plusAssign(o: Char) = this.add(o)
operator fun JsonArray.plusAssign(o: Number) = this.add(o)
operator fun JsonArray.plusAssign(o: Boolean) = this.add(o)
@Suppress("UNCHECKED_CAST")
inline fun <T : View> T.onClick(crossinline onClickListener: (v: T) -> Unit) {
setOnClickListener { v: View ->
onClickListener(v as T)
}
2019-11-12 16:35:47 -06:00
}
@Suppress("UNCHECKED_CAST")
inline fun <T : CompoundButton> T.onChange(crossinline onChangeListener: (v: T, isChecked: Boolean) -> Unit) {
setOnCheckedChangeListener { buttonView, isChecked ->
onChangeListener(buttonView as T, isChecked)
}
}
2019-11-12 16:35:47 -06:00
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
/**
* Convert a value in dp to pixels.
*/
val Int.dp: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
/**
* Convert a value in pixels to dp.
*/
val Int.px: Int
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
@ColorInt
fun @receiver:AttrRes Int.resolveAttr(context: Context?): Int {
val typedValue = TypedValue()
context?.theme?.resolveAttribute(this, typedValue, true)
return typedValue.data
}
@ColorInt
fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getColor(this, context.theme)
}
else {
context.resources.getColor(this)
}
}
fun @receiver:DrawableRes Int.resolveDrawable(context: Context): Drawable {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getDrawable(this, context.theme)
}
else {
context.resources.getDrawable(this)
}
}
fun View.findParentById(targetId: Int): View? {
if (id == targetId) {
return this
}
val viewParent = this.parent ?: return null
if (viewParent is View) {
return viewParent.findParentById(targetId)
}
return null
2019-11-24 05:08:14 -06:00
}
fun CoroutineScope.startCoroutineTimer(delayMillis: Long = 0, repeatMillis: Long = 0, action: () -> Unit) = launch {
delay(delayMillis)
if (repeatMillis > 0) {
while (true) {
action()
delay(repeatMillis)
}
} else {
action()
}
}
2019-11-24 14:09:49 -06:00
operator fun Time?.compareTo(other: Time?): Int {
if (this == null && other == null)
return 0
if (this == null)
return -1
if (other == null)
return 1
return this.compareTo(other)
}
operator fun StringBuilder.plusAssign(str: String?) {
this.append(str)
}
2019-11-25 15:15:36 -06:00
2019-12-22 13:04:20 -06:00
fun Context.timeTill(time: Int, delimiter: String = " ", countInSeconds: Boolean = false): String {
2019-11-25 15:15:36 -06:00
val parts = mutableListOf<Pair<Int, Int>>()
val hours = time / 3600
val minutes = (time - hours*3600) / 60
val seconds = time - minutes*60 - hours*3600
2019-12-22 13:04:20 -06:00
if (!countInSeconds) {
var prefixAdded = false
if (hours > 0) {
if (!prefixAdded) parts += R.plurals.time_till_text to hours
prefixAdded = true
parts += R.plurals.time_till_hours to hours
}
if (minutes > 0) {
if (!prefixAdded) parts += R.plurals.time_till_text to minutes
prefixAdded = true
parts += R.plurals.time_till_minutes to minutes
}
if (hours == 0 && minutes < 10) {
if (!prefixAdded) parts += R.plurals.time_till_text to seconds
prefixAdded = true
parts += R.plurals.time_till_seconds to seconds
}
} else {
parts += R.plurals.time_till_text to time
parts += R.plurals.time_till_seconds to time
2019-11-25 15:15:36 -06:00
}
return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) }
}
2019-12-22 13:04:20 -06:00
fun Context.timeLeft(time: Int, delimiter: String = " ", countInSeconds: Boolean = false): String {
2019-11-25 15:15:36 -06:00
val parts = mutableListOf<Pair<Int, Int>>()
val hours = time / 3600
val minutes = (time - hours*3600) / 60
val seconds = time - minutes*60 - hours*3600
2019-12-22 13:04:20 -06:00
if (!countInSeconds) {
var prefixAdded = false
if (hours > 0) {
if (!prefixAdded) parts += R.plurals.time_left_text to hours
prefixAdded = true
parts += R.plurals.time_left_hours to hours
}
if (minutes > 0) {
if (!prefixAdded) parts += R.plurals.time_left_text to minutes
prefixAdded = true
parts += R.plurals.time_left_minutes to minutes
}
if (hours == 0 && minutes < 10) {
if (!prefixAdded) parts += R.plurals.time_left_text to seconds
prefixAdded = true
parts += R.plurals.time_left_seconds to seconds
}
} else {
parts += R.plurals.time_left_text to time
parts += R.plurals.time_left_seconds to time
2019-11-25 15:15:36 -06:00
}
return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) }
}
inline fun <reified T> Any?.instanceOfOrNull(): T? {
return when (this) {
is T -> this
else -> null
}
}
fun Drawable.setTintColor(color: Int): Drawable {
colorFilter = PorterDuffColorFilter(
color,
PorterDuff.Mode.SRC_ATOP
)
return this
}
inline fun <T> List<T>.ifNotEmpty(block: (List<T>) -> Unit) {
if (!isEmpty())
block(this)
}
val String.firstLettersName: String
get() {
var nameShort = ""
this.split(" ").forEach {
if (it.isBlank())
return@forEach
nameShort += it[0].toLowerCase()
}
return nameShort
}
2019-12-26 16:44:24 -06:00
val Throwable.stackTraceString: String
get() {
val sw = StringWriter()
printStackTrace(PrintWriter(sw))
return sw.toString()
}
inline fun <T> LongSparseArray<T>.filter(predicate: (T) -> Boolean): List<T> {
val destination = ArrayList<T>()
this.forEach { _, element -> if (predicate(element)) destination.add(element) }
return destination
}
fun CharSequence.replace(oldValue: String, newValue: CharSequence, ignoreCase: Boolean = false): CharSequence =
splitToSequence(oldValue, ignoreCase = ignoreCase).toList().concat(newValue)
fun Int.toColorStateList(): ColorStateList {
val states = arrayOf(
intArrayOf( android.R.attr.state_enabled ),
intArrayOf(-android.R.attr.state_enabled ),
intArrayOf(-android.R.attr.state_checked ),
intArrayOf( android.R.attr.state_pressed )
)
val colors = intArrayOf(
this,
this,
this,
this
)
return ColorStateList(states, colors)
}
fun SpannableStringBuilder.appendText(text: CharSequence): SpannableStringBuilder {
append(text)
return this
}
fun SpannableStringBuilder.appendSpan(text: CharSequence, what: Any, flags: Int): SpannableStringBuilder {
val start: Int = length
append(text)
setSpan(what, start, length, flags)
return this
}
fun joinNotNullStrings(delimiter: String = "", vararg parts: String?): String {
var first = true
val sb = StringBuilder()
for (part in parts) {
if (part == null)
continue
if (!first)
sb += delimiter
first = false
sb += part
}
return sb.toString()
}
fun String.notEmptyOrNull(): String? {
return if (isEmpty())
null
else
this
}
fun String.base64Encode(): String {
return encodeToString(toByteArray(), NO_WRAP)
}
fun ByteArray.base64Encode(): String {
return encodeToString(this, NO_WRAP)
}
fun String.base64Decode(): ByteArray {
return Base64.decode(this, Base64.DEFAULT)
}
fun String.base64DecodeToString(): String {
return Base64.decode(this, Base64.DEFAULT).toString(Charset.defaultCharset())
}
fun CheckBox.trigger() { isChecked = !isChecked }
fun Context.plural(@PluralsRes resId: Int, value: Int): String = resources.getQuantityString(resId, value, value)
fun Context.getNotificationTitle(type: Int): String {
return getString(when (type) {
Notification.TYPE_UPDATE -> R.string.notification_type_update
Notification.TYPE_ERROR -> R.string.notification_type_error
Notification.TYPE_TIMETABLE_CHANGED -> R.string.notification_type_timetable_change
Notification.TYPE_TIMETABLE_LESSON_CHANGE -> R.string.notification_type_timetable_lesson_change
Notification.TYPE_NEW_GRADE -> R.string.notification_type_new_grade
Notification.TYPE_NEW_EVENT -> R.string.notification_type_new_event
Notification.TYPE_NEW_HOMEWORK -> R.string.notification_type_new_homework
Notification.TYPE_NEW_SHARED_EVENT -> R.string.notification_type_new_shared_event
Notification.TYPE_NEW_SHARED_HOMEWORK -> R.string.notification_type_new_shared_homework
Notification.TYPE_REMOVED_SHARED_EVENT -> R.string.notification_type_removed_shared_event
Notification.TYPE_NEW_MESSAGE -> R.string.notification_type_new_message
Notification.TYPE_NEW_NOTICE -> R.string.notification_type_notice
Notification.TYPE_NEW_ATTENDANCE -> R.string.notification_type_attendance
Notification.TYPE_SERVER_MESSAGE -> R.string.notification_type_server_message
Notification.TYPE_LUCKY_NUMBER -> R.string.notification_type_lucky_number
Notification.TYPE_FEEDBACK_MESSAGE -> R.string.notification_type_feedback_message
Notification.TYPE_NEW_ANNOUNCEMENT -> R.string.notification_type_new_announcement
Notification.TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving
Notification.TYPE_GENERAL -> R.string.notification_type_general
else -> R.string.notification_type_general
})
}
fun Cursor?.getString(columnName: String) = this?.getStringOrNull(getColumnIndex(columnName))
fun Cursor?.getInt(columnName: String) = this?.getIntOrNull(getColumnIndex(columnName))
fun Cursor?.getLong(columnName: String) = this?.getLongOrNull(getColumnIndex(columnName))
fun OkHttpClient.Builder.installHttpsSupport(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
try {
try {
ProviderInstaller.installIfNeeded(context)
} catch (e: Exception) {
Log.e("OkHttpTLSCompat", "Play Services not found or outdated")
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(null as KeyStore?)
val x509TrustManager = trustManagerFactory.trustManagers.singleOrNull { it is X509TrustManager } as X509TrustManager?
?: return
val sc = SSLContext.getInstance("TLSv1.2")
sc.init(null, null, null)
sslSocketFactory(TLSSocketFactory(sc.socketFactory), x509TrustManager)
val cs: ConnectionSpec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_0)
.tlsVersions(TlsVersion.TLS_1_1)
.tlsVersions(TlsVersion.TLS_1_2)
.build()
val specs: MutableList<ConnectionSpec> = ArrayList()
specs.add(cs)
specs.add(ConnectionSpec.COMPATIBLE_TLS)
specs.add(ConnectionSpec.CLEARTEXT)
connectionSpecs(specs)
}
} catch (exc: Exception) {
Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc)
}
}
}