Fixes the hyperlink confirmation settings

The setting was saved and read from different preferences. Fixed
by introducing an account settings repo, to have a single place
of accessing those and hide the shared prefs implementation detail.

MAILAND-2843
This commit is contained in:
Maciej Surmacz 2022-04-11 10:26:12 +02:00
parent 3a66cce7ab
commit 1e4f970d8c
11 changed files with 221 additions and 21 deletions

View File

@ -26,6 +26,7 @@ import android.webkit.WebView
import androidx.test.filters.SmallTest
import ch.protonmail.android.activities.composeMessage.ComposeMessageActivity
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.settings.data.AccountSettingsRepository
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
@ -51,10 +52,12 @@ class PmWebViewClientTest {
private val mockContext: Context = mockk(relaxed = true)
private val accountSettingsRepository: AccountSettingsRepository = mockk()
private val loadRemote = false
private val webViewClient = PmWebViewClient(
userManager, activity, loadRemote
userManager, accountSettingsRepository, activity, loadRemote
)
@Test
@ -77,7 +80,8 @@ class PmWebViewClientTest {
@Test
fun shouldOverrideUrlLoadingStartsComposeMessageActivityWhenAMailToLinkWithAllDetailsIsLoaded() {
// given
val url = """mailto:marino-test@protonmail.com?cc=marino-test-1@protonmail.com&bcc=test12345@gmail.com&subject=The%20subject%20of%20the%20email&body=The%20body%20of%20the%20email"""
val url =
"""mailto:marino-test@protonmail.com?cc=marino-test-1@protonmail.com&bcc=test12345@gmail.com&subject=The%20subject%20of%20the%20email&body=The%20body%20of%20the%20email"""
val expected = Intent(mockContext, ComposeMessageActivity::class.java)
.putExtra(EXTRA_TO_RECIPIENTS, arrayOf("marino-test@protonmail.com"))
.putExtra(EXTRA_CC_RECIPIENTS, arrayOf("marino-test-1@protonmail.com"))

View File

@ -25,22 +25,23 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import androidx.lifecycle.lifecycleScope
import ch.protonmail.android.R
import ch.protonmail.android.activities.settings.BaseSettingsActivity
import ch.protonmail.android.activities.settings.SettingsEnum
import ch.protonmail.android.core.Constants.Prefs.PREF_HYPERLINK_CONFIRM
import ch.protonmail.android.events.SettingsChangedEvent
import ch.protonmail.android.jobs.UpdateSettingsJob
import ch.protonmail.android.prefs.SecureSharedPreferences
import ch.protonmail.android.security.domain.usecase.GetIsPreventTakingScreenshots
import ch.protonmail.android.security.domain.usecase.SavePreventTakingScreenshots
import ch.protonmail.android.settings.data.AccountSettingsRepository
import ch.protonmail.android.uiModel.SettingsItemUiModel
import ch.protonmail.android.utils.extensions.showToast
import com.google.gson.Gson
import com.squareup.otto.Subscribe
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.activity_edit_settings_item.*
import me.proton.core.util.android.sharedpreferences.set
import kotlinx.coroutines.launch
import javax.inject.Inject
// region constants
@ -67,6 +68,9 @@ class EditSettingsItemActivity : BaseSettingsActivity() {
@Inject
lateinit var getIsPreventTakingScreenshots: GetIsPreventTakingScreenshots
@Inject
lateinit var accountSettingsRepository: AccountSettingsRepository
private val mailSettings by lazy {
checkNotNull(userManager.getCurrentUserMailSettingsBlocking())
}
@ -138,15 +142,19 @@ class EditSettingsItemActivity : BaseSettingsActivity() {
SettingsEnum.BACKGROUND_REFRESH,
if (mBackgroundSyncValue) getString(R.string.enabled) else getString(R.string.disabled)
)
setEnabled(
SettingsEnum.LINK_CONFIRMATION,
preferences!!.getBoolean(PREF_HYPERLINK_CONFIRM, true)
)
lifecycleScope.launch {
setEnabled(
SettingsEnum.LINK_CONFIRMATION,
accountSettingsRepository.getShouldShowLinkConfirmationSetting(user.id)
)
}
setToggleListener(SettingsEnum.LINK_CONFIRMATION) { view: View, isChecked: Boolean ->
val prefs = checkNotNull(preferences)
if (view.isPressed && isChecked != prefs.getBoolean(PREF_HYPERLINK_CONFIRM, true)) {
prefs[PREF_HYPERLINK_CONFIRM] = isChecked
lifecycleScope.launch {
if (view.isPressed && isChecked != accountSettingsRepository.getShouldShowLinkConfirmationSetting(user.id)) {
accountSettingsRepository.saveShouldShowLinkConfirmationSetting(isChecked, user.id)
}
}
}

View File

@ -139,6 +139,7 @@ import ch.protonmail.android.events.Status;
import ch.protonmail.android.events.contacts.SendPreferencesEvent;
import ch.protonmail.android.feature.account.AccountManagerKt;
import ch.protonmail.android.jobs.contacts.GetSendPreferenceJob;
import ch.protonmail.android.settings.data.AccountSettingsRepository;
import ch.protonmail.android.tasks.EmbeddedImagesThread;
import ch.protonmail.android.ui.view.ComposerBottomAppBar;
import ch.protonmail.android.usecase.model.FetchPublicKeysRequest;
@ -258,6 +259,9 @@ public class ComposeMessageActivity
@Inject
RenderDimensionsProvider renderDimensionsProvider;
@Inject
AccountSettingsRepository accountSettingsRepository;
String composerInstanceId;
Menu menu;
@ -387,7 +391,7 @@ public class ComposeMessageActivity
private void setUpQuotedMessageWebView() {
quotedMessageWebView = new WebView(this);
pmWebViewClient = new PmWebViewClient(mUserManager, this, true);
pmWebViewClient = new PmWebViewClient(mUserManager, accountSettingsRepository, this, true);
quotedMessageWebView.setWebViewClient(pmWebViewClient);
quotedMessageWebView.requestDisallowInterceptTouchEvent(true);

View File

@ -58,6 +58,7 @@ import ch.protonmail.android.details.presentation.model.MessageDetailsListItem
import ch.protonmail.android.details.presentation.ui.MessageDetailsActivity
import ch.protonmail.android.details.presentation.view.MessageDetailsActionsView
import ch.protonmail.android.labels.domain.model.Label
import ch.protonmail.android.settings.data.AccountSettingsRepository
import ch.protonmail.android.ui.model.LabelChipUiModel
import ch.protonmail.android.util.ProtonCalendarUtil
import ch.protonmail.android.utils.redirectToChrome
@ -89,6 +90,7 @@ internal class MessageDetailsAdapter(
private val messageDetailsRecyclerView: RecyclerView,
private val messageToMessageDetailsListItemMapper: MessageToMessageDetailsListItemMapper,
private val userManager: UserManager,
private val accountSettingsRepository: AccountSettingsRepository,
private val messageEncryptionUiModelMapper: MessageEncryptionUiModelMapper,
private val setUpWebViewDarkModeHandlingIfSupported: SetUpWebViewDarkModeHandlingIfSupported,
private val protonCalendarUtil: ProtonCalendarUtil,
@ -216,7 +218,9 @@ internal class MessageDetailsAdapter(
}
webView.id = R.id.item_message_body_web_view_id
val webViewClient = MessageDetailsPmWebViewClient(userManager, context, itemView, shouldShowRemoteImages())
val webViewClient = MessageDetailsPmWebViewClient(
userManager, accountSettingsRepository, context, itemView, shouldShowRemoteImages()
)
configureWebView(webView, webViewClient)
setUpScrollListener(webView, itemView.messageWebViewContainer)
@ -469,10 +473,11 @@ internal class MessageDetailsAdapter(
private class MessageDetailsPmWebViewClient(
userManager: UserManager,
accountSettingsRepository: AccountSettingsRepository,
activity: Activity,
private val itemView: View,
private val isAutoShowRemoteImages: Boolean
) : PmWebViewClient(userManager, activity, isAutoShowRemoteImages) {
) : PmWebViewClient(userManager, accountSettingsRepository, activity, isAutoShowRemoteImages) {
override fun onPageFinished(view: WebView, url: String) {
if (amountOfRemoteResourcesBlocked() > 0) {

View File

@ -105,7 +105,6 @@ object Constants {
const val PREF_PREVIOUS_APP_VERSION = "previousAppVersion"
const val PREF_PM_ADDRESS_CHANGED = "pmAddressChanged"
const val PREF_HYPERLINK_CONFIRM = "confirmHyperlinks"
const val PREF_NEW_USER_ONBOARDING_SHOWN = "new_user_onboarding_shown"
const val PREF_EXISTING_USER_ONBOARDING_SHOWN = "existing_user_onboarding_shown"

View File

@ -66,6 +66,7 @@ import ch.protonmail.android.jobs.PostSpamJob
import ch.protonmail.android.labels.domain.model.LabelId
import ch.protonmail.android.labels.domain.model.LabelType
import ch.protonmail.android.labels.presentation.ui.LabelsActionSheet
import ch.protonmail.android.settings.data.AccountSettingsRepository
import ch.protonmail.android.ui.actionsheet.MessageActionSheet
import ch.protonmail.android.ui.actionsheet.model.ActionSheetTarget
import ch.protonmail.android.util.ProtonCalendarUtil
@ -115,10 +116,12 @@ internal class MessageDetailsActivity : BaseStoragePermissionActivity() {
@Inject
lateinit var setUpWebViewDarkModeHandlingIfSupported: SetUpWebViewDarkModeHandlingIfSupported
@Inject
lateinit var protonCalendarUtil: ProtonCalendarUtil
@Inject
lateinit var accountSettingsRepository: AccountSettingsRepository
private lateinit var messageOrConversationId: String
private lateinit var messageExpandableAdapter: MessageDetailsAdapter
private lateinit var primaryBaseActivity: Context
@ -256,6 +259,7 @@ internal class MessageDetailsActivity : BaseStoragePermissionActivity() {
messageDetailsRecyclerView = messageDetailsRecyclerView,
messageToMessageDetailsListItemMapper = messageToMessageDetailsListItemMapper,
userManager = mUserManager,
accountSettingsRepository = accountSettingsRepository,
messageEncryptionUiModelMapper = messageEncryptionUiModelMapper,
setUpWebViewDarkModeHandlingIfSupported = setUpWebViewDarkModeHandlingIfSupported,
onLoadEmbeddedImagesClicked = ::onLoadEmbeddedImagesClicked,

View File

@ -31,6 +31,8 @@ import ch.protonmail.android.mailbox.data.ConversationsRepositoryImpl
import ch.protonmail.android.mailbox.domain.ConversationsRepository
import ch.protonmail.android.notifications.data.NotificationRepositoryImpl
import ch.protonmail.android.notifications.domain.NotificationRepository
import ch.protonmail.android.settings.data.AccountSettingsRepository
import ch.protonmail.android.settings.data.SharedPreferencesAccountSettingsRepository
import ch.protonmail.android.settings.data.SharedPreferencesDeviceSettingsRepository
import ch.protonmail.android.settings.domain.DeviceSettingsRepository
import dagger.Binds
@ -51,6 +53,9 @@ internal interface ApplicationBindsModule {
@Binds
fun SharedPreferencesDeviceSettingsRepository.deviceSettingsRepository(): DeviceSettingsRepository
@Binds
fun SharedPreferencesAccountSettingsRepository.accountSettingsRepository(): AccountSettingsRepository
@Binds
fun provideLabelRepository(repo: LabelRepositoryImpl): LabelRepository

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2022 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.settings.data
import me.proton.core.domain.entity.UserId
interface AccountSettingsRepository {
suspend fun getShouldShowLinkConfirmationSetting(userId: UserId): Boolean
suspend fun saveShouldShowLinkConfirmationSetting(
shouldShowHyperlinkConfirmation: Boolean,
userId: UserId
)
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2022 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.settings.data
import ch.protonmail.android.prefs.SecureSharedPreferences
import kotlinx.coroutines.withContext
import me.proton.core.domain.entity.UserId
import me.proton.core.util.android.sharedpreferences.set
import me.proton.core.util.kotlin.DispatcherProvider
import javax.inject.Inject
private const val PREF_HYPERLINK_CONFIRM = "confirmHyperlinks"
class SharedPreferencesAccountSettingsRepository @Inject constructor(
private val secureSharedPreferencesFactory: SecureSharedPreferences.Factory,
private val dispatchers: DispatcherProvider
) : AccountSettingsRepository {
override suspend fun getShouldShowLinkConfirmationSetting(userId: UserId): Boolean = withContext(dispatchers.Io) {
secureSharedPreferencesFactory.userPreferences(userId)
.getBoolean(PREF_HYPERLINK_CONFIRM, true)
}
override suspend fun saveShouldShowLinkConfirmationSetting(
shouldShowHyperlinkConfirmation: Boolean,
userId: UserId
) {
withContext(dispatchers.Io) {
secureSharedPreferencesFactory.userPreferences(userId)[PREF_HYPERLINK_CONFIRM] =
shouldShowHyperlinkConfirmation
}
}
}

View File

@ -23,7 +23,6 @@ import android.content.Context
import android.content.Intent
import android.net.MailTo
import android.net.Uri
import android.preference.PreferenceManager
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
@ -33,9 +32,11 @@ import ch.protonmail.android.activities.composeMessage.ComposeMessageActivity
import ch.protonmail.android.core.Constants
import ch.protonmail.android.core.Constants.DUMMY_URL_PREFIX
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.settings.data.AccountSettingsRepository
import ch.protonmail.android.utils.MessageUtils.addRecipientsToIntent
import ch.protonmail.android.utils.ui.dialogs.DialogUtils.Companion.showInfoDialogWithTwoButtonsAndCheckbox
import ch.protonmail.android.utils.ui.dialogs.DialogUtils.Companion.showTwoButtonInfoDialog
import kotlinx.coroutines.runBlocking
import me.proton.core.presentation.utils.showToast
import me.proton.core.util.kotlin.startsWith
import java.io.ByteArrayInputStream
@ -45,6 +46,7 @@ import java.util.Locale
open class PmWebViewClient(
private val userManager: UserManager,
private val accountSettingsRepository: AccountSettingsRepository,
private val activity: Activity,
private var shouldLoadRemoteContent: Boolean
) : WebViewClient() {
@ -143,8 +145,10 @@ open class PmWebViewClient(
} catch (e: MalformedURLException) {
e.printStackTrace()
}
val doesRequireHyperlinkConfirmation = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(Constants.Prefs.PREF_HYPERLINK_CONFIRM, true)
val doesRequireHyperlinkConfirmation = runBlocking {
accountSettingsRepository
.getShouldShowLinkConfirmationSetting(userManager.requireCurrentUserId())
}
return when {
isPhishingMessage -> {
@ -181,8 +185,12 @@ open class PmWebViewClient(
activity.startActivity(intent)
},
checkedListener = { isChecked ->
PreferenceManager.getDefaultSharedPreferences(activity).edit()
.putBoolean(Constants.Prefs.PREF_HYPERLINK_CONFIRM, isChecked.not()).apply()
runBlocking {
accountSettingsRepository.saveShouldShowLinkConfirmationSetting(
shouldShowHyperlinkConfirmation = isChecked.not(),
userId = userManager.requireCurrentUserId()
)
}
},
cancelable = true
)

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2022 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.settings.data
import android.content.SharedPreferences
import ch.protonmail.android.prefs.SecureSharedPreferences
import ch.protonmail.android.testdata.UserIdTestData
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.test.runBlockingTest
import me.proton.core.test.kotlin.TestDispatcherProvider
import org.junit.Test
import kotlin.test.assertEquals
internal class SharedPreferencesAccountSettingsRepositoryTest {
private val userSharedPreferencesEditorMock = mockk<SharedPreferences.Editor>(relaxUnitFun = true) {
every { putBoolean(any(), any()) } returns this
}
private val userPreferencesMock = mockk<SharedPreferences> {
every { edit() } returns userSharedPreferencesEditorMock
}
private val secureSharedPreferencesFactoryMock = mockk<SecureSharedPreferences.Factory> {
every { userPreferences(UserIdTestData.userId) } returns userPreferencesMock
}
private val sharedPreferencesAccountSettingsRepository = SharedPreferencesAccountSettingsRepository(
secureSharedPreferencesFactoryMock,
TestDispatcherProvider
)
@Test
fun `should get the show link confirmation setting`() = runBlockingTest {
// given
val expectedSettingValue = true
every { userPreferencesMock.getBoolean(PREF_HYPERLINK_CONFIRM, true) } returns expectedSettingValue
// when
val actualSettingValue = sharedPreferencesAccountSettingsRepository
.getShouldShowLinkConfirmationSetting(UserIdTestData.userId)
// then
assertEquals(expectedSettingValue, actualSettingValue)
}
@Test
fun `should save the show link confirmation setting`() = runBlockingTest {
// given
val expectedSettingValue = false
// when
sharedPreferencesAccountSettingsRepository
.saveShouldShowLinkConfirmationSetting(expectedSettingValue, UserIdTestData.userId)
// then
verify { userSharedPreferencesEditorMock.putBoolean(PREF_HYPERLINK_CONFIRM, expectedSettingValue) }
verify { userSharedPreferencesEditorMock.apply() }
}
private companion object TestData {
const val PREF_HYPERLINK_CONFIRM = "confirmHyperlinks"
}
}