Add basic support for swiping between messages
This commit is contained in:
parent
c1a97f2c97
commit
de6d4197f2
6 changed files with 416 additions and 134 deletions
|
@ -54,7 +54,9 @@ import com.fsck.k9.ui.changelog.RecentChangesActivity
|
|||
import com.fsck.k9.ui.changelog.RecentChangesViewModel
|
||||
import com.fsck.k9.ui.managefolders.ManageFoldersActivity
|
||||
import com.fsck.k9.ui.messagelist.DefaultFolderProvider
|
||||
import com.fsck.k9.ui.messageview.MessageViewFragment
|
||||
import com.fsck.k9.ui.messageview.Direction
|
||||
import com.fsck.k9.ui.messageview.MessageViewContainerFragment
|
||||
import com.fsck.k9.ui.messageview.MessageViewContainerFragment.MessageViewContainerListener
|
||||
import com.fsck.k9.ui.messageview.MessageViewFragment.MessageViewFragmentListener
|
||||
import com.fsck.k9.ui.messageview.PlaceholderFragment
|
||||
import com.fsck.k9.ui.onboarding.OnboardingActivity
|
||||
|
@ -80,6 +82,7 @@ open class MessageList :
|
|||
K9Activity(),
|
||||
MessageListFragmentListener,
|
||||
MessageViewFragmentListener,
|
||||
MessageViewContainerListener,
|
||||
FragmentManager.OnBackStackChangedListener,
|
||||
OnSwitchCompleteListener,
|
||||
PermissionUiHelper {
|
||||
|
@ -102,11 +105,16 @@ open class MessageList :
|
|||
private var progressBar: ProgressBar? = null
|
||||
private var messageViewPlaceHolder: PlaceholderFragment? = null
|
||||
private var messageListFragment: MessageListFragment? = null
|
||||
private var messageViewFragment: MessageViewFragment? = null
|
||||
private var messageViewContainerFragment: MessageViewContainerFragment? = null
|
||||
private var account: Account? = null
|
||||
private var search: LocalSearch? = null
|
||||
private var singleFolderMode = false
|
||||
private var lastDirection = if (K9.isMessageViewShowNext) NEXT else PREVIOUS
|
||||
|
||||
private val lastDirection: Direction
|
||||
get() {
|
||||
return messageViewContainerFragment?.lastDirection
|
||||
?: if (K9.isMessageViewShowNext) Direction.NEXT else Direction.PREVIOUS
|
||||
}
|
||||
|
||||
private var messageListActivityAppearance: MessageListActivityAppearance? = null
|
||||
|
||||
|
@ -225,7 +233,7 @@ open class MessageList :
|
|||
supportFragmentManager.popBackStackImmediate(FIRST_FRAGMENT_TRANSACTION, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
|
||||
removeMessageListFragment()
|
||||
removeMessageViewFragment()
|
||||
removeMessageViewContainerFragment()
|
||||
|
||||
messageReference = null
|
||||
search = null
|
||||
|
@ -253,9 +261,11 @@ open class MessageList :
|
|||
private fun findFragments() {
|
||||
val fragmentManager = supportFragmentManager
|
||||
messageListFragment = fragmentManager.findFragmentById(R.id.message_list_container) as MessageListFragment?
|
||||
messageViewFragment = fragmentManager.findFragmentByTag(FRAGMENT_TAG_MESSAGE_VIEW) as MessageViewFragment?
|
||||
messageViewContainerFragment =
|
||||
fragmentManager.findFragmentByTag(FRAGMENT_TAG_MESSAGE_VIEW_CONTAINER) as MessageViewContainerFragment?
|
||||
|
||||
messageListFragment?.let { messageListFragment ->
|
||||
messageViewContainerFragment?.setViewModel(messageListFragment.viewModel)
|
||||
initializeFromLocalSearch(messageListFragment.localSearch)
|
||||
}
|
||||
}
|
||||
|
@ -278,7 +288,7 @@ open class MessageList :
|
|||
|
||||
// Check if the fragment wasn't restarted and has a MessageReference in the arguments.
|
||||
// If so, open the referenced message.
|
||||
if (!hasMessageListFragment && messageViewFragment == null && messageReference != null) {
|
||||
if (!hasMessageListFragment && messageViewContainerFragment == null && messageReference != null) {
|
||||
openMessage(messageReference!!)
|
||||
}
|
||||
}
|
||||
|
@ -304,7 +314,7 @@ open class MessageList :
|
|||
}
|
||||
}
|
||||
|
||||
displayMode = if (messageViewFragment != null || messageReference != null) {
|
||||
displayMode = if (messageViewContainerFragment != null || messageReference != null) {
|
||||
DisplayMode.MESSAGE_VIEW
|
||||
} else {
|
||||
DisplayMode.MESSAGE_LIST
|
||||
|
@ -337,12 +347,12 @@ open class MessageList :
|
|||
messageListWasDisplayed = true
|
||||
messageListFragment.isActive = true
|
||||
|
||||
messageViewFragment.let { messageViewFragment ->
|
||||
if (messageViewFragment == null) {
|
||||
messageViewContainerFragment.let { messageViewContainerFragment ->
|
||||
if (messageViewContainerFragment == null) {
|
||||
showMessageViewPlaceHolder()
|
||||
} else {
|
||||
messageViewFragment.isActive = true
|
||||
val activeMessage = messageViewFragment.messageReference
|
||||
messageViewContainerFragment.isActive = true
|
||||
val activeMessage = messageViewContainerFragment.messageReference
|
||||
messageListFragment.setActiveMessage(activeMessage)
|
||||
}
|
||||
}
|
||||
|
@ -607,7 +617,7 @@ open class MessageList :
|
|||
|
||||
fun openFolder(folderId: Long) {
|
||||
if (displayMode == DisplayMode.SPLIT_VIEW) {
|
||||
removeMessageViewFragment()
|
||||
removeMessageViewContainerFragment()
|
||||
showMessageViewPlaceHolder()
|
||||
}
|
||||
|
||||
|
@ -736,7 +746,7 @@ open class MessageList :
|
|||
|
||||
when (event.keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
if (messageViewFragment != null && displayMode != DisplayMode.MESSAGE_LIST &&
|
||||
if (messageViewContainerFragment != null && displayMode != DisplayMode.MESSAGE_LIST &&
|
||||
K9.isUseVolumeKeysForNavigation
|
||||
) {
|
||||
showPreviousMessage()
|
||||
|
@ -747,7 +757,7 @@ open class MessageList :
|
|||
}
|
||||
}
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
if (messageViewFragment != null && displayMode != DisplayMode.MESSAGE_LIST &&
|
||||
if (messageViewContainerFragment != null && displayMode != DisplayMode.MESSAGE_LIST &&
|
||||
K9.isUseVolumeKeysForNavigation
|
||||
) {
|
||||
showNextMessage()
|
||||
|
@ -762,14 +772,14 @@ open class MessageList :
|
|||
return true
|
||||
}
|
||||
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
||||
return if (messageViewFragment != null && displayMode == DisplayMode.MESSAGE_VIEW) {
|
||||
return if (messageViewContainerFragment != null && displayMode == DisplayMode.MESSAGE_VIEW) {
|
||||
showPreviousMessage()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
||||
return if (messageViewFragment != null && displayMode == DisplayMode.MESSAGE_VIEW) {
|
||||
return if (messageViewContainerFragment != null && displayMode == DisplayMode.MESSAGE_VIEW) {
|
||||
showNextMessage()
|
||||
} else {
|
||||
false
|
||||
|
@ -801,69 +811,69 @@ open class MessageList :
|
|||
'g' -> {
|
||||
if (displayMode == DisplayMode.MESSAGE_LIST) {
|
||||
messageListFragment!!.onToggleFlagged()
|
||||
} else if (messageViewFragment != null) {
|
||||
messageViewFragment!!.onToggleFlagged()
|
||||
} else if (messageViewContainerFragment != null) {
|
||||
messageViewContainerFragment!!.onToggleFlagged()
|
||||
}
|
||||
return true
|
||||
}
|
||||
'm' -> {
|
||||
if (displayMode == DisplayMode.MESSAGE_LIST) {
|
||||
messageListFragment!!.onMove()
|
||||
} else if (messageViewFragment != null) {
|
||||
messageViewFragment!!.onMove()
|
||||
} else if (messageViewContainerFragment != null) {
|
||||
messageViewContainerFragment!!.onMove()
|
||||
}
|
||||
return true
|
||||
}
|
||||
'v' -> {
|
||||
if (displayMode == DisplayMode.MESSAGE_LIST) {
|
||||
messageListFragment!!.onArchive()
|
||||
} else if (messageViewFragment != null) {
|
||||
messageViewFragment!!.onArchive()
|
||||
} else if (messageViewContainerFragment != null) {
|
||||
messageViewContainerFragment!!.onArchive()
|
||||
}
|
||||
return true
|
||||
}
|
||||
'y' -> {
|
||||
if (displayMode == DisplayMode.MESSAGE_LIST) {
|
||||
messageListFragment!!.onCopy()
|
||||
} else if (messageViewFragment != null) {
|
||||
messageViewFragment!!.onCopy()
|
||||
} else if (messageViewContainerFragment != null) {
|
||||
messageViewContainerFragment!!.onCopy()
|
||||
}
|
||||
return true
|
||||
}
|
||||
'z' -> {
|
||||
if (displayMode == DisplayMode.MESSAGE_LIST) {
|
||||
messageListFragment!!.onToggleRead()
|
||||
} else if (messageViewFragment != null) {
|
||||
messageViewFragment!!.onToggleRead()
|
||||
} else if (messageViewContainerFragment != null) {
|
||||
messageViewContainerFragment!!.onToggleRead()
|
||||
}
|
||||
return true
|
||||
}
|
||||
'f' -> {
|
||||
if (messageViewFragment != null) {
|
||||
messageViewFragment!!.onForward()
|
||||
if (messageViewContainerFragment != null) {
|
||||
messageViewContainerFragment!!.onForward()
|
||||
}
|
||||
return true
|
||||
}
|
||||
'a' -> {
|
||||
if (messageViewFragment != null) {
|
||||
messageViewFragment!!.onReplyAll()
|
||||
if (messageViewContainerFragment != null) {
|
||||
messageViewContainerFragment!!.onReplyAll()
|
||||
}
|
||||
return true
|
||||
}
|
||||
'r' -> {
|
||||
if (messageViewFragment != null) {
|
||||
messageViewFragment!!.onReply()
|
||||
if (messageViewContainerFragment != null) {
|
||||
messageViewContainerFragment!!.onReply()
|
||||
}
|
||||
return true
|
||||
}
|
||||
'j', 'p' -> {
|
||||
if (messageViewFragment != null) {
|
||||
if (messageViewContainerFragment != null) {
|
||||
showPreviousMessage()
|
||||
}
|
||||
return true
|
||||
}
|
||||
'n', 'k' -> {
|
||||
if (messageViewFragment != null) {
|
||||
if (messageViewContainerFragment != null) {
|
||||
showNextMessage()
|
||||
}
|
||||
return true
|
||||
|
@ -885,8 +895,8 @@ open class MessageList :
|
|||
private fun onDeleteHotKey() {
|
||||
if (displayMode == DisplayMode.MESSAGE_LIST) {
|
||||
messageListFragment!!.onDelete()
|
||||
} else if (messageViewFragment != null) {
|
||||
messageViewFragment!!.onDelete()
|
||||
} else if (messageViewContainerFragment != null) {
|
||||
messageViewContainerFragment!!.onDelete()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -979,12 +989,16 @@ open class MessageList :
|
|||
messageListFragment!!.setActiveMessage(messageReference)
|
||||
}
|
||||
|
||||
val fragment = MessageViewFragment.newInstance(messageReference)
|
||||
val fragment = MessageViewContainerFragment.newInstance(messageReference)
|
||||
supportFragmentManager.commitNow {
|
||||
replace(R.id.message_view_container, fragment, FRAGMENT_TAG_MESSAGE_VIEW)
|
||||
replace(R.id.message_view_container, fragment, FRAGMENT_TAG_MESSAGE_VIEW_CONTAINER)
|
||||
}
|
||||
|
||||
messageViewFragment = fragment
|
||||
messageViewContainerFragment = fragment
|
||||
|
||||
messageListFragment?.let { messageListFragment ->
|
||||
fragment.setViewModel(messageListFragment.viewModel)
|
||||
}
|
||||
|
||||
if (displayMode == DisplayMode.SPLIT_VIEW) {
|
||||
fragment.isActive = true
|
||||
|
@ -1089,7 +1103,7 @@ open class MessageList :
|
|||
}
|
||||
|
||||
private fun showMessageViewPlaceHolder() {
|
||||
removeMessageViewFragment()
|
||||
removeMessageViewContainerFragment()
|
||||
|
||||
// Add placeholder fragment if necessary
|
||||
val fragmentManager = supportFragmentManager
|
||||
|
@ -1102,11 +1116,11 @@ open class MessageList :
|
|||
messageListFragment!!.setActiveMessage(null)
|
||||
}
|
||||
|
||||
private fun removeMessageViewFragment() {
|
||||
if (messageViewFragment != null) {
|
||||
private fun removeMessageViewContainerFragment() {
|
||||
if (messageViewContainerFragment != null) {
|
||||
val fragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
fragmentTransaction.remove(messageViewFragment!!)
|
||||
messageViewFragment = null
|
||||
fragmentTransaction.remove(messageViewContainerFragment!!)
|
||||
messageViewContainerFragment = null
|
||||
fragmentTransaction.commit()
|
||||
|
||||
showDefaultTitleView()
|
||||
|
@ -1129,29 +1143,35 @@ open class MessageList :
|
|||
}
|
||||
}
|
||||
|
||||
override fun closeMessageView() {
|
||||
returnToMessageList()
|
||||
}
|
||||
|
||||
override fun setActiveMessage(messageReference: MessageReference) {
|
||||
val messageListFragment = checkNotNull(messageListFragment)
|
||||
|
||||
messageListFragment.setActiveMessage(messageReference)
|
||||
}
|
||||
|
||||
override fun showNextMessageOrReturn() {
|
||||
if (K9.isMessageViewReturnToList || !showLogicalNextMessage()) {
|
||||
if (displayMode == DisplayMode.SPLIT_VIEW) {
|
||||
showMessageViewPlaceHolder()
|
||||
} else {
|
||||
showMessageList()
|
||||
}
|
||||
returnToMessageList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun returnToMessageList() {
|
||||
if (displayMode == DisplayMode.SPLIT_VIEW) {
|
||||
showMessageViewPlaceHolder()
|
||||
} else {
|
||||
showMessageList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLogicalNextMessage(): Boolean {
|
||||
var result = false
|
||||
if (lastDirection == NEXT) {
|
||||
result = showNextMessage()
|
||||
} else if (lastDirection == PREVIOUS) {
|
||||
result = showPreviousMessage()
|
||||
return when (lastDirection) {
|
||||
Direction.NEXT -> showNextMessage()
|
||||
Direction.PREVIOUS -> showPreviousMessage()
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
result = showNextMessage() || showPreviousMessage()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun setProgress(enable: Boolean) {
|
||||
|
@ -1159,25 +1179,15 @@ open class MessageList :
|
|||
}
|
||||
|
||||
private fun showNextMessage(): Boolean {
|
||||
val ref = messageViewFragment!!.messageReference
|
||||
if (ref != null) {
|
||||
if (messageListFragment!!.openNext(ref)) {
|
||||
lastDirection = NEXT
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
val messageViewContainerFragment = checkNotNull(messageViewContainerFragment)
|
||||
|
||||
return messageViewContainerFragment.showNextMessage()
|
||||
}
|
||||
|
||||
private fun showPreviousMessage(): Boolean {
|
||||
val ref = messageViewFragment!!.messageReference
|
||||
if (ref != null) {
|
||||
if (messageListFragment!!.openPrevious(ref)) {
|
||||
lastDirection = PREVIOUS
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
val messageViewContainerFragment = checkNotNull(messageViewContainerFragment)
|
||||
|
||||
return messageViewContainerFragment.showPreviousMessage()
|
||||
}
|
||||
|
||||
private fun showMessageList() {
|
||||
|
@ -1186,7 +1196,7 @@ open class MessageList :
|
|||
displayMode = DisplayMode.MESSAGE_LIST
|
||||
viewSwitcher!!.showFirstView()
|
||||
|
||||
messageViewFragment?.isActive = false
|
||||
messageViewContainerFragment?.isActive = false
|
||||
messageListFragment!!.isActive = true
|
||||
messageListFragment!!.setActiveMessage(null)
|
||||
|
||||
|
@ -1208,11 +1218,11 @@ open class MessageList :
|
|||
}
|
||||
|
||||
private fun showMessageView() {
|
||||
val messageViewFragment = checkNotNull(this.messageViewFragment)
|
||||
val messageViewContainerFragment = checkNotNull(this.messageViewContainerFragment)
|
||||
|
||||
displayMode = DisplayMode.MESSAGE_VIEW
|
||||
messageListFragment?.isActive = false
|
||||
messageViewFragment.isActive = true
|
||||
messageViewContainerFragment.isActive = true
|
||||
|
||||
if (!messageListWasDisplayed) {
|
||||
viewSwitcher!!.animateFirstView = false
|
||||
|
@ -1238,7 +1248,7 @@ open class MessageList :
|
|||
|
||||
override fun onSwitchComplete(displayedChild: Int) {
|
||||
if (displayedChild == 0) {
|
||||
removeMessageViewFragment()
|
||||
removeMessageViewContainerFragment()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1276,8 +1286,8 @@ open class MessageList :
|
|||
|
||||
if (requestCode and REQUEST_FLAG_PENDING_INTENT != 0) {
|
||||
val originalRequestCode = requestCode xor REQUEST_FLAG_PENDING_INTENT
|
||||
if (messageViewFragment != null) {
|
||||
messageViewFragment!!.onPendingIntentResult(originalRequestCode, resultCode, data)
|
||||
if (messageViewContainerFragment != null) {
|
||||
messageViewContainerFragment!!.onPendingIntentResult(originalRequestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1387,16 +1397,11 @@ open class MessageList :
|
|||
private const val STATE_DISPLAY_MODE = "displayMode"
|
||||
private const val STATE_MESSAGE_VIEW_ONLY = "messageViewOnly"
|
||||
private const val STATE_MESSAGE_LIST_WAS_DISPLAYED = "messageListWasDisplayed"
|
||||
private const val STATE_FIRST_BACK_STACK_ID = "firstBackstackId"
|
||||
|
||||
private const val FIRST_FRAGMENT_TRANSACTION = "first"
|
||||
private const val FRAGMENT_TAG_MESSAGE_VIEW = "MessageViewFragment"
|
||||
private const val FRAGMENT_TAG_MESSAGE_VIEW_CONTAINER = "MessageViewContainerFragment"
|
||||
private const val FRAGMENT_TAG_PLACEHOLDER = "MessageViewPlaceholder"
|
||||
|
||||
// Used for navigating to next/previous message
|
||||
private const val PREVIOUS = 1
|
||||
private const val NEXT = 2
|
||||
|
||||
private const val REQUEST_CODE_MASK = 0xFFFF0000.toInt()
|
||||
private const val REQUEST_FLAG_PENDING_INTENT = 1 shl 15
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ class MessageListFragment :
|
|||
ConfirmationDialogFragmentListener,
|
||||
MessageListItemActionListener {
|
||||
|
||||
private val viewModel: MessageListViewModel by viewModel()
|
||||
val viewModel: MessageListViewModel by viewModel()
|
||||
private val sortTypeToastProvider: SortTypeToastProvider by inject()
|
||||
private val folderNameFormatterFactory: FolderNameFormatterFactory by inject()
|
||||
private val folderNameFormatter: FolderNameFormatter by lazy { folderNameFormatterFactory.create(requireContext()) }
|
||||
|
@ -1299,30 +1299,6 @@ class MessageListFragment :
|
|||
}
|
||||
}
|
||||
|
||||
fun openPrevious(messageReference: MessageReference): Boolean {
|
||||
val position = getPosition(messageReference)
|
||||
if (position <= 0) return false
|
||||
|
||||
openMessageAtPosition(position - 1)
|
||||
return true
|
||||
}
|
||||
|
||||
fun openNext(messageReference: MessageReference): Boolean {
|
||||
val position = getPosition(messageReference)
|
||||
if (position < 0 || position == adapter.count - 1) return false
|
||||
|
||||
openMessageAtPosition(position + 1)
|
||||
return true
|
||||
}
|
||||
|
||||
fun isFirst(messageReference: MessageReference): Boolean {
|
||||
return adapter.isEmpty || messageReference == getReferenceForPosition(0)
|
||||
}
|
||||
|
||||
fun isLast(messageReference: MessageReference): Boolean {
|
||||
return adapter.isEmpty || messageReference == getReferenceForPosition(adapter.count - 1)
|
||||
}
|
||||
|
||||
private fun getReferenceForPosition(position: Int): MessageReference {
|
||||
val item = adapter.getItem(position)
|
||||
return MessageReference(item.account.uuid, item.folderId, item.messageUid)
|
||||
|
@ -1349,14 +1325,6 @@ class MessageListFragment :
|
|||
fragmentListener.openMessage(messageReference)
|
||||
}
|
||||
|
||||
private fun getPosition(messageReference: MessageReference): Int {
|
||||
return adapter.messages.indexOfFirst { messageListItem ->
|
||||
messageListItem.account.uuid == messageReference.accountUuid &&
|
||||
messageListItem.folderId == messageReference.folderId &&
|
||||
messageListItem.messageUid == messageReference.uid
|
||||
}
|
||||
}
|
||||
|
||||
fun onReverseSort() {
|
||||
changeSort(sortType)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package com.fsck.k9.ui.messageview
|
||||
|
||||
enum class Direction {
|
||||
PREVIOUS,
|
||||
NEXT
|
||||
}
|
|
@ -0,0 +1,311 @@
|
|||
package com.fsck.k9.ui.messageview
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.fsck.k9.controller.MessageReference
|
||||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.ui.messagelist.MessageListItem
|
||||
import com.fsck.k9.ui.messagelist.MessageListViewModel
|
||||
import com.fsck.k9.ui.withArguments
|
||||
|
||||
/**
|
||||
* A fragment that uses [ViewPager2] to allow the user to swipe between messages.
|
||||
*
|
||||
* Individual messages are displayed using a [MessageViewFragment].
|
||||
*/
|
||||
class MessageViewContainerFragment : Fragment() {
|
||||
var isActive: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
setMenuVisibility(value)
|
||||
}
|
||||
|
||||
lateinit var messageReference: MessageReference
|
||||
private set
|
||||
|
||||
var lastDirection: Direction? = null
|
||||
private set
|
||||
|
||||
private lateinit var fragmentListener: MessageViewContainerListener
|
||||
private lateinit var viewPager: ViewPager2
|
||||
private lateinit var adapter: MessageViewContainerAdapter
|
||||
|
||||
private var currentPosition: Int? = null
|
||||
|
||||
private val messageViewFragment: MessageViewFragment
|
||||
get() {
|
||||
check(isResumed)
|
||||
val itemId = adapter.getItemId(messageReference)
|
||||
|
||||
// ViewPager2/FragmentStateAdapter don't provide an easy way to get hold of the Fragment for the active
|
||||
// page. So we're using an implementation detail (the fragment tag) to find the fragment.
|
||||
return childFragmentManager.findFragmentByTag("f$itemId") as MessageViewFragment
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
messageReference = MessageReference.parse(arguments?.getString(ARG_REFERENCE))
|
||||
?: error("Missing argument $ARG_REFERENCE")
|
||||
} else {
|
||||
messageReference = MessageReference.parse(savedInstanceState.getString(STATE_MESSAGE_REFERENCE))
|
||||
?: error("Missing state $STATE_MESSAGE_REFERENCE")
|
||||
|
||||
lastDirection = savedInstanceState.getSerializable(STATE_LAST_DIRECTION) as Direction?
|
||||
}
|
||||
|
||||
adapter = MessageViewContainerAdapter(this)
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
fragmentListener = try {
|
||||
context as MessageViewContainerListener
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException("This fragment must be attached to a MessageViewContainerListener")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(R.layout.message_view_container, container, false)
|
||||
|
||||
viewPager = view.findViewById(R.id.message_viewpager)
|
||||
viewPager.isUserInputEnabled = true
|
||||
viewPager.offscreenPageLimit = ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT
|
||||
viewPager.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.HORIZONTAL))
|
||||
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
// The message list is updated each time the active message is changed. To avoid message list updates
|
||||
// during the animation, we only set the active message after the animation has finished.
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
if (state == ViewPager2.SCROLL_STATE_IDLE) {
|
||||
setActiveMessage(viewPager.currentItem)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
if (viewPager.scrollState == ViewPager2.SCROLL_STATE_IDLE) {
|
||||
setActiveMessage(position)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putString(STATE_MESSAGE_REFERENCE, messageReference.toIdentityString())
|
||||
outState.putSerializable(STATE_LAST_DIRECTION, lastDirection)
|
||||
}
|
||||
|
||||
fun setViewModel(viewModel: MessageListViewModel) {
|
||||
viewModel.getMessageListLiveData().observe(this) { messageListInfo ->
|
||||
updateMessageList(messageListInfo.messageListItems)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMessageList(messageListItems: List<MessageListItem>) {
|
||||
if (messageListItems.isEmpty() || messageListItems.none { it.messageReference == messageReference }) {
|
||||
fragmentListener.closeMessageView()
|
||||
return
|
||||
}
|
||||
|
||||
adapter.messageList = messageListItems
|
||||
|
||||
// We only set the adapter on ViewPager2 after the message list has been loaded. This way ViewPager2 can
|
||||
// restore its saved state after a configuration change.
|
||||
if (viewPager.adapter == null) {
|
||||
viewPager.adapter = adapter
|
||||
}
|
||||
|
||||
val position = adapter.getPosition(messageReference)
|
||||
viewPager.setCurrentItem(position, false)
|
||||
}
|
||||
|
||||
private fun setActiveMessage(position: Int) {
|
||||
rememberNavigationDirection(position, messageReference)
|
||||
|
||||
messageReference = adapter.getMessageReference(position)
|
||||
|
||||
fragmentListener.setActiveMessage(messageReference)
|
||||
}
|
||||
|
||||
private fun rememberNavigationDirection(newPosition: Int, currentMessageReference: MessageReference) {
|
||||
// When messages are added or removed from the list, the current position will change even though we're still
|
||||
// displaying the same message. In those cases we don't want to update `lastDirection`.
|
||||
val newMessageReference = adapter.getMessageReference(newPosition)
|
||||
if (newMessageReference == currentMessageReference) {
|
||||
currentPosition = newPosition
|
||||
return
|
||||
}
|
||||
|
||||
currentPosition?.let { currentPosition ->
|
||||
lastDirection = if (newPosition < currentPosition) Direction.PREVIOUS else Direction.NEXT
|
||||
}
|
||||
currentPosition = newPosition
|
||||
}
|
||||
|
||||
fun showPreviousMessage(): Boolean {
|
||||
val newPosition = viewPager.currentItem - 1
|
||||
return if (newPosition >= 0) {
|
||||
setActiveMessage(newPosition)
|
||||
|
||||
val smoothScroll = true
|
||||
viewPager.setCurrentItem(newPosition, smoothScroll)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun showNextMessage(): Boolean {
|
||||
val newPosition = viewPager.currentItem + 1
|
||||
return if (newPosition < adapter.itemCount) {
|
||||
setActiveMessage(newPosition)
|
||||
|
||||
val smoothScroll = true
|
||||
viewPager.setCurrentItem(newPosition, smoothScroll)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleFlagged() {
|
||||
messageViewFragment.onToggleFlagged()
|
||||
}
|
||||
|
||||
fun onMove() {
|
||||
messageViewFragment.onMove()
|
||||
}
|
||||
|
||||
fun onArchive() {
|
||||
messageViewFragment.onArchive()
|
||||
}
|
||||
|
||||
fun onCopy() {
|
||||
messageViewFragment.onCopy()
|
||||
}
|
||||
|
||||
fun onToggleRead() {
|
||||
messageViewFragment.onToggleRead()
|
||||
}
|
||||
|
||||
fun onForward() {
|
||||
messageViewFragment.onForward()
|
||||
}
|
||||
|
||||
fun onReplyAll() {
|
||||
messageViewFragment.onReplyAll()
|
||||
}
|
||||
|
||||
fun onReply() {
|
||||
messageViewFragment.onReply()
|
||||
}
|
||||
|
||||
fun onDelete() {
|
||||
messageViewFragment.onDelete()
|
||||
}
|
||||
|
||||
fun onPendingIntentResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
messageViewFragment.onPendingIntentResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
private class MessageViewContainerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
|
||||
var messageList: List<MessageListItem> = emptyList()
|
||||
set(value) {
|
||||
val diffResult = DiffUtil.calculateDiff(
|
||||
MessageListDiffCallback(oldMessageList = messageList, newMessageList = value)
|
||||
)
|
||||
|
||||
field = value
|
||||
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return messageList.size
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return messageList[position].uniqueId
|
||||
}
|
||||
|
||||
override fun containsItem(itemId: Long): Boolean {
|
||||
return messageList.any { it.uniqueId == itemId }
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
check(position in messageList.indices)
|
||||
|
||||
val messageReference = messageList[position].messageReference
|
||||
return MessageViewFragment.newInstance(messageReference)
|
||||
}
|
||||
|
||||
fun getMessageReference(position: Int): MessageReference {
|
||||
check(position in messageList.indices)
|
||||
|
||||
return messageList[position].messageReference
|
||||
}
|
||||
|
||||
fun getPosition(messageReference: MessageReference): Int {
|
||||
return messageList.indexOfFirst { it.messageReference == messageReference }
|
||||
}
|
||||
|
||||
fun getItemId(messageReference: MessageReference): Long {
|
||||
return messageList.first { it.messageReference == messageReference }.uniqueId
|
||||
}
|
||||
}
|
||||
|
||||
private class MessageListDiffCallback(
|
||||
private val oldMessageList: List<MessageListItem>,
|
||||
private val newMessageList: List<MessageListItem>
|
||||
) : DiffUtil.Callback() {
|
||||
override fun getOldListSize(): Int = oldMessageList.size
|
||||
|
||||
override fun getNewListSize(): Int = newMessageList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return oldMessageList[oldItemPosition].uniqueId == newMessageList[newItemPosition].uniqueId
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
// Let MessageViewFragment deal with content changes
|
||||
return areItemsTheSame(oldItemPosition, newItemPosition)
|
||||
}
|
||||
}
|
||||
|
||||
interface MessageViewContainerListener {
|
||||
fun closeMessageView()
|
||||
fun setActiveMessage(messageReference: MessageReference)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ARG_REFERENCE = "reference"
|
||||
|
||||
private const val STATE_MESSAGE_REFERENCE = "messageReference"
|
||||
private const val STATE_LAST_DIRECTION = "lastDirection"
|
||||
|
||||
fun newInstance(reference: MessageReference): MessageViewContainerFragment {
|
||||
return MessageViewContainerFragment().withArguments(
|
||||
ARG_REFERENCE to reference.toIdentityString()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val MessageListItem.messageReference: MessageReference
|
||||
get() = MessageReference(account.uuid, folderId, messageUid)
|
|
@ -90,17 +90,6 @@ class MessageViewFragment :
|
|||
private var currentAttachmentViewInfo: AttachmentViewInfo? = null
|
||||
private var isDeleteMenuItemDisabled: Boolean = false
|
||||
|
||||
/**
|
||||
* Set this to `true` when the fragment should be considered active. When active, the fragment adds its actions to
|
||||
* the toolbar. When inactive, the fragment won't add its actions to the toolbar, even it is still visible, e.g. as
|
||||
* part of an animation.
|
||||
*/
|
||||
var isActive: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
invalidateMenu()
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
|
@ -191,8 +180,6 @@ class MessageViewFragment :
|
|||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
if (!isActive) return
|
||||
|
||||
menu.findItem(R.id.delete).apply {
|
||||
isVisible = K9.isMessageViewDeleteActionVisible
|
||||
isEnabled = !isDeleteMenuItemDisabled
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/message_viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
Loading…
Reference in a new issue