diff --git a/adapt/build.gradle.kts b/adapt/build.gradle.kts index faacc71..3dc3a71 100644 --- a/adapt/build.gradle.kts +++ b/adapt/build.gradle.kts @@ -52,6 +52,13 @@ dependencies { testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + + // Paging 3 + implementation("androidx.paging:paging-runtime:3.2.1") + + // Lifecycle (if not already present) + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.3") + implementation("androidx.lifecycle:lifecycle-common-java8:2.8.3") } nmcp { diff --git a/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdaptAdapter.kt b/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdaptAdapter.kt index c95e994..30ab8cc 100644 --- a/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdaptAdapter.kt +++ b/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdaptAdapter.kt @@ -1,11 +1,13 @@ package io.github.vshnv.adapt.adapter +import android.widget.Filterable import androidx.recyclerview.widget.RecyclerView -abstract class AdaptAdapter: RecyclerView.Adapter>() { +abstract class AdaptAdapter: RecyclerView.Adapter>(), Filterable { abstract val currentList: List + abstract fun getUnfilteredList(): List - abstract suspend fun submitDataSuspending(data: List): Unit - - abstract fun submitData(data: List, callback: () -> Unit = {}): Unit + abstract suspend fun submitDataSuspending(data: List) + abstract fun submitData(data: List, callback: () -> Unit = {}) + abstract fun submitDataFromFilter(data: List, callback: () -> Unit = {}) } \ No newline at end of file diff --git a/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdaptViewHolder.kt b/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdaptViewHolder.kt index 529ac67..f3af8d1 100644 --- a/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdaptViewHolder.kt +++ b/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdaptViewHolder.kt @@ -4,5 +4,5 @@ import android.view.View import androidx.recyclerview.widget.RecyclerView abstract class AdaptViewHolder(view: View): RecyclerView.ViewHolder(view) { - abstract fun bind(idx: Int, data: T): Unit + abstract fun bind(idx: Int, data: T) } diff --git a/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdapterLifecycleRegistry.kt b/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdapterLifecycleRegistry.kt index c3a21bf..4d33341 100644 --- a/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdapterLifecycleRegistry.kt +++ b/adapt/src/main/java/io/github/vshnv/adapt/adapter/AdapterLifecycleRegistry.kt @@ -1,21 +1,17 @@ package io.github.vshnv.adapt.adapter import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry -import androidx.lifecycle.OnLifecycleEvent import java.lang.ref.WeakReference class AdapterLifecycleRegistry(owner: LifecycleOwner, private val parentLifecycle: Lifecycle): LifecycleRegistry(owner) { private val ownerWeakRef = WeakReference(owner) - private val parentLifecycleObserver = object: LifecycleObserver { - @OnLifecycleEvent(Event.ON_ANY) - fun onAny() { - if (ownerWeakRef.get() == null) { - ignoreParent() - return - } + private val parentLifecycleObserver = LifecycleEventObserver { _, _ -> + if (ownerWeakRef.get() == null) { + ignoreParent() + } else { currentState = parentLifecycle.currentState } } diff --git a/adapt/src/main/java/io/github/vshnv/adapt/adapter/LifecycleAwareAdaptAdapter.kt b/adapt/src/main/java/io/github/vshnv/adapt/adapter/LifecycleAwareAdaptAdapter.kt index 83d32b5..708d4c0 100644 --- a/adapt/src/main/java/io/github/vshnv/adapt/adapter/LifecycleAwareAdaptAdapter.kt +++ b/adapt/src/main/java/io/github/vshnv/adapt/adapter/LifecycleAwareAdaptAdapter.kt @@ -2,19 +2,27 @@ package io.github.vshnv.adapt.adapter import android.view.View import android.view.ViewGroup +import android.widget.Filter import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewTreeLifecycleOwner import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder import io.github.vshnv.adapt.dsl.collector.CollectingBindable -import io.github.vshnv.adapt.extensions.findViewTreeLifecycleOwner import java.util.Collections import java.util.WeakHashMap import kotlin.coroutines.suspendCoroutine -class LifecycleAwareAdaptAdapter(private val viewTypeMapper: ((T, Int) -> Int)?, private val defaultBinder: CollectingBindable?, private val viewBinders: MutableMap>, private val itemEquals: (T, T) -> Boolean, private val itemContentEquals: (T, T) -> Boolean): AdaptAdapter() { +class LifecycleAwareAdaptAdapter( + private val viewTypeMapper: ((T, Int) -> Int)?, + private val defaultBinder: CollectingBindable?, + private val viewBinders: MutableMap>, + private val itemEquals: (T, T) -> Boolean, + private val itemContentEquals: (T, T) -> Boolean, + private var searchFilter: Filter?, +) : AdaptAdapter() { private val knownAffectedViewHolders = Collections.newSetFromMap(WeakHashMap, Boolean>()) private val diffCallback: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: T, newItem: T): Boolean { @@ -28,12 +36,20 @@ class LifecycleAwareAdaptAdapter(private val viewTypeMapper: ((T, Int) private val mDiffer: AsyncListDiffer = AsyncListDiffer(this, diffCallback) override val currentList: List get() = mDiffer.currentList + private var unFilteredList: MutableList = mutableListOf() + + override fun getFilter(): Filter = requireNotNull(searchFilter) { + "Filterable.Filter of $this accessed before assigning" + } + + override fun getUnfilteredList(): List = unFilteredList override fun getItemViewType(position: Int): Int { return viewTypeMapper?.let { it(getItem(position), position) } ?: super.getItemViewType(position) } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdaptViewHolder { val binderItem: CollectingBindable = viewBinders[viewType] ?: defaultBinder ?: throw AssertionError("Adapt found ViewType with no bound view creator or any default view creator, Cannot proceed!") @@ -61,13 +77,21 @@ class LifecycleAwareAdaptAdapter(private val viewTypeMapper: ((T, Int) return mDiffer.currentList[position] } - override suspend fun submitDataSuspending(data: List) = suspendCoroutine { continuation -> - mDiffer.submitList(data) { - continuation.resumeWith(Result.success(Unit)) + override suspend fun submitDataSuspending(data: List) { + unFilteredList = data.toMutableList() + suspendCoroutine { continuation -> + mDiffer.submitList(data) { + continuation.resumeWith(Result.success(Unit)) + } } } override fun submitData(data: List, callback: () -> Unit) { + unFilteredList = data.toMutableList() + mDiffer.submitList(data, callback) + } + + override fun submitDataFromFilter(data: List, callback: () -> Unit) { mDiffer.submitList(data, callback) } @@ -81,12 +105,13 @@ class LifecycleAwareAdaptAdapter(private val viewTypeMapper: ((T, Int) override fun onViewAttachedToWindow(holder: AdaptViewHolder) { super.onViewAttachedToWindow(holder) val holder = (holder as LifecycleAwareAdaptViewHolder) - val lifecycleOwner = holder.itemView.findViewTreeLifecycleOwner() ?: return + val lifecycleOwner = ViewTreeLifecycleOwner.get(holder.itemView) ?: return holder.handleLifecycleSetup(lifecycleOwner) val registry = holder.lifecycleRegistry registry?.highestState = Lifecycle.State.RESUMED knownAffectedViewHolders.add(holder) } + override fun onViewDetachedFromWindow(holder: AdaptViewHolder) { val registry = (holder as LifecycleAwareAdaptViewHolder).lifecycleRegistry registry?.highestState = Lifecycle.State.CREATED diff --git a/adapt/src/main/java/io/github/vshnv/adapt/dsl/AdaptScope.kt b/adapt/src/main/java/io/github/vshnv/adapt/dsl/AdaptScope.kt index 021fe93..757783e 100644 --- a/adapt/src/main/java/io/github/vshnv/adapt/dsl/AdaptScope.kt +++ b/adapt/src/main/java/io/github/vshnv/adapt/dsl/AdaptScope.kt @@ -1,11 +1,13 @@ package io.github.vshnv.adapt.dsl import android.view.ViewGroup +import android.widget.Filter interface AdaptScope { fun itemEquals(checkEquality: (data: T, otherData: T) -> Boolean) fun contentEquals(checkContentEquality: (data: T, otherData: T) -> Boolean) fun defineViewTypes(mapToViewType: (data: T, position: Int) -> Int) + fun filter(searchFilter: Filter?) fun create(createView: (parent: ViewGroup) -> ViewSource): Bindable fun create(viewType: Int, createView: (parent: ViewGroup) -> ViewSource): Bindable } \ No newline at end of file diff --git a/adapt/src/main/java/io/github/vshnv/adapt/dsl/collector/CollectingAdaptScope.kt b/adapt/src/main/java/io/github/vshnv/adapt/dsl/collector/CollectingAdaptScope.kt index 44dd318..8b556a2 100644 --- a/adapt/src/main/java/io/github/vshnv/adapt/dsl/collector/CollectingAdaptScope.kt +++ b/adapt/src/main/java/io/github/vshnv/adapt/dsl/collector/CollectingAdaptScope.kt @@ -1,18 +1,20 @@ package io.github.vshnv.adapt.dsl.collector import android.view.ViewGroup +import android.widget.Filter import io.github.vshnv.adapt.adapter.AdaptAdapter import io.github.vshnv.adapt.adapter.LifecycleAwareAdaptAdapter import io.github.vshnv.adapt.dsl.AdaptScope import io.github.vshnv.adapt.dsl.Bindable import io.github.vshnv.adapt.dsl.ViewSource -internal class CollectingAdaptScope: AdaptScope { - private var itemEquals: (T, T) -> Boolean = {a, b -> a == b} - private var itemContentEquals: (T, T) -> Boolean = {a, b -> a == b} +internal class CollectingAdaptScope : AdaptScope { + private var itemEquals: (T, T) -> Boolean = { a, b -> a == b } + private var itemContentEquals: (T, T) -> Boolean = { a, b -> a == b } private var viewTypeMapper: ((T, Int) -> Int)? = null - private var defaultBinder: CollectingBindable? = null + private var defaultBinder: CollectingBindable? = null private val viewBinders: MutableMap> = mutableMapOf() + private var searchFilterable: Filter? = null override fun defineViewTypes(mapToViewType: (T, Int) -> Int) { viewTypeMapper = mapToViewType @@ -26,25 +28,30 @@ internal class CollectingAdaptScope: AdaptScope { itemContentEquals = checkContentEquality } + override fun filter(searchFilter: Filter?) { + searchFilterable = searchFilter + } + internal fun buildAdapter(): AdaptAdapter { return LifecycleAwareAdaptAdapter( viewTypeMapper, defaultBinder, viewBinders, itemEquals, - itemContentEquals + itemContentEquals, + searchFilterable ) } - override fun create(createView: (parent: ViewGroup) -> ViewSource): Bindable { + override fun create(createView: (parent: ViewGroup) -> ViewSource): Bindable { return CollectingBindable(createView).apply { defaultBinder = this } } - override fun create( + override fun create( viewType: Int, - createView: (parent: ViewGroup) -> ViewSource + createView: (parent: ViewGroup) -> ViewSource, ): Bindable { return CollectingBindable(createView).apply { viewBinders[viewType] = this diff --git a/adapt/src/main/java/io/github/vshnv/adapt/extensions/View.kt b/adapt/src/main/java/io/github/vshnv/adapt/extensions/View.kt index 9ebff54..d642106 100644 --- a/adapt/src/main/java/io/github/vshnv/adapt/extensions/View.kt +++ b/adapt/src/main/java/io/github/vshnv/adapt/extensions/View.kt @@ -2,12 +2,8 @@ package io.github.vshnv.adapt.extensions import android.view.View import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.runtime.R +import androidx.lifecycle.ViewTreeLifecycleOwner internal fun View.findViewTreeLifecycleOwner(): LifecycleOwner? { - return generateSequence(this) { currentView -> - currentView.parent as? View - }.mapNotNull { viewParent -> - viewParent.getTag(R.id.view_tree_lifecycle_owner) as? LifecycleOwner - }.firstOrNull() + return ViewTreeLifecycleOwner.get(this) } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index eb7219a..fda244a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.0.2" apply false - id("com.android.library") version "8.0.2" apply false - id("org.jetbrains.kotlin.android") version "1.8.20" apply false + id("com.android.application") version "8.7.3" apply false + id("com.android.library") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.0.20" apply false } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 15c1b4a..13a2690 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Feb 26 18:14:07 IST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists