Removed SplashActivity.

MailboxActivity is now the main launcher Activity, and support null current userId.
This commit is contained in:
Neil Marietta 2021-11-04 12:13:24 +01:00 committed by Zorica Stojchevska
parent 57553dceca
commit e3550fd8a8
26 changed files with 317 additions and 312 deletions

View File

@ -140,11 +140,15 @@
<activity android:name=".util.HiltViewTestActivity"/>
<!-- Begin - Root Activities -->
<activity
<activity-alias
android:name=".activities.SplashActivity"
android:targetActivity=".mailbox.presentation.MailboxActivity"/>
<activity
android:name=".mailbox.presentation.MailboxActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:screenOrientation="portrait"
android:launchMode="singleInstance"
android:launchMode="standard"
android:maxRecents="1"
android:theme="@style/ProtonTheme.Splash.Mail.Light">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -154,12 +158,6 @@
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
android:name=".mailbox.presentation.MailboxActivity"
android:exported="false"
android:configChanges="orientation|screenSize"
android:maxRecents="1"
android:theme="@style/ProtonTheme.Splash.Mail.Light" />
<!-- End - Root Activities -->
<activity
android:name=".settings.presentation.AttachmentStorageActivity"

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2020 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.activities
import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
import ch.protonmail.android.core.Constants
import ch.protonmail.android.details.presentation.MessageDetailsActivity
import ch.protonmail.android.utils.AppUtil
class StartMessageDetails : ActivityResultContract<StartMessageDetails.Input, Unit?>() {
data class Input(
val messageId: String,
val locationType: Constants.MessageLocationType,
val labelId: String?,
val messageSubject: String
)
override fun createIntent(context: Context, input: Input): Intent =
AppUtil.decorInAppIntent(Intent(context, MessageDetailsActivity::class.java)).apply {
putExtra(MessageDetailsActivity.EXTRA_MESSAGE_OR_CONVERSATION_ID, input.messageId)
putExtra(MessageDetailsActivity.EXTRA_MESSAGE_LOCATION_ID, input.locationType.messageLocationTypeValue)
putExtra(MessageDetailsActivity.EXTRA_MAILBOX_LABEL_ID, input.labelId)
putExtra(MessageDetailsActivity.EXTRA_MESSAGE_SUBJECT, input.messageSubject)
}
override fun parseResult(resultCode: Int, result: Intent?): Unit? {
if (resultCode != Activity.RESULT_OK) return null
return Unit
}
}

View File

@ -205,7 +205,7 @@ public abstract class BaseActivity extends AppCompatActivity implements INetwork
humanVerificationOrchestrator.register(this, false);
accountStateManager.setHumanVerificationOrchestrator(humanVerificationOrchestrator);
accountStateManager.observeHumanVerificationStateWithExternalLifecycle(getLifecycle());
accountStateManager.observeHVStateWithExternalLifecycle(getLifecycle());
}
@Override

View File

