Use adapter & recyclerview to display media & documents in conversation

This commit is contained in:
Sylvain Berfini 2024-02-14 12:21:14 +01:00
parent 940937a9b7
commit 476aec1916
8 changed files with 163 additions and 40 deletions

View file

@ -0,0 +1,106 @@
package org.linphone.ui.main.chat.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R
import org.linphone.databinding.ChatDocumentContentListCellBinding
import org.linphone.databinding.ChatMediaContentGridCellBinding
import org.linphone.ui.main.chat.model.FileModel
class ConversationsFilesAdapter : ListAdapter<FileModel, RecyclerView.ViewHolder>(
FilesDiffCallback()
) {
companion object {
const val MEDIA_FILE = 1
const val DOCUMENT_FILE = 2
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
MEDIA_FILE -> createMediaFileViewHolder(parent)
else -> createDocumentFileViewHolder(parent)
}
}
override fun getItemViewType(position: Int): Int {
val data = getItem(position)
if (data.isMedia) return MEDIA_FILE
return DOCUMENT_FILE
}
private fun createMediaFileViewHolder(parent: ViewGroup): RecyclerView.ViewHolder {
val binding: ChatMediaContentGridCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.chat_media_content_grid_cell,
parent,
false
)
val viewHolder = MediaFileViewHolder(binding)
binding.apply {
lifecycleOwner = parent.findViewTreeLifecycleOwner()
}
return viewHolder
}
private fun createDocumentFileViewHolder(parent: ViewGroup): RecyclerView.ViewHolder {
val binding: ChatDocumentContentListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.chat_document_content_list_cell,
parent,
false
)
val viewHolder = DocumentFileViewHolder(binding)
binding.apply {
lifecycleOwner = parent.findViewTreeLifecycleOwner()
}
return viewHolder
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val fileModel = getItem(position)
when (holder) {
is MediaFileViewHolder -> holder.bind(fileModel)
is DocumentFileViewHolder -> holder.bind(fileModel)
}
}
inner class MediaFileViewHolder(
val binding: ChatMediaContentGridCellBinding
) : RecyclerView.ViewHolder(binding.root) {
@UiThread
fun bind(fileModel: FileModel) {
with(binding) {
model = fileModel
executePendingBindings()
}
}
}
inner class DocumentFileViewHolder(
val binding: ChatDocumentContentListCellBinding
) : RecyclerView.ViewHolder(binding.root) {
@UiThread
fun bind(fileModel: FileModel) {
with(binding) {
model = fileModel
executePendingBindings()
}
}
}
private class FilesDiffCallback : DiffUtil.ItemCallback<FileModel>() {
override fun areItemsTheSame(oldItem: FileModel, newItem: FileModel): Boolean {
return oldItem.file == newItem.file && oldItem.fileName == newItem.fileName
}
override fun areContentsTheSame(oldItem: FileModel, newItem: FileModel): Boolean {
return true
}
}
}

View file

@ -31,10 +31,12 @@ import androidx.core.view.doOnPreDraw
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import org.linphone.R import org.linphone.R
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.databinding.ChatDocumentsFragmentBinding import org.linphone.databinding.ChatDocumentsFragmentBinding
import org.linphone.ui.main.MainActivity import org.linphone.ui.main.MainActivity
import org.linphone.ui.main.chat.adapter.ConversationsFilesAdapter
import org.linphone.ui.main.chat.viewmodel.ConversationDocumentsListViewModel import org.linphone.ui.main.chat.viewmodel.ConversationDocumentsListViewModel
import org.linphone.ui.main.fragment.SlidingPaneChildFragment import org.linphone.ui.main.fragment.SlidingPaneChildFragment
import org.linphone.utils.Event import org.linphone.utils.Event
@ -50,12 +52,20 @@ class ConversationDocumentsListFragment : SlidingPaneChildFragment() {
private lateinit var viewModel: ConversationDocumentsListViewModel private lateinit var viewModel: ConversationDocumentsListViewModel
private lateinit var adapter: ConversationsFilesAdapter
private val args: ConversationMediaListFragmentArgs by navArgs() private val args: ConversationMediaListFragmentArgs by navArgs()
override fun goBack(): Boolean { override fun goBack(): Boolean {
return findNavController().popBackStack() return findNavController().popBackStack()
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = ConversationsFilesAdapter()
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -82,6 +92,14 @@ class ConversationDocumentsListFragment : SlidingPaneChildFragment() {
val chatRoom = sharedViewModel.displayedChatRoom val chatRoom = sharedViewModel.displayedChatRoom
viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri) viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri)
binding.documentsList.setHasFixedSize(true)
val layoutManager = LinearLayoutManager(requireContext())
binding.documentsList.layoutManager = layoutManager
if (binding.documentsList.adapter != adapter) {
binding.documentsList.adapter = adapter
}
binding.setBackClickListener { binding.setBackClickListener {
goBack() goBack()
} }
@ -95,12 +113,12 @@ class ConversationDocumentsListFragment : SlidingPaneChildFragment() {
} }
} }
viewModel.documentsList.observe(viewLifecycleOwner) { viewModel.documentsList.observe(viewLifecycleOwner) { items ->
val count = it.size val count = items.size
Log.i( Log.i(
"$TAG Found [$count] documents for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]" "$TAG Found [$count] documents for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]"
) )
// TODO: FIXME: use Adapter adapter.submitList(items)
} }
viewModel.openDocumentEvent.observe(viewLifecycleOwner) { viewModel.openDocumentEvent.observe(viewLifecycleOwner) {

View file

@ -31,10 +31,13 @@ import androidx.core.view.doOnPreDraw
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R import org.linphone.R
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.databinding.ChatMediaFragmentBinding import org.linphone.databinding.ChatMediaFragmentBinding
import org.linphone.ui.main.MainActivity import org.linphone.ui.main.MainActivity
import org.linphone.ui.main.chat.adapter.ConversationsFilesAdapter
import org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel import org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel
import org.linphone.ui.main.fragment.SlidingPaneChildFragment import org.linphone.ui.main.fragment.SlidingPaneChildFragment
import org.linphone.utils.Event import org.linphone.utils.Event
@ -50,12 +53,20 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() {
private lateinit var viewModel: ConversationMediaListViewModel private lateinit var viewModel: ConversationMediaListViewModel
private lateinit var adapter: ConversationsFilesAdapter
private val args: ConversationMediaListFragmentArgs by navArgs() private val args: ConversationMediaListFragmentArgs by navArgs()
override fun goBack(): Boolean { override fun goBack(): Boolean {
return findNavController().popBackStack() return findNavController().popBackStack()
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = ConversationsFilesAdapter()
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -82,6 +93,19 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() {
val chatRoom = sharedViewModel.displayedChatRoom val chatRoom = sharedViewModel.displayedChatRoom
viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri) viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri)
binding.mediaList.setHasFixedSize(true)
val layoutManager = object : GridLayoutManager(requireContext(), 4) {
override fun checkLayoutParams(lp: RecyclerView.LayoutParams): Boolean {
lp.width = width / spanCount
return true
}
}
binding.mediaList.layoutManager = layoutManager
if (binding.mediaList.adapter != adapter) {
binding.mediaList.adapter = adapter
}
binding.setBackClickListener { binding.setBackClickListener {
goBack() goBack()
} }
@ -95,12 +119,12 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() {
} }
} }
viewModel.mediaList.observe(viewLifecycleOwner) { viewModel.mediaList.observe(viewLifecycleOwner) { items ->
val count = it.size val count = items.size
Log.i( Log.i(
"$TAG Found [$count] media for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]" "$TAG Found [$count] media for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]"
) )
// TODO: FIXME: use Adapter adapter.submitList(items)
} }
viewModel.openMediaEvent.observe(viewLifecycleOwner) { viewModel.openMediaEvent.observe(viewLifecycleOwner) {

View file

@ -128,8 +128,8 @@ class ConversationDocumentsListViewModel @UiThread constructor() : ViewModel() {
} }
Log.i("$TAG [${documents.size}] documents have been processed") Log.i("$TAG [${documents.size}] documents have been processed")
} }
documentsList.postValue(list)
documentsList.postValue(list)
operationInProgress.postValue(false) operationInProgress.postValue(false)
} }
} }

View file

@ -132,8 +132,8 @@ class ConversationMediaListViewModel @UiThread constructor() : ViewModel() {
} }
Log.i("$TAG [${media.size}] media have been processed") Log.i("$TAG [${media.size}] media have been processed")
} }
mediaList.postValue(list)
mediaList.postValue(list)
operationInProgress.postValue(false) operationInProgress.postValue(false)
} }
} }

View file

@ -48,26 +48,15 @@
app:layout_constraintStart_toEndOf="@id/back" app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintTop_toTopOf="parent"/>
<ScrollView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/scrollView" android:id="@+id/documentsList"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="?attr/color_grey_100" android:background="?attr/color_grey_100"
android:fillViewport="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"> app:layout_constraintTop_toBottomOf="@id/title" />
<LinearLayout
android:id="@+id/files_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
entries="@{viewModel.documentsList}"
layout="@{@layout/chat_document_content_list_cell}"/>
</ScrollView>
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_800" style="@style/default_text_style_800"

View file

@ -12,8 +12,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content">
app:layout_flexBasisPercent="25%">
<ImageView <ImageView
android:id="@+id/image" android:id="@+id/image"

View file

@ -48,28 +48,15 @@
app:layout_constraintStart_toEndOf="@id/back" app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintTop_toTopOf="parent"/>
<ScrollView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/scrollView" android:id="@+id/mediaList"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="?attr/color_grey_100" android:background="?attr/color_grey_100"
android:fillViewport="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"> app:layout_constraintTop_toBottomOf="@id/title" />
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/files_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:alignItems="stretch"
app:alignContent="flex_start"
entries="@{viewModel.mediaList}"
layout="@{@layout/chat_media_content_grid_cell}"/>
</ScrollView>
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_800" style="@style/default_text_style_800"