/* * Copyright 2018 Allan Wang * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @file:Suppress("UNCHECKED_CAST", "DEPRECATION") package ca.allanwang.kau.utils /** * Created by Allan Wang on 2017-05-29. * * Courtesy of Jake Wharton * * https://github.com/JakeWharton/kotterknife/blob/master/src/main/kotlin/kotterknife/ButterKnife.kt * * Note that while this is useful for binding ids, there also exists other alternatives, such as * `kotlin-android-extensions`. * * For fragments, make sure that the views are reset after the fragment lifecycle. */ import android.app.Activity import android.app.Dialog import android.app.DialogFragment import android.app.Fragment import android.view.View import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder import java.util.Collections import java.util.WeakHashMap import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty import androidx.fragment.app.DialogFragment as SupportDialogFragment import androidx.fragment.app.Fragment as SupportFragment fun View.bindView(id: Int): ReadOnlyProperty = required(id, viewFinder) fun Activity.bindView(id: Int): ReadOnlyProperty = required(id, viewFinder) fun Dialog.bindView(id: Int): ReadOnlyProperty = required(id, viewFinder) fun DialogFragment.bindView(id: Int): ReadOnlyProperty = required(id, viewFinder) fun SupportDialogFragment.bindView(id: Int): ReadOnlyProperty = required(id, viewFinder) fun Fragment.bindView(id: Int): ReadOnlyProperty = required(id, viewFinder) fun SupportFragment.bindView(id: Int): ReadOnlyProperty = required(id, viewFinder) fun RecyclerView.ViewHolder.bindView(id: Int): ReadOnlyProperty = required(id, viewFinder) fun View.bindOptionalView(id: Int): ReadOnlyProperty = optional(id, viewFinder) fun Activity.bindOptionalView(id: Int): ReadOnlyProperty = optional(id, viewFinder) fun Dialog.bindOptionalView(id: Int): ReadOnlyProperty = optional(id, viewFinder) fun DialogFragment.bindOptionalView(id: Int): ReadOnlyProperty = optional(id, viewFinder) fun SupportDialogFragment.bindOptionalView(id: Int): ReadOnlyProperty = optional(id, viewFinder) fun Fragment.bindOptionalView(id: Int): ReadOnlyProperty = optional(id, viewFinder) fun SupportFragment.bindOptionalView(id: Int): ReadOnlyProperty = optional(id, viewFinder) fun RecyclerView.ViewHolder.bindOptionalView(id: Int): ReadOnlyProperty = optional(id, viewFinder) fun View.bindViews(vararg ids: Int): ReadOnlyProperty> = required(ids, viewFinder) fun Activity.bindViews(vararg ids: Int): ReadOnlyProperty> = required(ids, viewFinder) fun Dialog.bindViews(vararg ids: Int): ReadOnlyProperty> = required(ids, viewFinder) fun DialogFragment.bindViews(vararg ids: Int): ReadOnlyProperty> = required(ids, viewFinder) fun SupportDialogFragment.bindViews(vararg ids: Int): ReadOnlyProperty> = required(ids, viewFinder) fun Fragment.bindViews(vararg ids: Int): ReadOnlyProperty> = required(ids, viewFinder) fun SupportFragment.bindViews(vararg ids: Int): ReadOnlyProperty> = required(ids, viewFinder) fun ViewHolder.bindViews(vararg ids: Int): ReadOnlyProperty> = required(ids, viewFinder) fun View.bindOptionalViews(vararg ids: Int): ReadOnlyProperty> = optional(ids, viewFinder) fun Activity.bindOptionalViews(vararg ids: Int): ReadOnlyProperty> = optional(ids, viewFinder) fun Dialog.bindOptionalViews(vararg ids: Int): ReadOnlyProperty> = optional(ids, viewFinder) fun DialogFragment.bindOptionalViews(vararg ids: Int): ReadOnlyProperty> = optional(ids, viewFinder) fun SupportDialogFragment.bindOptionalViews(vararg ids: Int): ReadOnlyProperty> = optional(ids, viewFinder) fun Fragment.bindOptionalViews(vararg ids: Int): ReadOnlyProperty> = optional(ids, viewFinder) fun SupportFragment.bindOptionalViews(vararg ids: Int): ReadOnlyProperty> = optional(ids, viewFinder) fun ViewHolder.bindOptionalViews(vararg ids: Int): ReadOnlyProperty> = optional(ids, viewFinder) private inline val View.viewFinder: View.(Int) -> View? get() = { findViewById(it) } private inline val Activity.viewFinder: Activity.(Int) -> View? get() = { findViewById(it) } private inline val Dialog.viewFinder: Dialog.(Int) -> View? get() = { findViewById(it) } private inline val DialogFragment.viewFinder: DialogFragment.(Int) -> View? get() = { dialog.findViewById(it) } private inline val SupportDialogFragment.viewFinder: SupportDialogFragment.(Int) -> View? get() = { dialog?.findViewById(it) } private inline val Fragment.viewFinder: Fragment.(Int) -> View? get() = { view?.findViewById(it) } private inline val SupportFragment.viewFinder: SupportFragment.(Int) -> View? get() = { view?.findViewById(it) } private inline val ViewHolder.viewFinder: ViewHolder.(Int) -> View? get() = { itemView.findViewById(it) } private fun viewNotFound(id: Int, desc: KProperty<*>): Nothing = throw IllegalStateException("View ID $id for '${desc.name}' not found.") private fun required(id: Int, finder: T.(Int) -> View?) = Lazy { t: T, desc -> (t.finder(id) as V?)?.apply { } ?: viewNotFound(id, desc) } private fun optional(id: Int, finder: T.(Int) -> View?) = Lazy { t: T, _ -> t.finder(id) as V? } private fun required(ids: IntArray, finder: T.(Int) -> View?) = Lazy { t: T, desc -> ids.map { t.finder(it) as V? ?: viewNotFound(it, desc) } } private fun optional(ids: IntArray, finder: T.(Int) -> View?) = Lazy { t: T, _ -> ids.map { t.finder(it) as V? }.filterNotNull() } // Like Kotlin's lazy delegate but the initializer gets the target and metadata passed to it private open class Lazy(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty { protected object EMPTY protected var value: Any? = EMPTY override fun getValue(thisRef: T, property: KProperty<*>): V { if (value == EMPTY) value = initializer(thisRef, property) return value as V } } /* * The components below are a variant of the view bindings with lazy resettables * All bindings are weakly held so that they may be reset through KotterknifeRegistry.reset * * This is typically only needed in cases such as Fragments, * where their lifecycle doesn't match that of an Activity or View * * Credits to MichaelRocks */ fun View.bindViewResettable(id: Int): ReadOnlyProperty = requiredResettable(id, viewFinder) fun Activity.bindViewResettable(id: Int): ReadOnlyProperty = requiredResettable(id, viewFinder) fun Dialog.bindViewResettable(id: Int): ReadOnlyProperty = requiredResettable(id, viewFinder) fun DialogFragment.bindViewResettable(id: Int): ReadOnlyProperty = requiredResettable(id, viewFinder) fun SupportDialogFragment.bindViewResettable(id: Int): ReadOnlyProperty = requiredResettable(id, viewFinder) fun Fragment.bindViewResettable(id: Int): ReadOnlyProperty = requiredResettable(id, viewFinder) fun SupportFragment.bindViewResettable(id: Int): ReadOnlyProperty = requiredResettable(id, viewFinder) fun ViewHolder.bindViewResettable(id: Int): ReadOnlyProperty = requiredResettable(id, viewFinder) fun View.bindOptionalViewResettable(id: Int): ReadOnlyProperty = optionalResettable(id, viewFinder) fun Activity.bindOptionalViewResettable(id: Int): ReadOnlyProperty = optionalResettable(id, viewFinder) fun Dialog.bindOptionalViewResettable(id: Int): ReadOnlyProperty = optionalResettable(id, viewFinder) fun DialogFragment.bindOptionalViewResettable(id: Int): ReadOnlyProperty = optionalResettable(id, viewFinder) fun SupportDialogFragment.bindOptionalViewResettable(id: Int): ReadOnlyProperty = optionalResettable(id, viewFinder) fun Fragment.bindOptionalViewResettable(id: Int): ReadOnlyProperty = optionalResettable(id, viewFinder) fun SupportFragment.bindOptionalViewResettable(id: Int): ReadOnlyProperty = optionalResettable(id, viewFinder) fun ViewHolder.bindOptionalViewResettable(id: Int): ReadOnlyProperty = optionalResettable(id, viewFinder) fun View.bindViewsResettable(vararg ids: Int): ReadOnlyProperty> = requiredResettable(ids, viewFinder) fun Activity.bindViewsResettable(vararg ids: Int): ReadOnlyProperty> = requiredResettable(ids, viewFinder) fun Dialog.bindViewsResettable(vararg ids: Int): ReadOnlyProperty> = requiredResettable(ids, viewFinder) fun DialogFragment.bindViewsResettable(vararg ids: Int): ReadOnlyProperty> = requiredResettable(ids, viewFinder) fun SupportDialogFragment.bindViewsResettable(vararg ids: Int): ReadOnlyProperty> = requiredResettable(ids, viewFinder) fun Fragment.bindViewsResettable(vararg ids: Int): ReadOnlyProperty> = requiredResettable(ids, viewFinder) fun SupportFragment.bindViewsResettable(vararg ids: Int): ReadOnlyProperty> = requiredResettable(ids, viewFinder) fun ViewHolder.bindViewsResettable(vararg ids: Int): ReadOnlyProperty> = requiredResettable(ids, viewFinder) fun View.bindOptionalViewsResettable(vararg ids: Int): ReadOnlyProperty> = optionalResettable(ids, viewFinder) fun Activity.bindOptionalViewsResettable(vararg ids: Int): ReadOnlyProperty> = optionalResettable(ids, viewFinder) fun Dialog.bindOptionalViewsResettable(vararg ids: Int): ReadOnlyProperty> = optionalResettable(ids, viewFinder) fun DialogFragment.bindOptionalViewsResettable(vararg ids: Int): ReadOnlyProperty> = optionalResettable(ids, viewFinder) fun SupportDialogFragment.bindOptionalViewsResettable(vararg ids: Int): ReadOnlyProperty> = optionalResettable(ids, viewFinder) fun Fragment.bindOptionalViewsResettable(vararg ids: Int): ReadOnlyProperty> = optionalResettable(ids, viewFinder) fun SupportFragment.bindOptionalViewsResettable(vararg ids: Int): ReadOnlyProperty> = optionalResettable(ids, viewFinder) fun ViewHolder.bindOptionalViewsResettable(vararg ids: Int): ReadOnlyProperty> = optionalResettable(ids, viewFinder) private fun requiredResettable(id: Int, finder: T.(Int) -> View?) = LazyResettable { t: T, desc -> (t.finder(id) as V?)?.apply { } ?: viewNotFound(id, desc) } private fun optionalResettable(id: Int, finder: T.(Int) -> View?) = LazyResettable { t: T, _ -> t.finder(id) as V? } private fun requiredResettable(ids: IntArray, finder: T.(Int) -> View?) = LazyResettable { t: T, desc -> ids.map { t.finder(it) as V? ?: viewNotFound(it, desc) } } private fun optionalResettable(ids: IntArray, finder: T.(Int) -> View?) = LazyResettable { t: T, _ -> ids.map { t.finder(it) as V? }.filterNotNull() } // Like Kotterknife's lazy delegate but is resettable private class LazyResettable(initializer: (T, KProperty<*>) -> V) : Lazy(initializer) { override fun getValue(thisRef: T, property: KProperty<*>): V { KotterknifeRegistry.register(thisRef!!, this) return super.getValue(thisRef, property) } fun reset() { value = EMPTY } } object Kotterknife { fun reset(target: Any) { KotterknifeRegistry.reset(target) } } private object KotterknifeRegistry { private val lazyMap = WeakHashMap>>() fun register(target: Any, lazy: LazyResettable<*, *>) = lazyMap.getOrPut(target, { Collections.newSetFromMap(WeakHashMap()) }).add(lazy) fun reset(target: Any) = lazyMap[target]?.forEach(LazyResettable<*, *>::reset) }