@ -38,7 +38,6 @@ import ch.protonmail.android.R
import ch.protonmail.android.activities.settings.EXTRA_CURRENT_MAILBOX_LABEL_ID
import ch.protonmail.android.activities.settings.EXTRA_CURRENT_MAILBOX_LOCATION
import ch.protonmail.android.api.AccountManager
import ch.protonmail.android.api.local.SnoozeSettings
import ch.protonmail.android.api.models.DatabaseProvider
import ch.protonmail.android.api.segments.event.AlarmReceiver
import ch.protonmail.android.contacts.ContactsActivity
@ -54,7 +53,6 @@ import ch.protonmail.android.feature.account.AccountStateManager
import ch.protonmail.android.labels.domain.model.LabelType
import ch.protonmail.android.labels.presentation.EXTRA_MANAGE_FOLDERS
import ch.protonmail.android.labels.presentation.LabelsManagerActivity
import ch.protonmail.android.prefs.SecureSharedPreferences
import ch.protonmail.android.servers.notification.EXTRA_USER_ID
import ch.protonmail.android.settings.pin.EXTRA_FRAGMENT_TITLE
import ch.protonmail.android.settings.pin.ValidatePinActivity
@ -62,21 +60,18 @@ import ch.protonmail.android.utils.AppUtil
import ch.protonmail.android.utils.UiUtil
import ch.protonmail.android.utils.extensions.app
import ch.protonmail.android.utils.extensions.setDrawBehindSystemBars
import ch.protonmail.android.utils.resettableManager
import ch.protonmail.android.utils.startSplashActivity
import ch.protonmail.android.utils.ui.dialogs.DialogUtils.Companion.showTwoButtonInfoDialog
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import me.proton.core.accountmanager.presentation.view.AccountPrimaryView
import me.proton.core.accountmanager.presentation.viewmodel.AccountSwitcherViewModel
import me.proton.core.auth.presentation.AuthOrchestrator
import me.proton.core.domain.entity.UserId
import me.proton.core.presentation.utils.setDarkStatusBar
import me.proton.core.presentation.utils.setLightStatusBar
import java.util.Calendar
import javax.inject.Inject
// region constants
@ -109,14 +104,9 @@ internal abstract class NavigationActivity : BaseActivity() {
private lateinit var drawerToggle: ActionBarDrawerToggle
// endregion
val lazyManager = resettableManager()
@Inject
lateinit var accountManager: AccountManager
@Inject
lateinit var authOrchestrator: AuthOrchestrator
@Inject
lateinit var databaseProvider: DatabaseProvider
@ -129,7 +119,6 @@ internal abstract class NavigationActivity : BaseActivity() {
private val accountSwitcherViewModel by viewModels<AccountSwitcherViewModel>()
protected abstract val currentMailboxLocation: Constants.MessageLocationType
protected abstract val currentLabelId: String?
/**
@ -152,6 +141,13 @@ internal abstract class NavigationActivity : BaseActivity() {
}
}
protected open fun onPrimaryUserId(userId: UserId) {
app.startJobManager()
mJobManager.addJobInBackground(FetchUpdatesJob())
val alarmReceiver = AlarmReceiver()
alarmReceiver.setAlarm(this)
}
protected abstract fun onInbox(type: Constants.DrawerOptionType)
protected abstract fun onOtherMailBox(type: Constants.DrawerOptionType)
@ -174,30 +170,36 @@ internal abstract class NavigationActivity : BaseActivity() {
}
super.onCreate(savedInstanceState)
authOrchestrator.register(this)
with(accountStateManager) {
setAuthOrchestrator(authOrchestrator)
observeAccountStateWithExternalLifecycle(lifecycle)
register(this@NavigationActivity)
// Start Splash on AccountNeeded.
state
.flowWithLifecycle(lifecycle, Lifecycle.State.CREATED)
.onEach {
when (it) {
AccountStateManager.State.Processing,
AccountStateManager.State.PrimaryExist ->
Unit
AccountStateManager.State.AccountNeeded -> {
startSplashActivity()
finishAndRemoveTask()
}
AccountStateManager.State.PrimaryExist -> Unit
AccountStateManager.State.AccountNeeded -> addAccount()
}
}.launchIn(lifecycleScope)
onAddAccountClosed {
if (userManager.currentUserId == null) {
finish()
}
}
onAccountSwitched()
.flowWithLifecycle(lifecycle, Lifecycle.State.CREATED)
.onEach { switch -> onAccountSwitched(switch) }
.launchIn(lifecycleScope)
getPrimaryUserId().filterNotNull()
.flowWithLifecycle(lifecycle, Lifecycle.State.CREATED)
.onEach { userId -> onPrimaryUserId(userId) }
.launchIn(lifecycleScope)
}
accountPrimaryView.setViewModel(accountSwitcherViewModel)
@ -269,18 +271,12 @@ internal abstract class NavigationActivity : BaseActivity() {
setUpDrawer()
}
override fun onDestroy() {
authOrchestrator.unregister()
super.onDestroy()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
}
override fun onResume() {
accountStateManager.setAuthOrchestrator(authOrchestrator)
super.onResume()
if (SHOULD_DRAW_DRAWER_BEHIND_SYSTEM_BARS)
if (resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES) {
@ -290,10 +286,6 @@ internal abstract class NavigationActivity : BaseActivity() {
}
checkUserId()
app.startJobManager()
val alarmReceiver = AlarmReceiver()
alarmReceiver.setAlarm(this)
closeDrawerAndDialog()
}
@ -311,11 +303,11 @@ internal abstract class NavigationActivity : BaseActivity() {
private fun checkUserId() {
// Requested UserId match the current ?
intent.extras?.getString(EXTRA_USER_ID)?.let { extraUserId ->
intent.extras?.remove(EXTRA_USER_ID)
val requestedUserId = UserId(extraUserId)
if (requestedUserId != accountStateManager.getPrimaryUserId().value) {
accountStateManager.switch(requestedUserId)
}
intent.extras?.remove(EXTRA_USER_ID)
}
}
@ -372,16 +364,6 @@ internal abstract class NavigationActivity : BaseActivity() {
})
}
private suspend fun areNotificationsSnoozed(userId: UserId): Boolean {
val userPreferences = SecureSharedPreferences.getPrefsForUser(this, userId)
with(SnoozeSettings.load(userPreferences)) {
val shouldShowNotification = !shouldSuppressNotification(Calendar.getInstance())
val isQuickSnoozeEnabled = snoozeQuick
val isScheduledSnoozeEnabled = getScheduledSnooze(userPreferences)
return isQuickSnoozeEnabled || (isScheduledSnoozeEnabled && !shouldShowNotification)
}
}
private fun setUpInitialDrawerItems(isPinEnabled: Boolean) {
val hasPin = isPinEnabled && userManager.getMailboxPin() != null

View File

@ -1,87 +0,0 @@
/*
* Copyright (c) 2020 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.activities
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import ch.protonmail.android.R
import ch.protonmail.android.feature.account.AccountStateManager
import ch.protonmail.android.utils.startMailboxActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.proton.core.auth.presentation.AuthOrchestrator
import javax.inject.Inject
@AndroidEntryPoint
internal class SplashActivity : AppCompatActivity() {
@Inject
lateinit var accountStateManager: AccountStateManager
@Inject
lateinit var authOrchestrator: AuthOrchestrator
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
authOrchestrator.register(this)
with(accountStateManager) {
setAuthOrchestrator(authOrchestrator)
observeAccountStateWithExternalLifecycle(lifecycle, isSplashActivity = true)
// Start Login or MailboxActivity.
state
.flowWithLifecycle(lifecycle, Lifecycle.State.CREATED)
.onEach {
when (it) {
AccountStateManager.State.Processing ->
Unit
AccountStateManager.State.AccountNeeded ->
addAccount()
AccountStateManager.State.PrimaryExist -> {
delay(resources.getInteger(R.integer.splash_transition_millis).toLong())
startMailboxActivity()
overridePendingTransition(0, 0)
finishAndRemoveTask()
}
}
}.launchIn(lifecycleScope)
// Finish if AddAccount closed.
onAddAccountClosed {
finishAndRemoveTask()
}
}
}
override fun onResume() {
super.onResume()
accountStateManager.setAuthOrchestrator(authOrchestrator)
}
override fun onDestroy() {
authOrchestrator.unregister()
super.onDestroy()
}
}

View File

@ -47,6 +47,9 @@ class DatabaseProvider @Inject constructor(
AttachmentMetadataDatabase.getInstance(context, userId).getDao()
// Contact
fun provideContactDatabase(userId: UserId): ContactDatabase =
ContactDatabase.getInstance(context, userId)
fun provideContactDao(userId: UserId): ContactDao =
ContactDatabase.getInstance(context, userId).getDao()

View File

@ -102,7 +102,6 @@ internal class EventHandler @AssistedInject constructor(
@AssistedInject.Factory
interface AssistedFactory {
fun create(userId: UserId): EventHandler
}

View File

@ -58,7 +58,6 @@ class ComposeMessageRepository @Inject constructor(
val jobManager: JobManager,
val api: ProtonMailApiManager,
val databaseProvider: DatabaseProvider,
private var messageDao: MessageDao,
private val messageDetailsRepository: MessageDetailsRepository,
private val accountManager: AccountManager,
private val userManager: UserManager,
@ -68,9 +67,11 @@ class ComposeMessageRepository @Inject constructor(
val lazyManager = resettableManager()
private val contactDao by resettableLazy(lazyManager) {
databaseProvider.provideContactDao(userManager.requireCurrentUserId())
}
private val messageDao: MessageDao
get() = databaseProvider.provideMessageDao(userManager.requireCurrentUserId())
private val contactDao: ContactDao
get() = databaseProvider.provideContactDao(userManager.requireCurrentUserId())
private val contactDaos: HashMap<UserId, ContactDao> by resettableLazy(lazyManager) {
val userIds = accountManager.allLoggedInBlocking()
@ -81,13 +82,6 @@ class ComposeMessageRepository @Inject constructor(
listOfDaos
}
/**
* Reloads all statically required dependencies when currently active user changes.
*/
fun reloadDependenciesForUser(userId: UserId) {
messageDao = databaseProvider.provideMessageDao(userId)
}
fun getContactGroupsFromDB(userId: UserId, combinedContacts: Boolean): Flow<List<ContactLabelUiModel>> {
return labelRepository.observeContactGroups(userId)
.map { list ->

View File

@ -259,7 +259,6 @@ class ComposeMessageViewModel @Inject constructor(
fun init(processor: HtmlProcessor) {
htmlProcessor = processor
composeMessageRepository.lazyManager.reset()
composeMessageRepository.reloadDependenciesForUser(userId)
getSenderEmailAddresses()
// if the user is free user, then we do not fetch contact groups and announce the setup is complete
if (!user.isPaidUser) {

View File

@ -89,7 +89,6 @@ import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
import kotlin.math.abs
private const val TITLE_ANIMATION_THRESHOLD = 0.9
private const val TITLE_ANIMATION_DURATION = 200L
private const val ONE_HUNDRED_PERCENT = 1.0

View File

@ -19,6 +19,8 @@
package ch.protonmail.android.di
import android.content.Context
import ch.protonmail.android.feature.account.SetupAccountUserCheck
import ch.protonmail.android.prefs.SecureSharedPreferences
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -29,7 +31,6 @@ import me.proton.core.auth.data.repository.AuthRepositoryImpl
import me.proton.core.auth.domain.repository.AuthRepository
import me.proton.core.auth.domain.usecase.SetupAccountCheck
import me.proton.core.auth.presentation.AuthOrchestrator
import me.proton.core.auth.presentation.DefaultUserCheck
import me.proton.core.crypto.android.srp.GOpenPGPSrpCrypto
import me.proton.core.crypto.common.srp.SrpCrypto
import me.proton.core.network.data.ApiProvider
@ -59,6 +60,7 @@ object CoreAuthModule {
fun provideUserCheck(
@ApplicationContext context: Context,
accountManager: AccountManager,
userManager: UserManager
): SetupAccountCheck.UserCheck = DefaultUserCheck(context, accountManager, userManager)
userManager: UserManager,
factory: SecureSharedPreferences.Factory
): SetupAccountCheck.UserCheck = SetupAccountUserCheck(context, accountManager, userManager, factory)
}

View File

@ -19,6 +19,7 @@
package ch.protonmail.android.feature.account
import androidx.activity.ComponentActivity
import androidx.core.content.edit
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
@ -87,6 +88,7 @@ internal class AccountStateManager @Inject constructor(
private val userManager: UserManager,
private val eventManager: EventManager,
private val jobManager: JobManager,
private val authOrchestrator: AuthOrchestrator,
private val humanVerificationManager: HumanVerificationManager,
private val oldUserManager: ch.protonmail.android.core.UserManager,
private val launchInitialDataFetch: LaunchInitialDataFetch,
@ -101,7 +103,6 @@ internal class AccountStateManager @Inject constructor(
private val scope = lifecycleOwner.lifecycleScope
private val lifecycle = lifecycleOwner.lifecycle
private lateinit var currentAuthOrchestrator: AuthOrchestrator
private lateinit var currentHumanVerificationOrchestrator: HumanVerificationOrchestrator
private val mutableStateFlow = MutableStateFlow(State.Processing)
@ -129,6 +130,8 @@ internal class AccountStateManager @Inject constructor(
}.launchIn(scope)
}
private suspend fun getAccountOrNull(userId: UserId) = getAccount(userId).firstOrNull()
private fun observeAccountManager(lifecycle: Lifecycle): AccountManagerObserver =
accountManager.observe(lifecycle, Lifecycle.State.CREATED)
@ -154,44 +157,36 @@ internal class AccountStateManager @Inject constructor(
*
* For example, SecondFactor Workflow, TwoPassMode Workflow or ChooseAddress Workflow.
*/
fun observeAccountStateWithExternalLifecycle(
lifecycle: Lifecycle,
isSplashActivity: Boolean = false
) {
// Don't start workflow if MailboxActivity will do it later.
fun shouldStart() = !isSplashActivity || state.value != State.PrimaryExist
private fun observeAccountStateWithExternalLifecycle(lifecycle: Lifecycle) {
observeAccountManager(lifecycle)
.onSessionSecondFactorNeeded { if (shouldStart()) currentAuthOrchestrator.startSecondFactorWorkflow(it) }
.onAccountTwoPassModeNeeded { if (shouldStart()) currentAuthOrchestrator.startTwoPassModeWorkflow(it) }
.onAccountCreateAddressNeeded { if (shouldStart()) currentAuthOrchestrator.startChooseAddressWorkflow(it) }
.onSessionSecondFactorNeeded { authOrchestrator.startSecondFactorWorkflow(it) }
.onAccountTwoPassModeNeeded { authOrchestrator.startTwoPassModeWorkflow(it) }
.onAccountCreateAddressNeeded { authOrchestrator.startChooseAddressWorkflow(it) }
}
/**
* Observe all human verification states that can be solved with [HumanVerificationOrchestrator].
*/
fun observeHumanVerificationStateWithExternalLifecycle(lifecycle: Lifecycle) {
fun observeHVStateWithExternalLifecycle(lifecycle: Lifecycle) {
observeHumanVerificationManager(lifecycle)
.onHumanVerificationNeeded { currentHumanVerificationOrchestrator.startHumanVerificationWorkflow(it) }
}
fun setAuthOrchestrator(authOrchestrator: AuthOrchestrator) {
currentAuthOrchestrator = authOrchestrator
}
fun setHumanVerificationOrchestrator(
humanVerificationOrchestrator: HumanVerificationOrchestrator
) {
fun setHumanVerificationOrchestrator(humanVerificationOrchestrator: HumanVerificationOrchestrator) {
currentHumanVerificationOrchestrator = humanVerificationOrchestrator
}
fun register(context: ComponentActivity) {
authOrchestrator.register(context)
observeAccountStateWithExternalLifecycle(context.lifecycle)
}
fun onAddAccountClosed(block: () -> Unit) {
currentAuthOrchestrator.onAddAccountResult { result -> if (result == null) block() }
authOrchestrator.onAddAccountResult { result -> if (result == null) block() }
}
fun getAccount(userId: UserId) = accountManager.getAccount(userId)
suspend fun getAccountOrNull(userId: UserId) = getAccount(userId).firstOrNull()
fun signOut(userId: UserId) = scope.launch {
accountManager.disableAccount(userId)
}
@ -202,14 +197,14 @@ internal class AccountStateManager @Inject constructor(
fun signIn(userId: UserId? = null) = scope.launch {
val account = userId?.let { getAccountOrNull(it) }
currentAuthOrchestrator.startLoginWorkflow(
authOrchestrator.startLoginWorkflow(
requiredAccountType = requiredAccountType,
username = account?.username
)
}
fun addAccount() = scope.launch {
currentAuthOrchestrator.startAddAccountWorkflow(
authOrchestrator.startAddAccountWorkflow(
requiredAccountType = requiredAccountType,
product = product
)
@ -218,7 +213,7 @@ internal class AccountStateManager @Inject constructor(
fun switch(userId: UserId) = scope.launch {
val account = getAccountOrNull(userId) ?: return@launch
when {
account.isDisabled() -> currentAuthOrchestrator.startLoginWorkflow(
account.isDisabled() -> authOrchestrator.startLoginWorkflow(
requiredAccountType = AccountType.Internal,
username = account.username
)
@ -259,11 +254,7 @@ internal class AccountStateManager @Inject constructor(
val prefs = oldUserManager.preferencesFor(userId)
val initialized = prefs.getBoolean(Constants.Prefs.PREF_USER_INITIALIZED, false)
if (!initialized) {
oldUserManager.preferencesFor(userId).edit {
putBoolean(Constants.Prefs.PREF_USER_INITIALIZED, true)
// See DatabaseFactory.usernameForUserId.
putString(Constants.Prefs.PREF_USER_NAME, account.username)
}
prefs.edit { putBoolean(Constants.Prefs.PREF_USER_INITIALIZED, true) }
// Workaround: Wait the primary key passphrase before proceeding.
userManager.waitPrimaryKeyPassphraseAvailable(account.userId)
// Workaround: Make sure this uninitialized User is fresh.
@ -290,9 +281,7 @@ internal class AccountStateManager @Inject constructor(
// Clear Data/State.
clearUserData.invoke(userId)
eventManager.clearState(userId)
oldUserManager.preferencesFor(UserId(account.userId.id)).clearAll(
/*excludedKeys*/ PREF_PIN, Constants.Prefs.PREF_USER_NAME
)
prefs.clearAll(/*excludedKeys*/ PREF_PIN, Constants.Prefs.PREF_USER_NAME)
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2020 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.feature.account
import android.content.Context
import androidx.core.content.edit
import ch.protonmail.android.core.Constants
import ch.protonmail.android.prefs.SecureSharedPreferences
import me.proton.core.accountmanager.domain.AccountManager
import me.proton.core.auth.domain.usecase.SetupAccountCheck
import me.proton.core.auth.presentation.DefaultUserCheck
import me.proton.core.user.domain.UserManager
import me.proton.core.user.domain.entity.User
class SetupAccountUserCheck(
context: Context,
accountManager: AccountManager,
userManager: UserManager,
private val secureSharedPreferencesFactory: SecureSharedPreferences.Factory
) : DefaultUserCheck(context, accountManager, userManager) {
override suspend fun invoke(user: User): SetupAccountCheck.UserCheckResult {
// Workaround: Make sure we have the preference user name by userId.
// See DatabaseFactory.usernameForUserId.
secureSharedPreferencesFactory.userPreferences(user.userId).edit(commit = true) {
putString(Constants.Prefs.PREF_USER_NAME, user.name)
}
return super.invoke(user)
}
}

View File

@ -32,13 +32,11 @@ import androidx.work.WorkerParameters
import androidx.work.workDataOf
import ch.protonmail.android.api.ProtonMailApi
import ch.protonmail.android.api.models.IDList
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.data.local.CounterRepository
import ch.protonmail.android.worker.KEY_WORKER_ERROR_DESCRIPTION
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import me.proton.core.accountmanager.domain.AccountManager
import timber.log.Timber
import java.util.concurrent.CancellationException
import javax.inject.Inject
@ -47,17 +45,17 @@ import javax.inject.Inject
internal class RemoveMessageLabelWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val accountManager: AccountManager,
private val userManager: UserManager,
private val counterRepository: CounterRepository,
private val protonMailApi: ProtonMailApi
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val userId = requireNotNull(userManager.currentUserId)
val messageIds = requireNotNull(inputData.getStringArray(KEY_INPUT_DATA_MESSAGES_IDS)) {
"Cannot continue without message ids!"
}
val labelId = inputData.getString(KEY_INPUT_DATA_LABEL_ID)
val userId = accountManager.getPrimaryUserId().filterNotNull().first()
Timber.v("Remove label $labelId for messages: $messageIds")
if (messageIds.isEmpty() || labelId == null) {

View File

@ -19,9 +19,11 @@
package ch.protonmail.android.mailbox.data
import ch.protonmail.android.api.ProtonMailApiManager
import ch.protonmail.android.api.models.DatabaseProvider
import ch.protonmail.android.api.models.messages.receive.MessageFactory
import ch.protonmail.android.core.Constants
import ch.protonmail.android.core.NetworkConnectivityManager
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.data.ProtonStore
import ch.protonmail.android.data.local.MessageDao
import ch.protonmail.android.data.local.model.Message
@ -88,10 +90,9 @@ private const val MAX_LOCATION_ID_LENGTH = 2
@Suppress("LongParameterList") // Every new parameter adds a new issue and breaks the build
internal class ConversationsRepositoryImpl @Inject constructor(
private val conversationDao: ConversationDao,
private val messageDao: MessageDao,
private val userManager: UserManager,
private val databaseProvider: DatabaseProvider,
private val labelsRepository: LabelRepository,
private val unreadCounterDao: UnreadCounterDao,
private val api: ProtonMailApiManager,
responseToConversationsMapper: ConversationsResponseToConversationsMapper,
private val databaseToConversationMapper: ConversationDatabaseModelToConversationMapper,
@ -109,6 +110,15 @@ internal class ConversationsRepositoryImpl @Inject constructor(
connectivityManager: NetworkConnectivityManager
) : ConversationsRepository {
private val conversationDao: ConversationDao
get() = databaseProvider.provideConversationDao(userManager.requireCurrentUserId())
private val messageDao: MessageDao
get() = databaseProvider.provideMessageDao(userManager.requireCurrentUserId())
private val unreadCounterDao: UnreadCounterDao
get() = databaseProvider.provideUnreadCounterDao(userManager.requireCurrentUserId())
private val refreshUnreadCountersTrigger = MutableSharedFlow<Unit>(replay = 1)
private val allConversationsStore by lazy {

View File

@ -63,6 +63,7 @@ import ch.protonmail.android.activities.EngagementActivity
import ch.protonmail.android.activities.NavigationActivity
import ch.protonmail.android.activities.SearchActivity
import ch.protonmail.android.activities.SettingsItem
import ch.protonmail.android.activities.StartMessageDetails
import ch.protonmail.android.activities.composeMessage.ComposeMessageActivity
import ch.protonmail.android.activities.messageDetails.repository.MessageDetailsRepository
import ch.protonmail.android.activities.settings.SettingsEnum
@ -89,7 +90,6 @@ import ch.protonmail.android.data.local.CounterDatabase
import ch.protonmail.android.data.local.PendingActionDao
import ch.protonmail.android.data.local.PendingActionDatabase
import ch.protonmail.android.data.local.model.Message
import ch.protonmail.android.details.presentation.MessageDetailsActivity
import ch.protonmail.android.di.DefaultSharedPreferences
import ch.protonmail.android.events.FetchLabelsEvent
import ch.protonmail.android.events.MailboxLoadedEvent
@ -99,7 +99,6 @@ import ch.protonmail.android.events.Status
import ch.protonmail.android.fcm.MultiUserFcmTokenManager
import ch.protonmail.android.fcm.RegisterDeviceWorker
import ch.protonmail.android.fcm.model.FirebaseToken
import ch.protonmail.android.feature.account.AccountStateManager
import ch.protonmail.android.jobs.EmptyFolderJob
import ch.protonmail.android.labels.domain.model.Label
import ch.protonmail.android.labels.domain.model.LabelType
@ -133,6 +132,7 @@ import kotlinx.android.synthetic.main.activity_mailbox.screenShotPreventerView
import kotlinx.android.synthetic.main.activity_message_details.*
import kotlinx.android.synthetic.main.navigation_drawer.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@ -172,7 +172,6 @@ internal class MailboxActivity :
@Inject
lateinit var messageDetailsRepositoryFactory: MessageDetailsRepository.AssistedFactory
lateinit var messageDetailsRepository: MessageDetailsRepository
@Inject
lateinit var networkSnackBarUtil: NetworkSnackBarUtil
@ -213,6 +212,8 @@ internal class MailboxActivity :
private var storageLimitApproachingAlertDialog: AlertDialog? = null
private val handler = Handler(Looper.getMainLooper())
private val startMessageDetailsLauncher = registerForActivityResult(StartMessageDetails()) {}
override val currentLabelId get() = mailboxLabelId
override fun getLayoutId(): Int = R.layout.activity_mailbox
@ -221,12 +222,6 @@ internal class MailboxActivity :
setTheme(R.style.ProtonTheme_Mail)
super.onCreate(savedInstanceState)
val userId = userManager.currentUserId ?: return
messageDetailsRepository = messageDetailsRepositoryFactory.create(userId)
counterDao = CounterDatabase.getInstance(this, userId).getDao()
pendingActionDao = PendingActionDatabase.getInstance(this, userId).getDao()
// TODO if we decide to use special flag for switching (and not login), change this
if (intent.getBooleanExtra(EXTRA_FIRST_LOGIN, false)) {
multiUserFcmTokenManager.setTokenUnsentForAllSavedUsersBlocking() // force FCM to re-register
@ -245,7 +240,11 @@ internal class MailboxActivity :
if (extras != null && extras.containsKey(EXTRA_MAILBOX_LOCATION)) {
switchToMailboxLocation(extras.getInt(EXTRA_MAILBOX_LOCATION))
}
startObserving()
startObservingPendingActions()
startObservingUsedSpace()
mailboxViewModel.toastMessageMaxLabelsReached.observe(this) { event: Event<MaxLabelsReached?> ->
val maxLabelsReached = event.getContentIfNotHandled()
if (maxLabelsReached != null) {
@ -260,8 +259,6 @@ internal class MailboxActivity :
mailboxViewModel.hasConnectivity.observe(this, ::onConnectivityEvent)
startObservingUsedSpace()
var actionModeAux: ActionMode? = null
mailboxAdapter = MailboxRecyclerViewAdapter(this) { selectionModeEvent ->
when (selectionModeEvent) {
@ -312,10 +309,11 @@ internal class MailboxActivity :
mailboxAdapter.setItemClick { mailboxUiItem: MailboxUiItem ->
OnMessageClickTask(
WeakReference(this@MailboxActivity),
messageDetailsRepository,
messageDetailsRepositoryFactory,
mailboxUiItem.itemId,
mailboxUiItem.subject,
currentMailboxLocation.messageLocationTypeValue
currentMailboxLocation,
userManager.requireCurrentUserId()
).execute()
}
@ -403,9 +401,22 @@ internal class MailboxActivity :
}
}
private fun startObservingPendingActions() {
val owner = this
mailboxViewModel.run {
pendingSendsLiveData.removeObservers(owner)
pendingUploadsLiveData.removeObservers(owner)
pendingSendsLiveData.observe(owner) { mailboxAdapter.setPendingForSendingList(it) }
pendingUploadsLiveData.observe(owner) { mailboxAdapter.setPendingUploadsList(it) }
}
}
private fun startObservingUsedSpace() {
val preferences = SecureSharedPreferences.getPrefsForUser(this, userManager.requireCurrentUserId())
preferences.observe<Long>(PREF_USED_SPACE)
mailboxViewModel.primaryUserId
.flatMapLatest { primaryUserId ->
val preferences = SecureSharedPreferences.getPrefsForUser(this, primaryUserId)
preferences.observe<Long>(PREF_USED_SPACE)
}
.onEach { mailboxViewModel.usedSpaceActionEvent(FLOW_USED_SPACE_CHANGED) }
.launchIn(lifecycleScope)
}
@ -513,39 +524,17 @@ internal class MailboxActivity :
private var firstLogin: Boolean? = null
private fun startObservingPendingActions() {
val owner = this
mailboxViewModel.run {
pendingSendsLiveData.removeObservers(owner)
pendingUploadsLiveData.removeObservers(owner)
reloadDependenciesForUser()
pendingSendsLiveData.observe(owner) { mailboxAdapter.setPendingForSendingList(it) }
pendingUploadsLiveData.observe(owner) { mailboxAdapter.setPendingUploadsList(it) }
}
}
override fun onAccountSwitched(switch: AccountStateManager.AccountSwitch) {
super.onAccountSwitched(switch)
val currentUserId = userManager.currentUserId ?: return
Timber.v("onAccountSwitched, new userId: $currentUserId")
override fun onPrimaryUserId(userId: UserId) {
super.onPrimaryUserId(userId)
mJobManager.start()
counterDao = CounterDatabase.getInstance(this, currentUserId).getDao()
pendingActionDao = PendingActionDatabase.getInstance(this, currentUserId).getDao()
counterDao = CounterDatabase.getInstance(this, userId).getDao()
pendingActionDao = PendingActionDatabase.getInstance(this, userId).getDao()
startObservingPendingActions()
AppUtil.clearNotifications(this, currentUserId)
lazyManager.reset()
setUpDrawer()
checkRegistration()
switchToMailboxLocation(DrawerOptionType.INBOX.drawerOptionTypeValue)
// Account has been switched, so used space changed as well
mailboxViewModel.usedSpaceActionEvent(FLOW_USED_SPACE_CHANGED)
// Observe used space for current account
startObservingUsedSpace()
// manually update the flags for preventing screenshots
if (isPreventingScreenshots || userManager.currentLegacyUser?.isPreventTakingScreenshots == true) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
@ -759,7 +748,7 @@ internal class MailboxActivity :
mailboxViewModel.checkConnectivity()
val mailboxLocation = mailboxViewModel.mailboxLocation.value
if (mailboxLocation == MessageLocationType.INBOX) {
AppUtil.clearNotifications(this, userManager.requireCurrentUserId())
userManager.currentUserId?.let { AppUtil.clearNotifications(this, it) }
}
if (shouldShowSwipeGesturesChangedDialog()) {
@ -1202,7 +1191,7 @@ internal class MailboxActivity :
) {
SetUpNewMessageLocationTask(
WeakReference(this),
messageDetailsRepository,
messageDetailsRepositoryFactory,
labelId,
isFolder,
newLocation,
@ -1273,12 +1262,15 @@ internal class MailboxActivity :
private class OnMessageClickTask internal constructor(
private val mailboxActivity: WeakReference<MailboxActivity>,
private val messageDetailsRepository: MessageDetailsRepository,
private val messageDetailsRepositoryFactory: MessageDetailsRepository.AssistedFactory,
private val messageId: String,
private val messageSubject: String,
private val currentMailboxLocationType: Int
private val currentMailboxLocationType: MessageLocationType,
private val userId: UserId
) : AsyncTask<Unit, Unit, Message>() {
private val messageDetailsRepository = messageDetailsRepositoryFactory.create(userId)
override fun doInBackground(vararg params: Unit): Message? =
messageDetailsRepository.findMessageByIdBlocking(messageId)
@ -1294,19 +1286,14 @@ internal class MailboxActivity :
savedMessage.addressID
).execute()
} else {
val intent = AppUtil.decorInAppIntent(
Intent(
mailboxActivity, MessageDetailsActivity::class.java
mailboxActivity?.startMessageDetailsLauncher?.launch(
StartMessageDetails.Input(
messageId,
currentMailboxLocationType,
mailboxActivity.mailboxLabelId,
messageSubject
)
)
intent.putExtra(MessageDetailsActivity.EXTRA_MESSAGE_OR_CONVERSATION_ID, messageId)
intent.putExtra(
MessageDetailsActivity.EXTRA_MESSAGE_LOCATION_ID,
currentMailboxLocationType
)
intent.putExtra(MessageDetailsActivity.EXTRA_MAILBOX_LABEL_ID, mailboxActivity?.mailboxLabelId)
intent.putExtra(MessageDetailsActivity.EXTRA_MESSAGE_SUBJECT, messageSubject)
mailboxActivity?.startActivity(intent)
}
}
}
@ -1343,7 +1330,7 @@ internal class MailboxActivity :
private class SetUpNewMessageLocationTask internal constructor(
private val mailboxActivity: WeakReference<MailboxActivity>,
private val messageDetailsRepository: MessageDetailsRepository,
private val messageDetailsRepositoryFactory: MessageDetailsRepository.AssistedFactory,
private val labelId: String,
private val isFolder: Boolean,
private val newLocation: Int,
@ -1353,6 +1340,7 @@ internal class MailboxActivity :
override fun doInBackground(vararg params: Unit): Label? {
return runBlocking {
val messageDetailsRepository = messageDetailsRepositoryFactory.create(userId)
val labels = messageDetailsRepository.findLabelsWithIds(listOf(labelId))
if (labels.isEmpty()) null else labels[0]
}

View File

@ -87,6 +87,7 @@ import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@ -150,7 +151,7 @@ internal class MailboxViewModel @Inject constructor(
private val mutableMailboxState = MutableStateFlow<MailboxState>(MailboxState.Loading)
private val mutableMailboxLocation = MutableStateFlow(INBOX)
private val mutableMailboxLabelId = MutableStateFlow(EMPTY_STRING)
private val mutableUserId = userManager.primaryUserId.filterNotNull()
private val mutableUserId = userManager.primaryUserId
private val mutableRefreshFlow = MutableSharedFlow<Boolean>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
@ -159,13 +160,15 @@ internal class MailboxViewModel @Inject constructor(
private val messageDetailsRepository: MessageDetailsRepository
get() = messageDetailsRepositoryFactory.create(userManager.requireCurrentUserId())
var pendingSendsLiveData = mutableUserId.asLiveData().switchMap {
var pendingSendsLiveData = mutableUserId.filterNotNull().asLiveData().switchMap {
messageDetailsRepository.findAllPendingSendsAsync()
}
var pendingUploadsLiveData = mutableUserId.asLiveData().switchMap {
var pendingUploadsLiveData = mutableUserId.filterNotNull().asLiveData().switchMap {
messageDetailsRepository.findAllPendingUploadsAsync()
}
val primaryUserId: Flow<UserId> = mutableUserId.filterNotNull()
val manageLimitReachedWarning: LiveData<Event<Boolean>>
get() = _manageLimitReachedWarning
val manageLimitApproachingWarning: LiveData<Event<Boolean>>
@ -184,10 +187,12 @@ internal class MailboxViewModel @Inject constructor(
val mailboxLocation = mutableMailboxLocation.asStateFlow()
val drawerLabels: Flow<DrawerFoldersAndLabelsSectionUiModel> = combine(
mutableUserId,
mutableUserId.filterNotNull(),
mutableRefreshFlow.onStart { emit(false) }
) { userId, isRefresh -> userId to isRefresh }
.flatMapLatest { userIdPair -> observeLabelsAndFoldersWithChildren(userIdPair.first, userIdPair.second) }
.flatMapLatest { userIdPair ->
observeLabelsAndFoldersWithChildren(userIdPair.first, userIdPair.second)
}
.map { labelsAndFolders ->
drawerFoldersAndLabelsSectionUiModelMapper.toUiModels(labelsAndFolders)
}
@ -197,6 +202,7 @@ internal class MailboxViewModel @Inject constructor(
mutableRefreshFlow.onStart { emit(false) }
) { userId, _ -> userId }
.flatMapLatest { userId ->
if (userId == null) return@flatMapLatest flowOf(emptyList())
combineTransform(
observeAllUnreadCounters(userId),
observeConversationModeEnabled(userId)
@ -223,7 +229,7 @@ internal class MailboxViewModel @Inject constructor(
combine(
mutableMailboxLocation,
mutableMailboxLabelId,
mutableUserId,
mutableUserId.filterNotNull(),
mutableRefreshFlow.onStart { emit(false) }
) { location, label, userId, isRefresh ->
Timber.v("New location: $location, label: $label, user: $userId, isRefresh: $isRefresh")
@ -260,11 +266,6 @@ internal class MailboxViewModel @Inject constructor(
.launchIn(viewModelScope)
}
fun reloadDependenciesForUser() {
pendingSendsLiveData = messageDetailsRepository.findAllPendingSendsAsync()
pendingUploadsLiveData = messageDetailsRepository.findAllPendingUploadsAsync()
}
fun usedSpaceActionEvent(limitReachedFlow: Int) {
viewModelScope.launch {
userManager.setShowStorageLimitReached(true)

View File

@ -39,7 +39,6 @@ import ch.protonmail.android.jobs.PostStarJob
import ch.protonmail.android.jobs.PostUnreadJob
import ch.protonmail.android.jobs.PostUnstarJob
import ch.protonmail.android.labels.domain.LabelRepository
import ch.protonmail.android.mailbox.data.local.UnreadCounterDao
import ch.protonmail.android.mailbox.data.local.model.UnreadCounterEntity.Type
import ch.protonmail.android.mailbox.data.mapper.ApiToDatabaseUnreadCounterMapper
import ch.protonmail.android.mailbox.data.mapper.DatabaseToDomainUnreadCounterMapper
@ -76,7 +75,6 @@ private const val FILE_PREFIX = "file://"
*/
class MessageRepository @Inject constructor(
private val dispatcherProvider: DispatcherProvider,
private val unreadCounterDao: UnreadCounterDao,
private val databaseProvider: DatabaseProvider,
private val protonMailApiManager: ProtonMailApiManager,
private val databaseToDomainUnreadCounterMapper: DatabaseToDomainUnreadCounterMapper,
@ -222,6 +220,7 @@ class MessageRepository @Inject constructor(
Timber.v("Fetch Messages Unread response: $response")
val counts = response.counts
.map(apiToDatabaseUnreadCounterMapper) { toDatabaseModel(it, userId, Type.MESSAGES) }
val unreadCounterDao = databaseProvider.provideUnreadCounterDao(userId)
unreadCounterDao.insertOrUpdate(counts)
}
@ -359,7 +358,9 @@ class MessageRepository @Inject constructor(
}
private fun observerUnreadCountersFromDatabase(userId: UserId): Flow<DataResult<List<UnreadCounter>>> =
unreadCounterDao.observeMessagesUnreadCounters(userId).map { list ->
databaseProvider
.provideUnreadCounterDao(userId)
.observeMessagesUnreadCounters(userId).map { list ->
val domainModels = databaseToDomainUnreadCounterMapper.toDomainModels(list)
DataResult.Success(ResponseSource.Local, domainModels)
}

View File

@ -20,7 +20,7 @@
package ch.protonmail.android.usecase.delete
import ch.protonmail.android.activities.messageDetails.repository.MessageDetailsRepository
import ch.protonmail.android.data.local.PendingActionDao
import ch.protonmail.android.api.models.DatabaseProvider
import ch.protonmail.android.data.local.model.Message
import ch.protonmail.android.data.local.model.PendingSend
import ch.protonmail.android.data.local.model.PendingUpload
@ -39,10 +39,10 @@ import javax.inject.Inject
* [DeleteMessageWorker] that will send a deferrable delete message network request.
*/
class DeleteMessage @Inject constructor(
private val databaseProvider: DatabaseProvider,
private val conversationsRepository: ConversationsRepository,
private val dispatchers: DispatcherProvider,
private val messageDetailsRepository: MessageDetailsRepository,
private val pendingActionDatabase: PendingActionDao,
private val messageDetailsRepositoryFactory: MessageDetailsRepository.AssistedFactory,
private val workerScheduler: DeleteMessageWorker.Enqueuer
) {
@ -52,8 +52,9 @@ class DeleteMessage @Inject constructor(
userId: UserId
): DeleteMessageResult =
withContext(dispatchers.Io) {
val messageDetailsRepository = messageDetailsRepositoryFactory.create(userId)
val (validMessageIdList, invalidMessageIdList) = getValidAndInvalidMessages(messageIds)
val (validMessageIdList, invalidMessageIdList) = getValidAndInvalidMessages(userId, messageIds)
val messagesToSave = mutableListOf<Message>()
@ -76,7 +77,7 @@ class DeleteMessage @Inject constructor(
)
}
private fun getValidAndInvalidMessages(messageIds: List<String>): Pair<List<String>, List<String>> {
private fun getValidAndInvalidMessages(userId: UserId, messageIds: List<String>): Pair<List<String>, List<String>> {
val validMessageIdList = mutableListOf<String>()
val invalidMessageIdList = mutableListOf<String>()
@ -84,8 +85,9 @@ class DeleteMessage @Inject constructor(
if (id.isEmpty()) {
continue
}
val pendingUploads = pendingActionDatabase.findPendingUploadByMessageId(id)
val pendingForSending = pendingActionDatabase.findPendingSendByMessageId(id)
val pendingActionDao = databaseProvider.providePendingActionDao(userId)
val pendingUploads = pendingActionDao.findPendingUploadByMessageId(id)
val pendingForSending = pendingActionDao.findPendingSendByMessageId(id)
if (areThereAnyPendingUplandsOrSends(pendingUploads, pendingForSending)) {
invalidMessageIdList.add(id)

View File

@ -21,7 +21,6 @@ package ch.protonmail.android.utils
import android.content.Context
import android.content.Intent
import ch.protonmail.android.activities.EXTRA_FIRST_LOGIN
import ch.protonmail.android.activities.SplashActivity
import ch.protonmail.android.core.Constants
import ch.protonmail.android.core.ProtonMailApplication
import ch.protonmail.android.mailbox.presentation.MailboxActivity
@ -29,12 +28,6 @@ import ch.protonmail.android.servers.notification.EXTRA_MAILBOX_LOCATION
import ch.protonmail.android.servers.notification.EXTRA_USER_ID
import me.proton.core.domain.entity.UserId
fun Context.startSplashActivity() =
startActivity(getSplashActivityIntent())
fun Context.getSplashActivityIntent(): Intent =
Intent(this, SplashActivity::class.java)
fun Context.startMailboxActivity(
userId: UserId? = null,
type: Constants.MessageLocationType? = null

View File

@ -30,8 +30,10 @@ import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import ch.protonmail.android.api.ProtonMailApiManager
import ch.protonmail.android.api.models.DatabaseProvider
import ch.protonmail.android.api.models.IDList
import ch.protonmail.android.core.Constants
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.data.local.ContactDao
import ch.protonmail.android.data.local.ContactDatabase
import dagger.assisted.Assisted
@ -55,13 +57,19 @@ class DeleteContactWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val api: ProtonMailApiManager,
private val contactDao: ContactDao,
private val contactDatabase: ContactDatabase,
private val userManager: UserManager,
private val databaseProvider: DatabaseProvider,
private val dispatchers: DispatcherProvider
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
private val contactDatabase: ContactDatabase
get() = databaseProvider.provideContactDatabase(userManager.requireCurrentUserId())
private val contactDao: ContactDao
get() = databaseProvider.provideContactDao(userManager.requireCurrentUserId())
override suspend fun doWork(): Result {
// TODO: Pass userId (requireCurrentUserId would not always be the right one).
val contactIds = inputData.getStringArray(KEY_INPUT_DATA_CONTACT_IDS)
?: emptyArray()

View File

@ -24,6 +24,7 @@ import androidx.work.ListenableWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import ch.protonmail.android.api.ProtonMailApi
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.data.local.CounterRepository
import ch.protonmail.android.data.local.model.Message
import ch.protonmail.android.worker.KEY_WORKER_ERROR_DESCRIPTION
@ -32,9 +33,7 @@ import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runBlockingTest
import me.proton.core.accountmanager.domain.AccountManager
import me.proton.core.domain.entity.UserId
import kotlin.test.BeforeTest
import kotlin.test.Test
@ -48,7 +47,7 @@ class RemoveMessageLabelWorkerTest {
private val api = mockk<ProtonMailApi>()
private val accountManager = mockk<AccountManager>()
private val userManager = mockk<UserManager>()
private val counterRepository = mockk<CounterRepository>()
@ -59,12 +58,12 @@ class RemoveMessageLabelWorkerTest {
@BeforeTest
fun setUp() {
MockKAnnotations.init(this)
every { accountManager.getPrimaryUserId() } returns flowOf(testUserId)
every { userManager.currentUserId } returns testUserId
worker = RemoveMessageLabelWorker(
context,
parameters,
accountManager,
userManager,
counterRepository,
api
)

View File

@ -21,11 +21,13 @@ package ch.protonmail.android.mailbox.data
import app.cash.turbine.test
import ch.protonmail.android.api.ProtonMailApiManager
import ch.protonmail.android.api.models.DatabaseProvider
import ch.protonmail.android.api.models.MessageRecipient
import ch.protonmail.android.api.models.messages.receive.MessageFactory
import ch.protonmail.android.api.models.messages.receive.ServerMessage
import ch.protonmail.android.core.Constants.MessageLocationType
import ch.protonmail.android.core.NetworkConnectivityManager
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.data.local.MessageDao
import ch.protonmail.android.data.local.model.Message
import ch.protonmail.android.data.local.model.MessageSender
@ -183,6 +185,11 @@ class ConversationsRepositoryImplTest : ArchTest {
)
)
private val userManager: UserManager = mockk {
every { currentUserId } returns testUserId
every { requireCurrentUserId() } returns testUserId
}
private val conversationDao: ConversationDao = mockk {
coEvery { updateLabels(any(), any()) } just Runs
coEvery { insertOrUpdate(*anyVararg()) } just Runs
@ -198,6 +205,12 @@ class ConversationsRepositoryImplTest : ArchTest {
coEvery { insertOrUpdate(any<Collection<UnreadCounterEntity>>()) } just Runs
}
private val databaseProvider: DatabaseProvider = mockk {
every { provideConversationDao(any()) } returns conversationDao
every { provideMessageDao(any()) } returns messageDao
every { provideUnreadCounterDao(any()) } returns unreadCounterDao
}
private val api: ProtonMailApiManager = mockk {
coEvery { fetchConversationsCounts(testUserId) } returns CountsResponse(emptyList())
}
@ -252,9 +265,8 @@ class ConversationsRepositoryImplTest : ArchTest {
}
private val conversationsRepository = ConversationsRepositoryImpl(
conversationDao = conversationDao,
messageDao = messageDao,
unreadCounterDao = unreadCounterDao,
userManager = userManager,
databaseProvider = databaseProvider,
api = api,
responseToConversationsMapper = ConversationsResponseToConversationsMapper(
conversationApiModelToConversationMapper

View File

@ -85,6 +85,7 @@ class MessageRepositoryTest {
private val databaseProvider: DatabaseProvider = mockk {
every { provideMessageDao(any()) } returns messageDao
every { provideUnreadCounterDao(any()) } returns unreadCounterDao
}
private val messageBodyFileManager: MessageBodyFileManager = mockk()
@ -126,7 +127,6 @@ class MessageRepositoryTest {
private val messageRepository = MessageRepository(
dispatcherProvider = TestDispatcherProvider,
unreadCounterDao = unreadCounterDao,
databaseProvider = databaseProvider,
protonMailApiManager = protonMailApiManager,
databaseToDomainUnreadCounterMapper = DatabaseToDomainUnreadCounterMapper(),

View File

@ -21,6 +21,7 @@ package ch.protonmail.android.usecase.delete
import androidx.work.Operation
import ch.protonmail.android.activities.messageDetails.repository.MessageDetailsRepository
import ch.protonmail.android.api.models.DatabaseProvider
import ch.protonmail.android.data.local.PendingActionDao
import ch.protonmail.android.data.local.model.Message
import ch.protonmail.android.data.local.model.PendingSend
@ -48,12 +49,18 @@ class DeleteMessageTest {
private val workScheduler: DeleteMessageWorker.Enqueuer = mockk()
private val db: PendingActionDao = mockk()
private val messageDetailsRepository: MessageDetailsRepository = mockk()
private val messageDetailsRepositoryFactory: MessageDetailsRepository.AssistedFactory = mockk() {
every { create(any()) } returns messageDetailsRepository
}
private val pendingActionDao: PendingActionDao = mockk()
private val conversationsRepository: ConversationsRepository = mockk()
private val databaseProvider: DatabaseProvider = mockk {
every { providePendingActionDao(any()) } returns pendingActionDao
}
private lateinit var deleteMessage: DeleteMessage
private val messId = "Id1"
@ -70,10 +77,10 @@ class DeleteMessageTest {
fun setUp() {
MockKAnnotations.init(this)
deleteMessage = DeleteMessage(
databaseProvider,
conversationsRepository,
TestDispatcherProvider,
messageDetailsRepository,
db,
messageDetailsRepositoryFactory,
workScheduler
)
@ -90,8 +97,8 @@ class DeleteMessageTest {
fun verifyThatMessageIsSuccessfullyDeletedWithoutPendingMessagesInTheDb() {
runBlockingTest {
// given
every { db.findPendingUploadByMessageId(any()) } returns null
every { db.findPendingSendByMessageId(any()) } returns null
every { pendingActionDao.findPendingUploadByMessageId(any()) } returns null
every { pendingActionDao.findPendingSendByMessageId(any()) } returns null
every { messageDetailsRepository.findMessageById(messId) } returns flowOf(message)
// when
@ -112,8 +119,8 @@ class DeleteMessageTest {
runBlockingTest {
// given
val pendingUpload = mockk<PendingUpload>(relaxed = true)
every { db.findPendingUploadByMessageId(any()) } returns pendingUpload
every { db.findPendingSendByMessageId(any()) } returns null
every { pendingActionDao.findPendingUploadByMessageId(any()) } returns pendingUpload
every { pendingActionDao.findPendingSendByMessageId(any()) } returns null
every { messageDetailsRepository.findMessageByIdBlocking(messId) } returns message
coEvery { messageDetailsRepository.saveMessage(message) } returns 1L
@ -137,8 +144,8 @@ class DeleteMessageTest {
val pendingSend = mockk<PendingSend>(relaxed = true) {
every { sent } returns true
}
every { db.findPendingUploadByMessageId(any()) } returns null
every { db.findPendingSendByMessageId(any()) } returns pendingSend
every { pendingActionDao.findPendingUploadByMessageId(any()) } returns null
every { pendingActionDao.findPendingSendByMessageId(any()) } returns pendingSend
every { messageDetailsRepository.findMessageByIdBlocking(messId) } returns null
coEvery { messageDetailsRepository.saveMessage(message) } returns 1L

View File

@ -24,8 +24,10 @@ import androidx.work.ListenableWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import ch.protonmail.android.api.ProtonMailApiManager
import ch.protonmail.android.api.models.DatabaseProvider
import ch.protonmail.android.api.models.DeleteResponse
import ch.protonmail.android.core.Constants
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.data.local.ContactDao
import ch.protonmail.android.data.local.ContactDatabase
import ch.protonmail.android.data.local.model.ContactData
@ -50,6 +52,12 @@ class DeleteContactWorkerTest {
@RelaxedMockK
private lateinit var parameters: WorkerParameters
@MockK
private lateinit var userManager: UserManager
@RelaxedMockK
private lateinit var databaseProvider: DatabaseProvider
@RelaxedMockK
private lateinit var contactDatabase: ContactDatabase
@ -64,12 +72,17 @@ class DeleteContactWorkerTest {
@BeforeTest
fun setUp() {
MockKAnnotations.init(this)
every { userManager.requireCurrentUserId() } returns mockk()
every { databaseProvider.provideContactDatabase(any()) } returns contactDatabase
every { databaseProvider.provideContactDao(any()) } returns contactDao
worker = DeleteContactWorker(
context,
parameters,
api,
contactDao,
contactDatabase,
userManager,
databaseProvider,
TestDispatcherProvider
)
}
@ -100,15 +113,14 @@ class DeleteContactWorkerTest {
every { code } returns Constants.RESPONSE_CODE_OK
}
val contactData = mockk<ContactData>()
val contactEmail= mockk<ContactEmail>()
val contactEmail = mockk<ContactEmail>()
val expected = ListenableWorker.Result.success()
every { contactDao.findContactDataByIdBlocking(contactId) } returns contactData
every { contactDao.findContactEmailsByContactIdBlocking(contactId) } returns listOf(contactEmail)
every { contactDao.deleteAllContactsEmailsBlocking(any()) } returns mockk()
every { contactDao.deleteContactData(any()) } returns mockk()
every { parameters.inputData } returns
workDataOf(KEY_INPUT_DATA_CONTACT_IDS to arrayOf(contactId))
every { parameters.inputData } returns workDataOf(KEY_INPUT_DATA_CONTACT_IDS to arrayOf(contactId))
coEvery { api.deleteContact(any()) } returns deleteResponse
// when
@ -129,7 +141,7 @@ class DeleteContactWorkerTest {
every { code } returns randomErrorCode
}
val contactData = mockk<ContactData>()
val contactEmail= mockk<ContactEmail>()
val contactEmail = mockk<ContactEmail>()
val expected = ListenableWorker.Result.failure(
workDataOf(KEY_WORKER_ERROR_DESCRIPTION to "ApiException response code $randomErrorCode")
)
@ -138,8 +150,7 @@ class DeleteContactWorkerTest {
every { contactDao.findContactEmailsByContactIdBlocking(contactId) } returns listOf(contactEmail)
every { contactDao.deleteAllContactsEmailsBlocking(any()) } returns mockk()
every { contactDao.deleteContactData(any()) } returns mockk()
every { parameters.inputData } returns
workDataOf(KEY_INPUT_DATA_CONTACT_IDS to arrayOf(contactId))
every { parameters.inputData } returns workDataOf(KEY_INPUT_DATA_CONTACT_IDS to arrayOf(contactId))
coEvery { api.deleteContact(any()) } returns deleteResponse
// when
@ -149,5 +160,4 @@ class DeleteContactWorkerTest {
assertEquals(operationResult, expected)
}
}
}