diff --git a/apps/flipcash/features/backupkey/src/main/kotlin/com/flipcash/app/backupkey/internal/BackupKeyScreenViewModel.kt b/apps/flipcash/features/backupkey/src/main/kotlin/com/flipcash/app/backupkey/internal/BackupKeyScreenViewModel.kt index 7cca89286..082361df7 100644 --- a/apps/flipcash/features/backupkey/src/main/kotlin/com/flipcash/app/backupkey/internal/BackupKeyScreenViewModel.kt +++ b/apps/flipcash/features/backupkey/src/main/kotlin/com/flipcash/app/backupkey/internal/BackupKeyScreenViewModel.kt @@ -2,6 +2,7 @@ package com.flipcash.app.backupkey.internal import com.flipcash.app.accesskey.BaseAccessKeyViewModel import com.flipcash.app.core.storage.MediaSaver +import com.flipcash.libs.coroutines.DispatcherProvider import com.flipcash.services.user.UserManager import com.getcode.libs.qr.QRCodeGenerator import com.getcode.opencode.managers.MnemonicManager @@ -20,12 +21,14 @@ internal class BackupKeyScreenViewModel @Inject constructor( mediaSaver: MediaSaver, userManager: UserManager, qrCodeGenerator: QRCodeGenerator, + dispatchers: DispatcherProvider, ) : BaseAccessKeyViewModel( resources, mnemonicManager, mediaSaver, userManager, - qrCodeGenerator + qrCodeGenerator, + dispatchers, ) { suspend fun saveImage(): Result = saveBitmapToFile().map { delay(150) diff --git a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/LoginAccessKeyViewModel.kt b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/LoginAccessKeyViewModel.kt index fa3c7f6d9..a9cfb932d 100644 --- a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/LoginAccessKeyViewModel.kt +++ b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/LoginAccessKeyViewModel.kt @@ -3,6 +3,7 @@ package com.flipcash.app.login.internal import com.flipcash.app.accesskey.BaseAccessKeyViewModel import com.flipcash.app.analytics.Button import com.flipcash.app.analytics.FlipcashAnalyticsService +import com.flipcash.libs.coroutines.DispatcherProvider import com.flipcash.app.auth.AuthManager import com.flipcash.app.core.storage.MediaSaver import com.flipcash.app.featureflags.FeatureFlag @@ -25,11 +26,12 @@ internal class LoginAccessKeyViewModel @Inject constructor( qrCodeGenerator: QRCodeGenerator, mediaSaver: MediaSaver, userManager: UserManager, + dispatchers: DispatcherProvider, private val userFlags: UserFlagsCoordinator, private val featureFlags: FeatureFlagController, private val authManager: AuthManager, private val analytics: FlipcashAnalyticsService, -): BaseAccessKeyViewModel(resources, mnemonicManager, mediaSaver, userManager, qrCodeGenerator) { +): BaseAccessKeyViewModel(resources, mnemonicManager, mediaSaver, userManager, qrCodeGenerator, dispatchers) { suspend fun onWroteDownInstead(): Result { trackButton(Button.WroteAccessKey) diff --git a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/SeedInputViewModel.kt b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/SeedInputViewModel.kt index cbe373602..4747f7f66 100644 --- a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/SeedInputViewModel.kt +++ b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/internal/SeedInputViewModel.kt @@ -11,11 +11,10 @@ import com.getcode.crypt.MnemonicPhrase import com.getcode.manager.BottomBarAction import com.getcode.manager.BottomBarManager import com.getcode.opencode.managers.MnemonicManager +import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.util.resources.ResourceHelper import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -42,6 +41,7 @@ internal class SeedInputViewModel @Inject constructor( private val userFlags: UserFlagsCoordinator, private val resources: ResourceHelper, private val mnemonicManager: MnemonicManager, + private val dispatchers: DispatcherProvider, ) : ViewModel() { val uiFlow = MutableStateFlow(SeedInputUiModel()) private val mnemonicCode = mnemonicManager.mnemonicCode @@ -78,7 +78,7 @@ internal class SeedInputViewModel @Inject constructor( uiFlow.value.wordsString.trim().replace(Regex("(\\s)+"), " ").lowercase(Locale.getDefault()).split(" ") val mnemonic = MnemonicPhrase.newInstance(userWordList) ?: return - CoroutineScope(Dispatchers.IO).launch { + viewModelScope.launch(dispatchers.IO) { val entropyB64: String try { entropyB64 = mnemonicManager.getEncodedBase64(mnemonic) diff --git a/apps/flipcash/features/userflags/build.gradle.kts b/apps/flipcash/features/userflags/build.gradle.kts index 917258ee0..75cd0bc03 100644 --- a/apps/flipcash/features/userflags/build.gradle.kts +++ b/apps/flipcash/features/userflags/build.gradle.kts @@ -8,6 +8,5 @@ android { dependencies { implementation(project(":apps:flipcash:shared:userflags")) - implementation(project(":libs:coroutines")) implementation(project(":libs:messaging")) } diff --git a/apps/flipcash/shared/accesskey/src/main/kotlin/com/flipcash/app/accesskey/BaseAccessKeyViewModel.kt b/apps/flipcash/shared/accesskey/src/main/kotlin/com/flipcash/app/accesskey/BaseAccessKeyViewModel.kt index 10edc9b5f..5d767e381 100644 --- a/apps/flipcash/shared/accesskey/src/main/kotlin/com/flipcash/app/accesskey/BaseAccessKeyViewModel.kt +++ b/apps/flipcash/shared/accesskey/src/main/kotlin/com/flipcash/app/accesskey/BaseAccessKeyViewModel.kt @@ -13,6 +13,7 @@ import androidx.core.graphics.createBitmap import androidx.core.graphics.drawable.toBitmap import androidx.lifecycle.viewModelScope import com.flipcash.app.core.storage.MediaSaver +import com.flipcash.libs.coroutines.DispatcherProvider import com.flipcash.app.theme.internal.Flipcash2ColorSpec import com.flipcash.services.user.UserManager import com.flipcash.shared.accesskey.R @@ -27,8 +28,6 @@ import com.getcode.util.resources.ResourceHelper import com.getcode.utils.decodeBase64 import androidx.lifecycle.ViewModel import com.getcode.view.LoadingSuccessState -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filterNotNull @@ -63,7 +62,8 @@ abstract class BaseAccessKeyViewModel( private val mnemonicManager: MnemonicManager, private val mediaSaver: MediaSaver, userManager: UserManager, - private val qrCodeGenerator: QRCodeGenerator + private val qrCodeGenerator: QRCodeGenerator, + protected val dispatchers: DispatcherProvider, ) : ViewModel() { val uiFlow = MutableStateFlow(AccessKeyUiModel()) @@ -88,7 +88,7 @@ abstract class BaseAccessKeyViewModel( wordsFormatted = wordsFormatted ) - CoroutineScope(Dispatchers.IO).launch { + viewModelScope.launch(dispatchers.IO) { val accessKeyBitmap = createBitmapForExport(words = words, entropyB64 = entropyB64) val accessKeyBitmapDisplay = createBitmapForExport(drawBackground = true, words, entropyB64) @@ -128,7 +128,7 @@ abstract class BaseAccessKeyViewModel( val bitmap = uiFlow.value.accessKeyBitmap ?: return Result.failure(IllegalStateException("No access key?")) - return withContext(Dispatchers.IO) { + return withContext(dispatchers.IO) { runCatching { val date: DateFormat = SimpleDateFormat("yyy-MM-dd-h-mm", Locale.CANADA) val filename = "Flipcash-Recovery-${date.format(Date())}.png" diff --git a/apps/flipcash/shared/appsettings/src/main/kotlin/com/flipcash/app/appsettings/inject/AppSettingModule.kt b/apps/flipcash/shared/appsettings/src/main/kotlin/com/flipcash/app/appsettings/inject/AppSettingModule.kt index c13a2295e..b8afc258f 100644 --- a/apps/flipcash/shared/appsettings/src/main/kotlin/com/flipcash/app/appsettings/inject/AppSettingModule.kt +++ b/apps/flipcash/shared/appsettings/src/main/kotlin/com/flipcash/app/appsettings/inject/AppSettingModule.kt @@ -3,6 +3,7 @@ package com.flipcash.app.appsettings.inject import android.content.Context import com.flipcash.app.appsettings.AppSettingsController import com.flipcash.app.appsettings.internal.InternalAppSettingsController +import com.flipcash.libs.coroutines.DispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -17,5 +18,6 @@ object AppSettingModule { @Singleton fun providesAppSettingsController( @ApplicationContext context: Context, - ): AppSettingsController = InternalAppSettingsController(context) + dispatchers: DispatcherProvider, + ): AppSettingsController = InternalAppSettingsController(context, dispatchers) } \ No newline at end of file diff --git a/apps/flipcash/shared/appsettings/src/main/kotlin/com/flipcash/app/appsettings/internal/InternalAppSettingsController.kt b/apps/flipcash/shared/appsettings/src/main/kotlin/com/flipcash/app/appsettings/internal/InternalAppSettingsController.kt index c2b7f1c41..1ed334d82 100644 --- a/apps/flipcash/shared/appsettings/src/main/kotlin/com/flipcash/app/appsettings/internal/InternalAppSettingsController.kt +++ b/apps/flipcash/shared/appsettings/src/main/kotlin/com/flipcash/app/appsettings/internal/InternalAppSettingsController.kt @@ -10,8 +10,8 @@ import androidx.datastore.preferences.preferencesDataStoreFile import com.flipcash.app.appsettings.AppSetting import com.flipcash.app.appsettings.AppSettingValue import com.flipcash.app.appsettings.AppSettingsController +import com.flipcash.libs.coroutines.DispatcherProvider import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -22,6 +22,7 @@ import kotlinx.coroutines.launch class InternalAppSettingsController( private val context: Context, + private val dispatchers: DispatcherProvider, ) : AppSettingsController { companion object { @@ -29,7 +30,7 @@ class InternalAppSettingsController( get() = booleanPreferencesKey(key) } - private val dataScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val dataScope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatchers.IO) private val appSettings = PreferenceDataStoreFactory.create( corruptionHandler = ReplaceFileCorruptionHandler( produceNewData = { emptyPreferences() } @@ -66,7 +67,7 @@ class InternalAppSettingsController( // TODO: analytics } - dataScope.launch(Dispatchers.IO) { + dataScope.launch { appSettings.edit { prefs -> prefs[setting.booleanPreferenceKey] = value } diff --git a/apps/flipcash/shared/appupdates/src/main/kotlin/com/flipcash/app/updates/inject/AppUpdateModule.kt b/apps/flipcash/shared/appupdates/src/main/kotlin/com/flipcash/app/updates/inject/AppUpdateModule.kt index ae64c5fa2..5a8ac4828 100644 --- a/apps/flipcash/shared/appupdates/src/main/kotlin/com/flipcash/app/updates/inject/AppUpdateModule.kt +++ b/apps/flipcash/shared/appupdates/src/main/kotlin/com/flipcash/app/updates/inject/AppUpdateModule.kt @@ -5,7 +5,7 @@ import com.flipcash.app.core.android.VersionInfo import com.flipcash.app.updates.AppUpdateController import com.flipcash.app.updates.internal.GooglePlayAppUpdateController import com.flipcash.app.userflags.UserFlagsCoordinator -import com.flipcash.services.user.UserManager +import com.flipcash.libs.coroutines.DispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -23,5 +23,6 @@ object AppUpdateModule { @ActivityContext context: Context, versionInfo: VersionInfo, userFlagsCoordinator: UserFlagsCoordinator, - ): AppUpdateController = GooglePlayAppUpdateController(context, versionInfo, userFlagsCoordinator) + dispatchers: DispatcherProvider, + ): AppUpdateController = GooglePlayAppUpdateController(context, versionInfo, userFlagsCoordinator, dispatchers) } \ No newline at end of file diff --git a/apps/flipcash/shared/appupdates/src/main/kotlin/com/flipcash/app/updates/internal/GooglePlayAppUpdateController.kt b/apps/flipcash/shared/appupdates/src/main/kotlin/com/flipcash/app/updates/internal/GooglePlayAppUpdateController.kt index c43c9e886..85f6a7b9d 100644 --- a/apps/flipcash/shared/appupdates/src/main/kotlin/com/flipcash/app/updates/internal/GooglePlayAppUpdateController.kt +++ b/apps/flipcash/shared/appupdates/src/main/kotlin/com/flipcash/app/updates/internal/GooglePlayAppUpdateController.kt @@ -15,10 +15,10 @@ import com.google.android.play.core.appupdate.AppUpdateManagerFactory import com.google.android.play.core.appupdate.AppUpdateOptions import com.google.android.play.core.install.model.AppUpdateType import com.google.android.play.core.install.model.UpdateAvailability +import com.flipcash.libs.coroutines.DispatcherProvider import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.scopes.ActivityScoped import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -35,8 +35,9 @@ class GooglePlayAppUpdateController @Inject constructor( private val context: Context, private val versionInfo: VersionInfo, private val userFlags: UserFlagsCoordinator, + private val dispatchers: DispatcherProvider, ) : AppUpdateController { - private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val scope = CoroutineScope(dispatchers.IO + SupervisorJob()) private val appUpdateManager: AppUpdateManager = AppUpdateManagerFactory.create(context) diff --git a/apps/flipcash/shared/authentication/build.gradle.kts b/apps/flipcash/shared/authentication/build.gradle.kts index 340df9402..72e3e9742 100644 --- a/apps/flipcash/shared/authentication/build.gradle.kts +++ b/apps/flipcash/shared/authentication/build.gradle.kts @@ -23,4 +23,5 @@ dependencies { testImplementation(kotlin("test")) testImplementation(libs.bundles.unit.testing) testImplementation(libs.robolectric) + testImplementation(testFixtures(project(":libs:coroutines"))) } diff --git a/apps/flipcash/shared/authentication/src/main/kotlin/com/flipcash/app/auth/AuthManager.kt b/apps/flipcash/shared/authentication/src/main/kotlin/com/flipcash/app/auth/AuthManager.kt index 0745d1b80..6464b85d2 100644 --- a/apps/flipcash/shared/authentication/src/main/kotlin/com/flipcash/app/auth/AuthManager.kt +++ b/apps/flipcash/shared/authentication/src/main/kotlin/com/flipcash/app/auth/AuthManager.kt @@ -21,10 +21,10 @@ import com.getcode.opencode.controllers.TokenController import com.getcode.opencode.model.core.ID import com.getcode.utils.TraceManager import com.getcode.utils.TraceType +import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.utils.network.retryable import com.getcode.utils.trace import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn @@ -49,8 +49,9 @@ class AuthManager @Inject constructor( private val appSettings: AppSettingsCoordinator, private val userFlags: UserFlagsCoordinator, private val contactCoordinator: ContactCoordinator, + private val dispatchers: DispatcherProvider, // private val analytics: AnalyticsService, -) : CoroutineScope by CoroutineScope(Dispatchers.IO) { +) : CoroutineScope by CoroutineScope(dispatchers.IO) { init { // Persist onboarding completion when the composable transitions Onboarding → Ready. diff --git a/apps/flipcash/shared/authentication/src/main/kotlin/com/flipcash/app/auth/internal/credentials/PassphraseCredentialManager.kt b/apps/flipcash/shared/authentication/src/main/kotlin/com/flipcash/app/auth/internal/credentials/PassphraseCredentialManager.kt index e210b719e..e54b22908 100644 --- a/apps/flipcash/shared/authentication/src/main/kotlin/com/flipcash/app/auth/internal/credentials/PassphraseCredentialManager.kt +++ b/apps/flipcash/shared/authentication/src/main/kotlin/com/flipcash/app/auth/internal/credentials/PassphraseCredentialManager.kt @@ -25,10 +25,10 @@ import com.getcode.opencode.managers.MnemonicManager import com.getcode.opencode.model.core.ID import com.getcode.utils.base58 import com.getcode.utils.encodeBase64 +import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.vendor.Base58 import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull @@ -46,6 +46,7 @@ class PassphraseCredentialManager @Inject constructor( private val userManager: UserManager, private val mnemonicManager: MnemonicManager, private val featureFlags: FeatureFlagController, + private val dispatchers: DispatcherProvider, ) { companion object { private val temporaryEntropyKey = stringPreferencesKey("temporaryEntropy") @@ -64,7 +65,7 @@ class PassphraseCredentialManager @Inject constructor( private val credentialLookupCache = mutableMapOf() - private val dataScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val dataScope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatchers.IO) private val storage = PreferenceDataStoreFactory.create( corruptionHandler = ReplaceFileCorruptionHandler( @@ -369,7 +370,7 @@ class PassphraseCredentialManager @Inject constructor( return storage.data .mapNotNull { preferences -> preferences[selectedAccountIdKey] } .map { accountId -> - withContext(Dispatchers.IO) { + withContext(dispatchers.IO) { getMetadata(accountId) } } diff --git a/apps/flipcash/shared/authentication/src/test/kotlin/com/flipcash/app/auth/AuthManagerTest.kt b/apps/flipcash/shared/authentication/src/test/kotlin/com/flipcash/app/auth/AuthManagerTest.kt index 0abdaf60a..b5e0d7ece 100644 --- a/apps/flipcash/shared/authentication/src/test/kotlin/com/flipcash/app/auth/AuthManagerTest.kt +++ b/apps/flipcash/shared/authentication/src/test/kotlin/com/flipcash/app/auth/AuthManagerTest.kt @@ -21,6 +21,7 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.verify +import com.flipcash.libs.coroutines.TestDispatcherProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -42,7 +43,8 @@ import kotlin.test.assertTrue @RunWith(RobolectricTestRunner::class) class AuthManagerTest { - private val testDispatcher = UnconfinedTestDispatcher() + private val dispatchers = TestDispatcherProvider(UnconfinedTestDispatcher()) + private val testDispatcher get() = dispatchers.testDispatcher private val credentialManager: PassphraseCredentialManager = mockk(relaxed = true) private val userManager: UserManager = mockk(relaxed = true) @@ -90,6 +92,7 @@ class AuthManagerTest { appSettings = appSettings, userFlags = userFlags, contactCoordinator = contactCoordinator, + dispatchers = dispatchers, ) } diff --git a/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/ChatCoordinator.kt b/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/ChatCoordinator.kt index 37e277f25..be956abb7 100644 --- a/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/ChatCoordinator.kt +++ b/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/ChatCoordinator.kt @@ -35,6 +35,7 @@ import com.flipcash.services.models.chat.PointerType import com.flipcash.services.models.chat.TypingNotification import com.flipcash.services.models.chat.TypingState import com.flipcash.app.tokens.TokenCoordinator +import com.flipcash.libs.coroutines.DispatcherProvider import com.flipcash.services.user.UserManager import com.getcode.opencode.model.accounts.AccountCluster import com.getcode.opencode.providers.SessionListener @@ -43,7 +44,6 @@ import com.getcode.utils.network.NetworkConnectivityListener import com.getcode.utils.decodeBase58 import com.getcode.utils.trace import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob @@ -82,6 +82,7 @@ class ChatCoordinator @Inject constructor( private val userManager: UserManager, private val tokenCoordinator: TokenCoordinator, private val featureFlags: FeatureFlagController, + private val dispatchers: DispatcherProvider, ) : SessionListener, DefaultLifecycleObserver { companion object { @@ -89,7 +90,8 @@ class ChatCoordinator @Inject constructor( private val HEARTBEAT_INTERVAL = 30.seconds } - private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val supervisorJob = SupervisorJob() + private val scope = CoroutineScope(dispatchers.IO + supervisorJob) private val cluster = MutableStateFlow(null) private val _state = MutableStateFlow(ChatState()) private var syncJob: Job? = null @@ -97,6 +99,7 @@ class ChatCoordinator @Inject constructor( private var eventStreamCollectJob: Job? = null private var feedObserverJob: Job? = null private var heartbeatJob: Job? = null + private var networkObserverJob: Job? = null private var backgroundedActiveChat: ChatId? = null val state: StateFlow @@ -148,7 +151,7 @@ class ChatCoordinator @Inject constructor( init { ProcessLifecycleOwner.get().lifecycle.addObserver(this) - cluster.filterNotNull() + networkObserverJob = cluster.filterNotNull() .flatMapLatest { networkObserver.state } .distinctUntilChanged() .filter { it.connected } @@ -337,12 +340,14 @@ class ChatCoordinator @Inject constructor( syncJob?.cancel() flagObserverJob?.cancel() feedObserverJob?.cancel() + networkObserverJob?.cancel() feedObserverJob = null _state.value = ChatState() cluster.value = null metadataDataSource.clear() messageDataSource.clear() memberDataSource.clear() + supervisorJob.cancel() trace(tag = TAG, message = "reset complete", type = TraceType.Process) } diff --git a/apps/flipcash/shared/chat/src/test/kotlin/com/flipcash/shared/chat/ChatCoordinatorEagerBalanceTest.kt b/apps/flipcash/shared/chat/src/test/kotlin/com/flipcash/shared/chat/ChatCoordinatorEagerBalanceTest.kt index 6d400abb4..cad89c8e1 100644 --- a/apps/flipcash/shared/chat/src/test/kotlin/com/flipcash/shared/chat/ChatCoordinatorEagerBalanceTest.kt +++ b/apps/flipcash/shared/chat/src/test/kotlin/com/flipcash/shared/chat/ChatCoordinatorEagerBalanceTest.kt @@ -6,6 +6,7 @@ import com.flipcash.app.persistence.sources.ChatMemberDataSource import com.flipcash.app.persistence.sources.ChatMessageDataSource import com.flipcash.app.persistence.sources.ChatMetadataDataSource import com.flipcash.app.persistence.sources.ContactDataSource +import com.flipcash.app.core.dispatchers.TestDispatchers import com.flipcash.app.tokens.TokenCoordinator import com.flipcash.services.controllers.ChatController import com.flipcash.services.controllers.ChatMessagingController @@ -26,6 +27,7 @@ import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Before @@ -47,6 +49,7 @@ class ChatCoordinatorEagerBalanceTest { private lateinit var tokenCoordinator: TokenCoordinator private lateinit var coordinator: ChatCoordinator + private lateinit var testDispatchers: TestDispatchers @Before fun setUp() { @@ -61,6 +64,8 @@ class ChatCoordinatorEagerBalanceTest { val chatController = mockk(relaxed = true) coEvery { chatController.getDmChatFeed() } returns Result.failure(RuntimeException("not needed")) + testDispatchers = TestDispatchers(TestCoroutineScheduler()) + coordinator = ChatCoordinator( chatController = chatController, messagingController = mockk(relaxed = true), @@ -74,6 +79,7 @@ class ChatCoordinatorEagerBalanceTest { userManager = userManager, tokenCoordinator = tokenCoordinator, featureFlags = mockk(relaxed = true), + dispatchers = testDispatchers, ) } @@ -114,35 +120,38 @@ class ChatCoordinatorEagerBalanceTest { } @Test - fun `incoming cash message triggers tokenCoordinator add`() = runTest { + fun `incoming cash message triggers tokenCoordinator add`() = runTest(testDispatchers.dispatcher) { triggerCollection() val amount = Fiat(fiat = 5.0, currencyCode = CurrencyCode.CAD) chatUpdatesChannel.send(chatUpdate(cashMessage(senderId = otherId, amount = amount))) advanceUntilIdle() coVerify(exactly = 1) { tokenCoordinator.add(mint, amount) } + coordinator.reset() } @Test - fun `self-sent cash message does not trigger tokenCoordinator add`() = runTest { + fun `self-sent cash message does not trigger tokenCoordinator add`() = runTest(testDispatchers.dispatcher) { triggerCollection() chatUpdatesChannel.send(chatUpdate(cashMessage(senderId = selfId))) advanceUntilIdle() coVerify(exactly = 0) { tokenCoordinator.add(any(), any()) } + coordinator.reset() } @Test - fun `text message does not trigger tokenCoordinator add`() = runTest { + fun `text message does not trigger tokenCoordinator add`() = runTest(testDispatchers.dispatcher) { triggerCollection() chatUpdatesChannel.send(chatUpdate(textMessage(senderId = otherId))) advanceUntilIdle() coVerify(exactly = 0) { tokenCoordinator.add(any(), any()) } + coordinator.reset() } @Test - fun `multiple cash messages in one update each trigger add`() = runTest { + fun `multiple cash messages in one update each trigger add`() = runTest(testDispatchers.dispatcher) { triggerCollection() val mintB = Mint("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBbbbbbbbbbbb") val amount1 = Fiat(fiat = 5.0, currencyCode = CurrencyCode.USD) @@ -155,10 +164,11 @@ class ChatCoordinatorEagerBalanceTest { coVerify(exactly = 1) { tokenCoordinator.add(mint, amount1) } coVerify(exactly = 1) { tokenCoordinator.add(mintB, amount2) } + coordinator.reset() } @Test - fun `mixed self and incoming messages only triggers add for incoming`() = runTest { + fun `mixed self and incoming messages only triggers add for incoming`() = runTest(testDispatchers.dispatcher) { triggerCollection() val incoming = cashMessage(senderId = otherId) val outgoing = cashMessage(senderId = selfId).copy(messageId = 3L) @@ -167,5 +177,6 @@ class ChatCoordinatorEagerBalanceTest { advanceUntilIdle() coVerify(exactly = 1) { tokenCoordinator.add(any(), any()) } + coordinator.reset() } } diff --git a/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt b/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt index 7ca232ba2..888ccd5f8 100644 --- a/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt +++ b/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt @@ -36,11 +36,11 @@ import com.getcode.opencode.providers.SessionListener import com.getcode.solana.keys.Checksum import com.getcode.solana.keys.PublicKey import com.getcode.utils.TraceType +import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.utils.network.NetworkConnectivityListener import com.getcode.utils.trace import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -77,6 +77,7 @@ class ContactCoordinator @Inject constructor( private val contactDataSource: ContactDataSource, private val userManager: UserManager, private val featureFlagController: FeatureFlagController, + private val dispatchers: DispatcherProvider, ) : SessionListener, DefaultLifecycleObserver { companion object { @@ -89,7 +90,7 @@ class ContactCoordinator @Inject constructor( produceNewData = { emptyPreferences() } ), migrations = listOf(), - scope = CoroutineScope(Dispatchers.IO + SupervisorJob()), + scope = CoroutineScope(dispatchers.IO + SupervisorJob()), produceFile = { context.preferencesDataStoreFile("contact-prefs") } ) @@ -104,7 +105,7 @@ class ContactCoordinator @Inject constructor( enum class SyncState { Idle, Syncing, Synced, Error } - private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val scope = CoroutineScope(dispatchers.IO + SupervisorJob()) private val cluster = MutableStateFlow(null) private val _state = MutableStateFlow(ContactState()) private val _isLinkedForPayment = MutableStateFlow(false) diff --git a/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/inject/FeatureFlagModule.kt b/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/inject/FeatureFlagModule.kt index ce296a461..677ce6bb2 100644 --- a/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/inject/FeatureFlagModule.kt +++ b/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/inject/FeatureFlagModule.kt @@ -3,6 +3,7 @@ package com.flipcash.app.featureflags.inject import android.content.Context import com.flipcash.app.featureflags.FeatureFlagController import com.flipcash.app.featureflags.internal.InternalFeatureFlagController +import com.flipcash.libs.coroutines.DispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -17,6 +18,7 @@ object FeatureFlagModule { @Singleton fun provideFeatureFlagController( @ApplicationContext context: Context, - ): FeatureFlagController = InternalFeatureFlagController(context) + dispatchers: DispatcherProvider, + ): FeatureFlagController = InternalFeatureFlagController(context, dispatchers) } \ No newline at end of file diff --git a/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/internal/InternalFeatureFlagController.kt b/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/internal/InternalFeatureFlagController.kt index 8fe222594..4b6b2f40b 100644 --- a/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/internal/InternalFeatureFlagController.kt +++ b/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/internal/InternalFeatureFlagController.kt @@ -11,9 +11,9 @@ import androidx.datastore.preferences.preferencesDataStoreFile import com.flipcash.app.featureflags.BetaFeature import com.flipcash.app.featureflags.FeatureFlag import com.flipcash.app.featureflags.FeatureFlagController +import com.flipcash.libs.coroutines.DispatcherProvider import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -27,6 +27,7 @@ import javax.inject.Inject internal class InternalFeatureFlagController @Inject constructor( @ApplicationContext private val context: Context, + private val dispatchers: DispatcherProvider, ) : FeatureFlagController { companion object { @@ -39,7 +40,7 @@ internal class InternalFeatureFlagController @Inject constructor( private val betaOverrideKey = booleanPreferencesKey("beta_override") } - private val dataScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val dataScope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatchers.IO) private val betaFlags = PreferenceDataStoreFactory.create( corruptionHandler = ReplaceFileCorruptionHandler( produceNewData = { emptyPreferences() } @@ -66,7 +67,7 @@ internal class InternalFeatureFlagController @Inject constructor( } override fun enableBetaFeatures() { - dataScope.launch(Dispatchers.IO) { + dataScope.launch { betaFlags.edit { prefs -> prefs[betaOverrideKey] = true } @@ -74,7 +75,7 @@ internal class InternalFeatureFlagController @Inject constructor( } override fun disableBetaFeatures() { - dataScope.launch(Dispatchers.IO) { + dataScope.launch { betaFlags.edit { prefs -> prefs[betaOverrideKey] = false FeatureFlag.availableEntries.forEach { flag -> @@ -92,7 +93,7 @@ internal class InternalFeatureFlagController @Inject constructor( .stateIn(dataScope, SharingStarted.Eagerly, false) override fun set(flag: FeatureFlag<*>, value: Boolean) { - dataScope.launch(Dispatchers.IO) { + dataScope.launch { betaFlags.edit { prefs -> prefs[flag.booleanPreferenceKey] = value } @@ -148,7 +149,7 @@ internal class InternalFeatureFlagController @Inject constructor( }.stateIn(dataScope, started = SharingStarted.Eagerly, flag.defaultEnabled) override fun setOption(flag: FeatureFlag<*>, optionKey: String) { - dataScope.launch(Dispatchers.IO) { + dataScope.launch { betaFlags.edit { prefs -> prefs[flag.optionPreferenceKey] = optionKey } diff --git a/apps/flipcash/shared/google-play-billing/src/main/kotlin/com/flipcash/app/billing/inject/BillingModule.kt b/apps/flipcash/shared/google-play-billing/src/main/kotlin/com/flipcash/app/billing/inject/BillingModule.kt index 288f28f93..7ed1ad433 100644 --- a/apps/flipcash/shared/google-play-billing/src/main/kotlin/com/flipcash/app/billing/inject/BillingModule.kt +++ b/apps/flipcash/shared/google-play-billing/src/main/kotlin/com/flipcash/app/billing/inject/BillingModule.kt @@ -4,6 +4,7 @@ import android.content.Context import com.flipcash.app.analytics.FlipcashAnalyticsService import com.flipcash.app.billing.BillingClient import com.flipcash.app.billing.internal.GooglePlayBillingClient +import com.flipcash.libs.coroutines.DispatcherProvider import com.flipcash.services.controllers.PurchaseController import com.flipcash.services.user.UserManager import dagger.Module @@ -22,6 +23,7 @@ object BillingModule { @ApplicationContext context: Context, purchases: PurchaseController, userManager: UserManager, - analytics: FlipcashAnalyticsService - ): BillingClient = GooglePlayBillingClient(context, userManager, purchases, analytics) + analytics: FlipcashAnalyticsService, + dispatchers: DispatcherProvider, + ): BillingClient = GooglePlayBillingClient(context, userManager, purchases, analytics, dispatchers) } \ No newline at end of file diff --git a/apps/flipcash/shared/google-play-billing/src/main/kotlin/com/flipcash/app/billing/internal/GooglePlayBillingClient.kt b/apps/flipcash/shared/google-play-billing/src/main/kotlin/com/flipcash/app/billing/internal/GooglePlayBillingClient.kt index 6e8a835ac..59f3c2940 100644 --- a/apps/flipcash/shared/google-play-billing/src/main/kotlin/com/flipcash/app/billing/internal/GooglePlayBillingClient.kt +++ b/apps/flipcash/shared/google-play-billing/src/main/kotlin/com/flipcash/app/billing/internal/GooglePlayBillingClient.kt @@ -37,7 +37,6 @@ import com.getcode.opencode.model.financial.CurrencyCode import com.getcode.utils.ErrorUtils import com.getcode.utils.MetadataBuilder import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow @@ -51,6 +50,7 @@ import kotlinx.coroutines.withContext import com.getcode.utils.SuppressibleException import com.getcode.utils.TraceType import com.getcode.utils.network.retryable +import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.utils.trace import dagger.hilt.android.qualifiers.ApplicationContext import kotlin.coroutines.resume @@ -61,6 +61,7 @@ internal class GooglePlayBillingClient( private val userManager: UserManager, private val purchases: PurchaseController, private val analytics: FlipcashAnalyticsService, + private val dispatchers: DispatcherProvider, ) : BillingClient, PurchasesUpdatedListener { companion object { @@ -70,7 +71,7 @@ internal class GooglePlayBillingClient( private const val baseDelayMillis = 1000L // Initial delay: 1 second } - private val scope = CoroutineScope(Dispatchers.IO) + private val scope = CoroutineScope(dispatchers.IO) private val _eventFlow: MutableSharedFlow = MutableSharedFlow() override val eventFlow: SharedFlow = _eventFlow.asSharedFlow() @@ -300,7 +301,7 @@ internal class GooglePlayBillingClient( scope.launch { if (product.isConsumable) { printLog("consumable") - val consumeResult = withContext(Dispatchers.IO) { + val consumeResult = withContext(dispatchers.IO) { client.consumePurchase( ConsumeParams.newBuilder() .setPurchaseToken(item.purchaseToken) @@ -320,7 +321,7 @@ internal class GooglePlayBillingClient( } } else { printLog("non-consumable") - val acknowledgeResult = withContext(Dispatchers.IO) { + val acknowledgeResult = withContext(dispatchers.IO) { client.acknowledgePurchase( AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(item.purchaseToken) diff --git a/apps/flipcash/shared/region-selection/core/src/main/kotlin/com/flipcash/app/currency/PreferredCurrencyController.kt b/apps/flipcash/shared/region-selection/core/src/main/kotlin/com/flipcash/app/currency/PreferredCurrencyController.kt index ee88dfa7e..6760b4090 100644 --- a/apps/flipcash/shared/region-selection/core/src/main/kotlin/com/flipcash/app/currency/PreferredCurrencyController.kt +++ b/apps/flipcash/shared/region-selection/core/src/main/kotlin/com/flipcash/app/currency/PreferredCurrencyController.kt @@ -15,10 +15,10 @@ import com.getcode.opencode.exchange.Exchange import com.getcode.opencode.model.financial.Currency import com.getcode.opencode.model.financial.CurrencyCode import com.getcode.util.locale.LocaleHelper +import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.utils.base58 import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged @@ -37,6 +37,7 @@ class PreferredCurrencyController @Inject constructor( private val userManager: UserManager, private val exchange: Exchange, private val settingsController: SettingsController, + private val dispatchers: DispatcherProvider, ) { companion object { fun initializedKeyForUser(userIdentifier: String) = @@ -49,7 +50,7 @@ class PreferredCurrencyController @Inject constructor( stringSetPreferencesKey("recents-$userIdentifier") } - private val dataScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val dataScope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatchers.IO) private val storage = PreferenceDataStoreFactory.create( corruptionHandler = ReplaceFileCorruptionHandler( diff --git a/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/RealSessionController.kt b/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/RealSessionController.kt index 27a2e6076..223a08156 100644 --- a/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/RealSessionController.kt +++ b/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/RealSessionController.kt @@ -36,6 +36,7 @@ import com.flipcash.app.tokens.TokenCoordinator import com.flipcash.app.tokens.TokenUpdater import com.flipcash.app.tokens.UsdcDepositSweep import com.flipcash.core.R +import com.flipcash.libs.coroutines.DispatcherProvider import com.flipcash.services.controllers.AccountController import com.flipcash.services.controllers.SettingsController import com.flipcash.services.user.AuthState @@ -64,7 +65,6 @@ import com.getcode.utils.network.NetworkConnectivityListener import com.getcode.utils.trace import com.kik.kikx.models.ScannableKikCode import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine @@ -139,9 +139,10 @@ class RealSessionController @Inject constructor( private val analytics: FlipcashAnalyticsService, private val usdcSweep: UsdcDepositSweep, appSettingsCoordinator: AppSettingsCoordinator, + private val dispatchers: DispatcherProvider, ) : SessionController { - private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val scope = CoroutineScope(dispatchers.IO + SupervisorJob()) private val _state = MutableStateFlow(SessionState()) override val state: StateFlow diff --git a/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/toast/ToastController.kt b/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/toast/ToastController.kt index f36e8da4f..5e8f6aaf3 100644 --- a/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/toast/ToastController.kt +++ b/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/toast/ToastController.kt @@ -3,10 +3,10 @@ package com.flipcash.app.session.internal.toast import com.flipcash.app.core.bill.BillToast import com.flipcash.app.core.internal.bill.BillController import com.flipcash.app.core.toast.ToastController +import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.opencode.model.financial.Fiat import com.getcode.opencode.model.financial.LocalFiat import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -18,13 +18,14 @@ import kotlin.time.Duration.Companion.seconds @Singleton class SessionToastController @Inject constructor( - private val billController: BillController + private val billController: BillController, + private val dispatchers: DispatcherProvider, ) : ToastController { companion object { val INITIAL_DELAY = 500.milliseconds val SHOW_DELAY = 3.seconds } - private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val scope = CoroutineScope(dispatchers.IO + SupervisorJob()) private val toastQueue = mutableListOf() private var isConsumingQueue = false diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt index b86f2b906..5bfb49f25 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt +++ b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt @@ -13,6 +13,7 @@ import androidx.lifecycle.ProcessLifecycleOwner import com.flipcash.app.featureflags.FeatureFlag import com.flipcash.app.featureflags.FeatureFlagController import com.flipcash.app.persistence.sources.TokenDataSource +import com.flipcash.libs.coroutines.DispatcherProvider import com.flipcash.app.tokens.core.ReservesBalanceProvider import com.getcode.opencode.controllers.AccountController import com.getcode.opencode.controllers.TokenController @@ -42,7 +43,6 @@ import com.getcode.utils.network.retryable import com.getcode.utils.trace import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay @@ -88,6 +88,7 @@ class TokenCoordinator @Inject constructor( private val verifiedFiatCalculator: VerifiedFiatCalculator, private val dataSource: TokenDataSource, private val featureFlags: FeatureFlagController, + private val dispatchers: DispatcherProvider, ) : TokenMetadataProvider, SessionListener, DefaultLifecycleObserver, ReservesBalanceProvider { companion object { @@ -95,7 +96,7 @@ class TokenCoordinator @Inject constructor( private val mintPreferenceKey = stringPreferencesKey("tokenMint") } - private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val scope = CoroutineScope(dispatchers.IO + SupervisorJob()) private var streamReserveStateJob: Job? = null private val selectedToken = PreferenceDataStoreFactory.create( @@ -470,7 +471,7 @@ class TokenCoordinator @Inject constructor( _state.update { it.copy(balances = it.balances + (token.address to newBalance)) } ensureValidTokenSelection() - scope.launch(Dispatchers.IO) { + scope.launch { updateTokenAccount(token.address) } } diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/UsdcDepositSweep.kt b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/UsdcDepositSweep.kt index 0e63e2ddd..758a2bcab 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/UsdcDepositSweep.kt +++ b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/UsdcDepositSweep.kt @@ -9,10 +9,10 @@ import com.getcode.opencode.model.financial.Fiat import com.getcode.solana.keys.Mint import com.getcode.solana.keys.base58 import com.getcode.utils.TraceType +import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.utils.network.retryable import com.getcode.utils.trace import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch @@ -25,6 +25,7 @@ class UsdcDepositSweep( private val accountController: AccountController, private val tokenCoordinator: TokenCoordinator, private val balancePoller: BalancePoller, + private val dispatchers: DispatcherProvider, private val maxRetries: Int = MAX_RETRIES, private val initialDelay: Duration = INITIAL_DELAY, private val backoffFactor: Double = BACKOFF_FACTOR, @@ -36,11 +37,13 @@ class UsdcDepositSweep( accountController: AccountController, tokenCoordinator: TokenCoordinator, balancePoller: BalancePoller, + dispatchers: DispatcherProvider, ) : this( transactionOperations = transactionOperations, accountController = accountController, tokenCoordinator = tokenCoordinator, balancePoller = balancePoller, + dispatchers = dispatchers, maxRetries = MAX_RETRIES, initialDelay = INITIAL_DELAY, backoffFactor = BACKOFF_FACTOR, @@ -48,7 +51,7 @@ class UsdcDepositSweep( pollMaxAttempts = POLL_MAX_ATTEMPTS, ) - private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val scope = CoroutineScope(dispatchers.IO + SupervisorJob()) private var activeJob: Job? = null fun execute(owner: AccountCluster) { diff --git a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt index 7d43cc7f1..41e79c754 100644 --- a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -32,6 +32,7 @@ class AndroidFeatureConventionPlugin : Plugin { "implementation"(libs.findLibrary("rinku-compose").get()) // Common project dependencies + "implementation"(project(":libs:coroutines")) "implementation"(project(":libs:logging")) "implementation"(project(":ui:core")) "implementation"(project(":ui:components")) diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 87bc9215e..ef11545d9 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -64,6 +64,7 @@ class AndroidLibraryConventionPlugin : Plugin { tasks.withType { failOnNoDiscoveredTests.set(false) + maxHeapSize = "2g" } dependencies { diff --git a/libs/coroutines/build.gradle.kts b/libs/coroutines/build.gradle.kts index d06a4c2b5..0be0cc0f8 100644 --- a/libs/coroutines/build.gradle.kts +++ b/libs/coroutines/build.gradle.kts @@ -5,4 +5,11 @@ plugins { android { namespace = "${Gradle.codeNamespace}.libs.coroutines" + testFixtures { + enable = true + } +} + +dependencies { + testFixturesImplementation(libs.kotlinx.coroutines.test) } diff --git a/apps/flipcash/core/src/test/kotlin/com/flipcash/app/core/dispatchers/TestDispatcherProvider.kt b/libs/coroutines/src/testFixtures/kotlin/com/flipcash/libs/coroutines/TestDispatcherProvider.kt similarity index 82% rename from apps/flipcash/core/src/test/kotlin/com/flipcash/app/core/dispatchers/TestDispatcherProvider.kt rename to libs/coroutines/src/testFixtures/kotlin/com/flipcash/libs/coroutines/TestDispatcherProvider.kt index b375b0c44..6c97dc853 100644 --- a/apps/flipcash/core/src/test/kotlin/com/flipcash/app/core/dispatchers/TestDispatcherProvider.kt +++ b/libs/coroutines/src/testFixtures/kotlin/com/flipcash/libs/coroutines/TestDispatcherProvider.kt @@ -1,6 +1,5 @@ -package com.flipcash.app.core.dispatchers +package com.flipcash.libs.coroutines -import com.flipcash.libs.coroutines.DispatcherProvider import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestDispatcher diff --git a/libs/opengraph/build.gradle.kts b/libs/opengraph/build.gradle.kts index 79f0aa3a1..3d9b149e8 100644 --- a/libs/opengraph/build.gradle.kts +++ b/libs/opengraph/build.gradle.kts @@ -8,6 +8,7 @@ android { dependencies { implementation("org.jsoup:jsoup:1.22.2") + implementation(project(":libs:coroutines")) implementation(project(":libs:encryption:utils")) implementation(libs.bundles.kotlinx.serialization) diff --git a/libs/opengraph/src/main/kotlin/com/getcode/libs/opengraph/OpenGraphCacheProvider.kt b/libs/opengraph/src/main/kotlin/com/getcode/libs/opengraph/OpenGraphCacheProvider.kt index 62f5f850f..a407964d1 100644 --- a/libs/opengraph/src/main/kotlin/com/getcode/libs/opengraph/OpenGraphCacheProvider.kt +++ b/libs/opengraph/src/main/kotlin/com/getcode/libs/opengraph/OpenGraphCacheProvider.kt @@ -7,13 +7,13 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStoreFile +import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.libs.opengraph.cache.CacheProvider import com.getcode.libs.opengraph.model.OpenGraphResult import com.getcode.utils.base64 import com.getcode.utils.decodeBase64 import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map @@ -25,10 +25,11 @@ import javax.inject.Inject class OpenGraphCacheProvider @Inject constructor( @ApplicationContext - context: Context + context: Context, + private val dispatchers: DispatcherProvider, ): CacheProvider { - private val dataScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val dataScope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatchers.IO) private val storage = PreferenceDataStoreFactory.create( corruptionHandler = ReplaceFileCorruptionHandler( @@ -47,7 +48,7 @@ class OpenGraphCacheProvider @Inject constructor( } override suspend fun set(openGraphResult: OpenGraphResult, url: String) { - dataScope.launch(Dispatchers.IO) { + dataScope.launch { storage.edit { prefs -> prefs[stringPreferencesKey(url)] = Json.encodeToString(openGraphResult).encodeToByteArray().base64 }