Merge pull request #6760 from thundernest/message_details_refresh
Message details: Update participants when system contacts change
This commit is contained in:
commit
16186ca62d
6 changed files with 121 additions and 50 deletions
|
@ -37,7 +37,9 @@ internal class CryptoStatusItem(val cryptoDetails: CryptoDetails) : AbstractItem
|
|||
descriptionTextView.text = context.getString(stringResId)
|
||||
}
|
||||
|
||||
if (!cryptoDetails.isClickable) {
|
||||
if (cryptoDetails.isClickable) {
|
||||
itemView.background = originalBackground
|
||||
} else {
|
||||
itemView.background = null
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +48,6 @@ internal class CryptoStatusItem(val cryptoDetails: CryptoDetails) : AbstractItem
|
|||
imageView.setImageDrawable(null)
|
||||
titleTextView.text = null
|
||||
descriptionTextView.text = null
|
||||
itemView.background = originalBackground
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.fsck.k9.ui.R
|
||||
import com.mikepenz.fastadapter.FastAdapter
|
||||
import com.mikepenz.fastadapter.items.AbstractItem
|
||||
|
||||
internal class EmptyItem : AbstractItem<EmptyItem.ViewHolder>() {
|
||||
override val type: Int = R.id.message_details_empty
|
||||
override val layoutRes = 0
|
||||
|
||||
override fun createView(ctx: Context, parent: ViewGroup?): View {
|
||||
return View(ctx)
|
||||
}
|
||||
|
||||
override fun getViewHolder(v: View) = ViewHolder(v)
|
||||
|
||||
class ViewHolder(view: View) : FastAdapter.ViewHolder<EmptyItem>(view) {
|
||||
override fun bindView(item: EmptyItem, payloads: List<Any>) = Unit
|
||||
|
||||
override fun unbindView(item: EmptyItem) = Unit
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ import androidx.core.view.isVisible
|
|||
import androidx.fragment.app.setFragmentResult
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
|
||||
import app.k9mail.ui.utils.bottomsheet.ToolbarBottomSheetDialog
|
||||
import app.k9mail.ui.utils.bottomsheet.ToolbarBottomSheetDialogFragment
|
||||
import com.fsck.k9.activity.MessageCompose
|
||||
import com.fsck.k9.contacts.ContactPictureLoader
|
||||
|
@ -44,6 +45,7 @@ class MessageDetailsFragment : ToolbarBottomSheetDialogFragment() {
|
|||
private val folderIconProvider: FolderIconProvider by inject { parametersOf(requireContext().theme) }
|
||||
|
||||
private lateinit var messageReference: MessageReference
|
||||
private val itemAdapter = ItemAdapter<GenericItem>()
|
||||
|
||||
// FIXME: Replace this with a mechanism that survives process death
|
||||
var cryptoResult: CryptoResultAnnotation? = null
|
||||
|
@ -87,15 +89,7 @@ class MessageDetailsFragment : ToolbarBottomSheetDialogFragment() {
|
|||
val errorView = view.findViewById<View>(R.id.message_details_error)
|
||||
val recyclerView = view.findViewById<RecyclerView>(R.id.message_details_list)
|
||||
|
||||
// Don't allow dragging down the bottom sheet (by dragging the toolbar) unless the list is scrolled all the way
|
||||
// to the top.
|
||||
recyclerView.addOnScrollListener(
|
||||
object : OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
dialog.behavior.isDraggable = !recyclerView.canScrollVertically(-1)
|
||||
}
|
||||
},
|
||||
)
|
||||
initializeRecyclerView(recyclerView, dialog)
|
||||
|
||||
viewModel.uiEvents.observe(this) { event ->
|
||||
when (event) {
|
||||
|
@ -105,7 +99,7 @@ class MessageDetailsFragment : ToolbarBottomSheetDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
viewModel.loadData(messageReference).observe(this) { state ->
|
||||
viewModel.uiState.observe(this) { state ->
|
||||
when (state) {
|
||||
MessageDetailsState.Loading -> {
|
||||
progressBar.isVisible = true
|
||||
|
@ -121,23 +115,44 @@ class MessageDetailsFragment : ToolbarBottomSheetDialogFragment() {
|
|||
progressBar.isVisible = false
|
||||
errorView.isVisible = false
|
||||
recyclerView.isVisible = true
|
||||
setMessageDetails(recyclerView, state.details, state.appearance)
|
||||
setMessageDetails(state.details, state.appearance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.initialize(messageReference)
|
||||
}
|
||||
|
||||
private fun setMessageDetails(
|
||||
recyclerView: RecyclerView,
|
||||
details: MessageDetailsUi,
|
||||
appearance: MessageDetailsAppearance,
|
||||
) {
|
||||
val itemAdapter = ItemAdapter<GenericItem>().apply {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
viewModel.reload()
|
||||
}
|
||||
|
||||
private fun initializeRecyclerView(recyclerView: RecyclerView, dialog: ToolbarBottomSheetDialog) {
|
||||
// Don't allow dragging down the bottom sheet (by dragging the toolbar) unless the list is scrolled all the way
|
||||
// to the top.
|
||||
recyclerView.addOnScrollListener(
|
||||
object : OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
dialog.behavior.isDraggable = !recyclerView.canScrollVertically(-1)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
recyclerView.adapter = FastAdapter.with(itemAdapter).apply {
|
||||
addEventHook(cryptoStatusClickEventHook)
|
||||
addEventHook(participantClickEventHook)
|
||||
addEventHook(addToContactsClickEventHook)
|
||||
addEventHook(overflowClickEventHook)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMessageDetails(details: MessageDetailsUi, appearance: MessageDetailsAppearance) {
|
||||
val items = buildList {
|
||||
add(MessageDateItem(details.date ?: getString(R.string.message_details_missing_date)))
|
||||
|
||||
if (details.cryptoDetails != null) {
|
||||
add(CryptoStatusItem(details.cryptoDetails))
|
||||
}
|
||||
addCryptoStatus(details)
|
||||
|
||||
addParticipants(details.from, R.string.message_details_from_section_title, appearance)
|
||||
addParticipants(details.sender, R.string.message_details_sender_section_title, appearance)
|
||||
|
@ -149,22 +164,27 @@ class MessageDetailsFragment : ToolbarBottomSheetDialogFragment() {
|
|||
addParticipants(details.cc, R.string.message_details_cc_section_title, appearance)
|
||||
addParticipants(details.bcc, R.string.message_details_bcc_section_title, appearance)
|
||||
|
||||
if (details.folder != null) {
|
||||
addFolderName(details.folder)
|
||||
}
|
||||
addFolderName(details.folder)
|
||||
}
|
||||
|
||||
val adapter = FastAdapter.with(itemAdapter).apply {
|
||||
addEventHook(cryptoStatusClickEventHook)
|
||||
addEventHook(participantClickEventHook)
|
||||
addEventHook(addToContactsClickEventHook)
|
||||
addEventHook(overflowClickEventHook)
|
||||
// Use list index as stable identifier. This means changes to the list may only update existing items or add
|
||||
// new items to the end of the list. Use place holder items (EmptyItem) if necessary.
|
||||
items.forEachIndexed { index, item ->
|
||||
item.identifier = index.toLong()
|
||||
}
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
itemAdapter.setNewList(items)
|
||||
}
|
||||
|
||||
private fun ItemAdapter<GenericItem>.addParticipants(
|
||||
private fun MutableList<GenericItem>.addCryptoStatus(details: MessageDetailsUi) {
|
||||
if (details.cryptoDetails != null) {
|
||||
add(CryptoStatusItem(details.cryptoDetails))
|
||||
} else {
|
||||
add(EmptyItem())
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<GenericItem>.addParticipants(
|
||||
participants: List<Participant>,
|
||||
@StringRes title: Int,
|
||||
appearance: MessageDetailsAppearance,
|
||||
|
@ -186,12 +206,16 @@ class MessageDetailsFragment : ToolbarBottomSheetDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun ItemAdapter<GenericItem>.addFolderName(folder: FolderInfoUi) {
|
||||
val folderNameItem = FolderNameItem(
|
||||
displayName = folder.displayName,
|
||||
iconResourceId = folderIconProvider.getFolderIcon(folder.type),
|
||||
)
|
||||
add(folderNameItem)
|
||||
private fun MutableList<GenericItem>.addFolderName(folder: FolderInfoUi?) {
|
||||
if (folder != null) {
|
||||
val folderNameItem = FolderNameItem(
|
||||
displayName = folder.displayName,
|
||||
iconResourceId = folderIconProvider.getFolderIcon(folder.type),
|
||||
)
|
||||
add(folderNameItem)
|
||||
} else {
|
||||
add(EmptyItem())
|
||||
}
|
||||
}
|
||||
|
||||
private val cryptoStatusClickEventHook = object : ClickEventHook<CryptoStatusItem>() {
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.app.PendingIntent
|
|||
import android.content.res.Resources
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.k9mail.core.android.common.contact.CachingRepository
|
||||
import app.k9mail.core.android.common.contact.ContactPermissionResolver
|
||||
import app.k9mail.core.android.common.contact.ContactRepository
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
|
@ -24,8 +25,8 @@ import java.text.DateFormat
|
|||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
@ -42,15 +43,35 @@ internal class MessageDetailsViewModel(
|
|||
private val folderNameFormatter: FolderNameFormatter,
|
||||
) : ViewModel() {
|
||||
private val dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, Locale.getDefault())
|
||||
private val uiState = MutableStateFlow<MessageDetailsState>(MessageDetailsState.Loading)
|
||||
private val eventChannel = Channel<MessageDetailEvent>()
|
||||
|
||||
private val internalUiState = MutableStateFlow<MessageDetailsState>(MessageDetailsState.Loading)
|
||||
val uiState: Flow<MessageDetailsState>
|
||||
get() = internalUiState
|
||||
|
||||
private val eventChannel = Channel<MessageDetailEvent>()
|
||||
val uiEvents = eventChannel.receiveAsFlow()
|
||||
|
||||
private var messageReference: MessageReference? = null
|
||||
var cryptoResult: CryptoResultAnnotation? = null
|
||||
|
||||
fun loadData(messageReference: MessageReference): StateFlow<MessageDetailsState> {
|
||||
fun initialize(messageReference: MessageReference) {
|
||||
this.messageReference = messageReference
|
||||
loadData(messageReference)
|
||||
}
|
||||
|
||||
fun reload() {
|
||||
messageReference?.let { messageReference ->
|
||||
if (contactRepository is CachingRepository) {
|
||||
contactRepository.clearCache()
|
||||
}
|
||||
|
||||
loadData(messageReference)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadData(messageReference: MessageReference) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
uiState.value = try {
|
||||
internalUiState.value = try {
|
||||
val account = accountManager.getAccount(messageReference.accountUuid) ?: error("Account not found")
|
||||
val messageDetails = messageRepository.getMessageDetails(messageReference)
|
||||
|
||||
|
@ -82,8 +103,6 @@ internal class MessageDetailsViewModel(
|
|||
MessageDetailsState.Error
|
||||
}
|
||||
}
|
||||
|
||||
return uiState
|
||||
}
|
||||
|
||||
private fun buildDisplayDate(messageDate: MessageDate): String? {
|
||||
|
|
|
@ -40,6 +40,7 @@ internal class ParticipantItem(
|
|||
|
||||
if (participant.displayName != null) {
|
||||
name.text = participant.displayName
|
||||
name.isVisible = true
|
||||
} else {
|
||||
name.isVisible = false
|
||||
}
|
||||
|
@ -48,12 +49,16 @@ internal class ParticipantItem(
|
|||
menuAddContact.isVisible = !item.alwaysHideAddContactsButton && !participant.isInContacts
|
||||
|
||||
if (item.showContactsPicture) {
|
||||
contactPicture.isVisible = true
|
||||
item.contactPictureLoader.setContactPicture(contactPicture, participant.address)
|
||||
} else {
|
||||
contactPicture.isVisible = false
|
||||
}
|
||||
|
||||
if (!item.participant.isInContacts) {
|
||||
if (item.participant.isInContacts) {
|
||||
itemView.isClickable = true
|
||||
itemView.background = originalBackground
|
||||
} else {
|
||||
itemView.isClickable = false
|
||||
itemView.background = null
|
||||
}
|
||||
|
@ -61,11 +66,7 @@ internal class ParticipantItem(
|
|||
|
||||
override fun unbindView(item: ParticipantItem) {
|
||||
name.text = null
|
||||
name.isVisible = true
|
||||
email.text = null
|
||||
contactPicture.isVisible = true
|
||||
itemView.background = originalBackground
|
||||
itemView.isClickable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,4 +6,5 @@
|
|||
<item type="id" name="message_details_participant"/>
|
||||
<item type="id" name="message_details_folder_name"/>
|
||||
<item type="id" name="message_details_divider"/>
|
||||
<item type="id" name="message_details_empty"/>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue