Skip to content

Commit

Permalink
feat(ImproveMyKSuite): Make MyKSuiteDataUtils injectable
Browse files Browse the repository at this point in the history
  • Loading branch information
FabianDevel committed Mar 6, 2025
1 parent dcff1a7 commit bbc0abc
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 36 deletions.
54 changes: 35 additions & 19 deletions app/src/main/java/com/infomaniak/mail/MainApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import coil.ImageLoaderFactory
import coil.decode.SvgDecoder
import com.facebook.stetho.Stetho
import com.infomaniak.lib.core.InfomaniakCore
import com.infomaniak.lib.core.api.ApiController
import com.infomaniak.lib.core.auth.TokenInterceptorListener
import com.infomaniak.lib.core.models.user.User
import com.infomaniak.lib.core.networking.AccessTokenUsageInterceptor
Expand Down Expand Up @@ -105,6 +104,9 @@ open class MainApplication : Application(), ImageLoaderFactory, DefaultLifecycle
@Inject
lateinit var appUpdateWorkerScheduler: AppUpdateScheduler

@Inject
lateinit var myKSuiteDataUtils: MyKSuiteDataUtils

@Inject
@IoDispatcher
lateinit var ioDispatcher: CoroutineDispatcher
Expand Down Expand Up @@ -168,23 +170,37 @@ open class MainApplication : Application(), ImageLoaderFactory, DefaultLifecycle
SentryAndroid.init(this) { options: SentryAndroidOptions ->
// Register the callback as an option
options.beforeSend = SentryOptions.BeforeSendCallback { event: SentryEvent, _: Any? ->
val exception = event.throwable
/**
* Reasons to discard Sentry events :
* - Application is in Debug mode
* - User deactivated Sentry tracking in DataManagement settings
* - The exception was an [ApiController.NetworkException], and we don't want to send them to Sentry
* - The exception was an [ApiErrorException] with an [ErrorCode.ACCESS_DENIED] or
* [ErrorCode.NOT_AUTHORIZED] error code, and we don't want to send them to Sentry
*/
when {
BuildConfig.DEBUG -> null
!localSettings.isSentryTrackingEnabled -> null
exception is ApiController.NetworkException -> null
exception is ApiErrorException && exception.errorCode == ErrorCode.ACCESS_DENIED -> null
exception is ApiErrorException && exception.errorCode == ErrorCode.NOT_AUTHORIZED -> null
else -> event
}

val shouldLog = mutableListOf<Boolean>()

// Sentry events are discarded if the app is in Debug mode
val isInReleaseMode = !BuildConfig.DEBUG
shouldLog.add(isInReleaseMode)

// Sentry events are discarded if the user deactivated Sentry tracking in DataManagement settings
val isSentryTrackingEnabled = localSettings.isSentryTrackingEnabled
shouldLog.add(isSentryTrackingEnabled)

// Network exceptions are discarded
// TODO: It doesn't work anymore :(
val isNetworkException = event.exceptions?.any { it.type == "ApiController\$NetworkException" } ?: false
shouldLog.add(!isNetworkException)

// AccessDenied exceptions are discarded
val isAccessDeniedException = event.exceptions?.any {
// TODO: Check in Sentry if this `value.contains()` is the correct way to find this exception.
it.type == "ApiErrorException" && it.value?.contains("access_denied") == true
} ?: false
shouldLog.add(!isAccessDeniedException)

// NotAuthorized exceptions are discarded
val isNotAuthorizedException = event.exceptions?.any {
// TODO: Check in Sentry if this `value.contains()` is the correct way to find this exception.
it.type == "ApiErrorException" && it.value?.contains("not_authorized") == true
} ?: false
shouldLog.add(!isNotAuthorizedException)

if (shouldLog.all { true }) event else null
}
options.addIntegration(
FragmentLifecycleIntegration(
Expand All @@ -209,7 +225,7 @@ open class MainApplication : Application(), ImageLoaderFactory, DefaultLifecycle

private fun configureRoomDatabases() {
AccountUtils.init(this)
MyKSuiteDataUtils.initDatabase(this)
myKSuiteDataUtils.initDatabase(this)
}

private fun configureAppReloading() {
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class MainViewModel @Inject constructor(
private val mailboxController: MailboxController,
private val mergedContactController: MergedContactController,
private val messageController: MessageController,
private val myKSuiteDataUtils: MyKSuiteDataUtils,
private val notificationUtils: NotificationUtils,
private val permissionsController: PermissionsController,
private val quotasController: QuotasController,
Expand Down Expand Up @@ -317,7 +318,7 @@ class MainViewModel @Inject constructor(
AccountUtils.updateCurrentUser()

// Refresh My kSuite asynchronously, because it's not required for the threads list display
launch { MyKSuiteDataUtils.fetchDataIfMyKSuite(mailboxController) }
launch { myKSuiteDataUtils.fetchData() }

// Refresh Mailboxes
SentryLog.d(TAG, "Refresh mailboxes from remote")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import javax.inject.Inject
class MykSuiteViewModel @Inject constructor(
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val mailboxController: MailboxController,
private val myKSuiteDataUtils: MyKSuiteDataUtils,
) : ViewModel() {

private val ioCoroutineContext = viewModelScope.coroutineContext(ioDispatcher)
Expand All @@ -48,6 +49,6 @@ class MykSuiteViewModel @Inject constructor(
}

fun refreshMyKSuite() = viewModelScope.launch(ioCoroutineContext) {
myKSuiteDataResult.postValue(MyKSuiteDataUtils.fetchDataIfMyKSuite(mailboxController))
myKSuiteDataResult.postValue(myKSuiteDataUtils.fetchData())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class SettingsFragment : Fragment() {
@Inject
lateinit var localSettings: LocalSettings

@Inject
lateinit var myKSuiteDataUtils: MyKSuiteDataUtils

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
saveFocusWhenNavigatingBack(getLayout = { binding.linearLayoutContainer }, lifecycle)
Expand All @@ -87,8 +90,8 @@ class SettingsFragment : Fragment() {
}

private fun setupMyKSuite() {
binding.myKSuiteLayout.isGone = MyKSuiteDataUtils.myKSuite == null
MyKSuiteDataUtils.myKSuite?.let { setupMyKSuiteLayout(it) } ?: myKSuiteViewModel.refreshMyKSuite()
binding.myKSuiteLayout.isGone = myKSuiteDataUtils.myKSuite == null
myKSuiteDataUtils.myKSuite?.let { setupMyKSuiteLayout(it) } ?: myKSuiteViewModel.refreshMyKSuite()
}

private fun setupMyKSuiteLayout(myKSuiteData: MyKSuiteData) = with(binding) {
Expand Down Expand Up @@ -149,7 +152,7 @@ class SettingsFragment : Fragment() {

binding.mailboxesList.adapter = mailboxesAdapter
mainViewModel.mailboxesLive.observe(viewLifecycleOwner) { mailboxes ->
mailboxesAdapter.setMailboxes(mailboxes.filterNot { it.mailboxId == MyKSuiteDataUtils.myKSuite?.mail?.mailboxId })
mailboxesAdapter.setMailboxes(mailboxes.filterNot { it.mailboxId == myKSuiteDataUtils.myKSuite?.mail?.mailboxId })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import javax.inject.Inject
class SwitchUserViewModel @Inject constructor(
application: Application,
private val mailboxController: MailboxController,
private val myKSuiteDataUtils: MyKSuiteDataUtils,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : AndroidViewModel(application) {

Expand All @@ -52,7 +53,7 @@ class SwitchUserViewModel @Inject constructor(
if (user.id != AccountUtils.currentUserId) {
appContext.trackAccountEvent("switch")
RealmDatabase.backupPreviousRealms()
MyKSuiteDataUtils.myKSuite = null
myKSuiteDataUtils.myKSuite = null
AccountUtils.currentUser = user
AccountUtils.currentMailboxId = mailboxController.getFirstValidMailbox(user.id)?.mailboxId ?: AppSettings.DEFAULT_ID
AccountUtils.reloadApp?.invoke()
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/infomaniak/mail/utils/LogoutUser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class LogoutUser @Inject constructor(
private val globalCoroutineScope: CoroutineScope,
private val localSettings: LocalSettings,
private val mailboxController: MailboxController,
private val myKSuiteDataUtils: MyKSuiteDataUtils,
private val playServicesUtils: PlayServicesUtils,
private val storesSettingsRepository: StoresSettingsRepository,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
Expand All @@ -52,7 +53,7 @@ class LogoutUser @Inject constructor(

user.logoutToken()
AccountUtils.removeUser(user)
MyKSuiteDataUtils.deleteData(user.id)
myKSuiteDataUtils.deleteData(user.id)
RealmDatabase.removeUserData(appContext, user.id)
mailboxController.deleteUserMailboxes(user.id)
localSettings.removeRegisteredFirebaseUser(userId = user.id)
Expand Down
22 changes: 12 additions & 10 deletions app/src/main/java/com/infomaniak/mail/utils/MyKSuiteDataUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,25 @@ import com.infomaniak.mail.data.api.ApiRepository
import com.infomaniak.mail.data.cache.mailboxInfo.MailboxController
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.MissingFieldException
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.cancellation.CancellationException

object MyKSuiteDataUtils : MyKSuiteDataManager() {

private val TAG = MyKSuiteDataUtils::class.simpleName.toString()
@Singleton
class MyKSuiteDataUtils @Inject constructor(private val mailboxController: MailboxController) : MyKSuiteDataManager() {

override val currentUserId get() = AccountUtils.currentUserId

override var myKSuite: MyKSuiteData? = null

/** Only call this if you are sure that you want to do the call without checking if it's necessary or not
* To avoid useless call for account that does not have a my kSuite offer, use [fetchDataIfMyKSuite] */
override suspend fun fetchData(): MyKSuiteData? = runCatching {
MyKSuiteDataUtils.requestKSuiteData()
requestKSuiteData()
// Only fetch the Data if the current user has a my kSuite mailbox
if (mailboxController.getMyKSuiteMailboxCount(userId = AccountUtils.currentUserId) == 0L) return@runCatching null

val apiResponse = ApiRepository.getMyKSuiteData(HttpClient.okHttpClient)
if (apiResponse.data != null) {
MyKSuiteDataUtils.upsertKSuiteData(apiResponse.data!!)
upsertKSuiteData(apiResponse.data!!)
} else {
@OptIn(ExperimentalSerializationApi::class)
apiResponse.error?.exception?.let {
Expand All @@ -56,8 +58,8 @@ object MyKSuiteDataUtils : MyKSuiteDataManager() {
null
}

suspend fun fetchDataIfMyKSuite(mailboxController: MailboxController): MyKSuiteData? {
// Only fetch the my kSuite Data if the current account has a my kSuite
return if (mailboxController.getMyKSuiteMailboxCount(userId = AccountUtils.currentUserId) != 0L) fetchData() else null
companion object {

private val TAG = MyKSuiteDataUtils::class.simpleName.toString()
}
}

0 comments on commit bbc0abc

Please sign in to comment.