Improved media grid, making sub-sections by months

This commit is contained in:
Sylvain Berfini 2024-05-06 16:26:58 +02:00
parent 54a775e0fd
commit d8f0338f7c
10 changed files with 64 additions and 16 deletions

View file

@ -19,7 +19,9 @@
*/
package org.linphone.ui.main.chat.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
@ -30,16 +32,35 @@ import androidx.recyclerview.widget.RecyclerView
import org.linphone.R
import org.linphone.databinding.ChatDocumentContentListCellBinding
import org.linphone.databinding.ChatMediaContentGridCellBinding
import org.linphone.databinding.MeetingsListDecorationBinding
import org.linphone.ui.main.chat.model.FileModel
import org.linphone.utils.HeaderAdapter
class ConversationsFilesAdapter : ListAdapter<FileModel, RecyclerView.ViewHolder>(
FilesDiffCallback()
) {
class ConversationsFilesAdapter :
ListAdapter<FileModel, RecyclerView.ViewHolder>(
FilesDiffCallback()
),
HeaderAdapter {
companion object {
const val MEDIA_FILE = 1
const val DOCUMENT_FILE = 2
}
override fun displayHeaderForPosition(position: Int): Boolean {
if (position == 0) return true
val previous = getItem(position - 1)
val item = getItem(position)
return previous.month != item.month
}
override fun getHeaderViewForPosition(context: Context, position: Int): View {
val binding = MeetingsListDecorationBinding.inflate(LayoutInflater.from(context))
val item = getItem(position)
binding.header.text = item.month
return binding.root
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
MEDIA_FILE -> createMediaFileViewHolder(parent)

View file

@ -41,6 +41,7 @@ import org.linphone.ui.main.chat.viewmodel.ConversationDocumentsListViewModel
import org.linphone.ui.main.fragment.SlidingPaneChildFragment
import org.linphone.utils.Event
import org.linphone.utils.FileUtils
import org.linphone.utils.RecyclerViewHeaderDecoration
@UiThread
class ConversationDocumentsListFragment : SlidingPaneChildFragment() {
@ -92,6 +93,9 @@ class ConversationDocumentsListFragment : SlidingPaneChildFragment() {
val chatRoom = sharedViewModel.displayedChatRoom
viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri)
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
binding.documentsList.addItemDecoration(headerItemDecoration)
binding.documentsList.setHasFixedSize(true)
val layoutManager = LinearLayoutManager(requireContext())
binding.documentsList.layoutManager = layoutManager

View file

@ -42,6 +42,7 @@ import org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel
import org.linphone.ui.main.fragment.SlidingPaneChildFragment
import org.linphone.utils.Event
import org.linphone.utils.FileUtils
import org.linphone.utils.RecyclerViewHeaderDecoration
@UiThread
class ConversationMediaListFragment : SlidingPaneChildFragment() {
@ -93,14 +94,29 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() {
val chatRoom = sharedViewModel.displayedChatRoom
viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri)
val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter)
binding.mediaList.addItemDecoration(headerItemDecoration)
binding.mediaList.setHasFixedSize(true)
val layoutManager = object : GridLayoutManager(requireContext(), 4) {
val spanCount = 4
val layoutManager = object : GridLayoutManager(requireContext(), spanCount) {
override fun checkLayoutParams(lp: RecyclerView.LayoutParams): Boolean {
lp.width = width / spanCount
lp.height = lp.width
return true
}
}
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
if (position < adapter.currentList.size - 1) {
// Have last item of month takes all remaining horizontal space to have a "line break"
if (adapter.displayHeaderForPosition(position + 1)) {
return spanCount - (position % spanCount)
}
}
return 1
}
}
// This isn't supported by GridLayoutManager, it will crash
// layoutManager.stackFromEnd = true
binding.mediaList.layoutManager = layoutManager

View file

@ -38,7 +38,7 @@ class FileModel @AnyThread constructor(
val file: String,
val fileName: String,
val fileSize: Long,
val fileCreationTimestamp: Long,
private val fileCreationTimestamp: Long,
private val isEncrypted: Boolean,
val isWaitingToBeDownloaded: Boolean = false,
private val onClicked: ((model: FileModel) -> Unit)? = null
@ -67,6 +67,14 @@ class FileModel @AnyThread constructor(
val isAudio: Boolean
val month = TimestampUtils.month(fileCreationTimestamp)
val dateTime = TimestampUtils.toString(
fileCreationTimestamp,
shortDate = false,
hideYear = false
)
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
init {

View file

@ -370,7 +370,7 @@ class MessageModel @WorkerThread constructor(
)
val fileSize = content.fileSize.toLong()
val timestamp = -1L // TODO FIXME: use content.creationTimestamp
val timestamp = content.creationTimestamp
when (content.type) {
"image", "video" -> {
val fileModel = FileModel(
@ -411,7 +411,7 @@ class MessageModel @WorkerThread constructor(
allFilesDownloaded = false
filesContentCount += 1
val name = content.name ?: ""
val timestamp = -1L // TODO FIXME: use content.creationTimestamp
val timestamp = content.creationTimestamp
if (name.isNotEmpty()) {
val fileModel = if (isOutgoing && chatMessage.isFileTransferInProgress) {
val path = content.filePath ?: ""

View file

@ -72,7 +72,7 @@ class ConversationDocumentsListViewModel @UiThread constructor() : AbstractConve
}
val name = documentContent.name.orEmpty()
val size = documentContent.size.toLong()
val timestamp = -1L // TODO FIXME: use documentContent.creationTimestamp
val timestamp = documentContent.creationTimestamp
if (path.isNotEmpty() && name.isNotEmpty()) {
val model = FileModel(path, name, size, timestamp, isEncrypted) {
openDocumentEvent.postValue(Event(it))

View file

@ -76,7 +76,7 @@ class ConversationMediaListViewModel @UiThread constructor() : AbstractConversat
}
val name = mediaContent.name.orEmpty()
val size = mediaContent.size.toLong()
val timestamp = -1L // TODO FIXME: use mediaContent.creationTimestamp
val timestamp = mediaContent.creationTimestamp
if (path.isNotEmpty() && name.isNotEmpty()) {
val model = FileModel(path, name, size, timestamp, isEncrypted) {
openMediaEvent.postValue(Event(it))

View file

@ -67,7 +67,7 @@ class MediaListViewerFragment : GenericFragment() {
val list = viewModel.mediaList.value.orEmpty()
if (position >= 0 && position < list.size) {
val model = list[position]
viewModel.currentlyDisplayedFileName.value = model.fileName
viewModel.currentlyDisplayedFileName.value = "${model.fileName}\n${model.dateTime}"
}
}
}

View file

@ -15,8 +15,8 @@
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp">
android:paddingTop="10dp"
android:paddingBottom="10dp">
<ImageView
android:id="@+id/file_icon"

View file

@ -25,7 +25,6 @@
coilBubbleGrid="@{model.file}"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
@ -57,16 +56,16 @@
<ImageView
android:id="@+id/audio_file"
android:layout_width="@dimen/chat_bubble_grid_image_size"
android:layout_height="@dimen/chat_bubble_grid_image_size"
android:layout_width="0dp"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:padding="18dp"
android:background="@drawable/shape_squircle_main2_200"
android:src="@drawable/file_audio"
android:contentDescription="@null"
android:visibility="@{model.isAudio ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:tint="?attr/color_main2_600" />