Skip to content

Commit

Permalink
[AND-3] Create ThreadListItemViewHolderFactory for ViewHolder customi…
Browse files Browse the repository at this point in the history
…zation.
  • Loading branch information
PetarVelikov committed Nov 28, 2024
1 parent 9e84534 commit 5f34bf8
Show file tree
Hide file tree
Showing 11 changed files with 572 additions and 258 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ class ThreadsFragment : Fragment() {
.navigateSafely(
HomeFragmentDirections.actionOpenChat(
cid = thread.parentMessage.cid,
messageId = thread.parentMessageId,
parentMessageId = thread.parentMessageId,
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3773,6 +3773,7 @@ public final class io/getstream/chat/android/ui/feature/threads/list/ThreadListV
public final fun setLoadMoreListener (Lio/getstream/chat/android/ui/feature/threads/list/ThreadListView$LoadMoreListener;)V
public final fun setThreadClickListener (Lio/getstream/chat/android/ui/feature/threads/list/ThreadListView$ThreadClickListener;)V
public final fun setUnreadThreadsBannerClickListener (Lio/getstream/chat/android/ui/feature/threads/list/ThreadListView$UnreadThreadsBannerClickListener;)V
public final fun setViewHolderFactory (Lio/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItemViewHolderFactory;)V
public final fun showLoading ()V
public final fun showThreads (Ljava/util/List;Z)V
public final fun showUnreadThreadsBanner (I)V
Expand Down Expand Up @@ -3841,30 +3842,52 @@ public final class io/getstream/chat/android/ui/feature/threads/list/ThreadListV
public fun toString ()Ljava/lang/String;
}

public abstract interface class io/getstream/chat/android/ui/feature/threads/list/internal/ThreadListItem {
public abstract interface class io/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItem {
public abstract fun getStableId ()J
}

public final class io/getstream/chat/android/ui/feature/threads/list/internal/ThreadListItem$LoadingMoreItem : io/getstream/chat/android/ui/feature/threads/list/internal/ThreadListItem {
public static final field INSTANCE Lio/getstream/chat/android/ui/feature/threads/list/internal/ThreadListItem$LoadingMoreItem;
public final class io/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItem$LoadingMoreItem : io/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItem {
public static final field INSTANCE Lio/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItem$LoadingMoreItem;
public fun equals (Ljava/lang/Object;)Z
public fun getStableId ()J
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/chat/android/ui/feature/threads/list/internal/ThreadListItem$ThreadItem : io/getstream/chat/android/ui/feature/threads/list/internal/ThreadListItem {
public final class io/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItem$ThreadItem : io/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItem {
public fun <init> (Lio/getstream/chat/android/models/Thread;)V
public final fun component1 ()Lio/getstream/chat/android/models/Thread;
public final fun copy (Lio/getstream/chat/android/models/Thread;)Lio/getstream/chat/android/ui/feature/threads/list/internal/ThreadListItem$ThreadItem;
public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/threads/list/internal/ThreadListItem$ThreadItem;Lio/getstream/chat/android/models/Thread;ILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/threads/list/internal/ThreadListItem$ThreadItem;
public final fun copy (Lio/getstream/chat/android/models/Thread;)Lio/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItem$ThreadItem;
public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItem$ThreadItem;Lio/getstream/chat/android/models/Thread;ILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItem$ThreadItem;
public fun equals (Ljava/lang/Object;)Z
public fun getStableId ()J
public final fun getThread ()Lio/getstream/chat/android/models/Thread;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public class io/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItemViewHolderFactory {
public fun <init> ()V
protected fun createLoadingMoreViewHolder (Landroid/view/ViewGroup;)Lio/getstream/chat/android/ui/feature/threads/list/adapter/viewholder/BaseThreadListItemViewHolder;
protected fun createThreadItemViewHolder (Landroid/view/ViewGroup;)Lio/getstream/chat/android/ui/feature/threads/list/adapter/viewholder/BaseThreadListItemViewHolder;
public fun createViewHolder (Landroid/view/ViewGroup;I)Lio/getstream/chat/android/ui/feature/threads/list/adapter/viewholder/BaseThreadListItemViewHolder;
protected final fun getClickListener ()Lio/getstream/chat/android/ui/feature/threads/list/ThreadListView$ThreadClickListener;
public fun getItemViewType (Lio/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItem;)I
public fun getItemViewType (Lio/getstream/chat/android/ui/feature/threads/list/adapter/viewholder/BaseThreadListItemViewHolder;)I
protected final fun getStyle ()Lio/getstream/chat/android/ui/feature/threads/list/ThreadListViewStyle;
}

public final class io/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItemViewType {
public static final field INSTANCE Lio/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItemViewType;
public static final field ITEM_LOADING_MORE I
public static final field ITEM_THREAD I
}

public abstract class io/getstream/chat/android/ui/feature/threads/list/adapter/viewholder/BaseThreadListItemViewHolder : androidx/recyclerview/widget/RecyclerView$ViewHolder {
public fun <init> (Landroid/view/View;)V
public abstract fun bind (Lio/getstream/chat/android/ui/feature/threads/list/adapter/ThreadListItem;)V
}

public abstract interface class io/getstream/chat/android/ui/font/ChatFonts {
public abstract fun getFont (Lio/getstream/chat/android/ui/font/TextStyle;)Landroid/graphics/Typeface;
public abstract fun setFont (Lio/getstream/chat/android/ui/font/TextStyle;Landroid/widget/TextView;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import androidx.core.view.updatePadding
import io.getstream.chat.android.models.Thread
import io.getstream.chat.android.ui.R
import io.getstream.chat.android.ui.databinding.StreamUiThreadListViewBinding
import io.getstream.chat.android.ui.feature.threads.list.internal.ThreadListAdapter
import io.getstream.chat.android.ui.feature.threads.list.internal.ThreadListItem
import io.getstream.chat.android.ui.feature.threads.list.adapter.ThreadListItem
import io.getstream.chat.android.ui.feature.threads.list.adapter.ThreadListItemViewHolderFactory
import io.getstream.chat.android.ui.feature.threads.list.adapter.internal.ThreadListAdapter
import io.getstream.chat.android.ui.font.setTextStyle
import io.getstream.chat.android.ui.utils.extensions.createStreamThemeWrapper
import io.getstream.chat.android.ui.utils.extensions.streamThemeInflater
Expand All @@ -40,7 +41,9 @@ public class ThreadListView : ConstraintLayout {

private val binding = StreamUiThreadListViewBinding.inflate(streamThemeInflater, this)
private lateinit var style: ThreadListViewStyle
private lateinit var viewHolderFactory: ThreadListItemViewHolderFactory
private lateinit var adapter: ThreadListAdapter
private var clickListener: ThreadClickListener? = null
private val scrollListener = EndlessScrollListener(LOAD_MORE_THRESHOLD) {
loadMoreListener?.onLoadMore()
}
Expand All @@ -63,10 +66,7 @@ public class ThreadListView : ConstraintLayout {

private fun init(attrs: AttributeSet?) {
style = ThreadListViewStyle(context, attrs)
adapter = ThreadListAdapter(style)

binding.threadListRecyclerView.setHasFixedSize(true)
binding.threadListRecyclerView.adapter = adapter
binding.threadListRecyclerView.addOnScrollListener(scrollListener)

setBackgroundColor(style.backgroundColor)
Expand All @@ -81,7 +81,7 @@ public class ThreadListView : ConstraintLayout {
* @param isLoadingMore Indicator if the loading more view should be shown.
*/
public fun showThreads(threads: List<Thread>, isLoadingMore: Boolean) {
val isCurrentlyEmpty = adapter.itemCount == 0
val isCurrentlyEmpty = requireAdapter().itemCount == 0
val hasThreads = threads.isNotEmpty()

binding.threadListRecyclerView.isVisible = hasThreads
Expand All @@ -90,7 +90,7 @@ public class ThreadListView : ConstraintLayout {

val threadItems = threads.map(ThreadListItem::ThreadItem)
val loadingMoreItems = if (isLoadingMore) listOf(ThreadListItem.LoadingMoreItem) else emptyList()
adapter.submitList(threadItems + loadingMoreItems)
requireAdapter().submitList(threadItems + loadingMoreItems)

scrollListener.enablePagination()

Expand All @@ -104,7 +104,7 @@ public class ThreadListView : ConstraintLayout {
* Shows the loading state of the thread list.
*/
public fun showLoading() {
adapter.submitList(emptyList()) // clear current list
requireAdapter().submitList(emptyList()) // clear current list
binding.threadListRecyclerView.isVisible = false
binding.emptyContainer.isVisible = false
binding.progressBar.isVisible = true
Expand All @@ -127,6 +127,21 @@ public class ThreadListView : ConstraintLayout {
binding.unreadThreadsBannerTextView.text = bannerText
}

/**
* Sets the [ThreadListItemViewHolderFactory] used to create the thread list view holders.
* Use if you want completely custom views for the thread list items.
* Make sure to call this before setting/updating the data in the thread list view.
*
* @param factory The [ThreadListItemViewHolderFactory] to be used for creating the item view holders.
* @throws IllegalStateException if called when a [factory] was already set.
*/
public fun setViewHolderFactory(factory: ThreadListItemViewHolderFactory) {
check(::adapter.isInitialized.not()) {
"Adapter was already initialized, please set ChannelListItemViewHolderFactory first"
}
viewHolderFactory = factory
}

/**
* Sets the listener for clicks on the unread threads banner.
*
Expand All @@ -145,7 +160,7 @@ public class ThreadListView : ConstraintLayout {
* @param listener The [ThreadClickListener] to be invoked when the user clicks on a thread.
*/
public fun setThreadClickListener(listener: ThreadClickListener) {
adapter.setThreadClickListener(listener)
this.clickListener = listener
}

/**
Expand All @@ -157,6 +172,28 @@ public class ThreadListView : ConstraintLayout {
this.loadMoreListener = listener
}

/**
* Ensures the [adapter] is initialized before accessing it.
* Useful for cases where a custom [viewHolderFactory] is provided.
*/
private fun requireAdapter(): ThreadListAdapter {
if (::adapter.isInitialized.not()) {
initAdapter()
}
return adapter
}

private fun initAdapter() {
// Ensure the viewHolderFactory is initialized
if (::viewHolderFactory.isInitialized.not()) {
viewHolderFactory = ThreadListItemViewHolderFactory()
}
viewHolderFactory.setStyle(style)
viewHolderFactory.setThreadClickListener(clickListener)
adapter = ThreadListAdapter(style, viewHolderFactory)
binding.threadListRecyclerView.adapter = adapter
}

private fun applyEmptyStateStyle(style: ThreadListViewStyle) {
binding.emptyImage.setImageDrawable(style.emptyStateDrawable)
binding.emptyTextView.text = style.emptyStateText
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
* limitations under the License.
*/

package io.getstream.chat.android.ui.feature.threads.list.internal
package io.getstream.chat.android.ui.feature.threads.list.adapter

import io.getstream.chat.android.models.Thread

/**
* Class representing the different types of items that can be rendered in the
* by the [ThreadListAdapter].
* by the [io.getstream.chat.android.ui.feature.threads.list.adapter.internal.ThreadListAdapter].
*/
public sealed interface ThreadListItem {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
*
* 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.
*/

package io.getstream.chat.android.ui.feature.threads.list.adapter

import android.view.ViewGroup
import io.getstream.chat.android.ui.feature.threads.list.ThreadListView
import io.getstream.chat.android.ui.feature.threads.list.ThreadListViewStyle
import io.getstream.chat.android.ui.feature.threads.list.adapter.viewholder.BaseThreadListItemViewHolder
import io.getstream.chat.android.ui.feature.threads.list.adapter.viewholder.internal.ThreadItemViewHolder
import io.getstream.chat.android.ui.feature.threads.list.adapter.viewholder.internal.ThreadListLoadingMoreViewHolder

/**
* Factory responsible for creating ViewHolder instances for the RecyclerView used in the
* [io.getstream.chat.android.ui.feature.threads.list.ThreadListView].
*/
public open class ThreadListItemViewHolderFactory {

/**
* The [ThreadListViewStyle] for styling the viewHolders.
*/
protected lateinit var style: ThreadListViewStyle
private set

/**
* The listener for clicks on the thread items.
*/
protected var clickListener: ThreadListView.ThreadClickListener? = null
private set

/**
* Returns a view type value based on the type of the given [item].
* The view type returned here will be used as a parameter in [createViewHolder].
*
* For built-in view types, see [ThreadListItemViewType] and its constants.
*
* @param item The [ThreadListItem] to check the type of.
*/
public open fun getItemViewType(item: ThreadListItem): Int {
return when (item) {
is ThreadListItem.ThreadItem -> ThreadListItemViewType.ITEM_THREAD
is ThreadListItem.LoadingMoreItem -> ThreadListItemViewType.ITEM_LOADING_MORE
}
}

/**
* Returns a view type value based on the type of the given [viewHolder].
*
* For built-in view types, see [ThreadListItemViewType] and its constants.
*
* @param viewHolder The [BaseThreadListItemViewHolder] to check the type of.
*/
public open fun getItemViewType(viewHolder: BaseThreadListItemViewHolder<out ThreadListItem>): Int {
return when (viewHolder) {
is ThreadItemViewHolder -> ThreadListItemViewType.ITEM_THREAD
is ThreadListLoadingMoreViewHolder -> ThreadListItemViewType.ITEM_LOADING_MORE
else -> throw IllegalArgumentException("Unhandled ThreadList view holder: $viewHolder")
}
}

/**
* Creates a new ViewHolder based on the provided [viewType] to be used in the Thread List.
* The [viewType] parameter is determined by [getItemViewType].
*
* @param parentView The parent of the view.
* @param viewType The type of the item for which the viewHolder is created.
*/
public open fun createViewHolder(
parentView: ViewGroup,
viewType: Int,
): BaseThreadListItemViewHolder<out ThreadListItem> {
return when (viewType) {
ThreadListItemViewType.ITEM_THREAD -> createThreadItemViewHolder(parentView)
ThreadListItemViewType.ITEM_LOADING_MORE -> createLoadingMoreViewHolder(parentView)
else -> throw IllegalArgumentException("Unhandled ThreadList view type: $viewType")
}
}

/**
* Creates the ViewHolder for the [ThreadListItemViewType.ITEM_THREAD] ([ThreadListItem.ThreadItem]) type.
*
* @param parentView The parent of the view.
*/
protected open fun createThreadItemViewHolder(
parentView: ViewGroup,
): BaseThreadListItemViewHolder<ThreadListItem.ThreadItem> {
return ThreadItemViewHolder(parentView, style, clickListener)
}

/**
* Creates the ViewHolder for the [ThreadListItemViewType.ITEM_LOADING_MORE] ([ThreadListItem.LoadingMoreItem])
* type.
*
* @param parentView The parent of the view.
*/
protected open fun createLoadingMoreViewHolder(
parentView: ViewGroup,
): BaseThreadListItemViewHolder<ThreadListItem.LoadingMoreItem> {
return ThreadListLoadingMoreViewHolder(parentView)
}

/**
* Sets the [ThreadListViewStyle] to be used by the created ViewHolders.
*/
internal fun setStyle(style: ThreadListViewStyle) {
this.style = style
}

/**
* Sets the [ThreadListView.ThreadClickListener] for clicks on the thread items.
*/
internal fun setThreadClickListener(clickListener: ThreadListView.ThreadClickListener?) {
this.clickListener = clickListener
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
*
* 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.
*/

package io.getstream.chat.android.ui.feature.threads.list.adapter

/**
* Defines the possible types of a Thread List item.
*/
public object ThreadListItemViewType {

/**
* Represents an item of type 'thread'.
*/
public const val ITEM_THREAD: Int = 0

/**
* Represent a loading more indicator item.
*/
public const val ITEM_LOADING_MORE: Int = 1
}
Loading

0 comments on commit 5f34bf8

Please sign in to comment.