diff --git a/CHANGELOG.md b/CHANGELOG.md index ccb1d2a8..70d318f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2 ## [Unreleased] +### Added +- Balance now also displays USD value +- An option to enter USD amount in Send Transaction screen +- Dependency injection using Koin has been added to the project. This helps us keep the codebase organized while + adding new app features. + ## [1.1.6 (712)] - 2024-09-04 ### Added diff --git a/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt b/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt index 91165649..092c7b2c 100644 --- a/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt +++ b/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt @@ -1,14 +1,24 @@ package co.electriccoin.zcash.app import co.electriccoin.zcash.crash.android.GlobalCrashReporter +import co.electriccoin.zcash.di.coreModule +import co.electriccoin.zcash.di.repositoryModule +import co.electriccoin.zcash.di.useCaseModule +import co.electriccoin.zcash.di.viewModelModule +import co.electriccoin.zcash.preference.StandardPreferenceProvider import co.electriccoin.zcash.spackle.StrictModeCompat import co.electriccoin.zcash.spackle.Twig import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys -import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton import kotlinx.coroutines.launch +import org.koin.android.ext.android.inject +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.context.startKoin @Suppress("unused") class ZcashApplication : CoroutineApplication() { + private val standardPreferenceProvider by inject() + override fun onCreate() { super.onCreate() @@ -16,6 +26,17 @@ class ZcashApplication : CoroutineApplication() { configureStrictMode() + startKoin { + androidLogger() + androidContext(this@ZcashApplication) + modules( + coreModule, + repositoryModule, + useCaseModule, + viewModelModule + ) + } + // Since analytics will need disk IO internally, we want this to be registered after strict // mode is configured to ensure none of that IO happens on the main thread configureAnalytics() @@ -40,8 +61,7 @@ class ZcashApplication : CoroutineApplication() { private fun configureAnalytics() { if (GlobalCrashReporter.register(this)) { applicationScope.launch { - val prefs = StandardPreferenceSingleton.getInstance(applicationContext) - StandardPreferenceKeys.IS_ANALYTICS_ENABLED.observe(prefs).collect { + StandardPreferenceKeys.IS_ANALYTICS_ENABLED.observe(standardPreferenceProvider()).collect { if (it) { GlobalCrashReporter.enable() } else { diff --git a/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/AndroidConfigurationFactory.kt b/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/AndroidConfigurationFactory.kt index 0f695df8..e0112df2 100644 --- a/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/AndroidConfigurationFactory.kt +++ b/configuration-impl-android-lib/src/main/java/co/electriccoin/zcash/configuration/AndroidConfigurationFactory.kt @@ -1,25 +1,12 @@ package co.electriccoin.zcash.configuration -import android.content.Context import co.electriccoin.zcash.configuration.api.ConfigurationProvider import co.electriccoin.zcash.configuration.api.MergingConfigurationProvider import co.electriccoin.zcash.configuration.internal.intent.IntentConfigurationProvider -import co.electriccoin.zcash.spackle.LazyWithArgument import kotlinx.collections.immutable.toPersistentList object AndroidConfigurationFactory { - private val instance = - LazyWithArgument { context -> - new(context) - } - - fun getInstance(context: Context): ConfigurationProvider = instance.getInstance(context) - - // Context will be needed for most cloud providers, e.g. to integrate with Firebase or other - // remote configuration providers. - private fun new( - @Suppress("UNUSED_PARAMETER") context: Context - ): ConfigurationProvider { + fun new(): ConfigurationProvider { val configurationProviders = buildList { // For ordering, ensure the IntentConfigurationProvider is first so that it can diff --git a/docs/Architecture.md b/docs/Architecture.md index 59bc9b8e..66cc3760 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -117,8 +117,6 @@ As a specific example, the "Request ZEC" button on the home screen is currently # Shared Resources There are some app-wide resources that share a common namespace, and these should be documented here to make it easy to ensure there are no collisions. -* SharedPreferences - * "co.electriccoin.zcash.encrypted" is defined as a preference file in `EncryptedPreferenceSingleton.kt` * Databases * Some databases are defined by the SDK * Notification IDs diff --git a/gradle.properties b/gradle.properties index 2ba6339a..0346ee31 100644 --- a/gradle.properties +++ b/gradle.properties @@ -155,6 +155,7 @@ GOOGLE_PLAY_SERVICES_GRADLE_PLUGIN_VERSION=4.4.1 GRADLE_VERSIONS_PLUGIN_VERSION=0.51.0 JGIT_VERSION=6.4.0.202211300538-r KTLINT_VERSION=1.2.1 +KOIN_VERSION=3.5.6 ACCOMPANIST_PERMISSIONS_VERSION=0.34.0 ANDROIDX_ACTIVITY_VERSION=1.8.2 diff --git a/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/AndroidPreferenceProvider.kt b/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/AndroidPreferenceProvider.kt index af5037b4..f54aa23e 100644 --- a/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/AndroidPreferenceProvider.kt +++ b/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/AndroidPreferenceProvider.kt @@ -28,12 +28,11 @@ import java.util.concurrent.Executors * this instance lives for the lifetime of the application. Constructing multiple instances will * potentially corrupt preference data and will leak resources. */ -class AndroidPreferenceProvider( +class AndroidPreferenceProvider private constructor( private val sharedPreferences: SharedPreferences, private val dispatcher: CoroutineDispatcher ) : PreferenceProvider { private val mutex = Mutex() - /* * Implementation note: EncryptedSharedPreferences are not thread-safe, so this implementation * confines them to a single background thread. @@ -76,7 +75,7 @@ class AndroidPreferenceProvider( } override fun observe(key: PreferenceKey): Flow = - callbackFlow { + callbackFlow { val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> // Callback on main thread @@ -122,15 +121,13 @@ class AndroidPreferenceProvider( */ val singleThreadedDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() - val mainKey = - withContext(singleThreadedDispatcher) { - MasterKey.Builder(context).apply { - setKeyScheme(MasterKey.KeyScheme.AES256_GCM) - }.build() - } - val sharedPreferences = withContext(singleThreadedDispatcher) { + val mainKey = + MasterKey.Builder(context).apply { + setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + }.build() + EncryptedSharedPreferences.create( context, filename, diff --git a/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/EncryptedPreferenceProvider.kt b/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/EncryptedPreferenceProvider.kt new file mode 100644 index 00000000..c2b6f95c --- /dev/null +++ b/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/EncryptedPreferenceProvider.kt @@ -0,0 +1,10 @@ +package co.electriccoin.zcash.preference + +import android.content.Context +import co.electriccoin.zcash.preference.api.PreferenceProvider + +class EncryptedPreferenceProvider(private val context: Context) : PreferenceHolder() { + override suspend fun create(): PreferenceProvider { + return AndroidPreferenceProvider.newEncrypted(context, "co.electriccoin.zcash.encrypted") + } +} diff --git a/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/PreferenceHolder.kt b/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/PreferenceHolder.kt new file mode 100644 index 00000000..8108b7ae --- /dev/null +++ b/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/PreferenceHolder.kt @@ -0,0 +1,26 @@ +package co.electriccoin.zcash.preference + +import co.electriccoin.zcash.preference.api.PreferenceProvider +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +abstract class PreferenceHolder { + private var preferenceProvider: PreferenceProvider? = null + + private val mutex = Mutex() + + suspend operator fun invoke(): PreferenceProvider = + mutex.withLock { + val preferenceProvider = preferenceProvider + + if (preferenceProvider != null) { + return preferenceProvider + } + + val newPreferenceProvider = create() + this.preferenceProvider = newPreferenceProvider + return newPreferenceProvider + } + + protected abstract suspend fun create(): PreferenceProvider +} diff --git a/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/StandardPreferenceProvider.kt b/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/StandardPreferenceProvider.kt new file mode 100644 index 00000000..f698dcdd --- /dev/null +++ b/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/StandardPreferenceProvider.kt @@ -0,0 +1,10 @@ +package co.electriccoin.zcash.preference + +import android.content.Context +import co.electriccoin.zcash.preference.api.PreferenceProvider + +class StandardPreferenceProvider(private val context: Context) : PreferenceHolder() { + override suspend fun create(): PreferenceProvider { + return AndroidPreferenceProvider.newStandard(context, "co.electriccoin.zcash") + } +} diff --git a/sdk-ext-lib/src/main/java/cash/z/ecc/sdk/Constants.kt b/sdk-ext-lib/src/main/java/cash/z/ecc/sdk/Constants.kt new file mode 100644 index 00000000..c3fc040d --- /dev/null +++ b/sdk-ext-lib/src/main/java/cash/z/ecc/sdk/Constants.kt @@ -0,0 +1,5 @@ +package cash.z.ecc.sdk + +import kotlin.time.Duration.Companion.seconds + +val ANDROID_STATE_FLOW_TIMEOUT = 5.seconds diff --git a/settings.gradle.kts b/settings.gradle.kts index cd151647..04c97daa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -183,6 +183,7 @@ dependencyResolutionManagement { val zcashBip39Version = extra["ZCASH_BIP39_VERSION"].toString() val zcashSdkVersion = extra["ZCASH_SDK_VERSION"].toString() val zxingVersion = extra["ZXING_VERSION"].toString() + val koinVersion = extra["KOIN_VERSION"].toString() // Standalone versions version("flank", flankVersion) @@ -241,6 +242,8 @@ dependencyResolutionManagement { library("zcash-sdk-incubator", "cash.z.ecc.android:zcash-android-sdk-incubator:$zcashSdkVersion") library("zcash-bip39", "cash.z.ecc.android:kotlin-bip39:$zcashBip39Version") library("zxing", "com.google.zxing:core:$zxingVersion") + library("koin", "io.insert-koin:koin-android:$koinVersion") + library("koin-compose", "io.insert-koin:koin-androidx-compose:$koinVersion") // Test libraries library("androidx-compose-test-junit", "androidx.compose.ui:ui-test-junit4:$androidxComposeVersion") @@ -294,13 +297,6 @@ dependencyResolutionManagement { "androidx-viewmodel-compose" ) ) - bundle( - "play-update", - listOf( - "play-update", - "play-update-ktx", - ) - ) bundle( "androidx-test", listOf( @@ -312,6 +308,20 @@ dependencyResolutionManagement { "androidx-test-runner" ) ) + bundle( + "koin", + listOf( + "koin", + "koin-compose", + ) + ) + bundle( + "play-update", + listOf( + "play-update", + "play-update-ktx", + ) + ) } } } diff --git a/test-lib/src/main/kotlin/co/electriccoin/zcash/test/ZcashUiTestRunner.kt b/test-lib/src/main/kotlin/co/electriccoin/zcash/test/ZcashUiTestRunner.kt index 1e54cf84..9d9a9db3 100644 --- a/test-lib/src/main/kotlin/co/electriccoin/zcash/test/ZcashUiTestRunner.kt +++ b/test-lib/src/main/kotlin/co/electriccoin/zcash/test/ZcashUiTestRunner.kt @@ -6,7 +6,7 @@ import android.os.PowerManager import androidx.test.core.app.ApplicationProvider import androidx.test.runner.AndroidJUnitRunner -class ZcashUiTestRunner : AndroidJUnitRunner() { +open class ZcashUiTestRunner : AndroidJUnitRunner() { private lateinit var wakeLock: PowerManager.WakeLock override fun onCreate(arguments: Bundle?) { diff --git a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Override.kt b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Override.kt index 6f36aca0..51b7663b 100644 --- a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Override.kt +++ b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Override.kt @@ -29,9 +29,8 @@ fun Override( val contextWrapper = run { val context = LocalContext.current - object : ContextThemeWrapper() { + object : ContextThemeWrapper(context, null) { init { - attachBaseContext(context) applyOverrideConfiguration(configuration) } } diff --git a/ui-lib/build.gradle.kts b/ui-lib/build.gradle.kts index 55d430a9..14d1cc3d 100644 --- a/ui-lib/build.gradle.kts +++ b/ui-lib/build.gradle.kts @@ -95,10 +95,11 @@ dependencies { implementation(libs.androidx.lifecycle.livedata) implementation(libs.androidx.splash) implementation(libs.androidx.workmanager) - implementation(libs.bundles.androidx.biometric) + api(libs.bundles.androidx.biometric) implementation(libs.bundles.androidx.camera) implementation(libs.bundles.androidx.compose.core) implementation(libs.bundles.androidx.compose.extended) + api(libs.bundles.koin) implementation(libs.bundles.play.update) implementation(libs.kotlin.stdlib) implementation(libs.kotlinx.coroutines.android) @@ -114,12 +115,12 @@ dependencies { implementation(projects.buildInfoLib) implementation(projects.configurationApiLib) - implementation(projects.configurationImplAndroidLib) implementation(projects.crashAndroidLib) implementation(projects.preferenceApiLib) implementation(projects.preferenceImplAndroidLib) - implementation(projects.sdkExtLib) implementation(projects.spackleAndroidLib) + api(projects.configurationImplAndroidLib) + api(projects.sdkExtLib) api(projects.uiDesignLib) androidTestImplementation(projects.testLib) diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceKeysTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceKeysTest.kt deleted file mode 100644 index be7c5403..00000000 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceKeysTest.kt +++ /dev/null @@ -1,26 +0,0 @@ -package co.electriccoin.zcash.ui.preference - -import androidx.test.filters.SmallTest -import co.electriccoin.zcash.preference.model.entry.PreferenceDefault -import org.junit.Test -import kotlin.reflect.full.memberProperties -import kotlin.test.assertFalse - -class EncryptedPreferenceKeysTest { - // This test is primary to prevent copy-paste errors in preference keys - @SmallTest - @Test - fun unique_keys() { - val fieldValueSet = mutableSetOf() - - EncryptedPreferenceKeys::class.memberProperties - .map { it.getter.call(EncryptedPreferenceKeys) } - .map { it as PreferenceDefault<*> } - .map { it.key } - .forEach { - assertFalse(fieldValueSet.contains(it.key), "Duplicate key $it") - - fieldValueSet.add(it.key) - } - } -} diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfoTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfoTest.kt index fe908727..ef623582 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfoTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfoTest.kt @@ -1,5 +1,6 @@ package co.electriccoin.zcash.ui.screen.support.model +import co.electriccoin.zcash.configuration.AndroidConfigurationFactory import co.electriccoin.zcash.ui.test.getAppContext import kotlinx.coroutines.test.runTest import org.junit.Test @@ -10,7 +11,7 @@ class SupportInfoTest { @Test fun filter_time() = runTest { - val supportInfo = SupportInfo.new(getAppContext()) + val supportInfo = SupportInfo.new(getAppContext(), AndroidConfigurationFactory.new()) val individualExpected = supportInfo.timeInfo.toSupportString() @@ -24,7 +25,7 @@ class SupportInfoTest { @Test fun filter_app() = runTest { - val supportInfo = SupportInfo.new(getAppContext()) + val supportInfo = SupportInfo.new(getAppContext(), AndroidConfigurationFactory.new()) val individualExpected = supportInfo.appInfo.toSupportString() @@ -38,7 +39,7 @@ class SupportInfoTest { @Test fun filter_os() = runTest { - val supportInfo = SupportInfo.new(getAppContext()) + val supportInfo = SupportInfo.new(getAppContext(), AndroidConfigurationFactory.new()) val individualExpected = supportInfo.operatingSystemInfo.toSupportString() @@ -52,7 +53,7 @@ class SupportInfoTest { @Test fun filter_device() = runTest { - val supportInfo = SupportInfo.new(getAppContext()) + val supportInfo = SupportInfo.new(getAppContext(), AndroidConfigurationFactory.new()) val individualExpected = supportInfo.deviceInfo.toSupportString() @@ -66,7 +67,7 @@ class SupportInfoTest { @Test fun filter_crash() = runTest { - val supportInfo = SupportInfo.new(getAppContext()) + val supportInfo = SupportInfo.new(getAppContext(), AndroidConfigurationFactory.new()) val individualExpected = supportInfo.crashInfo.toCrashSupportString() @@ -77,7 +78,7 @@ class SupportInfoTest { @Test fun filter_environment() = runTest { - val supportInfo = SupportInfo.new(getAppContext()) + val supportInfo = SupportInfo.new(getAppContext(), AndroidConfigurationFactory.new()) val individualExpected = supportInfo.environmentInfo.toSupportString() @@ -91,7 +92,7 @@ class SupportInfoTest { @Test fun filter_permission() = runTest { - val supportInfo = SupportInfo.new(getAppContext()) + val supportInfo = SupportInfo.new(getAppContext(), AndroidConfigurationFactory.new()) val individualExpected = supportInfo.permissionInfo.toPermissionSupportString() diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/util/AppUpdateCheckerImpTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/util/AppUpdateCheckerImplTest.kt similarity index 96% rename from ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/util/AppUpdateCheckerImpTest.kt rename to ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/util/AppUpdateCheckerImplTest.kt index 56c3415d..174a87eb 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/util/AppUpdateCheckerImpTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/util/AppUpdateCheckerImplTest.kt @@ -6,7 +6,7 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.filters.MediumTest import cash.z.ecc.android.sdk.ext.onFirst -import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp +import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImpl import co.electriccoin.zcash.ui.screen.update.model.UpdateInfo import co.electriccoin.zcash.ui.screen.update.model.UpdateState import co.electriccoin.zcash.ui.test.getAppContext @@ -19,13 +19,13 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue -class AppUpdateCheckerImpTest { +class AppUpdateCheckerImplTest { @get:Rule val composeTestRule = createAndroidComposeRule() companion object { val context: Context = getAppContext() - val updateChecker = AppUpdateCheckerImp.new() + val updateChecker = AppUpdateCheckerImpl() } private fun getAppUpdateInfoFlow(): Flow { diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewAndroidTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewAndroidTest.kt index 2668e34b..b6833455 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewAndroidTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewAndroidTest.kt @@ -23,8 +23,8 @@ class UpdateViewAndroidTest : UiTestPrerequisites() { private fun newTestSetup(updateInfo: UpdateInfo) = UpdateViewAndroidTestSetup( - composeTestRule, - updateInfo + updateInfo, + composeTestRule ).apply { setDefaultContent() } diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewAndroidTestSetup.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewAndroidTestSetup.kt index ed7c660b..7e3de608 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewAndroidTestSetup.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/view/UpdateViewAndroidTestSetup.kt @@ -3,21 +3,36 @@ package co.electriccoin.zcash.ui.screen.update.view import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.electriccoin.zcash.ui.common.compose.LocalActivity import co.electriccoin.zcash.ui.design.theme.ZcashTheme +import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerMock import co.electriccoin.zcash.ui.screen.update.WrapUpdate import co.electriccoin.zcash.ui.screen.update.model.UpdateInfo +import co.electriccoin.zcash.ui.screen.update.viewmodel.UpdateViewModel class UpdateViewAndroidTestSetup( - private val composeTestRule: AndroidComposeTestRule<*, *>, - private val updateInfo: UpdateInfo + updateInfo: UpdateInfo, + private val composeTestRule: AndroidComposeTestRule<*, *> ) { + private val viewModel = + UpdateViewModel( + application = composeTestRule.activity.application, + updateInfo = updateInfo, + appUpdateChecker = AppUpdateCheckerMock.new() + ) + @Composable @Suppress("TestFunctionName") fun DefaultContent() { CompositionLocalProvider(LocalActivity provides composeTestRule.activity) { + val updateInfo = viewModel.updateInfo.collectAsStateWithLifecycle().value + // val appUpdateInfo = updateInfo.appUpdateInfo WrapUpdate( - updateInfo + updateInfo = updateInfo, + checkForUpdate = viewModel::checkForAppUpdate, + remindLater = viewModel::remindLater, + goForUpdate = {} ) } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/di/CoreModule.kt b/ui-lib/src/main/java/co/electriccoin/zcash/di/CoreModule.kt new file mode 100644 index 00000000..8c38f8d0 --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/di/CoreModule.kt @@ -0,0 +1,40 @@ +package co.electriccoin.zcash.di + +import androidx.biometric.BiometricManager +import cash.z.ecc.android.sdk.WalletCoordinator +import co.electriccoin.zcash.configuration.AndroidConfigurationFactory +import co.electriccoin.zcash.global.newInstance +import co.electriccoin.zcash.preference.EncryptedPreferenceProvider +import co.electriccoin.zcash.preference.StandardPreferenceProvider +import co.electriccoin.zcash.preference.model.entry.PreferenceKey +import co.electriccoin.zcash.ui.preference.PersistableWalletPreferenceDefault +import co.electriccoin.zcash.ui.screen.update.AppUpdateChecker +import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImpl +import org.koin.core.module.dsl.factoryOf +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module + +val coreModule = + module { + single { + WalletCoordinator.newInstance( + context = get(), + encryptedPreferenceProvider = get(), + persistableWalletPreference = get(), + ) + } + + single { + PersistableWalletPreferenceDefault(PreferenceKey("persistable_wallet")) + } + + singleOf(::StandardPreferenceProvider) + singleOf(::EncryptedPreferenceProvider) + + single { BiometricManager.from(get()) } + + factoryOf(::AppUpdateCheckerImpl) bind AppUpdateChecker::class + + factory { AndroidConfigurationFactory.new() } + } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/di/KoinActivityViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/di/KoinActivityViewModel.kt new file mode 100644 index 00000000..4faccaa1 --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/di/KoinActivityViewModel.kt @@ -0,0 +1,47 @@ +package co.electriccoin.zcash.di + +import android.content.Context +import android.content.ContextWrapper +import androidx.activity.ComponentActivity +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.CreationExtras +import org.koin.androidx.compose.defaultExtras +import org.koin.androidx.compose.koinViewModel +import org.koin.compose.currentKoinScope +import org.koin.core.parameter.ParametersDefinition +import org.koin.core.qualifier.Qualifier +import org.koin.core.scope.Scope + +@Suppress("LongParameterList") +@Composable +inline fun koinActivityViewModel( + qualifier: Qualifier? = null, + viewModelStoreOwner: ViewModelStoreOwner = LocalContext.componentActivity(), + key: String? = null, + extras: CreationExtras = defaultExtras(LocalContext.componentActivity()), + scope: Scope = currentKoinScope(), + noinline parameters: ParametersDefinition? = null, +) = koinViewModel( + qualifier = qualifier, + viewModelStoreOwner = viewModelStoreOwner, + key = key, + extras = extras, + scope = scope, + parameters = parameters, +) + +@Composable +fun ProvidableCompositionLocal.componentActivity(): ComponentActivity { + val context = this.current + return when { + context is ComponentActivity -> context + context is ContextWrapper && context.baseContext is ComponentActivity -> + context.baseContext as ComponentActivity + + else -> throw ClassCastException("Context is not a ComponentActivity") + } +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/di/RepositoryModule.kt b/ui-lib/src/main/java/co/electriccoin/zcash/di/RepositoryModule.kt new file mode 100644 index 00000000..5712afdc --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/di/RepositoryModule.kt @@ -0,0 +1,12 @@ +package co.electriccoin.zcash.di + +import co.electriccoin.zcash.ui.common.repository.WalletRepository +import co.electriccoin.zcash.ui.common.repository.WalletRepositoryImpl +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module + +val repositoryModule = + module { + singleOf(::WalletRepositoryImpl) bind WalletRepository::class + } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt b/ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt new file mode 100644 index 00000000..855fbc10 --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt @@ -0,0 +1,12 @@ +package co.electriccoin.zcash.di + +import co.electriccoin.zcash.ui.common.usecase.GetSynchronizerUseCase +import co.electriccoin.zcash.ui.common.usecase.ObserveSynchronizerUseCase +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val useCaseModule = + module { + singleOf(::ObserveSynchronizerUseCase) + singleOf(::GetSynchronizerUseCase) + } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/di/ViewModelModule.kt b/ui-lib/src/main/java/co/electriccoin/zcash/di/ViewModelModule.kt new file mode 100644 index 00000000..ef876a20 --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/di/ViewModelModule.kt @@ -0,0 +1,38 @@ +package co.electriccoin.zcash.di + +import co.electriccoin.zcash.ui.common.viewmodel.AuthenticationViewModel +import co.electriccoin.zcash.ui.common.viewmodel.CheckUpdateViewModel +import co.electriccoin.zcash.ui.common.viewmodel.HomeViewModel +import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel +import co.electriccoin.zcash.ui.screen.account.viewmodel.TransactionHistoryViewModel +import co.electriccoin.zcash.ui.screen.onboarding.viewmodel.OnboardingViewModel +import co.electriccoin.zcash.ui.screen.restore.viewmodel.RestoreViewModel +import co.electriccoin.zcash.ui.screen.restoresuccess.viewmodel.RestoreSuccessViewModel +import co.electriccoin.zcash.ui.screen.sendconfirmation.viewmodel.CreateTransactionsViewModel +import co.electriccoin.zcash.ui.screen.settings.viewmodel.ScreenBrightnessViewModel +import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel +import co.electriccoin.zcash.ui.screen.support.viewmodel.SupportViewModel +import co.electriccoin.zcash.ui.screen.update.viewmodel.UpdateViewModel +import co.electriccoin.zcash.ui.screen.warning.viewmodel.StorageCheckViewModel +import co.electriccoin.zcash.ui.screen.whatsnew.viewmodel.WhatsNewViewModel +import org.koin.androidx.viewmodel.dsl.viewModelOf +import org.koin.dsl.module + +val viewModelModule = + module { + viewModelOf(::WalletViewModel) + viewModelOf(::AuthenticationViewModel) + viewModelOf(::CheckUpdateViewModel) + viewModelOf(::HomeViewModel) + viewModelOf(::TransactionHistoryViewModel) + viewModelOf(::OnboardingViewModel) + viewModelOf(::StorageCheckViewModel) + viewModelOf(::RestoreViewModel) + viewModelOf(::ScreenBrightnessViewModel) + viewModelOf(::SettingsViewModel) + viewModelOf(::SupportViewModel) + viewModelOf(::CreateTransactionsViewModel) + viewModelOf(::RestoreSuccessViewModel) + viewModelOf(::WhatsNewViewModel) + viewModelOf(::UpdateViewModel) + } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/global/WalletCoordinator.kt b/ui-lib/src/main/java/co/electriccoin/zcash/global/WalletCoordinator.kt index 6182c6c2..2d05f8f5 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/global/WalletCoordinator.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/global/WalletCoordinator.kt @@ -2,30 +2,21 @@ package co.electriccoin.zcash.global import android.content.Context import cash.z.ecc.android.sdk.WalletCoordinator -import co.electriccoin.zcash.spackle.LazyWithArgument -import co.electriccoin.zcash.ui.preference.EncryptedPreferenceKeys -import co.electriccoin.zcash.ui.preference.EncryptedPreferenceSingleton +import co.electriccoin.zcash.preference.EncryptedPreferenceProvider +import co.electriccoin.zcash.ui.preference.PersistableWalletPreferenceDefault import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow -private val lazy = - LazyWithArgument { - /** - * A flow of the user's stored wallet. Null indicates that no wallet has been stored. - */ - val persistableWallet = +fun WalletCoordinator.Companion.newInstance( + context: Context, + encryptedPreferenceProvider: EncryptedPreferenceProvider, + persistableWalletPreference: PersistableWalletPreferenceDefault +): WalletCoordinator { + return WalletCoordinator( + context = context, + persistableWallet = flow { - // EncryptedPreferenceSingleton.getInstance() is a suspending function, which is why we need - // the flow builder to provide a coroutine context. - val encryptedPreferenceProvider = EncryptedPreferenceSingleton.getInstance(it) - - emitAll(EncryptedPreferenceKeys.PERSISTABLE_WALLET.observe(encryptedPreferenceProvider)) + emitAll(persistableWalletPreference.observe(encryptedPreferenceProvider())) } - - WalletCoordinator( - context = it, - persistableWallet = persistableWallet - ) - } - -fun WalletCoordinator.Companion.getInstance(context: Context) = lazy.getInstance(context) + ) +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/MainActivity.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/MainActivity.kt index d69ec296..a66c978d 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/MainActivity.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/MainActivity.kt @@ -5,7 +5,6 @@ import android.content.pm.ActivityInfo import android.os.Bundle import android.os.SystemClock import androidx.activity.enableEdgeToEdge -import androidx.activity.viewModels import androidx.annotation.VisibleForTesting import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth @@ -58,20 +57,19 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.viewModel import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds class MainActivity : FragmentActivity() { - private val homeViewModel by viewModels() + private val homeViewModel by viewModel() - val walletViewModel by viewModels() + val walletViewModel by viewModel() - val storageCheckViewModel by viewModels() + val storageCheckViewModel by viewModel() - internal val authenticationViewModel by viewModels { - AuthenticationViewModel.AuthenticationViewModelFactory(application) - } + internal val authenticationViewModel by viewModel() lateinit var navControllerForTesting: NavHostController @@ -276,7 +274,6 @@ class MainActivity : FragmentActivity() { private fun monitorForBackgroundSync() { val isEnableBackgroundSyncFlow = run { - val homeViewModel by viewModels() val isSecretReadyFlow = walletViewModel.secretState.map { it is SecretState.Ready } val isBackgroundSyncEnabledFlow = homeViewModel.isBackgroundSyncEnabled.filterNotNull() diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/Constants.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/Constants.kt deleted file mode 100644 index 600c96bf..00000000 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/Constants.kt +++ /dev/null @@ -1,6 +0,0 @@ -package co.electriccoin.zcash.ui.common - -import kotlin.time.Duration.Companion.seconds - -// Recommended timeout for Android configuration changes to keep Kotlin Flow from restarting -val ANDROID_STATE_FLOW_TIMEOUT = 5.seconds diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/CompositionLocalBinder.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/CompositionLocalBinder.kt index cdcc7200..33100dbe 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/CompositionLocalBinder.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/CompositionLocalBinder.kt @@ -33,7 +33,7 @@ fun MainActivity.BindCompLocalProvider(content: @Composable () -> Unit) { LocalScreenBrightness provides screenBrightness, LocalScreenTimeout provides screenTimeout, LocalNavController provides navController, - LocalActivity provides this, + LocalActivity provides this@BindCompLocalProvider, content = content ) } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/WalletRepository.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/WalletRepository.kt new file mode 100644 index 00000000..19fb3b0b --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/WalletRepository.kt @@ -0,0 +1,29 @@ +package co.electriccoin.zcash.ui.common.repository + +import cash.z.ecc.android.sdk.Synchronizer +import cash.z.ecc.android.sdk.WalletCoordinator +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.WhileSubscribed +import kotlinx.coroutines.flow.stateIn + +interface WalletRepository { + val synchronizer: StateFlow +} + +class WalletRepositoryImpl( + walletCoordinator: WalletCoordinator, +) : WalletRepository { + private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + override val synchronizer: StateFlow = + walletCoordinator.synchronizer.stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), + initialValue = null + ) +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/GetSynchronizerUseCase.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/GetSynchronizerUseCase.kt new file mode 100644 index 00000000..c2fcc9ac --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/GetSynchronizerUseCase.kt @@ -0,0 +1,11 @@ +package co.electriccoin.zcash.ui.common.usecase + +import co.electriccoin.zcash.ui.common.repository.WalletRepository +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first + +class GetSynchronizerUseCase( + private val walletRepository: WalletRepository +) { + suspend operator fun invoke() = walletRepository.synchronizer.filterNotNull().first() +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/ObserveSynchronizerUseCase.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/ObserveSynchronizerUseCase.kt new file mode 100644 index 00000000..044f700a --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/ObserveSynchronizerUseCase.kt @@ -0,0 +1,9 @@ +package co.electriccoin.zcash.ui.common.usecase + +import co.electriccoin.zcash.ui.common.repository.WalletRepository + +class ObserveSynchronizerUseCase( + private val walletRepository: WalletRepository +) { + operator fun invoke() = walletRepository.synchronizer +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/AuthenticationViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/AuthenticationViewModel.kt index 6ca6b17c..4ea2e6e5 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/AuthenticationViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/AuthenticationViewModel.kt @@ -7,17 +7,15 @@ import androidx.biometric.BiometricManager.Authenticators import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT +import co.electriccoin.zcash.preference.StandardPreferenceProvider import co.electriccoin.zcash.preference.model.entry.BooleanPreferenceDefault import co.electriccoin.zcash.spackle.AndroidApiVersion import co.electriccoin.zcash.spackle.Twig import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.R -import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys -import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton import co.electriccoin.zcash.ui.screen.authentication.AuthenticationUseCase import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -37,7 +35,9 @@ import kotlin.time.Duration.Companion.milliseconds private const val DEFAULT_INITIAL_DELAY = 0 class AuthenticationViewModel( - private val application: Application, + application: Application, + private val standardPreferenceProvider: StandardPreferenceProvider, + private val biometricManager: BiometricManager, ) : AndroidViewModel(application) { private val executor: Executor by lazy { ContextCompat.getMainExecutor(application) } private lateinit var biometricPrompt: BiometricPrompt @@ -54,6 +54,7 @@ class AuthenticationViewModel( // Android SDK version == 28 || 29 (AndroidApiVersion.isExactlyP || AndroidApiVersion.isExactlyQ) -> Authenticators.BIOMETRIC_WEAK or Authenticators.DEVICE_CREDENTIAL + else -> error("Unsupported Android SDK version") } @@ -128,6 +129,7 @@ class AuthenticationViewModel( BiometricSupportResult.Success -> { // No action needed, let user proceed to the authentication steps } + else -> { // Otherwise biometric authentication might not be available, but users still can use the // device credential authentication path @@ -253,24 +255,28 @@ class AuthenticationViewModel( promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle( - application.applicationContext.run { + getApplication().applicationContext.run { getString(R.string.authentication_system_ui_title, getString(R.string.app_name)) } ) .setSubtitle( - application.applicationContext.run { + getApplication().applicationContext.run { getString( R.string.authentication_system_ui_subtitle, getString( when (useCase) { AuthenticationUseCase.AppAccess -> R.string.app_name + AuthenticationUseCase.DeleteWallet -> R.string.authentication_use_case_delete_wallet + AuthenticationUseCase.ExportPrivateData -> R.string.authentication_use_case_export_data + AuthenticationUseCase.SeedRecovery -> R.string.authentication_use_case_seed_recovery + AuthenticationUseCase.SendFunds -> R.string.authentication_use_case_send_funds } @@ -292,13 +298,12 @@ class AuthenticationViewModel( } private fun getBiometricAuthenticationSupport(allowedAuthenticators: Int): BiometricSupportResult { - val biometricManager = BiometricManager.from(application) - return when (biometricManager.canAuthenticate(allowedAuthenticators)) { BiometricManager.BIOMETRIC_SUCCESS -> { Twig.debug { "Auth canAuthenticate BIOMETRIC_SUCCESS: App can authenticate using biometrics." } BiometricSupportResult.Success } + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> { Twig.info { "Auth canAuthenticate BIOMETRIC_ERROR_NO_HARDWARE: No biometric features available on " + @@ -306,6 +311,7 @@ class AuthenticationViewModel( } BiometricSupportResult.ErrorNoHardware } + BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> { Twig.error { "Auth canAuthenticate BIOMETRIC_ERROR_HW_UNAVAILABLE: Biometric features are currently " + @@ -313,6 +319,7 @@ class AuthenticationViewModel( } BiometricSupportResult.ErrorHwUnavailable } + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> { Twig.warn { "Auth canAuthenticate BIOMETRIC_ERROR_NONE_ENROLLED: Prompts the user to create " + @@ -320,6 +327,7 @@ class AuthenticationViewModel( } BiometricSupportResult.ErrorNoneEnrolled } + BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> { Twig.error { "Auth canAuthenticate BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED: The user can't authenticate " + @@ -328,6 +336,7 @@ class AuthenticationViewModel( } BiometricSupportResult.ErrorSecurityUpdateRequired } + BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> { Twig.error { "Auth canAuthenticate BIOMETRIC_ERROR_UNSUPPORTED: The user can't authenticate because " + @@ -335,6 +344,7 @@ class AuthenticationViewModel( } BiometricSupportResult.ErrorUnsupported } + BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> { Twig.error { "Auth canAuthenticate BIOMETRIC_STATUS_UNKNOWN: Unable to determine whether the user can" + @@ -345,6 +355,7 @@ class AuthenticationViewModel( } BiometricSupportResult.StatusUnknown } + else -> { Twig.error { "Unexpected biometric framework status" } BiometricSupportResult.StatusUnexpected @@ -352,20 +363,9 @@ class AuthenticationViewModel( } } - @Suppress("UNCHECKED_CAST") - class AuthenticationViewModelFactory( - private val application: Application - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - require(modelClass.isAssignableFrom(AuthenticationViewModel::class.java)) { "ViewModel Not Found." } - return AuthenticationViewModel(application) as T - } - } - private fun booleanStateFlow(default: BooleanPreferenceDefault): StateFlow = flow { - val preferenceProvider = StandardPreferenceSingleton.getInstance(getApplication()) - emitAll(default.observe(preferenceProvider)) + emitAll(default.observe(standardPreferenceProvider())) }.stateIn( viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/CheckUpdateViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/CheckUpdateViewModel.kt index c3bfc8a0..b2527f30 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/CheckUpdateViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/CheckUpdateViewModel.kt @@ -2,8 +2,6 @@ package co.electriccoin.zcash.ui.common.viewmodel import android.app.Application import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import cash.z.ecc.android.sdk.ext.onFirst import co.electriccoin.zcash.ui.screen.update.AppUpdateChecker @@ -26,18 +24,4 @@ class CheckUpdateViewModel( } } } - - @Suppress("UNCHECKED_CAST") - class CheckUpdateViewModelFactory( - private val application: Application, - private val appUpdateChecker: AppUpdateChecker - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return if (modelClass.isAssignableFrom(CheckUpdateViewModel::class.java)) { - CheckUpdateViewModel(application, appUpdateChecker) as T - } else { - throw IllegalArgumentException("ViewModel Not Found.") - } - } - } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/HomeViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/HomeViewModel.kt index 9294f9f2..d4296a5b 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/HomeViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/HomeViewModel.kt @@ -1,14 +1,13 @@ package co.electriccoin.zcash.ui.common.viewmodel -import android.app.Application -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import co.electriccoin.zcash.configuration.AndroidConfigurationFactory +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT +import co.electriccoin.zcash.configuration.api.ConfigurationProvider import co.electriccoin.zcash.configuration.model.map.Configuration +import co.electriccoin.zcash.preference.StandardPreferenceProvider import co.electriccoin.zcash.preference.model.entry.BooleanPreferenceDefault -import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys -import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.WhileSubscribed @@ -19,7 +18,10 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -class HomeViewModel(application: Application) : AndroidViewModel(application) { +class HomeViewModel( + androidConfigurationProvider: ConfigurationProvider, + private val standardPreferenceProvider: StandardPreferenceProvider, +) : ViewModel() { /** * A flow of whether background sync is enabled */ @@ -54,7 +56,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { } val configurationFlow: StateFlow = - AndroidConfigurationFactory.getInstance(application).getConfigurationFlow() + androidConfigurationProvider.getConfigurationFlow() .stateIn( viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT.inWholeMilliseconds), @@ -67,8 +69,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { private fun booleanStateFlow(default: BooleanPreferenceDefault): StateFlow = flow { - val preferenceProvider = StandardPreferenceSingleton.getInstance(getApplication()) - emitAll(default.observe(preferenceProvider)) + emitAll(default.observe(standardPreferenceProvider())) }.stateIn( viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), @@ -80,8 +81,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { newState: Boolean ) { viewModelScope.launch { - val prefs = StandardPreferenceSingleton.getInstance(getApplication()) - default.putValue(prefs, newState) + default.putValue(standardPreferenceProvider(), newState) } } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/WalletViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/WalletViewModel.kt index d7f24cb1..4bfff739 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/WalletViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/WalletViewModel.kt @@ -24,13 +24,14 @@ import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.tool.DerivationTool +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT import cash.z.ecc.sdk.type.fromResources -import co.electriccoin.zcash.global.getInstance +import co.electriccoin.zcash.preference.EncryptedPreferenceProvider +import co.electriccoin.zcash.preference.StandardPreferenceProvider import co.electriccoin.zcash.preference.model.entry.NullableBooleanPreferenceDefault import co.electriccoin.zcash.spackle.Twig import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.NavigationTargets.EXCHANGE_RATE_OPT_IN -import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT import co.electriccoin.zcash.ui.common.compose.BalanceState import co.electriccoin.zcash.ui.common.extension.throttle import co.electriccoin.zcash.ui.common.model.OnboardingState @@ -41,13 +42,12 @@ import co.electriccoin.zcash.ui.common.model.hasChangePending import co.electriccoin.zcash.ui.common.model.hasValuePending import co.electriccoin.zcash.ui.common.model.spendableBalance import co.electriccoin.zcash.ui.common.model.totalBalance +import co.electriccoin.zcash.ui.common.usecase.ObserveSynchronizerUseCase import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState import co.electriccoin.zcash.ui.common.wallet.RefreshLock import co.electriccoin.zcash.ui.common.wallet.StaleLock -import co.electriccoin.zcash.ui.preference.EncryptedPreferenceKeys -import co.electriccoin.zcash.ui.preference.EncryptedPreferenceSingleton +import co.electriccoin.zcash.ui.preference.PersistableWalletPreferenceDefault import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys -import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton import co.electriccoin.zcash.ui.screen.account.ext.TransactionOverviewExt import co.electriccoin.zcash.ui.screen.account.ext.getSortHeight import co.electriccoin.zcash.ui.screen.account.state.TransactionHistorySyncState @@ -88,12 +88,17 @@ import kotlin.time.Duration.Companion.seconds // for loading the preferences. // TODO [#292]: Should be moved to SDK-EXT-UI module. // TODO [#292]: https://github.com/Electric-Coin-Company/zashi-android/issues/292 -@Suppress("TooManyFunctions") +@Suppress( + "TooManyFunctions" +) class WalletViewModel( - application: Application + application: Application, + observeSynchronizer: ObserveSynchronizerUseCase, + private val persistableWalletPreference: PersistableWalletPreferenceDefault, + private val walletCoordinator: WalletCoordinator, + private val encryptedPreferenceProvider: EncryptedPreferenceProvider, + private val standardPreferenceProvider: StandardPreferenceProvider, ) : AndroidViewModel(application) { - private val walletCoordinator = WalletCoordinator.getInstance(application) - /* * Using the Mutex may be overkill, but it ensures that if multiple calls are accidentally made * that they have a consistent ordering. @@ -107,23 +112,18 @@ class WalletViewModel( /** * Synchronizer that is retained long enough to survive configuration changes. */ - val synchronizer = - walletCoordinator.synchronizer.stateIn( - viewModelScope, - SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), - null - ) + val synchronizer = observeSynchronizer() /** * A flow of the wallet block synchronization state. */ val walletRestoringState: StateFlow = flow { - val preferenceProvider = StandardPreferenceSingleton.getInstance(application) emitAll( - StandardPreferenceKeys.WALLET_RESTORING_STATE.observe(preferenceProvider).map { persistedNumber -> - WalletRestoringState.fromNumber(persistedNumber) - } + StandardPreferenceKeys.WALLET_RESTORING_STATE + .observe(standardPreferenceProvider()).map { persistedNumber -> + WalletRestoringState.fromNumber(persistedNumber) + } ) }.stateIn( viewModelScope, @@ -162,11 +162,11 @@ class WalletViewModel( */ private val onboardingState = flow { - val preferenceProvider = StandardPreferenceSingleton.getInstance(application) emitAll( - StandardPreferenceKeys.ONBOARDING_STATE.observe(preferenceProvider).map { persistedNumber -> - OnboardingState.fromNumber(persistedNumber) - } + StandardPreferenceKeys.ONBOARDING_STATE + .observe(standardPreferenceProvider()).map { persistedNumber -> + OnboardingState.fromNumber(persistedNumber) + } ) } @@ -510,12 +510,9 @@ class WalletViewModel( * Persists a wallet asynchronously. Clients observe [secretState] to see the side effects. */ private fun persistWallet(persistableWallet: PersistableWallet) { - val application = getApplication() - viewModelScope.launch { - val preferenceProvider = EncryptedPreferenceSingleton.getInstance(application) persistWalletMutex.withLock { - EncryptedPreferenceKeys.PERSISTABLE_WALLET.putValue(preferenceProvider, persistableWallet) + persistableWalletPreference.putValue(encryptedPreferenceProvider(), persistableWallet) } } } @@ -526,17 +523,17 @@ class WalletViewModel( * for a user creating a new wallet. */ fun persistOnboardingState(onboardingState: OnboardingState) { - val application = getApplication() - viewModelScope.launch { - val preferenceProvider = StandardPreferenceSingleton.getInstance(application) - // Use the Mutex here to avoid timing issues. During wallet restore, persistOnboardingState() // is called prior to persistExistingWallet(). Although persistOnboardingState() should // complete quickly, it isn't guaranteed to complete before persistExistingWallet() // unless a mutex is used here. persistWalletMutex.withLock { - StandardPreferenceKeys.ONBOARDING_STATE.putValue(preferenceProvider, onboardingState.toNumber()) + StandardPreferenceKeys.ONBOARDING_STATE.putValue( + standardPreferenceProvider(), + onboardingState + .toNumber() + ) } } } @@ -548,11 +545,11 @@ class WalletViewModel( * state from the SDK, and thus, we need to note the wallet restoring state here on the client side. */ fun persistWalletRestoringState(walletRestoringState: WalletRestoringState) { - val application = getApplication() - viewModelScope.launch { - val preferenceProvider = StandardPreferenceSingleton.getInstance(application) - StandardPreferenceKeys.WALLET_RESTORING_STATE.putValue(preferenceProvider, walletRestoringState.toNumber()) + StandardPreferenceKeys.WALLET_RESTORING_STATE.putValue( + standardPreferenceProvider(), + walletRestoringState.toNumber() + ) } } @@ -589,16 +586,12 @@ class WalletViewModel( private fun clearAppStateFlow(): Flow = callbackFlow { - val application = getApplication() - viewModelScope.launch { val standardPrefsCleared = - StandardPreferenceSingleton - .getInstance(application) + standardPreferenceProvider() .clearPreferences() val encryptedPrefsCleared = - EncryptedPreferenceSingleton - .getInstance(application) + encryptedPreferenceProvider() .clearPreferences() Twig.info { "Both preferences cleared: ${standardPrefsCleared && encryptedPrefsCleared}" } @@ -650,8 +643,7 @@ class WalletViewModel( private fun nullableBooleanStateFlow(default: NullableBooleanPreferenceDefault): StateFlow = flow { - val preferenceProvider = StandardPreferenceSingleton.getInstance(getApplication()) - emitAll(default.observe(preferenceProvider)) + emitAll(default.observe(standardPreferenceProvider())) }.stateIn( viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), @@ -663,8 +655,7 @@ class WalletViewModel( newState: Boolean ) { viewModelScope.launch { - val prefs = StandardPreferenceSingleton.getInstance(getApplication()) - default.putValue(prefs, newState) + default.putValue(standardPreferenceProvider(), newState) } } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceKeys.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceKeys.kt deleted file mode 100644 index 3eae26db..00000000 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceKeys.kt +++ /dev/null @@ -1,7 +0,0 @@ -package co.electriccoin.zcash.ui.preference - -import co.electriccoin.zcash.preference.model.entry.PreferenceKey - -object EncryptedPreferenceKeys { - val PERSISTABLE_WALLET = PersistableWalletPreferenceDefault(PreferenceKey("persistable_wallet")) -} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceSingleton.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceSingleton.kt deleted file mode 100644 index 2c58b20a..00000000 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/preference/EncryptedPreferenceSingleton.kt +++ /dev/null @@ -1,17 +0,0 @@ -package co.electriccoin.zcash.ui.preference - -import android.content.Context -import co.electriccoin.zcash.preference.AndroidPreferenceProvider -import co.electriccoin.zcash.preference.api.PreferenceProvider -import co.electriccoin.zcash.spackle.SuspendingLazy - -object EncryptedPreferenceSingleton { - private const val PREF_FILENAME = "co.electriccoin.zcash.encrypted" - - private val lazy = - SuspendingLazy { - AndroidPreferenceProvider.newEncrypted(it, PREF_FILENAME) - } - - suspend fun getInstance(context: Context) = lazy.getInstance(context) -} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/preference/StandardPreferenceSingleton.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/preference/StandardPreferenceSingleton.kt deleted file mode 100644 index 774388e3..00000000 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/preference/StandardPreferenceSingleton.kt +++ /dev/null @@ -1,17 +0,0 @@ -package co.electriccoin.zcash.ui.preference - -import android.content.Context -import co.electriccoin.zcash.preference.AndroidPreferenceProvider -import co.electriccoin.zcash.preference.api.PreferenceProvider -import co.electriccoin.zcash.spackle.SuspendingLazy - -object StandardPreferenceSingleton { - private const val PREF_FILENAME = "co.electriccoin.zcash" - - private val lazy = - SuspendingLazy { - AndroidPreferenceProvider.newStandard(it, PREF_FILENAME) - } - - suspend fun getInstance(context: Context) = lazy.getInstance(context) -} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/AndroidAboutView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/AndroidAboutView.kt index 78923688..68a2f93b 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/AndroidAboutView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/about/AndroidAboutView.kt @@ -4,14 +4,14 @@ package co.electriccoin.zcash.ui.screen.about import android.content.Context import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.lifecycle.compose.collectAsStateWithLifecycle -import co.electriccoin.zcash.configuration.AndroidConfigurationFactory +import co.electriccoin.zcash.configuration.api.ConfigurationProvider +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.compose.LocalActivity import co.electriccoin.zcash.ui.common.model.VersionInfo @@ -21,6 +21,7 @@ import co.electriccoin.zcash.ui.screen.about.view.About import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.koin.compose.koinInject @Composable internal fun WrapAbout( @@ -29,7 +30,7 @@ internal fun WrapAbout( ) { val activity = LocalActivity.current - val walletViewModel by activity.viewModels() + val walletViewModel = koinActivityViewModel() val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value @@ -37,12 +38,13 @@ internal fun WrapAbout( goBack() } - val configInfo = ConfigInfo.new(AndroidConfigurationFactory.getInstance(activity.applicationContext)) + val androidConfigurationProvider = koinInject() + val configInfo = ConfigInfo.new(androidConfigurationProvider) val versionInfo = VersionInfo.new(activity.applicationContext) // Allows an implicit way to force configuration refresh by simply visiting the About screen LaunchedEffect(key1 = true) { - AndroidConfigurationFactory.getInstance(activity.applicationContext).hintToRefresh() + androidConfigurationProvider.hintToRefresh() } val snackbarHostState = remember { SnackbarHostState() } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/AndroidAccount.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/AndroidAccount.kt index 621bf724..494e86d5 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/AndroidAccount.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/AndroidAccount.kt @@ -3,7 +3,6 @@ package co.electriccoin.zcash.ui.screen.account import android.content.Context -import androidx.activity.viewModels import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf @@ -13,6 +12,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.internal.Twig +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.spackle.ClipboardManagerUtil import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.compose.BalanceState @@ -40,11 +40,11 @@ internal fun WrapAccount( ) { val activity = LocalActivity.current - val walletViewModel by activity.viewModels() + val walletViewModel = koinActivityViewModel() - val transactionHistoryViewModel by activity.viewModels() + val transactionHistoryViewModel = koinActivityViewModel() - val homeViewModel by activity.viewModels() + val homeViewModel = koinActivityViewModel() val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/viewmodel/TransactionHistoryViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/viewmodel/TransactionHistoryViewModel.kt index 2accdd13..b050a3ed 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/viewmodel/TransactionHistoryViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/viewmodel/TransactionHistoryViewModel.kt @@ -1,13 +1,12 @@ package co.electriccoin.zcash.ui.screen.account.viewmodel -import android.app.Application -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.model.FirstClassByteArray import cash.z.ecc.android.sdk.model.TransactionOverview -import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT import co.electriccoin.zcash.ui.screen.account.ext.TransactionOverviewExt import co.electriccoin.zcash.ui.screen.account.model.TransactionUi import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState @@ -24,7 +23,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.toList -class TransactionHistoryViewModel(application: Application) : AndroidViewModel(application) { +class TransactionHistoryViewModel : ViewModel() { private val state: MutableStateFlow = MutableStateFlow(State.LOADING) private val transactions: MutableStateFlow> = MutableStateFlow(persistentListOf()) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/AndroidAdvancedSettings.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/AndroidAdvancedSettings.kt index fc78c38e..7fce5882 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/AndroidAdvancedSettings.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/AndroidAdvancedSettings.kt @@ -3,9 +3,9 @@ package co.electriccoin.zcash.ui.screen.advancedsettings import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel @@ -21,7 +21,7 @@ internal fun MainActivity.WrapAdvancedSettings( goSeedRecovery: () -> Unit, onCurrencyConversion: () -> Unit ) { - val walletViewModel by viewModels() + val walletViewModel = koinActivityViewModel() val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/authentication/AndroidAuthentication.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/authentication/AndroidAuthentication.kt index 14f497e5..1f152b57 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/authentication/AndroidAuthentication.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/authentication/AndroidAuthentication.kt @@ -3,11 +3,11 @@ package co.electriccoin.zcash.ui.screen.authentication import android.widget.Toast -import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.spackle.Twig import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.R @@ -115,7 +115,7 @@ private fun WrapDeleteWalletAuth( onCancel: () -> Unit, onFailed: () -> Unit, ) { - val authenticationViewModel by activity.viewModels() + val authenticationViewModel = koinActivityViewModel() val authenticationResult = authenticationViewModel.authenticationResult @@ -188,7 +188,7 @@ private fun WrapAppExportPrivateDataAuth( onCancel: () -> Unit, onFailed: () -> Unit, ) { - val authenticationViewModel by activity.viewModels() + val authenticationViewModel = koinActivityViewModel() val authenticationResult = authenticationViewModel.authenticationResult @@ -261,7 +261,7 @@ private fun WrapSeedRecoveryAuth( onCancel: () -> Unit, onFailed: () -> Unit, ) { - val authenticationViewModel by activity.viewModels() + val authenticationViewModel = koinActivityViewModel() val authenticationResult = authenticationViewModel.authenticationResult @@ -335,7 +335,7 @@ private fun WrapSendFundsAuth( onCancel: () -> Unit, onFailed: () -> Unit, ) { - val authenticationViewModel by activity.viewModels() + val authenticationViewModel = koinActivityViewModel() val authenticationResult = authenticationViewModel.authenticationResult @@ -409,7 +409,7 @@ private fun WrapAppAccessAuth( onCancel: () -> Unit, onFailed: () -> Unit, ) { - val authenticationViewModel by activity.viewModels() + val authenticationViewModel = koinActivityViewModel() val welcomeAnimVisibility = authenticationViewModel.showWelcomeAnimation.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/AndroidBalances.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/AndroidBalances.kt index 82828bd3..c6982d61 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/AndroidBalances.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/AndroidBalances.kt @@ -4,7 +4,6 @@ package co.electriccoin.zcash.ui.screen.balances import android.content.Context import android.widget.Toast -import androidx.activity.viewModels import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf @@ -18,6 +17,7 @@ import cash.z.ecc.android.sdk.SdkSynchronizer import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.Zatoshi +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.spackle.Twig import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.compose.BalanceState @@ -36,7 +36,6 @@ import co.electriccoin.zcash.ui.screen.balances.model.StatusAction import co.electriccoin.zcash.ui.screen.balances.view.Balances import co.electriccoin.zcash.ui.screen.sendconfirmation.model.SubmitResult import co.electriccoin.zcash.ui.screen.sendconfirmation.viewmodel.CreateTransactionsViewModel -import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp import co.electriccoin.zcash.ui.screen.update.model.UpdateState import co.electriccoin.zcash.ui.util.PlayStoreUtil import kotlinx.coroutines.CoroutineScope @@ -51,11 +50,11 @@ internal fun WrapBalances( ) { val activity = LocalActivity.current - val walletViewModel by activity.viewModels() + val walletViewModel = koinActivityViewModel() - val createTransactionsViewModel by activity.viewModels() + val createTransactionsViewModel = koinActivityViewModel() - val homeViewModel by activity.viewModels() + val homeViewModel = koinActivityViewModel() val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value @@ -69,12 +68,7 @@ internal fun WrapBalances( val isHideBalances = homeViewModel.isHideBalances.collectAsStateWithLifecycle().value ?: false - val checkUpdateViewModel by activity.viewModels { - CheckUpdateViewModel.CheckUpdateViewModelFactory( - activity.application, - AppUpdateCheckerImp.new() - ) - } + val checkUpdateViewModel = koinActivityViewModel() val balanceState = walletViewModel.balanceState.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/chooseserver/AndroidChooseServer.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/chooseserver/AndroidChooseServer.kt index d1757eec..2dd4ccf7 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/chooseserver/AndroidChooseServer.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/chooseserver/AndroidChooseServer.kt @@ -3,7 +3,6 @@ package co.electriccoin.zcash.ui.screen.chooseserver import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -18,6 +17,7 @@ import cash.z.ecc.android.sdk.model.PersistableWallet import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.type.ServerValidation import cash.z.ecc.sdk.type.fromResources +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState import co.electriccoin.zcash.ui.common.viewmodel.SecretState @@ -28,7 +28,7 @@ import kotlinx.coroutines.launch @Composable internal fun MainActivity.WrapChooseServer(goBack: () -> Unit) { - val walletViewModel by viewModels() + val walletViewModel = koinActivityViewModel() val secretState = walletViewModel.secretState.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/deletewallet/AndroidDeleteWallet.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/deletewallet/AndroidDeleteWallet.kt index 29a95726..03d13e14 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/deletewallet/AndroidDeleteWallet.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/deletewallet/AndroidDeleteWallet.kt @@ -2,12 +2,12 @@ package co.electriccoin.zcash.ui.screen.deletewallet import android.app.Activity import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.spackle.Twig import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.R @@ -18,7 +18,7 @@ import kotlinx.coroutines.launch @Composable internal fun MainActivity.WrapDeleteWallet(goBack: () -> Unit) { - val walletViewModel by viewModels() + val walletViewModel = koinActivityViewModel() val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/exportdata/AndroidExportPrivateData.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/exportdata/AndroidExportPrivateData.kt index 960000e1..34f764a3 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/exportdata/AndroidExportPrivateData.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/exportdata/AndroidExportPrivateData.kt @@ -2,7 +2,6 @@ package co.electriccoin.zcash.ui.screen.exportdata import android.content.Context import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -12,7 +11,7 @@ import cash.z.ecc.android.sdk.SdkSynchronizer import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.sdk.type.fromResources -import co.electriccoin.zcash.ui.MainActivity +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.compose.LocalActivity import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState @@ -27,11 +26,11 @@ import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.launch @Composable -internal fun MainActivity.WrapExportPrivateData( +internal fun WrapExportPrivateData( goBack: () -> Unit, onConfirm: () -> Unit ) { - val walletViewModel by viewModels() + val walletViewModel = koinActivityViewModel() val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/AndroidHome.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/AndroidHome.kt index 376e633d..eb41d41f 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/AndroidHome.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/AndroidHome.kt @@ -3,7 +3,6 @@ package co.electriccoin.zcash.ui.screen.home import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable @@ -19,6 +18,7 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import cash.z.ecc.android.sdk.model.ZecSend +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.compose.LocalActivity import co.electriccoin.zcash.ui.common.compose.RestoreScreenBrightness @@ -47,11 +47,9 @@ internal fun WrapHome( goSendConfirmation: (ZecSend) -> Unit, sendArguments: SendArguments ) { - val activity = LocalActivity.current + val homeViewModel = koinActivityViewModel() - val homeViewModel by activity.viewModels() - - val walletViewModel by activity.viewModels() + val walletViewModel = koinActivityViewModel() val isKeepScreenOnWhileSyncing = homeViewModel.isKeepScreenOnWhileSyncing.collectAsStateWithLifecycle().value @@ -117,7 +115,7 @@ internal fun WrapHome( val focusManager = LocalFocusManager.current - val walletViewModel by activity.viewModels() + val walletViewModel = koinActivityViewModel() val scope = rememberCoroutineScope() diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/onboarding/AndroidOnboarding.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/onboarding/AndroidOnboarding.kt index 96927d6f..7a13c6c1 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/onboarding/AndroidOnboarding.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/onboarding/AndroidOnboarding.kt @@ -3,7 +3,6 @@ package co.electriccoin.zcash.ui.screen.onboarding import android.content.Context -import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.lifecycle.compose.collectAsStateWithLifecycle import cash.z.ecc.android.sdk.WalletInitMode @@ -13,6 +12,7 @@ import cash.z.ecc.android.sdk.model.PersistableWallet import cash.z.ecc.android.sdk.model.SeedPhrase import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.sdk.type.fromResources +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.spackle.FirebaseTestLabUtil import co.electriccoin.zcash.ui.common.compose.LocalActivity import co.electriccoin.zcash.ui.common.model.OnboardingState @@ -28,8 +28,8 @@ import co.electriccoin.zcash.ui.screen.restore.WrapRestore @Composable internal fun WrapOnboarding() { val activity = LocalActivity.current - val walletViewModel by activity.viewModels() - val onboardingViewModel by activity.viewModels() + val walletViewModel = koinActivityViewModel() + val onboardingViewModel = koinActivityViewModel() val versionInfo = VersionInfo.new(activity.applicationContext) @@ -75,7 +75,7 @@ internal fun WrapOnboarding() { activity.reportFullyDrawn() } else { - WrapRestore(activity) + WrapRestore() } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/AndroidReceive.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/AndroidReceive.kt index 7f7d47dd..f52263bb 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/AndroidReceive.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/receive/AndroidReceive.kt @@ -4,13 +4,13 @@ package co.electriccoin.zcash.ui.screen.receive import android.content.Context import android.graphics.Bitmap -import androidx.activity.viewModels import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.graphics.asAndroidBitmap import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.spackle.ClipboardManagerUtil import co.electriccoin.zcash.spackle.Twig import co.electriccoin.zcash.spackle.getInternalCacheDirSuspend @@ -34,9 +34,9 @@ import java.io.File internal fun WrapReceive(onSettings: () -> Unit) { val activity = LocalActivity.current - val walletViewModel by activity.viewModels() + val walletViewModel = koinActivityViewModel() - val brightnessViewModel by activity.viewModels() + val brightnessViewModel = koinActivityViewModel() val screenBrightnessState = brightnessViewModel.screenBrightnessState.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/AndroidRestore.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/AndroidRestore.kt index e0dfbe0f..fb546ea5 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/AndroidRestore.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/AndroidRestore.kt @@ -1,14 +1,13 @@ package co.electriccoin.zcash.ui.screen.restore import android.content.ClipboardManager -import androidx.activity.ComponentActivity -import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle import cash.z.ecc.android.sdk.model.SeedPhrase import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.sdk.type.fromResources +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel import co.electriccoin.zcash.ui.screen.onboarding.persistExistingWalletWithSeedPhrase import co.electriccoin.zcash.ui.screen.onboarding.viewmodel.OnboardingViewModel @@ -17,10 +16,10 @@ import co.electriccoin.zcash.ui.screen.restore.viewmodel.CompleteWordSetState import co.electriccoin.zcash.ui.screen.restore.viewmodel.RestoreViewModel @Composable -fun WrapRestore(activity: ComponentActivity) { - val walletViewModel by activity.viewModels() - val onboardingViewModel by activity.viewModels() - val restoreViewModel by activity.viewModels() +fun WrapRestore() { + val walletViewModel = koinActivityViewModel() + val onboardingViewModel = koinActivityViewModel() + val restoreViewModel = koinActivityViewModel() val applicationContext = LocalContext.current.applicationContext diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/viewmodel/RestoreViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/viewmodel/RestoreViewModel.kt index 18e29a70..d12cb4f5 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/viewmodel/RestoreViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/viewmodel/RestoreViewModel.kt @@ -8,8 +8,8 @@ import cash.z.ecc.android.bip39.Mnemonics import cash.z.ecc.android.sdk.ext.collectWith import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.ZcashNetwork +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT import cash.z.ecc.sdk.type.fromResources -import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT import co.electriccoin.zcash.ui.screen.restore.model.RestoreStage import co.electriccoin.zcash.ui.screen.restore.state.RestoreState import co.electriccoin.zcash.ui.screen.restore.state.WordList diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restoresuccess/AndroidRestoreSuccess.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restoresuccess/AndroidRestoreSuccess.kt index 344bb22d..be477eeb 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restoresuccess/AndroidRestoreSuccess.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restoresuccess/AndroidRestoreSuccess.kt @@ -1,17 +1,14 @@ package co.electriccoin.zcash.ui.screen.restoresuccess -import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.lifecycle.compose.collectAsStateWithLifecycle -import co.electriccoin.zcash.ui.common.compose.LocalActivity +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.screen.restoresuccess.view.RestoreSuccess import co.electriccoin.zcash.ui.screen.restoresuccess.viewmodel.RestoreSuccessViewModel @Composable fun WrapRestoreSuccess(onDone: () -> Unit) { - val activity = LocalActivity.current - - val viewModel by activity.viewModels() + val viewModel = koinActivityViewModel() val state = viewModel.state.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restoresuccess/viewmodel/RestoreSuccessViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restoresuccess/viewmodel/RestoreSuccessViewModel.kt index c2a106fd..d67d7e35 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restoresuccess/viewmodel/RestoreSuccessViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restoresuccess/viewmodel/RestoreSuccessViewModel.kt @@ -3,10 +3,10 @@ package co.electriccoin.zcash.ui.screen.restoresuccess.viewmodel import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT +import co.electriccoin.zcash.preference.StandardPreferenceProvider import co.electriccoin.zcash.preference.model.entry.BooleanPreferenceDefault -import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys -import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton import co.electriccoin.zcash.ui.screen.restoresuccess.view.RestoreSuccessViewState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -16,7 +16,10 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -class RestoreSuccessViewModel(application: Application) : AndroidViewModel(application) { +class RestoreSuccessViewModel( + application: Application, + private val standardPreferenceProvider: StandardPreferenceProvider, +) : AndroidViewModel(application) { private val keepScreenOn = MutableStateFlow(DEFAULT_KEEP_SCREEN_ON) val state = @@ -45,8 +48,7 @@ class RestoreSuccessViewModel(application: Application) : AndroidViewModel(appli default: BooleanPreferenceDefault, newState: Boolean ) = viewModelScope.launch { - val prefs = StandardPreferenceSingleton.getInstance(getApplication()) - default.putValue(prefs, newState) + default.putValue(standardPreferenceProvider(), newState) } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/scan/AndroidScan.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/scan/AndroidScan.kt index 0eb3782e..91eb24be 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/scan/AndroidScan.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/scan/AndroidScan.kt @@ -1,7 +1,6 @@ package co.electriccoin.zcash.ui.screen.scan import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -13,6 +12,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.type.AddressType +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.model.SerializableAddress @@ -28,7 +28,7 @@ internal fun MainActivity.WrapScanValidator( onScanValid: (address: SerializableAddress) -> Unit, goBack: () -> Unit ) { - val walletViewModel by viewModels() + val walletViewModel = koinActivityViewModel() val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/securitywarning/AndroidSecurityWarning.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/securitywarning/AndroidSecurityWarning.kt index 6abf25b0..1b4c3622 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/securitywarning/AndroidSecurityWarning.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/securitywarning/AndroidSecurityWarning.kt @@ -3,10 +3,11 @@ package co.electriccoin.zcash.ui.screen.securitywarning import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import co.electriccoin.zcash.configuration.AndroidConfigurationFactory +import co.electriccoin.zcash.configuration.api.ConfigurationProvider import co.electriccoin.zcash.ui.common.compose.LocalActivity import co.electriccoin.zcash.ui.common.model.VersionInfo import co.electriccoin.zcash.ui.screen.securitywarning.view.SecurityWarning +import org.koin.compose.koinInject @Composable internal fun WrapSecurityWarning( @@ -14,7 +15,7 @@ internal fun WrapSecurityWarning( onConfirm: () -> Unit ) { val activity = LocalActivity.current - + val androidConfigurationProvider = koinInject() BackHandler { onBack() } @@ -29,6 +30,6 @@ internal fun WrapSecurityWarning( ) LaunchedEffect(key1 = true) { - AndroidConfigurationFactory.getInstance(activity.applicationContext).hintToRefresh() + androidConfigurationProvider.hintToRefresh() } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/seedrecovery/AndroidSeedRecovery.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/seedrecovery/AndroidSeedRecovery.kt index 5a2a3e74..bc6963d3 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/seedrecovery/AndroidSeedRecovery.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/seedrecovery/AndroidSeedRecovery.kt @@ -1,10 +1,10 @@ package co.electriccoin.zcash.ui.screen.seedrecovery import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.lifecycle.compose.collectAsStateWithLifecycle import cash.z.ecc.android.sdk.Synchronizer +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.spackle.ClipboardManagerUtil import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.compose.LocalActivity @@ -20,9 +20,7 @@ internal fun WrapSeedRecovery( goBack: () -> Unit, onDone: () -> Unit, ) { - val activity = LocalActivity.current - - val walletViewModel by activity.viewModels() + val walletViewModel = koinActivityViewModel() val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/AndroidSend.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/AndroidSend.kt index d5376a72..5a39c2e0 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/AndroidSend.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/AndroidSend.kt @@ -3,7 +3,6 @@ package co.electriccoin.zcash.ui.screen.send import android.content.pm.PackageManager -import androidx.activity.viewModels import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -19,6 +18,7 @@ import cash.z.ecc.android.sdk.model.ZecSend import cash.z.ecc.android.sdk.model.proposeSend import cash.z.ecc.android.sdk.model.toZecString import cash.z.ecc.android.sdk.type.AddressType +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.spackle.Twig import co.electriccoin.zcash.ui.common.compose.BalanceState import co.electriccoin.zcash.ui.common.compose.LocalActivity @@ -50,9 +50,9 @@ internal fun WrapSend( ) { val activity = LocalActivity.current - val walletViewModel by activity.viewModels() + val walletViewModel = koinActivityViewModel() - val homeViewModel by activity.viewModels() + val homeViewModel = koinActivityViewModel() val hasCameraFeature = activity.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/AndroidSendConfirmation.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/AndroidSendConfirmation.kt index 8730ab27..d8a5dfb8 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/AndroidSendConfirmation.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/AndroidSendConfirmation.kt @@ -5,7 +5,6 @@ package co.electriccoin.zcash.ui.screen.sendconfirmation import android.content.Context import android.content.Intent import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.annotation.VisibleForTesting import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable @@ -23,6 +22,7 @@ import cash.z.ecc.android.sdk.model.Proposal import cash.z.ecc.android.sdk.model.TransactionSubmitResult import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.ZecSend +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.spackle.Twig import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.R @@ -57,15 +57,13 @@ internal fun MainActivity.WrapSendConfirmation( goSupport: () -> Unit, arguments: SendConfirmationArguments ) { - val walletViewModel by viewModels() + val walletViewModel = koinActivityViewModel() - val createTransactionsViewModel by viewModels() + val createTransactionsViewModel = koinActivityViewModel() - val supportViewModel by viewModels() + val supportViewModel = koinActivityViewModel() - val authenticationViewModel by viewModels { - AuthenticationViewModel.AuthenticationViewModelFactory(application) - } + val authenticationViewModel = koinActivityViewModel() val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/viewmodel/CreateTransactionsViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/viewmodel/CreateTransactionsViewModel.kt index 3b7f5185..0dcb5a73 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/viewmodel/CreateTransactionsViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/viewmodel/CreateTransactionsViewModel.kt @@ -1,7 +1,6 @@ package co.electriccoin.zcash.ui.screen.sendconfirmation.viewmodel -import android.app.Application -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.model.Proposal import cash.z.ecc.android.sdk.model.TransactionSubmitResult @@ -10,7 +9,7 @@ import co.electriccoin.zcash.spackle.Twig import co.electriccoin.zcash.ui.screen.sendconfirmation.model.SubmitResult import kotlinx.coroutines.flow.MutableStateFlow -class CreateTransactionsViewModel(application: Application) : AndroidViewModel(application) { +class CreateTransactionsViewModel : ViewModel() { // Technically this value will not survive process dead, but will survive all possible configuration changes // Possible solution would be storing the value within [SavedStateHandle] val submissions: MutableStateFlow> = MutableStateFlow(emptyList()) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/AndroidSettings.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/AndroidSettings.kt index c202eb9e..c28aed53 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/AndroidSettings.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/AndroidSettings.kt @@ -1,9 +1,9 @@ package co.electriccoin.zcash.ui.screen.settings import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.common.compose.LocalActivity import co.electriccoin.zcash.ui.common.model.VersionInfo import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel @@ -23,9 +23,9 @@ internal fun WrapSettings( ) { val activity = LocalActivity.current - val walletViewModel by activity.viewModels() + val walletViewModel = koinActivityViewModel() - val settingsViewModel by activity.viewModels() + val settingsViewModel = koinActivityViewModel() val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/viewmodel/SettingsViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/viewmodel/SettingsViewModel.kt index b3efa1f7..fb871a80 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/viewmodel/SettingsViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/viewmodel/SettingsViewModel.kt @@ -1,12 +1,11 @@ package co.electriccoin.zcash.ui.screen.settings.viewmodel -import android.app.Application -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT +import co.electriccoin.zcash.preference.StandardPreferenceProvider import co.electriccoin.zcash.preference.model.entry.BooleanPreferenceDefault -import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys -import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.WhileSubscribed @@ -15,7 +14,9 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -class SettingsViewModel(application: Application) : AndroidViewModel(application) { +class SettingsViewModel( + private val standardPreferenceProvider: StandardPreferenceProvider, +) : ViewModel() { val isAnalyticsEnabled: StateFlow = booleanStateFlow(StandardPreferenceKeys.IS_ANALYTICS_ENABLED) val isBackgroundSync: StateFlow = booleanStateFlow(StandardPreferenceKeys.IS_BACKGROUND_SYNC_ENABLED) @@ -25,8 +26,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application private fun booleanStateFlow(default: BooleanPreferenceDefault): StateFlow = flow { - val preferenceProvider = StandardPreferenceSingleton.getInstance(getApplication()) - emitAll(default.observe(preferenceProvider)) + emitAll(default.observe(standardPreferenceProvider())) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null) fun setAnalyticsEnabled(enabled: Boolean) { @@ -46,8 +46,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application newState: Boolean ) { viewModelScope.launch { - val prefs = StandardPreferenceSingleton.getInstance(getApplication()) - default.putValue(prefs, newState) + default.putValue(standardPreferenceProvider(), newState) } } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/AndroidSupport.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/AndroidSupport.kt index a597ddd5..df367555 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/AndroidSupport.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/AndroidSupport.kt @@ -4,7 +4,6 @@ package co.electriccoin.zcash.ui.screen.support import android.content.Intent import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.annotation.VisibleForTesting import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable @@ -13,6 +12,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.compose.LocalActivity import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState @@ -26,11 +26,9 @@ import kotlinx.coroutines.launch @Composable internal fun WrapSupport(goBack: () -> Unit) { - val activity = LocalActivity.current + val supportViewModel = koinActivityViewModel() - val supportViewModel by activity.viewModels() - - val walletViewModel by activity.viewModels() + val walletViewModel = koinActivityViewModel() val supportInfo = supportViewModel.supportInfo.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfo.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfo.kt index ce3c0c52..104ab5a0 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfo.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/model/SupportInfo.kt @@ -1,7 +1,7 @@ package co.electriccoin.zcash.ui.screen.support.model import android.content.Context -import co.electriccoin.zcash.configuration.AndroidConfigurationFactory +import co.electriccoin.zcash.configuration.api.ConfigurationProvider import co.electriccoin.zcash.spackle.getPackageInfoCompatSuspend import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList @@ -63,15 +63,17 @@ data class SupportInfo( companion object { // Although most of our calls now are non-blocking, we expect more of them to be blocking // in the future. - suspend fun new(context: Context): SupportInfo { + suspend fun new( + context: Context, + androidConfigurationProvider: ConfigurationProvider + ): SupportInfo { val applicationContext = context.applicationContext val packageInfo = applicationContext.packageManager.getPackageInfoCompatSuspend(context.packageName, 0L) - val configurationProvider = AndroidConfigurationFactory.getInstance(applicationContext) return SupportInfo( TimeInfo.new(packageInfo), AppInfo.new(packageInfo), - ConfigInfo.new(configurationProvider), + ConfigInfo.new(androidConfigurationProvider), OperatingSystemInfo.new(), DeviceInfo.new(), EnvironmentInfo.new(applicationContext), diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/viewmodel/SupportViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/viewmodel/SupportViewModel.kt index 28c27392..1a9c957b 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/viewmodel/SupportViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/support/viewmodel/SupportViewModel.kt @@ -3,7 +3,8 @@ package co.electriccoin.zcash.ui.screen.support.viewmodel import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT +import co.electriccoin.zcash.configuration.api.ConfigurationProvider import co.electriccoin.zcash.ui.screen.support.model.SupportInfo import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -12,13 +13,16 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.stateIn import kotlin.time.Duration -class SupportViewModel(application: Application) : AndroidViewModel(application) { +class SupportViewModel( + application: Application, + androidConfigurationProvider: ConfigurationProvider +) : AndroidViewModel(application) { // Technically, some of the support info could be invalidated after a configuration change, // such as the user's current locale. However it really doesn't matter here since all we // care about is capturing a snapshot of the app, OS, and device state. val supportInfo: StateFlow = flow { - emit(SupportInfo.new(application)) + emit(SupportInfo.new(application, androidConfigurationProvider)) } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT, Duration.ZERO), null) } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AndroidUpdate.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AndroidUpdate.kt index e2b5eb96..d67fcf82 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AndroidUpdate.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AndroidUpdate.kt @@ -2,7 +2,6 @@ package co.electriccoin.zcash.ui.screen.update import android.content.Context import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.annotation.VisibleForTesting import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable @@ -10,6 +9,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.compose.LocalActivity import co.electriccoin.zcash.ui.common.viewmodel.CheckUpdateViewModel @@ -20,29 +20,36 @@ import co.electriccoin.zcash.ui.screen.update.viewmodel.UpdateViewModel import co.electriccoin.zcash.ui.util.PlayStoreUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.koin.core.parameter.parametersOf @Composable internal fun WrapCheckForUpdate() { - val activity = LocalActivity.current - // TODO [#403]: Manual testing of already implemented in-app update mechanisms // TODO [#403]: https://github.com/Electric-Coin-Company/zashi-android/issues/403 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - val checkUpdateViewModel by activity.viewModels { - CheckUpdateViewModel.CheckUpdateViewModelFactory( - activity.application, - AppUpdateCheckerImp.new() + val checkUpdateViewModel = koinActivityViewModel() + + val activity = LocalActivity.current + + val inputUpdateInfo = checkUpdateViewModel.updateInfo.collectAsStateWithLifecycle().value ?: return + + val viewModel = koinActivityViewModel { parametersOf(inputUpdateInfo) } + val updateInfo = viewModel.updateInfo.collectAsStateWithLifecycle().value + + if (updateInfo.appUpdateInfo != null && updateInfo.state == UpdateState.Prepared) { + WrapUpdate( + updateInfo = updateInfo, + checkForUpdate = viewModel::checkForAppUpdate, + remindLater = viewModel::remindLater, + goForUpdate = { + viewModel.goForUpdate( + activity = activity, + appUpdateInfo = updateInfo.appUpdateInfo + ) + } ) } - val updateInfo = checkUpdateViewModel.updateInfo.collectAsStateWithLifecycle().value - - updateInfo?.let { - if (it.appUpdateInfo != null && it.state == UpdateState.Prepared) { - WrapUpdate(updateInfo) - } - } - // Check for an app update asynchronously. We create an effect that matches the activity // lifecycle. If the wrapping compose recomposes, the check shouldn't run again. LaunchedEffect(true) { @@ -52,22 +59,17 @@ internal fun WrapCheckForUpdate() { @VisibleForTesting @Composable -internal fun WrapUpdate(inputUpdateInfo: UpdateInfo) { +internal fun WrapUpdate( + updateInfo: UpdateInfo, + checkForUpdate: () -> Unit, + remindLater: () -> Unit, + goForUpdate: () -> Unit, +) { val activity = LocalActivity.current - val viewModel by activity.viewModels { - UpdateViewModel.UpdateViewModelFactory( - activity.application, - inputUpdateInfo, - AppUpdateCheckerImp.new() - ) - } - val snackbarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() - val updateInfo = viewModel.updateInfo.collectAsStateWithLifecycle().value - when (updateInfo.state) { UpdateState.Done, UpdateState.Canceled -> { // just return as we are already in Home compose @@ -76,7 +78,7 @@ internal fun WrapUpdate(inputUpdateInfo: UpdateInfo) { UpdateState.Failed -> { // we need to refresh AppUpdateInfo object, as it can be used only once - viewModel.checkForAppUpdate() + checkForUpdate() } UpdateState.Prepared, UpdateState.Running -> { @@ -86,7 +88,7 @@ internal fun WrapUpdate(inputUpdateInfo: UpdateInfo) { val onLaterAction = { if (!updateInfo.isForce && updateInfo.state != UpdateState.Running) { - viewModel.remindLater() + remindLater() } } @@ -100,11 +102,7 @@ internal fun WrapUpdate(inputUpdateInfo: UpdateInfo) { onDownload = { // in this state of the update we have the AppUpdateInfo filled requireNotNull(updateInfo.appUpdateInfo) - - viewModel.goForUpdate( - activity, - updateInfo.appUpdateInfo - ) + goForUpdate() }, onLater = onLaterAction, onReference = { diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AppUpdateCheckerImp.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AppUpdateCheckerImpl.kt similarity index 96% rename from ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AppUpdateCheckerImp.kt rename to ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AppUpdateCheckerImpl.kt index 56aeec61..ec4c6366 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AppUpdateCheckerImp.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AppUpdateCheckerImpl.kt @@ -15,13 +15,7 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow -class AppUpdateCheckerImp private constructor() : AppUpdateChecker { - companion object { - private const val DEFAULT_STALENESS_DAYS = 3 - - fun new() = AppUpdateCheckerImp() - } - +class AppUpdateCheckerImpl : AppUpdateChecker { override val stalenessDays = DEFAULT_STALENESS_DAYS /** @@ -133,3 +127,5 @@ class AppUpdateCheckerImp private constructor() : AppUpdateChecker { } } } + +private const val DEFAULT_STALENESS_DAYS = 3 diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/viewmodel/UpdateViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/viewmodel/UpdateViewModel.kt index f0d4bbda..59b6c744 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/viewmodel/UpdateViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/viewmodel/UpdateViewModel.kt @@ -4,8 +4,6 @@ import android.app.Activity import android.app.Application import androidx.activity.ComponentActivity import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import cash.z.ecc.android.sdk.ext.onFirst import co.electriccoin.zcash.ui.screen.update.AppUpdateChecker @@ -14,6 +12,7 @@ import co.electriccoin.zcash.ui.screen.update.model.UpdateState import com.google.android.play.core.appupdate.AppUpdateInfo import com.google.android.play.core.install.model.ActivityResult import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch class UpdateViewModel( @@ -62,21 +61,6 @@ class UpdateViewModel( fun remindLater() { // for mvp we just return user back to the previous screen - updateInfo.value = updateInfo.value.copy(state = UpdateState.Canceled) - } - - @Suppress("UNCHECKED_CAST") - class UpdateViewModelFactory( - private val application: Application, - private val updateInfo: UpdateInfo, - private val appUpdateChecker: AppUpdateChecker - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return if (modelClass.isAssignableFrom(UpdateViewModel::class.java)) { - UpdateViewModel(application, updateInfo, appUpdateChecker) as T - } else { - throw IllegalArgumentException("ViewModel Not Found.") - } - } + updateInfo.update { it.copy(state = UpdateState.Canceled) } } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/AndroidNotEnoughSpace.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/AndroidNotEnoughSpace.kt index b5d789d5..b9f755e1 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/AndroidNotEnoughSpace.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/AndroidNotEnoughSpace.kt @@ -3,13 +3,13 @@ package co.electriccoin.zcash.ui.screen.warning import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState @@ -24,9 +24,9 @@ fun MainActivity.WrapNotEnoughSpace( goPrevious: () -> Unit, goSettings: () -> Unit ) { - val walletViewModel by viewModels() + val walletViewModel = koinActivityViewModel() - val storageCheckViewModel by viewModels() + val storageCheckViewModel = koinActivityViewModel() val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/viewmodel/StorageCheckViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/viewmodel/StorageCheckViewModel.kt index 26955780..1dc1c3b1 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/viewmodel/StorageCheckViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/warning/viewmodel/StorageCheckViewModel.kt @@ -2,8 +2,8 @@ package co.electriccoin.zcash.ui.screen.warning.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT import co.electriccoin.zcash.global.StorageChecker -import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.WhileSubscribed import kotlinx.coroutines.flow.flow diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/whatsnew/WrapWhatsNew.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/whatsnew/WrapWhatsNew.kt index e62f7efc..c73dde9f 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/whatsnew/WrapWhatsNew.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/whatsnew/WrapWhatsNew.kt @@ -1,11 +1,10 @@ package co.electriccoin.zcash.ui.screen.whatsnew import androidx.activity.compose.BackHandler -import androidx.activity.viewModels import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle -import co.electriccoin.zcash.ui.common.compose.LocalActivity +import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.ui.common.compose.LocalNavController import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel import co.electriccoin.zcash.ui.screen.whatsnew.view.WhatsNewView @@ -13,10 +12,9 @@ import co.electriccoin.zcash.ui.screen.whatsnew.viewmodel.WhatsNewViewModel @Composable fun WrapWhatsNew() { - val activity = LocalActivity.current val navController = LocalNavController.current - val viewModel by activity.viewModels() - val walletViewModel by activity.viewModels() + val viewModel = koinActivityViewModel() + val walletViewModel = koinActivityViewModel() val walletState by walletViewModel.walletStateInformation.collectAsStateWithLifecycle() val state = viewModel.state.collectAsStateWithLifecycle().value ?: return diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/whatsnew/viewmodel/WhatsNewViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/whatsnew/viewmodel/WhatsNewViewModel.kt index e1cee169..23d21854 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/whatsnew/viewmodel/WhatsNewViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/whatsnew/viewmodel/WhatsNewViewModel.kt @@ -3,7 +3,7 @@ package co.electriccoin.zcash.ui.screen.whatsnew.viewmodel import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT import co.electriccoin.zcash.ui.common.model.VersionInfo import co.electriccoin.zcash.ui.screen.whatsnew.model.WhatsNewState import kotlinx.coroutines.flow.SharingStarted diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/work/SyncWorker.kt b/ui-lib/src/main/java/co/electriccoin/zcash/work/SyncWorker.kt index ce530138..1c5f15de 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/work/SyncWorker.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/work/SyncWorker.kt @@ -9,10 +9,9 @@ import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkerParameters import cash.z.ecc.android.sdk.Synchronizer -import cash.z.ecc.android.sdk.WalletCoordinator import cash.z.ecc.android.sdk.model.PercentDecimal -import co.electriccoin.zcash.global.getInstance import co.electriccoin.zcash.spackle.Twig +import co.electriccoin.zcash.ui.common.usecase.ObserveSynchronizerUseCase import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine @@ -27,6 +26,8 @@ import kotlinx.datetime.atTime import kotlinx.datetime.toInstant import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.until +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import kotlin.random.Random import kotlin.time.Duration import kotlin.time.Duration.Companion.days @@ -38,14 +39,18 @@ import kotlin.time.toJavaDuration // TODO [#1249]: Add documentation and tests on background syncing // TODO [#1249]: https://github.com/Electric-Coin-Company/zashi-android/issues/1249 - @Keep -class SyncWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters) { +class SyncWorker( + context: Context, + workerParameters: WorkerParameters +) : CoroutineWorker(context, workerParameters), KoinComponent { + private val observeSynchronizer: ObserveSynchronizerUseCase by inject() + @OptIn(ExperimentalCoroutinesApi::class) override suspend fun doWork(): Result { Twig.debug { "BG Sync: starting..." } - WalletCoordinator.getInstance(applicationContext).synchronizer + observeSynchronizer() .flatMapLatest { Twig.debug { "BG Sync: synchronizer: $it" } diff --git a/ui-screenshot-test/build.gradle.kts b/ui-screenshot-test/build.gradle.kts index 0f8d63dd..3fe3c9a4 100644 --- a/ui-screenshot-test/build.gradle.kts +++ b/ui-screenshot-test/build.gradle.kts @@ -21,7 +21,7 @@ android { testInstrumentationRunnerArguments["clearPackageData"] = "true" } - testInstrumentationRunner = "co.electriccoin.zcash.test.ZcashUiTestRunner" + testInstrumentationRunner = "co.electroniccoin.zcash.ui.screenshot.ZcashScreenshotTestRunner" } // Define the same flavors as in app module diff --git a/ui-screenshot-test/src/main/java/co/electroniccoin/zcash/ui/screenshot/ZcashUiTestRunner.kt b/ui-screenshot-test/src/main/java/co/electroniccoin/zcash/ui/screenshot/ZcashUiTestRunner.kt new file mode 100644 index 00000000..2a07a82d --- /dev/null +++ b/ui-screenshot-test/src/main/java/co/electroniccoin/zcash/ui/screenshot/ZcashUiTestRunner.kt @@ -0,0 +1,38 @@ +package co.electroniccoin.zcash.ui.screenshot + +import android.app.Application +import android.content.Context +import co.electriccoin.zcash.di.coreModule +import co.electriccoin.zcash.di.repositoryModule +import co.electriccoin.zcash.di.useCaseModule +import co.electriccoin.zcash.di.viewModelModule +import co.electriccoin.zcash.test.ZcashUiTestRunner +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.context.startKoin + +class ZcashScreenshotTestRunner : ZcashUiTestRunner() { + override fun newApplication( + cl: ClassLoader?, + className: String?, + context: Context? + ): Application { + return super.newApplication(cl, ZcashUiTestApplication::class.java.name, context) + } +} + +class ZcashUiTestApplication : Application() { + override fun onCreate() { + super.onCreate() + startKoin { + androidLogger() + androidContext(this@ZcashUiTestApplication) + modules( + coreModule, + repositoryModule, + useCaseModule, + viewModelModule + ) + } + } +}