forked from github/wulkanowy-mirror
Add option to select multiple messages to delete (#1780)
This commit is contained in:
parent
63380d3e12
commit
2131e892ad
@ -103,7 +103,7 @@ class MessageRepository @Inject constructor(
|
||||
message: Message,
|
||||
markAsRead: Boolean = false,
|
||||
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
||||
isResultEmpty = { it == null },
|
||||
isResultEmpty = { it?.message?.content.isNullOrBlank() },
|
||||
shouldFetch = {
|
||||
checkNotNull(it) { "This message no longer exist!" }
|
||||
Timber.d("Message content in db empty: ${it.message.content.isEmpty()}")
|
||||
@ -151,20 +151,27 @@ class MessageRepository @Inject constructor(
|
||||
recipients = recipients.mapFromEntities()
|
||||
)
|
||||
|
||||
suspend fun deleteMessage(student: Student, message: Message) {
|
||||
val isDeleted = sdk.init(student).deleteMessages(
|
||||
messages = listOf(message.messageId), message.folderId
|
||||
)
|
||||
suspend fun deleteMessages(student: Student, messages: List<Message>) {
|
||||
val folderId = messages.first().folderId
|
||||
val isDeleted = sdk.init(student)
|
||||
.deleteMessages(messages = messages.map { it.messageId }, folderId = folderId)
|
||||
|
||||
if (message.folderId != MessageFolder.TRASHED.id && isDeleted) {
|
||||
val deletedMessage = message.copy(folderId = MessageFolder.TRASHED.id).apply {
|
||||
id = message.id
|
||||
content = message.content
|
||||
if (folderId != MessageFolder.TRASHED.id && isDeleted) {
|
||||
val deletedMessages = messages.map {
|
||||
it.copy(folderId = MessageFolder.TRASHED.id)
|
||||
.apply {
|
||||
id = it.id
|
||||
content = it.content
|
||||
}
|
||||
}
|
||||
messagesDb.updateAll(listOf(deletedMessage))
|
||||
} else messagesDb.deleteAll(listOf(message))
|
||||
|
||||
messagesDb.updateAll(deletedMessages)
|
||||
} else messagesDb.deleteAll(messages)
|
||||
}
|
||||
|
||||
suspend fun deleteMessage(student: Student, message: Message) =
|
||||
deleteMessages(student, listOf(message))
|
||||
|
||||
var draftMessage: MessageDraft?
|
||||
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
|
||||
?.let { json.decodeFromString(it) }
|
||||
|
@ -2,14 +2,8 @@ package io.github.wulkanowy.ui.modules.attendance
|
||||
|
||||
import android.content.DialogInterface.BUTTON_POSITIVE
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.*
|
||||
import android.view.View.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.view.isVisible
|
||||
@ -68,7 +62,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
|
||||
private val actionModeCallback = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
val inflater = mode.menuInflater
|
||||
inflater.inflate(R.menu.context_menu_excuse, menu)
|
||||
inflater.inflate(R.menu.context_menu_attendance, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,14 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updateMargins
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.SENT
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.*
|
||||
import io.github.wulkanowy.databinding.FragmentMessageBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
|
||||
@ -78,7 +80,6 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
|
||||
}
|
||||
|
||||
binding.messageTabLayout.elevation = requireContext().dpToPx(4f)
|
||||
|
||||
binding.openSendMessageButton.setOnClickListener { presenter.onSendMessageButtonClicked() }
|
||||
}
|
||||
|
||||
@ -93,12 +94,37 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
|
||||
binding.messageProgress.visibility = if (show) VISIBLE else INVISIBLE
|
||||
}
|
||||
|
||||
override fun showNewMessage(show: Boolean) {
|
||||
binding.openSendMessageButton.run {
|
||||
if (show) show() else hide()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showTabLayout(show: Boolean) {
|
||||
binding.messageTabLayout.isVisible = show
|
||||
|
||||
with(binding.messageViewPager) {
|
||||
isUserInputEnabled = show
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
updateMargins(top = if (show) requireContext().dpToPx(48f).toInt() else 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onChildFragmentShowActionMode(show: Boolean) {
|
||||
presenter.onChildViewShowActionMode(show)
|
||||
}
|
||||
|
||||
fun onChildFragmentLoaded() {
|
||||
presenter.onChildViewLoaded()
|
||||
}
|
||||
|
||||
override fun notifyChildMessageDeleted(tabId: Int) {
|
||||
(pagerAdapter.getFragmentInstance(tabId) as? MessageTabFragment)?.onParentDeleteMessage()
|
||||
fun onChildFragmentShowNewMessage(show: Boolean) {
|
||||
presenter.onChildViewShowNewMessage(show)
|
||||
}
|
||||
|
||||
fun onFragmentChanged() {
|
||||
presenter.onFragmentChanged()
|
||||
}
|
||||
|
||||
override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) {
|
||||
@ -106,6 +132,13 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
|
||||
?.onParentLoadData(forceRefresh)
|
||||
}
|
||||
|
||||
override fun notifyChildrenFinishActionMode() {
|
||||
repeat(3) {
|
||||
(pagerAdapter.getFragmentInstance(it) as? MessageTabFragment)
|
||||
?.onParentFinishActionMode()
|
||||
}
|
||||
}
|
||||
|
||||
override fun openSendMessage() {
|
||||
context?.let { it.startActivity(SendMessageActivity.getStartIntent(it)) }
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.modules.message
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@ -24,6 +23,7 @@ class MessagePresenter @Inject constructor(
|
||||
|
||||
fun onPageSelected(index: Int) {
|
||||
loadChild(index)
|
||||
view?.notifyChildrenFinishActionMode()
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
@ -35,6 +35,10 @@ class MessagePresenter @Inject constructor(
|
||||
view?.notifyChildLoadData(index, forceRefresh)
|
||||
}
|
||||
|
||||
fun onFragmentChanged() {
|
||||
view?.notifyChildrenFinishActionMode()
|
||||
}
|
||||
|
||||
fun onChildViewLoaded() {
|
||||
view?.apply {
|
||||
showContent(true)
|
||||
@ -42,6 +46,14 @@ class MessagePresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onChildViewShowNewMessage(show: Boolean) {
|
||||
view?.showNewMessage(show)
|
||||
}
|
||||
|
||||
fun onChildViewShowActionMode(show: Boolean) {
|
||||
view?.showTabLayout(!show)
|
||||
}
|
||||
|
||||
fun onSendMessageButtonClicked() {
|
||||
view?.openSendMessage()
|
||||
}
|
||||
|
@ -12,9 +12,13 @@ interface MessageView : BaseView {
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showNewMessage(show: Boolean)
|
||||
|
||||
fun showTabLayout(show: Boolean)
|
||||
|
||||
fun notifyChildLoadData(index: Int, forceRefresh: Boolean)
|
||||
|
||||
fun notifyChildMessageDeleted(tabId: Int)
|
||||
fun notifyChildrenFinishActionMode()
|
||||
|
||||
fun openSendMessage()
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ class MessagePreviewFragment :
|
||||
}
|
||||
|
||||
override fun setNotDeletedOptionsLabels() {
|
||||
menuDeleteButton?.setTitle(R.string.message_move_to_bin)
|
||||
menuDeleteButton?.setTitle(R.string.message_move_to_trash)
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
|
@ -2,15 +2,12 @@ package io.github.wulkanowy.ui.modules.message.tab
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CompoundButton
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.databinding.ItemMessageBinding
|
||||
import io.github.wulkanowy.databinding.ItemMessageChipsBinding
|
||||
@ -20,118 +17,141 @@ import javax.inject.Inject
|
||||
class MessageTabAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
enum class ViewType { HEADER, ITEM }
|
||||
var onItemClickListener: (MessageTabDataItem.MessageItem, position: Int) -> Unit = { _, _ -> }
|
||||
|
||||
var onItemClickListener: (Message, position: Int) -> Unit = { _, _ -> }
|
||||
var onHeaderClickListener: (chip: CompoundButton, isChecked: Boolean) -> Unit = { _, _ -> }
|
||||
var onLongItemClickListener: (MessageTabDataItem.MessageItem) -> Unit = {}
|
||||
|
||||
var onHeaderClickListener: (CompoundButton, Boolean) -> Unit = { _, _ -> }
|
||||
|
||||
var onChangesDetectedListener = {}
|
||||
|
||||
private var items = mutableListOf<MessageTabDataItem>()
|
||||
private var onlyUnread: Boolean? = null
|
||||
private var onlyWithAttachments = false
|
||||
|
||||
fun setDataItems(
|
||||
data: List<MessageTabDataItem>,
|
||||
onlyUnread: Boolean?,
|
||||
onlyWithAttachments: Boolean
|
||||
) {
|
||||
if (items.size != data.size) onChangesDetectedListener()
|
||||
fun submitData(data: List<MessageTabDataItem>) {
|
||||
val originalMessagesSize = items.count { it.viewType == MessageItemViewType.MESSAGE }
|
||||
val newMessagesSize = data.count { it.viewType == MessageItemViewType.MESSAGE }
|
||||
|
||||
if (originalMessagesSize != newMessagesSize) onChangesDetectedListener()
|
||||
|
||||
val diffResult = DiffUtil.calculateDiff(MessageTabDiffUtil(items, data))
|
||||
items = data.toMutableList()
|
||||
this.onlyUnread = onlyUnread
|
||||
this.onlyWithAttachments = onlyWithAttachments
|
||||
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (position) {
|
||||
0 -> ViewType.HEADER.ordinal
|
||||
else -> ViewType.ITEM.ordinal
|
||||
}
|
||||
}
|
||||
override fun getItemViewType(position: Int) = items[position].viewType.ordinal
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return when (viewType) {
|
||||
ViewType.ITEM.ordinal -> ItemViewHolder(
|
||||
|
||||
return when (MessageItemViewType.values()[viewType]) {
|
||||
MessageItemViewType.MESSAGE -> ItemViewHolder(
|
||||
ItemMessageBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
ViewType.HEADER.ordinal -> HeaderViewHolder(
|
||||
MessageItemViewType.FILTERS -> HeaderViewHolder(
|
||||
ItemMessageChipsBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is ItemViewHolder -> {
|
||||
val item = (items[position] as MessageTabDataItem.MessageItem).message
|
||||
is ItemViewHolder -> bindItemViewHolder(holder, position)
|
||||
is HeaderViewHolder -> bindHeaderViewHolder(holder, position)
|
||||
}
|
||||
}
|
||||
|
||||
with(holder.binding) {
|
||||
val style = if (item.unread) Typeface.BOLD else Typeface.NORMAL
|
||||
private fun bindHeaderViewHolder(holder: HeaderViewHolder, position: Int) {
|
||||
val item = items[position] as MessageTabDataItem.FilterHeader
|
||||
|
||||
messageItemAuthor.run {
|
||||
text =
|
||||
if (item.folderId == MessageFolder.SENT.id) item.recipient else item.sender
|
||||
setTypeface(null, style)
|
||||
}
|
||||
messageItemSubject.run {
|
||||
text =
|
||||
if (item.subject.isNotBlank()) item.subject else context.getString(R.string.message_no_subject)
|
||||
setTypeface(null, style)
|
||||
}
|
||||
messageItemDate.run {
|
||||
text = item.date.toFormattedString()
|
||||
setTypeface(null, style)
|
||||
}
|
||||
messageItemAttachmentIcon.visibility =
|
||||
if (item.hasAttachments) View.VISIBLE else View.GONE
|
||||
with(holder.binding) {
|
||||
if (item.onlyUnread == null) {
|
||||
chipUnread.isVisible = false
|
||||
} else {
|
||||
chipUnread.isVisible = true
|
||||
chipUnread.isChecked = item.onlyUnread
|
||||
chipUnread.setOnCheckedChangeListener(onHeaderClickListener)
|
||||
}
|
||||
chipUnread.isEnabled = item.isEnabled
|
||||
chipAttachments.isEnabled = item.isEnabled
|
||||
chipAttachments.isChecked = item.onlyWithAttachments
|
||||
chipAttachments.setOnCheckedChangeListener(onHeaderClickListener)
|
||||
}
|
||||
}
|
||||
|
||||
root.setOnClickListener {
|
||||
holder.bindingAdapterPosition.let {
|
||||
if (it != NO_POSITION) onItemClickListener(item, it)
|
||||
}
|
||||
private fun bindItemViewHolder(holder: ItemViewHolder, position: Int) {
|
||||
val item = (items[position] as MessageTabDataItem.MessageItem)
|
||||
val message = item.message
|
||||
|
||||
with(holder.binding) {
|
||||
val style = if (message.unread) Typeface.BOLD else Typeface.NORMAL
|
||||
|
||||
messageItemAuthor.run {
|
||||
text = if (message.folderId == MessageFolder.SENT.id) {
|
||||
message.recipient
|
||||
} else {
|
||||
message.sender
|
||||
}
|
||||
setTypeface(null, style)
|
||||
}
|
||||
messageItemSubject.run {
|
||||
text = message.subject.ifBlank { context.getString(R.string.message_no_subject) }
|
||||
setTypeface(null, style)
|
||||
}
|
||||
messageItemDate.run {
|
||||
text = message.date.toFormattedString()
|
||||
setTypeface(null, style)
|
||||
}
|
||||
messageItemAttachmentIcon.isVisible = message.hasAttachments
|
||||
|
||||
root.setOnClickListener {
|
||||
holder.bindingAdapterPosition.let {
|
||||
if (it != RecyclerView.NO_POSITION) {
|
||||
onItemClickListener(item, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
is HeaderViewHolder -> {
|
||||
with(holder.binding) {
|
||||
if (onlyUnread == null) chipUnread.isVisible = false
|
||||
else {
|
||||
chipUnread.isVisible = true
|
||||
chipUnread.isChecked = onlyUnread!!
|
||||
chipUnread.setOnCheckedChangeListener(onHeaderClickListener)
|
||||
}
|
||||
chipAttachments.isChecked = onlyWithAttachments
|
||||
chipAttachments.setOnCheckedChangeListener(onHeaderClickListener)
|
||||
}
|
||||
|
||||
root.setOnLongClickListener {
|
||||
onLongItemClickListener(item)
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
|
||||
with(messageItemCheckbox) {
|
||||
isChecked = item.isSelected
|
||||
isVisible = item.isActionMode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ItemViewHolder(val binding: ItemMessageBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
class HeaderViewHolder(val binding: ItemMessageChipsBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
private class MessageTabDiffUtil(
|
||||
private val old: List<MessageTabDataItem>,
|
||||
private val new: List<MessageTabDataItem>
|
||||
) :
|
||||
DiffUtil.Callback() {
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize(): Int = old.size
|
||||
|
||||
override fun getNewListSize(): Int = new.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return old[oldItemPosition].id == new[newItemPosition].id
|
||||
val oldItem = old[oldItemPosition]
|
||||
val newItem = new[newItemPosition]
|
||||
|
||||
return if (oldItem is MessageTabDataItem.MessageItem && newItem is MessageTabDataItem.MessageItem) {
|
||||
oldItem.message.id == newItem.message.id
|
||||
} else {
|
||||
oldItem.viewType == newItem.viewType
|
||||
}
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return old[oldItemPosition] == new[newItemPosition]
|
||||
}
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||
old[oldItemPosition] == new[newItemPosition]
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,19 @@ package io.github.wulkanowy.ui.modules.message.tab
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
|
||||
sealed class MessageTabDataItem {
|
||||
data class MessageItem(val message: Message) : MessageTabDataItem() {
|
||||
override val id = message.id
|
||||
}
|
||||
sealed class MessageTabDataItem(val viewType: MessageItemViewType) {
|
||||
|
||||
object Header : MessageTabDataItem() {
|
||||
override val id = Long.MIN_VALUE
|
||||
}
|
||||
data class MessageItem(
|
||||
val message: Message,
|
||||
val isSelected: Boolean,
|
||||
val isActionMode: Boolean
|
||||
) : MessageTabDataItem(MessageItemViewType.MESSAGE)
|
||||
|
||||
abstract val id: Long
|
||||
data class FilterHeader(
|
||||
val onlyUnread: Boolean?,
|
||||
val onlyWithAttachments: Boolean,
|
||||
val isEnabled: Boolean
|
||||
) : MessageTabDataItem(MessageItemViewType.FILTERS)
|
||||
}
|
||||
|
||||
enum class MessageItemViewType { FILTERS, MESSAGE }
|
||||
|
@ -3,12 +3,13 @@ package io.github.wulkanowy.ui.modules.message.tab
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.View.*
|
||||
import android.widget.CompoundButton
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
@ -20,7 +21,9 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.hideSoftInput
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -31,9 +34,10 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
lateinit var presenter: MessageTabPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var tabAdapter: MessageTabAdapter
|
||||
lateinit var messageTabAdapter: MessageTabAdapter
|
||||
|
||||
companion object {
|
||||
|
||||
const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id"
|
||||
|
||||
fun newInstance(folder: MessageFolder): MessageTabFragment {
|
||||
@ -46,11 +50,38 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
}
|
||||
|
||||
override val isViewEmpty
|
||||
get() = tabAdapter.itemCount == 0
|
||||
get() = messageTabAdapter.itemCount == 0
|
||||
|
||||
override var onlyUnread: Boolean? = false
|
||||
private var actionMode: ActionMode? = null
|
||||
|
||||
override var onlyWithAttachments = false
|
||||
private val actionModeCallback = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
val inflater = mode.menuInflater
|
||||
inflater.inflate(R.menu.context_menu_message_tab, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
if (presenter.folder == MessageFolder.TRASHED) {
|
||||
val menuItem = menu.findItem(R.id.messageTabContextMenuDelete)
|
||||
menuItem.setTitle(R.string.message_delete_forever)
|
||||
}
|
||||
return presenter.onPrepareActionMode()
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode) {
|
||||
presenter.onDestroyActionMode()
|
||||
actionMode = null
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
|
||||
when (menu.itemId) {
|
||||
R.id.messageTabContextMenuDelete -> presenter.onActionModeSelectDelete()
|
||||
R.id.messageTabContextMenuSelectAll -> presenter.onActionModeSelectCheckAll()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -69,24 +100,25 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
with(tabAdapter) {
|
||||
with(messageTabAdapter) {
|
||||
onItemClickListener = presenter::onMessageItemSelected
|
||||
onLongItemClickListener = presenter::onMessageItemLongSelected
|
||||
onHeaderClickListener = ::onChipChecked
|
||||
onChangesDetectedListener = ::resetListPosition
|
||||
}
|
||||
|
||||
with(binding.messageTabRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = tabAdapter
|
||||
adapter = messageTabAdapter
|
||||
addItemDecoration(DividerItemDecoration(context, false))
|
||||
itemAnimator = null
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
messageTabSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
messageTabSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
|
||||
messageTabSwipe.setProgressBackgroundColorSchemeColor(
|
||||
requireContext().getThemeAttrColor(
|
||||
R.attr.colorSwipeRefresh
|
||||
)
|
||||
requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)
|
||||
)
|
||||
messageTabErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
@ -109,9 +141,28 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
})
|
||||
}
|
||||
|
||||
override fun updateData(data: List<MessageTabDataItem>, hide: Boolean) {
|
||||
if (hide) onlyUnread = null
|
||||
tabAdapter.setDataItems(data, onlyUnread, onlyWithAttachments)
|
||||
override fun updateData(data: List<MessageTabDataItem>) {
|
||||
messageTabAdapter.submitData(data)
|
||||
}
|
||||
|
||||
override fun updateActionModeTitle(selectedMessagesSize: Int) {
|
||||
actionMode?.title = resources.getQuantityString(
|
||||
R.plurals.message_selected_messages_count,
|
||||
selectedMessagesSize,
|
||||
selectedMessagesSize
|
||||
)
|
||||
}
|
||||
|
||||
override fun updateSelectAllMenu(isAllSelected: Boolean) {
|
||||
val menuItem = actionMode?.menu?.findItem(R.id.messageTabContextMenuSelectAll) ?: return
|
||||
|
||||
if (isAllSelected) {
|
||||
menuItem.setTitle(R.string.message_unselect_all)
|
||||
menuItem.setIcon(R.drawable.ic_message_unselect_all)
|
||||
} else {
|
||||
menuItem.setTitle(R.string.message_select_all)
|
||||
menuItem.setIcon(R.drawable.ic_message_select_all)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
@ -146,6 +197,14 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
binding.messageTabSwipe.isRefreshing = show
|
||||
}
|
||||
|
||||
override fun showMessagesDeleted() {
|
||||
showMessage(getString(R.string.message_messages_deleted))
|
||||
}
|
||||
|
||||
override fun notifyParentShowNewMessage(show: Boolean) {
|
||||
(parentFragment as? MessageFragment)?.onChildFragmentShowNewMessage(show)
|
||||
}
|
||||
|
||||
override fun openMessage(message: Message) {
|
||||
(activity as? MainActivity)?.pushView(MessagePreviewFragment.newInstance(message))
|
||||
}
|
||||
@ -154,12 +213,16 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
(parentFragment as? MessageFragment)?.onChildFragmentLoaded()
|
||||
}
|
||||
|
||||
fun onParentLoadData(
|
||||
forceRefresh: Boolean,
|
||||
onlyUnread: Boolean? = this.onlyUnread,
|
||||
onlyWithAttachments: Boolean = this.onlyWithAttachments
|
||||
) {
|
||||
presenter.onParentViewLoadData(forceRefresh, onlyUnread, onlyWithAttachments)
|
||||
override fun notifyParentShowActionMode(show: Boolean) {
|
||||
(parentFragment as? MessageFragment)?.onChildFragmentShowActionMode(show)
|
||||
}
|
||||
|
||||
fun onParentLoadData(forceRefresh: Boolean) {
|
||||
presenter.onParentViewLoadData(forceRefresh)
|
||||
}
|
||||
|
||||
fun onParentFinishActionMode() {
|
||||
presenter.onParentFinishActionMode()
|
||||
}
|
||||
|
||||
private fun onChipChecked(chip: CompoundButton, isChecked: Boolean) {
|
||||
@ -169,8 +232,22 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
}
|
||||
}
|
||||
|
||||
fun onParentDeleteMessage() {
|
||||
presenter.onDeleteMessage()
|
||||
override fun showActionMode(show: Boolean) {
|
||||
if (show) {
|
||||
actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback)
|
||||
} else {
|
||||
actionMode?.finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showRecyclerBottomPadding(show: Boolean) {
|
||||
binding.messageTabRecycler.updatePadding(
|
||||
bottom = if (show) requireContext().dpToPx(64f).toInt() else 0
|
||||
)
|
||||
}
|
||||
|
||||
override fun hideKeyboard() {
|
||||
activity?.hideSoftInput()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
@ -12,7 +12,10 @@ import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||
import timber.log.Timber
|
||||
@ -37,6 +40,14 @@ class MessageTabPresenter @Inject constructor(
|
||||
|
||||
private val searchChannel = Channel<String>()
|
||||
|
||||
private val messagesToDelete = mutableSetOf<Message>()
|
||||
|
||||
private var onlyUnread: Boolean? = false
|
||||
|
||||
private var onlyWithAttachments = false
|
||||
|
||||
private var isActionMode = false
|
||||
|
||||
fun onAttachView(view: MessageTabView, folder: MessageFolder) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
@ -47,14 +58,14 @@ class MessageTabPresenter @Inject constructor(
|
||||
|
||||
fun onSwipeRefresh() {
|
||||
Timber.i("Force refreshing the $folder message")
|
||||
view?.run { onParentViewLoadData(true, onlyUnread, onlyWithAttachments) }
|
||||
view?.run { loadData(true) }
|
||||
}
|
||||
|
||||
fun onRetry() {
|
||||
view?.run {
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
loadData(true, onlyUnread == true, onlyWithAttachments)
|
||||
loadData(true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,42 +73,135 @@ class MessageTabPresenter @Inject constructor(
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
fun onDeleteMessage() {
|
||||
view?.run { loadData(true, onlyUnread == true, onlyWithAttachments) }
|
||||
fun onParentViewLoadData(forceRefresh: Boolean) {
|
||||
loadData(forceRefresh)
|
||||
}
|
||||
|
||||
fun onParentViewLoadData(
|
||||
forceRefresh: Boolean,
|
||||
onlyUnread: Boolean? = view?.onlyUnread,
|
||||
onlyWithAttachments: Boolean = view?.onlyWithAttachments == true
|
||||
) {
|
||||
loadData(forceRefresh, onlyUnread == true, onlyWithAttachments)
|
||||
fun onParentFinishActionMode() {
|
||||
view?.showActionMode(false)
|
||||
}
|
||||
|
||||
fun onMessageItemSelected(message: Message, position: Int) {
|
||||
Timber.i("Select message ${message.id} item (position: $position)")
|
||||
view?.openMessage(message)
|
||||
fun onDestroyActionMode() {
|
||||
isActionMode = false
|
||||
messagesToDelete.clear()
|
||||
updateDataInView()
|
||||
|
||||
view?.run {
|
||||
enableSwipe(true)
|
||||
notifyParentShowNewMessage(true)
|
||||
notifyParentShowActionMode(false)
|
||||
showRecyclerBottomPadding(true)
|
||||
}
|
||||
}
|
||||
|
||||
fun onPrepareActionMode(): Boolean {
|
||||
isActionMode = true
|
||||
messagesToDelete.clear()
|
||||
updateDataInView()
|
||||
|
||||
view?.apply {
|
||||
enableSwipe(false)
|
||||
notifyParentShowNewMessage(false)
|
||||
notifyParentShowActionMode(true)
|
||||
showRecyclerBottomPadding(false)
|
||||
hideKeyboard()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun onActionModeSelectDelete() {
|
||||
Timber.i("Delete ${messagesToDelete.size} messages)")
|
||||
val messageList = messagesToDelete.toList()
|
||||
|
||||
presenterScope.launch {
|
||||
view?.run {
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
showActionMode(false)
|
||||
}
|
||||
|
||||
runCatching {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
messageRepository.deleteMessages(student, messageList)
|
||||
}
|
||||
.onFailure(errorHandler::dispatch)
|
||||
.onSuccess { view?.showMessagesDeleted() }
|
||||
}
|
||||
}
|
||||
|
||||
fun onActionModeSelectCheckAll() {
|
||||
val messagesToSelect = getFilteredData()
|
||||
val isAllSelected = messagesToDelete.containsAll(messagesToSelect)
|
||||
|
||||
if (isAllSelected) {
|
||||
messagesToDelete.clear()
|
||||
view?.showActionMode(false)
|
||||
} else {
|
||||
messagesToDelete.addAll(messagesToSelect)
|
||||
updateDataInView()
|
||||
}
|
||||
|
||||
view?.run {
|
||||
updateSelectAllMenu(!isAllSelected)
|
||||
updateActionModeTitle(messagesToDelete.size)
|
||||
}
|
||||
}
|
||||
|
||||
fun onMessageItemLongSelected(messageItem: MessageTabDataItem.MessageItem) {
|
||||
if (!isActionMode) {
|
||||
view?.showActionMode(true)
|
||||
|
||||
messagesToDelete.add(messageItem.message)
|
||||
|
||||
view?.updateActionModeTitle(messagesToDelete.size)
|
||||
updateDataInView()
|
||||
}
|
||||
}
|
||||
|
||||
fun onMessageItemSelected(messageItem: MessageTabDataItem.MessageItem, position: Int) {
|
||||
Timber.i("Select message ${messageItem.message.id} item (position: $position)")
|
||||
|
||||
if (!isActionMode) {
|
||||
view?.run {
|
||||
showActionMode(false)
|
||||
openMessage(messageItem.message)
|
||||
}
|
||||
} else {
|
||||
if (!messageItem.isSelected) {
|
||||
messagesToDelete.add(messageItem.message)
|
||||
} else {
|
||||
messagesToDelete.remove(messageItem.message)
|
||||
}
|
||||
|
||||
if (messagesToDelete.isEmpty()) {
|
||||
view?.showActionMode(false)
|
||||
}
|
||||
|
||||
val filteredData = getFilteredData()
|
||||
|
||||
view?.run {
|
||||
updateActionModeTitle(messagesToDelete.size)
|
||||
updateSelectAllMenu(messagesToDelete.containsAll(filteredData))
|
||||
}
|
||||
updateDataInView()
|
||||
}
|
||||
}
|
||||
|
||||
fun onUnreadFilterSelected(isChecked: Boolean) {
|
||||
view?.run {
|
||||
onlyUnread = isChecked
|
||||
onParentViewLoadData(false, onlyUnread, onlyWithAttachments)
|
||||
loadData(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun onAttachmentsFilterSelected(isChecked: Boolean) {
|
||||
view?.run {
|
||||
onlyWithAttachments = isChecked
|
||||
onParentViewLoadData(false, onlyUnread, onlyWithAttachments)
|
||||
loadData(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadData(
|
||||
forceRefresh: Boolean,
|
||||
onlyUnread: Boolean,
|
||||
onlyWithAttachments: Boolean
|
||||
) {
|
||||
private fun loadData(forceRefresh: Boolean) {
|
||||
Timber.i("Loading $folder message data started")
|
||||
|
||||
flatResourceFlow {
|
||||
@ -106,54 +210,29 @@ class MessageTabPresenter @Inject constructor(
|
||||
messageRepository.getMessages(student, semester, folder, forceRefresh)
|
||||
}
|
||||
.logResourceStatus("load $folder message")
|
||||
.onEach {
|
||||
when (it) {
|
||||
is Resource.Intermediate -> {
|
||||
if (it.data.isNotEmpty()) {
|
||||
view?.run {
|
||||
enableSwipe(true)
|
||||
showErrorView(false)
|
||||
showRefresh(true)
|
||||
showProgress(false)
|
||||
showContent(true)
|
||||
messages = it.data
|
||||
val filteredData = getFilteredData(
|
||||
lastSearchQuery,
|
||||
onlyUnread,
|
||||
onlyWithAttachments
|
||||
)
|
||||
val messageItems = filteredData.map { message ->
|
||||
MessageTabDataItem.MessageItem(message)
|
||||
}
|
||||
val messageItemsWithHeader =
|
||||
listOf(MessageTabDataItem.Header) + messageItems
|
||||
.onResourceData {
|
||||
messages = it
|
||||
|
||||
updateData(
|
||||
messageItemsWithHeader,
|
||||
folder.id == MessageFolder.SENT.id
|
||||
)
|
||||
notifyParentDataLoaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
is Resource.Success -> {
|
||||
messages = it.data
|
||||
updateData(
|
||||
getFilteredData(
|
||||
lastSearchQuery,
|
||||
onlyUnread,
|
||||
onlyWithAttachments
|
||||
)
|
||||
)
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "messages",
|
||||
"items" to it.data.size,
|
||||
"folder" to folder.name
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
val filteredData = getFilteredData()
|
||||
|
||||
view?.run {
|
||||
enableSwipe(true)
|
||||
showErrorView(false)
|
||||
showProgress(false)
|
||||
showContent(true)
|
||||
showEmpty(filteredData.isEmpty())
|
||||
}
|
||||
|
||||
updateDataInView()
|
||||
}
|
||||
.onResourceIntermediate { view?.showRefresh(true) }
|
||||
.onResourceSuccess {
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "messages",
|
||||
"items" to it.size,
|
||||
"folder" to folder.name
|
||||
)
|
||||
}
|
||||
.onResourceNotLoading {
|
||||
view?.run {
|
||||
@ -196,56 +275,71 @@ class MessageTabPresenter @Inject constructor(
|
||||
.debounce(250)
|
||||
.map { query ->
|
||||
lastSearchQuery = query
|
||||
val isOnlyUnread = view?.onlyUnread == true
|
||||
val isOnlyWithAttachments = view?.onlyWithAttachments == true
|
||||
getFilteredData(query, isOnlyUnread, isOnlyWithAttachments)
|
||||
|
||||
getFilteredData()
|
||||
}
|
||||
.catch { Timber.e(it) }
|
||||
.collect {
|
||||
Timber.d("Applying filter. Full list: ${messages.size}, filtered: ${it.size}")
|
||||
updateData(it)
|
||||
|
||||
view?.run {
|
||||
showEmpty(it.isEmpty())
|
||||
showContent(true)
|
||||
showErrorView(false)
|
||||
}
|
||||
|
||||
updateDataInView()
|
||||
view?.resetListPosition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFilteredData(
|
||||
query: String,
|
||||
onlyUnread: Boolean = false,
|
||||
onlyWithAttachments: Boolean = false
|
||||
): List<Message> {
|
||||
if (query.trim().isEmpty()) {
|
||||
private fun getFilteredData(): List<Message> {
|
||||
if (lastSearchQuery.trim().isEmpty()) {
|
||||
val sortedMessages = messages.sortedByDescending { it.date }
|
||||
return when {
|
||||
onlyUnread && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments }
|
||||
onlyUnread -> sortedMessages.filter { it.unread == onlyUnread }
|
||||
(onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments }
|
||||
(onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread }
|
||||
onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments }
|
||||
else -> sortedMessages
|
||||
}
|
||||
} else {
|
||||
val sortedMessages = messages
|
||||
.map { it to calculateMatchRatio(it, query) }
|
||||
.map { it to calculateMatchRatio(it, lastSearchQuery) }
|
||||
.sortedWith(compareBy<Pair<Message, Int>> { -it.second }.thenByDescending { it.first.date })
|
||||
.filter { it.second > 6000 }
|
||||
.map { it.first }
|
||||
return when {
|
||||
onlyUnread && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments }
|
||||
onlyUnread -> sortedMessages.filter { it.unread == onlyUnread }
|
||||
(onlyUnread == true) && onlyWithAttachments -> sortedMessages.filter { it.unread == onlyUnread && it.hasAttachments == onlyWithAttachments }
|
||||
(onlyUnread == true) -> sortedMessages.filter { it.unread == onlyUnread }
|
||||
onlyWithAttachments -> sortedMessages.filter { it.hasAttachments == onlyWithAttachments }
|
||||
else -> sortedMessages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateData(data: List<Message>) {
|
||||
view?.run {
|
||||
showEmpty(data.isEmpty())
|
||||
showContent(true)
|
||||
showErrorView(false)
|
||||
val newItems =
|
||||
listOf(MessageTabDataItem.Header) + data.map { MessageTabDataItem.MessageItem(it) }
|
||||
updateData(newItems, folder.id == MessageFolder.SENT.id)
|
||||
private fun updateDataInView() {
|
||||
val data = getFilteredData()
|
||||
|
||||
val list = buildList {
|
||||
add(
|
||||
MessageTabDataItem.FilterHeader(
|
||||
onlyUnread = onlyUnread.takeIf { folder != MessageFolder.SENT },
|
||||
onlyWithAttachments = onlyWithAttachments,
|
||||
isEnabled = !isActionMode
|
||||
)
|
||||
)
|
||||
|
||||
addAll(data.map { message ->
|
||||
MessageTabDataItem.MessageItem(
|
||||
message = message,
|
||||
isSelected = messagesToDelete.any { it.id == message.id },
|
||||
isActionMode = isActionMode
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
view?.updateData(list)
|
||||
}
|
||||
|
||||
private fun calculateMatchRatio(message: Message, query: String): Int {
|
||||
|
@ -7,15 +7,15 @@ interface MessageTabView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
var onlyUnread: Boolean?
|
||||
|
||||
var onlyWithAttachments: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun resetListPosition()
|
||||
|
||||
fun updateData(data: List<MessageTabDataItem>, hide: Boolean)
|
||||
fun updateData(data: List<MessageTabDataItem>)
|
||||
|
||||
fun updateActionModeTitle(selectedMessagesSize: Int)
|
||||
|
||||
fun updateSelectAllMenu(isAllSelected: Boolean)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
@ -25,8 +25,12 @@ interface MessageTabView : BaseView {
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showMessagesDeleted()
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
||||
fun notifyParentShowNewMessage(show: Boolean)
|
||||
|
||||
fun setErrorDetails(message: String)
|
||||
|
||||
fun showRefresh(show: Boolean)
|
||||
@ -34,4 +38,12 @@ interface MessageTabView : BaseView {
|
||||
fun openMessage(message: Message)
|
||||
|
||||
fun notifyParentDataLoaded()
|
||||
|
||||
fun notifyParentShowActionMode(show: Boolean)
|
||||
|
||||
fun hideKeyboard()
|
||||
|
||||
fun showActionMode(show: Boolean)
|
||||
|
||||
fun showRecyclerBottomPadding(show: Boolean)
|
||||
}
|
||||
|
@ -89,6 +89,11 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
|
||||
if (::presenter.isInitialized) presenter.onViewReselected()
|
||||
}
|
||||
|
||||
override fun onFragmentChanged() {
|
||||
(parentFragmentManager.fragments.find { it is MessageFragment } as MessageFragment?)
|
||||
?.onFragmentChanged()
|
||||
}
|
||||
|
||||
override fun updateData(data: List<Pair<String, Drawable?>>) {
|
||||
with(moreAdapter) {
|
||||
items = data
|
||||
|
17
app/src/main/res/drawable/ic_message_select_all.xml
Normal file
17
app/src/main/res/drawable/ic_message_select_all.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M14,7L9,12 6,9"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M16,2H4C2.895,2 2,2.895 2,4v12c0,1.105 0.895,2 2,2h12c1.105,0 2,-0.895 2,-2V4C18,2.895 17.105,2 16,2zM16,4v12H4V4H16z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M6,20v2h14c1.105,0 2,-0.895 2,-2V6h-2v14H6z" />
|
||||
</vector>
|
12
app/src/main/res/drawable/ic_message_unselect_all.xml
Normal file
12
app/src/main/res/drawable/ic_message_unselect_all.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M16,2H4C2.895,2 2,2.895 2,4v12c0,1.105 0.895,2 2,2h12c1.105,0 2,-0.895 2,-2V4C18,2.895 17.105,2 16,2zM16,4v12H4V4H16z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M6,20v2h14c1.105,0 2,-0.895 2,-2V6h-2v14H6z" />
|
||||
</vector>
|
@ -9,8 +9,10 @@
|
||||
android:id="@+id/messagePreviewRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
tools:itemCount="1"
|
||||
tools:listitem="@layout/item_message_preview" />
|
||||
tools:listitem="@layout/item_message_preview"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/messagePreviewError"
|
||||
|
@ -5,22 +5,34 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="10dp"
|
||||
tools:context=".ui.modules.message.tab.MessageTabAdapter">
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/messageItemCheckbox"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:text="@null"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageItemAuthor"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/messageItemDate"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/messageItemCheckbox"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
@ -45,6 +57,7 @@
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toStartOf="@id/messageItemAttachmentIcon"
|
||||
app:layout_constraintStart_toEndOf="@id/messageItemCheckbox"
|
||||
app:layout_constraintStart_toStartOf="@id/messageItemAuthor"
|
||||
app:layout_constraintTop_toBottomOf="@+id/messageItemAuthor"
|
||||
app:layout_goneMarginEnd="0dp"
|
||||
|
@ -19,7 +19,7 @@
|
||||
android:id="@+id/messagePreviewMenuDelete"
|
||||
android:icon="@drawable/ic_menu_message_delete"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/message_delete"
|
||||
android:title="@string/message_move_to_trash"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
|
18
app/src/main/res/menu/context_menu_message_tab.xml
Normal file
18
app/src/main/res/menu/context_menu_message_tab.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/messageTabContextMenuDelete"
|
||||
android:icon="@drawable/ic_menu_message_delete"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/message_move_to_trash"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/messageTabContextMenuSelectAll"
|
||||
android:icon="@drawable/ic_message_select_all"
|
||||
android:orderInCategory="2"
|
||||
android:title="@string/message_select_all"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
@ -287,7 +287,7 @@
|
||||
<string name="message_reply">Odpověď</string>
|
||||
<string name="message_forward">Poslat dále</string>
|
||||
<string name="message_delete">Odstranit</string>
|
||||
<string name="message_move_to_bin">Přesunout do koše</string>
|
||||
<string name="message_move_to_trash">Přesunout do koše</string>
|
||||
<string name="message_delete_forever">Odstranit natrvalo</string>
|
||||
<string name="message_delete_success">Zpráva byla úspěšně odstraněna</string>
|
||||
<string name="message_share">Sdílet</string>
|
||||
|
@ -253,7 +253,7 @@
|
||||
<string name="message_reply">Antwort</string>
|
||||
<string name="message_forward">Weiterleiten</string>
|
||||
<string name="message_delete">Löschen</string>
|
||||
<string name="message_move_to_bin">In den Korb wandern</string>
|
||||
<string name="message_move_to_trash">In den Korb wandern</string>
|
||||
<string name="message_delete_forever">Dauerhaft löschen</string>
|
||||
<string name="message_delete_success">Nachricht erfolgreich gelöscht</string>
|
||||
<string name="message_share">Teilen</string>
|
||||
|
@ -287,7 +287,7 @@
|
||||
<string name="message_reply">Odpowiedz</string>
|
||||
<string name="message_forward">Prześlij dalej</string>
|
||||
<string name="message_delete">Usuń</string>
|
||||
<string name="message_move_to_bin">Przenieś do kosza</string>
|
||||
<string name="message_move_to_trash">Przenieś do kosza</string>
|
||||
<string name="message_delete_forever">Usuń trwale</string>
|
||||
<string name="message_delete_success">Wiadomość usunięta pomyślnie</string>
|
||||
<string name="message_share">Udostępnij</string>
|
||||
|
@ -287,7 +287,7 @@
|
||||
<string name="message_reply">Ответ</string>
|
||||
<string name="message_forward">Переслать</string>
|
||||
<string name="message_delete">Удалить</string>
|
||||
<string name="message_move_to_bin">Перенести в корзину</string>
|
||||
<string name="message_move_to_trash">Перенести в корзину</string>
|
||||
<string name="message_delete_forever">Удалить навсегда</string>
|
||||
<string name="message_delete_success">Сообщение успешно удалено</string>
|
||||
<string name="message_share">Поделиться</string>
|
||||
|
@ -287,7 +287,7 @@
|
||||
<string name="message_reply">Odpoveď</string>
|
||||
<string name="message_forward">Poslať ďalej</string>
|
||||
<string name="message_delete">Odstrániť</string>
|
||||
<string name="message_move_to_bin">Presunúť do koša</string>
|
||||
<string name="message_move_to_trash">Presunúť do koša</string>
|
||||
<string name="message_delete_forever">Odstrániť natrvalo</string>
|
||||
<string name="message_delete_success">Správa bola úspešne odstránená</string>
|
||||
<string name="message_share">Zdieľať</string>
|
||||
|
@ -287,7 +287,7 @@
|
||||
<string name="message_reply">Відповісти</string>
|
||||
<string name="message_forward">Переслати</string>
|
||||
<string name="message_delete">Видалити</string>
|
||||
<string name="message_move_to_bin">Перемістити у кошик</string>
|
||||
<string name="message_move_to_trash">Перемістити у кошик</string>
|
||||
<string name="message_delete_forever">Видалити назавжди</string>
|
||||
<string name="message_delete_success">Повідомлення було успішно видалено</string>
|
||||
<string name="message_share">Поділіться</string>
|
||||
|
@ -277,11 +277,12 @@
|
||||
<string name="message_no_items">No messages</string>
|
||||
<string name="message_from">From:</string>
|
||||
<string name="message_to">To:</string>
|
||||
<string name="message_date">Date: %s</string>
|
||||
<string name="message_date">Date: %1$s</string>
|
||||
<string name="message_reply">Reply</string>
|
||||
<string name="message_forward">Forward</string>
|
||||
<string name="message_delete">Delete</string>
|
||||
<string name="message_move_to_bin">Move to trash</string>
|
||||
<string name="message_select_all">Select all</string>
|
||||
<string name="message_unselect_all">Unselect all</string>
|
||||
<string name="message_move_to_trash">Move to trash</string>
|
||||
<string name="message_delete_forever">Delete permanently</string>
|
||||
<string name="message_delete_success">Message deleted successfully</string>
|
||||
<string name="message_share">Share</string>
|
||||
@ -297,8 +298,8 @@
|
||||
<string name="message_read">Read: %s</string>
|
||||
<string name="message_read_by">Read by: %1$d of %2$d people</string>
|
||||
<plurals name="message_number_item">
|
||||
<item quantity="one">%d message</item>
|
||||
<item quantity="other">%d messages</item>
|
||||
<item quantity="one">%1$d message</item>
|
||||
<item quantity="other">%1$d messages</item>
|
||||
</plurals>
|
||||
<plurals name="message_new_items">
|
||||
<item quantity="one">New message</item>
|
||||
@ -310,6 +311,11 @@
|
||||
<item quantity="one">You received %1$d message</item>
|
||||
<item quantity="other">You received %1$d messages</item>
|
||||
</plurals>
|
||||
<plurals name="message_selected_messages_count">
|
||||
<item quantity="one">%1$d selected</item>
|
||||
<item quantity="other">%1$d selected</item>
|
||||
</plurals>
|
||||
<string name="message_messages_deleted">Messages deleted</string>
|
||||
|
||||
|
||||
<!--Note-->
|
||||
|
Loading…
x
Reference in New Issue
Block a user