Added view binding to MessageDetailsActionSheet and some basic actions handling.

MAILAND-1401
This commit is contained in:
Tomasz Giszczak 2021-04-16 12:51:16 +02:00
parent 7b93efad7c
commit 6dbe8ac2c3
10 changed files with 158 additions and 44 deletions

View File

@ -23,7 +23,7 @@ package ch.protonmail.android.activities.messageDetails
* Contains types of actions executed from message details screen.
*/
enum class MessageDetailsAction {
CLOSE,
DELETE_MESSAGE,
FORWARD,
LABEL_AS,
MARK_UNREAD,

View File

@ -24,11 +24,12 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import ch.protonmail.android.R
import ch.protonmail.android.activities.messageDetails.viewmodel.MessageDetailsViewModel
import ch.protonmail.android.databinding.FragmentMessageDetailsActionSheetBinding
import ch.protonmail.android.databinding.LayoutMessageDetailsActionsSheetButtonsBinding
import ch.protonmail.android.databinding.LayoutMessageDetailsActionsSheetHeaderBinding
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetDialog
@ -44,24 +45,17 @@ class MessageDetailsActionSheet : BottomSheetDialogFragment() {
private val viewModel: MessageDetailsViewModel by activityViewModels()
private lateinit var closeViewIcon: View
private var _binding: FragmentMessageDetailsActionSheetBinding? = null
private val binding get() = requireNotNull(_binding)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.fragment_message_details_action_sheet, container, false)
val title = arguments?.getString(EXTRA_ARG_TITLE)
if (!title.isNullOrEmpty()) {
rootView.findViewById<TextView>(R.id.detailsActionsTitleTextView).text = title
}
val subtitle = arguments?.getString(EXTRA_ARG_SUBTITLE)
if (!subtitle.isNullOrEmpty()) {
rootView.findViewById<TextView>(R.id.detailsActionsSubTitleTextView).text = subtitle
}
closeViewIcon = rootView.findViewById<TextView>(R.id.detailsActionsCloseView)
closeViewIcon.setOnClickListener {
viewModel.handleAction(MessageDetailsAction.CLOSE)
dismiss()
}
return rootView
_binding = FragmentMessageDetailsActionSheetBinding.inflate(inflater)
setupHeaderBindings(binding.includeLayoutActionSheetHeader, arguments)
setupMainButtonsBindings(binding.includeLayoutActionSheetButtons, viewModel)
setupOtherButtonsBindings(binding, viewModel)
return binding.root
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@ -76,9 +70,9 @@ class MessageDetailsActionSheet : BottomSheetDialogFragment() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
Timber.v("State changed to $newState")
if (newState == STATE_EXPANDED) {
showCloseIcon()
setCloseIconVisibility(true)
} else {
hideCloseIcon()
setCloseIconVisibility(false)
}
}
@ -86,19 +80,96 @@ class MessageDetailsActionSheet : BottomSheetDialogFragment() {
Timber.v("onSlide to offset $slideOffset")
}
}
)
}
}
return bottomSheetDialog
}
private fun hideCloseIcon() {
closeViewIcon.isVisible = false
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun showCloseIcon() {
closeViewIcon.isVisible = true
private fun setupHeaderBindings(
binding: LayoutMessageDetailsActionsSheetHeaderBinding,
arguments: Bundle?
) = with(binding) {
val title = arguments?.getString(EXTRA_ARG_TITLE)
if (!title.isNullOrEmpty()) {
detailsActionsTitleTextView.text = title
}
val subtitle = arguments?.getString(EXTRA_ARG_SUBTITLE)
if (!subtitle.isNullOrEmpty()) {
detailsActionsSubTitleTextView.text = subtitle
}
detailsActionsCloseView.setOnClickListener {
dismiss()
}
}
private fun setupMainButtonsBindings(
binding: LayoutMessageDetailsActionsSheetButtonsBinding,
detailsViewModel: MessageDetailsViewModel
) = with(binding) {
detailsActionsReplyTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.REPLY)
dismiss()
}
detailsActionsReplyAllTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.REPLY_ALL)
dismiss()
}
detailsActionsForwardTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.FORWARD)
dismiss()
}
detailsActionsMarkAsUnreadTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.MARK_UNREAD)
dismiss()
}
}
private fun setupOtherButtonsBindings(
binding: FragmentMessageDetailsActionSheetBinding,
detailsViewModel: MessageDetailsViewModel
) = with(binding) {
detailsActionsStarUnstarTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.STAR_UNSTAR)
dismiss()
}
detailsActionsTrashTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.MOVE_TO_TRASH)
dismiss()
}
detailsActionsMoveToArchiveTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.MOVE_TO_ARCHIVE)
dismiss()
}
detailsActionsMoveToSpamTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.MOVE_TO_SPAM)
dismiss()
}
detailsActionsLabelAsTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.LABEL_AS)
dismiss()
}
detailsActionsMoveToTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.MOVE_TO)
dismiss()
}
detailsActionsReportPhishingTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.REPORT_PHISHING)
dismiss()
}
detailsActionsPrintTextView.setOnClickListener {
detailsViewModel.handleAction(MessageDetailsAction.PRINT, activity)
dismiss()
}
}
private fun setCloseIconVisibility(shouldBeVisible: Boolean) {
binding.includeLayoutActionSheetHeader.detailsActionsCloseView.isVisible = shouldBeVisible
}
companion object {

View File

@ -18,7 +18,7 @@
*/
package ch.protonmail.android.activities.messageDetails
import android.content.Context
import android.app.Activity
import android.content.res.Resources
import android.os.Build
import android.print.PrintAttributes
@ -36,7 +36,7 @@ import ch.protonmail.android.utils.extensions.showToast
import timber.log.Timber
internal class MessagePrinter(
private val context: Context,
private val activity: Activity,
private val resources: Resources,
private val printManager: PrintManager,
private val loadRemoteImages: Boolean
@ -56,7 +56,7 @@ internal class MessagePrinter(
@RequiresApi(Build.VERSION_CODES.KITKAT)
fun printMessage(message: Message, bodyString: String) {
val webView = WebView(context)
val webView = WebView(activity)
webView.webViewClient = PrinterWebViewClient(message)
webView.settings.blockNetworkImage = !loadRemoteImages
val messageString = StringBuilder("<p>")
@ -70,12 +70,16 @@ internal class MessagePrinter(
messageString.appendRecipientsList(message.toList, R.string.print_to_template)
messageString.appendRecipientsList(message.ccList, R.string.print_cc_template)
messageString.appendRecipientsList(message.bccList, R.string.print_bcc_template)
messageString.append(String.format(resources.getString(R.string.print_date_template), DateUtil.formatDetailedDateTime(context, message.timeMs)))
messageString.append(String.format(resources.getString(R.string.print_date_template), DateUtil.formatDetailedDateTime(activity, message.timeMs)))
messageString.append("<br/>")
val attachmentList = message.Attachments
val attachmentsCount = attachmentList.size
messageString.append(resources.getQuantityString(R.plurals.attachments_non_descriptive,
attachmentsCount, attachmentsCount))
messageString.append(
resources.getQuantityString(
R.plurals.attachments_non_descriptive,
attachmentsCount, attachmentsCount
)
)
messageString.append("<br/>")
attachmentList.forEach { attachment ->
messageString.append(String.format(resources.getString(R.string.print_attachment_template), attachment.fileName))
@ -103,7 +107,7 @@ internal class MessagePrinter(
printManager.print(jobName, printAdapter, PrintAttributes.Builder().build())
} catch (e: Exception) {
Timber.e(e)
context.showToast(R.string.print_error)
activity.showToast(R.string.print_error)
}
}
}

View File

@ -19,6 +19,7 @@
package ch.protonmail.android.activities.messageDetails.viewmodel
import android.annotation.TargetApi
import android.app.Activity
import android.content.Context
import android.net.Uri
import android.os.Build
@ -53,6 +54,7 @@ import ch.protonmail.android.data.local.AttachmentMetadataDao
import ch.protonmail.android.data.local.model.*
import ch.protonmail.android.events.DownloadEmbeddedImagesEvent
import ch.protonmail.android.events.Status
import ch.protonmail.android.jobs.PostTrashJobV2
import ch.protonmail.android.jobs.helper.EmbeddedImage
import ch.protonmail.android.usecase.VerifyConnection
import ch.protonmail.android.usecase.delete.DeleteMessage
@ -67,6 +69,7 @@ import ch.protonmail.android.utils.crypto.KeyInformation
import ch.protonmail.android.viewmodel.ConnectivityBaseViewModel
import dagger.assisted.Assisted
import dagger.hilt.android.lifecycle.HiltViewModel
import com.birbit.android.jobqueue.JobManager
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -99,6 +102,7 @@ internal class MessageDetailsViewModel @Inject constructor(
private val dispatchers: DispatcherProvider,
private val attachmentsHelper: AttachmentsHelper,
private val downloadUtils: DownloadUtils,
private val jobManager: JobManager,
messageRendererFactory: MessageRenderer.Factory,
verifyConnection: VerifyConnection,
networkConfigurator: NetworkConfigurator
@ -643,13 +647,16 @@ internal class MessageDetailsViewModel @Inject constructor(
fun deleteMessage(messageId: String) {
viewModelScope.launch {
deleteMessageUseCase(listOf(messageId), Constants.MessageLocationType.TRASH.messageLocationTypeValue.toString())
deleteMessageUseCase(
listOf(messageId), Constants.MessageLocationType.TRASH.messageLocationTypeValue.toString()
)
}
}
fun printMessage(activityContext: Context) {
private fun printMessage(activityContext: Activity?) {
val message = message.value
message?.let {
requireNotNull(activityContext)
MessagePrinter(
activityContext,
activityContext.resources,
@ -678,7 +685,30 @@ internal class MessageDetailsViewModel @Inject constructor(
return bodyString
}
fun handleAction(action: MessageDetailsAction) {
fun handleAction(
action: MessageDetailsAction,
activity: Activity? = null
) {
Timber.v("Handle action: $action")
when (action) {
MessageDetailsAction.DELETE_MESSAGE -> deleteMessage(messageId)
MessageDetailsAction.FORWARD -> TODO()
MessageDetailsAction.LABEL_AS -> TODO()
MessageDetailsAction.MARK_UNREAD -> TODO()
MessageDetailsAction.MOVE_TO -> TODO()
MessageDetailsAction.MOVE_TO_ARCHIVE -> TODO()
MessageDetailsAction.MOVE_TO_SPAM -> TODO()
MessageDetailsAction.MOVE_TO_TRASH -> moveToTrash(messageId)
MessageDetailsAction.PRINT -> printMessage(activity)
MessageDetailsAction.REPLY -> TODO()
MessageDetailsAction.REPLY_ALL -> TODO()
MessageDetailsAction.REPORT_PHISHING -> TODO()
MessageDetailsAction.STAR_UNSTAR -> TODO()
}
}
private fun moveToTrash(messageId: String) {
val job = PostTrashJobV2(listOf(messageId), null)
jobManager.addJobInBackground(job)
}
}

View File

@ -84,7 +84,6 @@ import ch.protonmail.android.events.PostPhishingReportEvent
import ch.protonmail.android.events.Status
import ch.protonmail.android.jobs.PostArchiveJob
import ch.protonmail.android.jobs.PostSpamJob
import ch.protonmail.android.jobs.PostTrashJobV2
import ch.protonmail.android.jobs.PostUnreadJob
import ch.protonmail.android.jobs.ReportPhishingJob
import ch.protonmail.android.utils.AppUtil
@ -808,8 +807,7 @@ internal class MessageDetailsActivity :
)
messageDetailsActionsView.bind(actionsUiModel)
messageDetailsActionsView.setOnThirdActionClickListener {
val job = PostTrashJobV2(listOf(message.messageId), null)
mJobManager.addJobInBackground(job)
viewModel.handleAction(MessageDetailsAction.MOVE_TO_TRASH)
onBackPressed()
}
messageDetailsActionsView.setOnSecondActionClickListener {

View File

@ -19,7 +19,6 @@
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:animateLayoutChanges="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -30,9 +29,13 @@
android:paddingTop="@dimen/space_m"
android:paddingBottom="@dimen/space_l">
<include layout="@layout/layout_message_details_actions_sheet_header" />
<include
android:id="@+id/includeLayoutActionSheetHeader"
layout="@layout/layout_message_details_actions_sheet_header" />
<include layout="@layout/layout_message_details_actions_sheet_buttons" />
<include
android:id="@+id/includeLayoutActionSheetButtons"
layout="@layout/layout_message_details_actions_sheet_buttons" />
<TextView
android:id="@+id/detailsActionsStarUnstarTextView"

View File

@ -22,6 +22,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:paddingStart="@dimen/space_l"
android:paddingEnd="@dimen/space_l"
tools:showIn="@layout/fragment_message_details_action_sheet">

View File

@ -33,6 +33,7 @@ import ch.protonmail.android.usecase.VerifyConnection
import ch.protonmail.android.usecase.delete.DeleteMessage
import ch.protonmail.android.usecase.fetch.FetchVerificationKeys
import ch.protonmail.android.utils.DownloadUtils
import com.birbit.android.jobqueue.JobManager
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.channels.Channel
@ -54,6 +55,8 @@ class MessageDetailsViewModelTest : ArchTest, CoroutinesTest {
private val userManager: UserManager = mockk(relaxed = true)
private val jobManager: JobManager = mockk(relaxed = true)
private val contactsRepository: ContactsRepository = mockk(relaxed = true)
private val attachmentsHelper: AttachmentsHelper = mockk(relaxed = true)
@ -88,6 +91,7 @@ class MessageDetailsViewModelTest : ArchTest, CoroutinesTest {
dispatchers,
attachmentsHelper,
downloadUtils,
jobManager,
messageRendererFactory,
verifyConnection,
networkConfigurator
@ -100,7 +104,8 @@ class MessageDetailsViewModelTest : ArchTest, CoroutinesTest {
val windowWidth = 500
val defaultErrorMessage = "errorHappened"
val cssContent = "css"
val expected = "<html>\n <head>\n <style>$cssContent</style>\n <meta name=\"viewport\" content=\"width=$windowWidth, maximum-scale=2\"> \n </head>\n <body>\n <div id=\"pm-body\" class=\"inbox-body\"> $decryptedMessage \n </div>\n </body>\n</html>"
val expected =
"<html>\n <head>\n <style>$cssContent</style>\n <meta name=\"viewport\" content=\"width=$windowWidth, maximum-scale=2\"> \n </head>\n <body>\n <div id=\"pm-body\" class=\"inbox-body\"> $decryptedMessage \n </div>\n </body>\n</html>"
// when
val parsedMessage = viewModel.getParsedMessage(decryptedMessage, windowWidth, cssContent, defaultErrorMessage)

View File

@ -47,4 +47,4 @@ compileKotlin.kotlinOptions {
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
}
}

View File

@ -111,6 +111,8 @@ fun org.gradle.api.Project.android(
execution = "ANDROIDX_TEST_ORCHESTRATOR"
}
buildFeatures.viewBinding = true
packagingOptions {
exclude("META-INF/*.kotlin_module")
exclude("META-INF/AL2.0")