Last active
January 3, 2023 10:25
-
-
Save Jeevuz/cdc9a2dd3c9fa3fdddb28bdf3bf2738f to your computer and use it in GitHub Desktop.
Here I collect some of my most useful Kotlin extensions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
inline fun SharedPreferences.edit(changes: SharedPreferences.Editor.() -> SharedPreferences.Editor) { | |
edit().changes().apply() | |
} | |
fun ImageView.tintSrc(@ColorRes colorRes: Int) { | |
val drawable = DrawableCompat.wrap(drawable) | |
DrawableCompat.setTint(drawable, ContextCompat.getColor(context, colorRes)) | |
setImageDrawable(drawable) | |
if (drawable is TintAwareDrawable) invalidate() // Because in this case setImageDrawable will not call invalidate() | |
} | |
fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View { | |
return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot) | |
} | |
/** | |
* Compat version of setExactAndAllowWhileIdle() | |
*/ | |
fun AlarmManager.setExactAndAllowWhileIdleCompat(alarmType: Int, timeMillis: Long, pendingIntent: PendingIntent) { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | |
// This version added Doze | |
setExactAndAllowWhileIdle(alarmType, timeMillis, pendingIntent) | |
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | |
// This version changed set() to be inexact | |
setExact(alarmType, timeMillis, pendingIntent) | |
} else { | |
set(alarmType, timeMillis, pendingIntent) | |
} | |
} | |
/** | |
* Helps to set clickable part in text. | |
* | |
* Don't forget to set android:textColorLink="@color/link" (click selector) and | |
* android:textColorHighlight="@color/window_background" (background color while clicks) | |
* in the TextView where you will use this. | |
*/ | |
fun SpannableString.withClickableSpan(clickablePart: String, onClickListener: () -> Unit): SpannableString { | |
val clickableSpan = object : ClickableSpan() { | |
override fun onClick(widget: View?) = onClickListener.invoke() | |
} | |
val clickablePartStart = indexOf(clickablePart) | |
setSpan(clickableSpan, | |
clickablePartStart, | |
clickablePartStart + clickablePart.length, | |
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) | |
return this | |
} | |
/** | |
* Helps to get Map, List, Set or other generic type from Json using Gson. | |
*/ | |
inline fun <reified T: Any> Gson.fromJsonToGeneric(json: String): T { | |
val type = object : TypeToken<T>() {}.type | |
return fromJson(json, type) | |
} | |
// The way to pass server errors from custom error response to the Rx onError. | |
// BaseReply has field error: ApiError and ApiError extends Exception. | |
fun <T : BaseReply> Single<T>.apiErrorsToOnError(): Single<T> { | |
// .map the source and | |
return this.map { reply -> | |
// throw if reply with error or | |
reply.error?.let { throw it } | |
// return if not | |
reply | |
} | |
} | |
// Adds polling to the request. BaseReply is general response type that has error field | |
fun <T : BaseReply> Single<T>.poll(delay: Long, errorConsumer: Consumer<Throwable>): Observable<T> = this | |
.repeatWhen { completed -> completed.delay(delay, TimeUnit.SECONDS) } | |
.doOnError { errorConsumer.accept(it) } | |
.retryWhen { errors -> errors.delay(delay, TimeUnit.SECONDS) } | |
.toObservable() | |
fun View.visible(visible: Boolean, useGone: Boolean = true) { | |
this.visibility = if (visible) View.VISIBLE else if (useGone) View.GONE else View.INVISIBLE | |
} | |
// Helps to set status bar color with api version check | |
fun Activity.setStatusBarColor(@ColorRes colorRes: Int): Unit { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | |
window.statusBarColor = ContextCompat.getColor(this, colorRes) | |
} | |
} | |
// Adds flags to make window fullscreen | |
fun Activity.setFullscreenLayoutFlags() { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | |
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or | |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | |
} | |
} | |
// Adds window insets to the view while entire activity is fullscreen. | |
fun View.applyWindowInsets(applyTopInset: Boolean = true, applyOtherInsets: Boolean = true): Unit { | |
if (applyTopInset || applyOtherInsets) { | |
ViewCompat.setOnApplyWindowInsetsListener( | |
this, | |
{ view, insets -> | |
// Set padding for needed insets | |
view.setPadding( | |
if (applyOtherInsets) insets.systemWindowInsetLeft else view.paddingLeft, | |
if (applyTopInset) insets.systemWindowInsetTop else view.paddingTop, | |
if (applyOtherInsets) insets.systemWindowInsetRight else view.paddingRight, | |
if (applyOtherInsets) insets.systemWindowInsetBottom else view.paddingBottom | |
) | |
// Return without consumed insets | |
insets.replaceSystemWindowInsets( | |
if (applyOtherInsets) 0 else insets.systemWindowInsetLeft, | |
if (applyTopInset) 0 else insets.systemWindowInsetTop, | |
if (applyOtherInsets) 0 else insets.systemWindowInsetRight, | |
if (applyOtherInsets) 0 else insets.systemWindowInsetBottom | |
) | |
}) | |
} else { | |
// Listener is not needed | |
ViewCompat.setOnApplyWindowInsetsListener(this, null) | |
} | |
} | |
fun Activity.getScreenHeight(): Int { | |
val size = Point() | |
windowManager.defaultDisplay.getSize(size) | |
return size.y | |
} | |
fun String.onlyDigits(): String = replace(Regex("\\D*"), "") | |
fun View.showKeyboard(show: Boolean) { | |
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager | |
if (show) { | |
if (requestFocus()) imm.showSoftInput(this, 0) | |
} else { | |
imm.hideSoftInputFromWindow(windowToken, 0) | |
} | |
} | |
/** | |
* Loads image with Glide into the [ImageView]. | |
* | |
* @param url url to load | |
* @param previousUrl url that already loaded in this target. Needed to prevent white flickering. | |
* @param round if set, the image will be round. | |
* @param cornersRadius the corner radius to set. Only used if [round] is `false`(by default). | |
* @param crop if set to `true` then [CenterCrop] will be used. Default is `false` so [FitCenter] is used. | |
*/ | |
@SuppressLint("CheckResult") | |
fun ImageView.load( | |
url: String, | |
previousUrl: String? = null, | |
round: Boolean = false, | |
cornersRadius: Int = 0, | |
crop: Boolean = false | |
) { | |
val requestOptions = when { | |
round -> RequestOptions.circleCropTransform() | |
cornersRadius > 0 -> { | |
RequestOptions().transforms( | |
if (crop) CenterCrop() else FitCenter(), | |
RoundedCorners(cornersRadius) | |
) | |
} | |
else -> null | |
} | |
Glide | |
.with(context) | |
.load(url) | |
.let { | |
// Apply request options | |
if (requestOptions != null) { | |
it.apply(requestOptions) | |
} else { | |
it | |
} | |
} | |
.let { | |
// Workaround for the white flickering. | |
// See https://github.com/bumptech/glide/issues/527 | |
// Thumbnail changes must be the same to catch the memory cache. | |
if (previousUrl != null) { | |
it.thumbnail( | |
Glide | |
.with(context) | |
.load(previousUrl) | |
.let { | |
// Apply request options | |
if (requestOptions != null) { | |
it.apply(requestOptions) | |
} else { | |
it | |
} | |
} | |
) | |
} else { | |
it | |
} | |
} | |
.into(this) | |
} | |
inline fun <reified C : Collection<T>, reified T : Any> Moshi.collectionAdapter(): JsonAdapter<C> { | |
val parametrizedType = Types.newParameterizedType(C::class.java, T::class.java) | |
return this.adapter<C>(parametrizedType) | |
} |
Thank you
I have a improvement for the withClickableSpan
extension, basically if you want search by multiple values.
fun SpannableString.withClickableSpan(pattern: Pattern, onClickListener: (String) -> Unit): SpannableString {
val matcher: Matcher = pattern.matcher(this)
while (matcher.find()) {
val tag: String = matcher.group(0)
val clickableSpan = object : ClickableSpan() {
override fun onClick(widget: View?) = onClickListener.invoke(tag)
}
setSpan(clickableSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
return this
}
Even you can use this..
fun TextView.makeLinks(vararg links: Pair<String, View.OnClickListener>) {
val spannableString = SpannableString(this.text)
for (link in links) {
val clickableSpan = object : ClickableSpan() {
override fun onClick(view: View) {
Selection.setSelection((view as TextView).text as Spannable, 0)
view.invalidate()
link.second.onClick(view)
}
}
val startIndexOfLink = this.text.toString().indexOf(link.first)
spannableString.setSpan(
clickableSpan, startIndexOfLink, startIndexOfLink + link.first.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
this.movementMethod =
LinkMovementMethod.getInstance() // without LinkMovementMethod, link can not click
this.setText(spannableString, TextView.BufferType.SPANNABLE)
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for the
withClickableSpan
extension.