Updated on 2026-08-14
This commit is contained in:
parent
629d42fc34
commit
0d2f6e2410
35 changed files with 389 additions and 113 deletions
|
|
@ -47,6 +47,7 @@ android {
|
|||
Field.Environment("prod"),
|
||||
Field.TestActionEnabled(false),
|
||||
Field.LogEnabled(false),
|
||||
Field.TesterMenuAvailability(false),
|
||||
),
|
||||
builder = ::buildConfigField,
|
||||
).create()
|
||||
|
|
@ -67,6 +68,7 @@ android {
|
|||
Field.Environment("dev"),
|
||||
Field.TestActionEnabled(true),
|
||||
Field.LogEnabled(true),
|
||||
Field.TesterMenuAvailability(true),
|
||||
),
|
||||
builder = ::buildConfigField,
|
||||
).create()
|
||||
|
|
@ -77,6 +79,10 @@ android {
|
|||
versionNameSuffix = "-beta"
|
||||
applicationIdSuffix = ".debug"
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
BuildConfigFieldFactory(
|
||||
fields = listOf(Field.TesterMenuAvailability(true)),
|
||||
builder = ::buildConfigField,
|
||||
).create()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +133,8 @@ dependencies {
|
|||
implementation(project(":features:swap:presentation"))
|
||||
implementation(project(":features:swap:domain"))
|
||||
implementation(project(":features:swap:data"))
|
||||
implementation(project(":features:tester:api"))
|
||||
implementation(project(":features:tester:impl"))
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(AndroidX.coreKtx)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.core.view.WindowInsetsControllerCompat
|
|||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tangem_sdk_new.extensions.initWithBiometrics
|
||||
|
|
@ -33,6 +34,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
|||
import com.tangem.tap.features.shop.redux.ShopAction
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ActivityMainBinding
|
||||
|
|
@ -71,6 +73,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
@Inject
|
||||
lateinit var appStateHolder: AppStateHolder
|
||||
|
||||
/** Router for opening tester menu */
|
||||
@Inject
|
||||
lateinit var testerRouter: TesterRouter
|
||||
|
||||
private var snackbar: Snackbar? = null
|
||||
private val dialogManager = DialogManager()
|
||||
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
|
||||
|
|
@ -97,6 +103,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
GooglePayService(createPaymentsClient(this), this),
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatch(DaggerGraphAction.SetActivityDependencies(testerRouter))
|
||||
}
|
||||
|
||||
private fun initUserWalletsListManager() {
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@ import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository
|
|||
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||
import com.tangem.tap.features.di.redux.DaggerGraphAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -166,7 +166,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
appStateHolder.userTokensRepository = userTokensRepository
|
||||
appStateHolder.walletStoresManager = walletStoresManager
|
||||
|
||||
store.dispatch(DaggerGraphAction.SetDependencies(assetReader))
|
||||
store.dispatch(DaggerGraphAction.SetApplicationDependencies(assetReader))
|
||||
|
||||
scope.launch {
|
||||
featureTogglesManager.init()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.tangem.tap.common.redux.global.globalReducer
|
|||
import com.tangem.tap.common.redux.navigation.NavigationReducer
|
||||
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
|
||||
import com.tangem.tap.features.di.redux.DaggerGraphReducer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer
|
||||
import com.tangem.tap.features.home.redux.HomeReducer
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer
|
||||
|
|
@ -20,6 +19,7 @@ import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
|
|||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
import org.rekotlin.Action
|
||||
|
||||
fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder): AppState {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ import com.tangem.tap.features.details.redux.DetailsMiddleware
|
|||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.di.redux.DaggerGraphMiddleware
|
||||
import com.tangem.tap.features.di.redux.DaggerGraphState
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerMiddleware
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware
|
||||
|
|
@ -43,6 +41,8 @@ import com.tangem.tap.features.walletSelector.redux.WalletSelectorMiddleware
|
|||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphMiddleware
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import org.rekotlin.Middleware
|
||||
import org.rekotlin.StateType
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ enum class SettingsElement(
|
|||
AppSettings(R.drawable.ic_settings, R.string.app_settings_title),
|
||||
LinkMoreCards(R.drawable.ic_more_cards, R.string.details_row_title_create_backup),
|
||||
TermsOfService(R.drawable.ic_text, R.string.disclaimer_title), // General Terms of Service of the App
|
||||
PrivacyPolicy(R.drawable.ic_lock_24, R.string.details_row_privacy_policy);
|
||||
PrivacyPolicy(R.drawable.ic_lock_24, R.string.details_row_privacy_policy),
|
||||
TesterMenu(R.drawable.ic_alert_24, R.string.tester_menu)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class DetailsViewModel(private val store: Store<AppState>) {
|
|||
SettingsElement.AppSettings -> if (state.appSettingsState.isBiometricsAvailable) it else null
|
||||
SettingsElement.AppCurrency -> if (cardTypesResolver?.isMultiwalletAllowed() != true) it else null
|
||||
SettingsElement.ReferralProgram -> if (cardTypesResolver?.isTangemWallet() == true) it else null
|
||||
SettingsElement.TesterMenu -> if (BuildConfig.TESTER_MENU_ENABLED) it else null
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
|
|
@ -96,6 +97,9 @@ class DetailsViewModel(private val store: Store<AppState>) {
|
|||
SettingsElement.ReferralProgram -> {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram))
|
||||
}
|
||||
SettingsElement.TesterMenu -> {
|
||||
store.state.daggerGraphState.testerRouter?.startTesterScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.tap.features.di.redux
|
||||
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed interface DaggerGraphAction : Action {
|
||||
|
||||
data class SetDependencies(val assetReader: AssetReader) : DaggerGraphAction
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.features.di.redux
|
||||
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class DaggerGraphState(val assetReader: AssetReader? = null) : StateType
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tap.proxy.redux
|
||||
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed interface DaggerGraphAction : Action {
|
||||
|
||||
data class SetApplicationDependencies(val assetReader: AssetReader) : DaggerGraphAction
|
||||
|
||||
data class SetActivityDependencies(val testerRouter: TesterRouter) : DaggerGraphAction
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.di.redux
|
||||
package com.tangem.tap.proxy.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.di.redux
|
||||
package com.tangem.tap.proxy.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -12,8 +12,11 @@ object DaggerGraphReducer {
|
|||
|
||||
private fun internalReduce(action: DaggerGraphAction, state: AppState): DaggerGraphState {
|
||||
return when (action) {
|
||||
is DaggerGraphAction.SetDependencies -> state.daggerGraphState.copy(
|
||||
assetReader = action.assetReader,
|
||||
is DaggerGraphAction.SetApplicationDependencies -> state.daggerGraphState.copy(
|
||||
assetReader = action.assetReader
|
||||
)
|
||||
is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy(
|
||||
testerRouter = action.testerRouter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.tap.proxy.redux
|
||||
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class DaggerGraphState(
|
||||
val assetReader: AssetReader? = null,
|
||||
val testerRouter: TesterRouter? = null,
|
||||
) : StateType
|
||||
|
|
@ -15,7 +15,9 @@ object Compose {
|
|||
const val animation = "androidx.compose.animation:animation:" + Versions.compose
|
||||
const val coil = "io.coil-kt:coil-compose:" + Versions.coil
|
||||
const val foundation = "androidx.compose.foundation:foundation:" + Versions.composeFoundation
|
||||
const val hiltNavigation = "androidx.hilt:hilt-navigation-compose:" + Versions.composeHiltNavigation
|
||||
const val material = "androidx.compose.material:material:" + Versions.compose
|
||||
const val navigation = "androidx.navigation:navigation-compose:" + Versions.composeNavigation
|
||||
const val ui = "androidx.compose.ui:ui:" + Versions.compose
|
||||
const val uiTooling = "androidx.compose.ui:ui-tooling:" + Versions.compose
|
||||
const val constraintLayout = "androidx.constraintlayout:constraintlayout-compose:" + Versions.composeConstraint
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ object Versions {
|
|||
// region Compose
|
||||
const val compose = "1.3.1"
|
||||
const val composeFoundation = "1.3.1"
|
||||
const val composeHiltNavigation = "1.0.0"
|
||||
const val composeNavigation = "2.5.1"
|
||||
const val composeConstraint = "1.0.1"
|
||||
// endregion Compose
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,12 @@ internal class DefaultVersionProvider @Inject constructor(
|
|||
) : VersionProvider {
|
||||
|
||||
override fun get(): String? {
|
||||
val versionName = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
return runCatching { getVersionName().substringBefore(VERSION_NAME_DELIMITER) }
|
||||
.fold(onSuccess = { it }, onFailure = { null })
|
||||
}
|
||||
|
||||
private fun getVersionName(): String {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.packageManager
|
||||
.getPackageInfo(
|
||||
context.packageName,
|
||||
|
|
@ -26,9 +31,6 @@ internal class DefaultVersionProvider @Inject constructor(
|
|||
} else {
|
||||
context.packageManager.getPackageInfo(context.packageName, 0).versionName
|
||||
}
|
||||
|
||||
return runCatching { versionName.substringBefore(VERSION_NAME_DELIMITER) }
|
||||
.fold(onSuccess = { it }, onFailure = { null })
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
1
features/tester/api/.gitignore
vendored
Normal file
1
features/tester/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
36
features/tester/api/build.gradle.kts
Normal file
36
features/tester/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
create("debug_beta") {
|
||||
initWith(getByName("release"))
|
||||
BuildConfigFieldFactory(
|
||||
fields = listOf(
|
||||
Field.Environment("release"),
|
||||
Field.TestActionEnabled(true),
|
||||
Field.LogEnabled(true),
|
||||
),
|
||||
builder = ::buildConfigField,
|
||||
).create()
|
||||
}
|
||||
}
|
||||
}
|
||||
2
features/tester/api/src/main/AndroidManifest.xml
Normal file
2
features/tester/api/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.features.tester.api" />
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.features.tester.api
|
||||
|
||||
/**
|
||||
* Outer tester feature router
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface TesterRouter {
|
||||
|
||||
/** Open tester menu */
|
||||
fun startTesterScreen()
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ android {
|
|||
Field.Environment("release"),
|
||||
Field.TestActionEnabled(true),
|
||||
Field.LogEnabled(true),
|
||||
Field.TesterMenuAvailability(true)
|
||||
Field.TesterMenuAvailability(true),
|
||||
),
|
||||
builder = ::buildConfigField,
|
||||
).create()
|
||||
|
|
@ -67,14 +67,22 @@ dependencies {
|
|||
implementation(AndroidX.activityCompose)
|
||||
|
||||
/** Compose */
|
||||
implementation(Compose.foundation)
|
||||
implementation(Compose.hiltNavigation)
|
||||
implementation(Compose.material)
|
||||
implementation(Compose.navigation)
|
||||
implementation(Compose.ui)
|
||||
implementation(Compose.uiTooling)
|
||||
|
||||
/** DI */
|
||||
implementation(Library.accompanistSystemUiController)
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
|
||||
/** Core modules */
|
||||
implementation(project(":core:featuretoggles"))
|
||||
implementation(project(":core:ui"))
|
||||
|
||||
/** Feature Apis */
|
||||
implementation(project(":features:tester:api"))
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.feature.tester.di
|
||||
|
||||
import com.tangem.feature.tester.presentation.navigation.DefaultTesterRouter
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
internal interface TesterRouterModule {
|
||||
|
||||
@Binds
|
||||
@ActivityScoped
|
||||
fun bindTesterRouter(defaultTesterRouter: DefaultTesterRouter): TesterRouter
|
||||
}
|
||||
|
|
@ -3,15 +3,73 @@ package com.tangem.feature.tester.presentation
|
|||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.ui.FeatureTogglesScreen
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.viewmodels.FeatureTogglesViewModel
|
||||
import com.tangem.feature.tester.presentation.menu.state.TesterMenuContentState
|
||||
import com.tangem.feature.tester.presentation.menu.ui.TesterMenuScreen
|
||||
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
|
||||
import com.tangem.feature.tester.presentation.navigation.TesterScreen
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
/** Activity for testers */
|
||||
@AndroidEntryPoint
|
||||
internal class TesterActivity : ComponentActivity() {
|
||||
|
||||
/** Router for inner feature navigation */
|
||||
@Inject
|
||||
lateinit var testerRouter: TesterRouter
|
||||
|
||||
private val innerTesterRouter: InnerTesterRouter
|
||||
get() = requireNotNull(testerRouter as? InnerTesterRouter) {
|
||||
"TesterRouter must be InnerTesterRouter for tester feature"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContent {
|
||||
// TODO("2949") add logic to screens
|
||||
TangemTheme {
|
||||
val statusBarColor = TangemTheme.colors.background.secondary
|
||||
SystemBarsEffect {
|
||||
setStatusBarColor(color = statusBarColor)
|
||||
}
|
||||
|
||||
TesterNavHost()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("TopLevelComposableFunctions")
|
||||
@Composable
|
||||
private fun TesterNavHost() {
|
||||
val navController = rememberNavController().also { innerTesterRouter.setNavController(it) }
|
||||
|
||||
NavHost(navController = navController, startDestination = TesterScreen.MENU.name) {
|
||||
composable(route = TesterScreen.MENU.name) {
|
||||
TesterMenuScreen(
|
||||
state = TesterMenuContentState(
|
||||
onBackClick = innerTesterRouter::back,
|
||||
onFeatureTogglesClick = { innerTesterRouter.open(TesterScreen.FEATURE_TOGGLES) },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composable(route = TesterScreen.FEATURE_TOGGLES.name) {
|
||||
val viewModel = hiltViewModel<FeatureTogglesViewModel>().apply {
|
||||
setupNavigation(innerTesterRouter)
|
||||
}
|
||||
|
||||
FeatureTogglesScreen(state = viewModel.uiState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ package com.tangem.feature.tester.presentation.featuretoggles.models
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class TesterFeatureToggle(
|
||||
internal data class TesterFeatureToggle(
|
||||
val name: String,
|
||||
val isEnabled: Boolean
|
||||
val isEnabled: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.feature.tester.presentation.featuretoggles.state
|
||||
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle
|
||||
|
||||
/**
|
||||
* Content state of feature toggles screen
|
||||
*
|
||||
* @property featureToggles feature toggles list
|
||||
* @property onBackClick the lambda to be invoked when back button is pressed
|
||||
* @property onToggleValueChange the lambda to be invoked when switch button is pressed
|
||||
*/
|
||||
internal data class FeatureTogglesContentState(
|
||||
val featureToggles: List<TesterFeatureToggle>,
|
||||
val onBackClick: () -> Unit,
|
||||
val onToggleValueChange: (String, Boolean) -> Unit,
|
||||
)
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.feature.tester.presentation.featuretoggles.state
|
||||
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle
|
||||
|
||||
/**
|
||||
* State holder of feature toggles screen
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed interface FeatureTogglesStateHolder {
|
||||
|
||||
/**
|
||||
* Content
|
||||
*
|
||||
* @property featureToggles feature toggles list
|
||||
* @property onToggleValueChange the lambda to be invoked when switch button is pressed
|
||||
* @property onBackClicked the lambda to be invoked when back button is pressed
|
||||
*/
|
||||
data class Content(
|
||||
val onBackClicked: () -> Unit,
|
||||
val featureToggles: List<TesterFeatureToggle>,
|
||||
val onToggleValueChange: (String, Boolean) -> Unit,
|
||||
) : FeatureTogglesStateHolder
|
||||
}
|
||||
|
|
@ -21,23 +21,16 @@ import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.tester.impl.R
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesStateHolder
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesContentState
|
||||
|
||||
/**
|
||||
* Screen with feature toggles list
|
||||
*
|
||||
* @param stateHolder screen state
|
||||
* @param state screen state
|
||||
*/
|
||||
@Composable
|
||||
fun FeatureTogglesScreen(stateHolder: FeatureTogglesStateHolder) {
|
||||
when (stateHolder) {
|
||||
is FeatureTogglesStateHolder.Content -> FeatureTogglesContent(content = stateHolder)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun FeatureTogglesContent(content: FeatureTogglesStateHolder.Content) {
|
||||
internal fun FeatureTogglesScreen(state: FeatureTogglesContentState) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -45,14 +38,14 @@ private fun FeatureTogglesContent(content: FeatureTogglesStateHolder.Content) {
|
|||
) {
|
||||
stickyHeader {
|
||||
AppBarWithBackButton(
|
||||
onBackClick = content.onBackClicked,
|
||||
onBackClick = state.onBackClick,
|
||||
text = stringResource(id = R.string.feature_toggles),
|
||||
)
|
||||
}
|
||||
items(content.featureToggles) { featureToggle ->
|
||||
items(state.featureToggles) { featureToggle ->
|
||||
FeatureToggleItem(
|
||||
toggle = featureToggle,
|
||||
onCheckedChange = { isChange -> content.onToggleValueChange(featureToggle.name, isChange) },
|
||||
onCheckedChange = { isChange -> state.onToggleValueChange(featureToggle.name, isChange) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -89,13 +82,13 @@ private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boo
|
|||
private fun PreviewFeatureTogglesScreen_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
FeatureTogglesScreen(
|
||||
stateHolder = FeatureTogglesStateHolder.Content(
|
||||
state = FeatureTogglesContentState(
|
||||
featureToggles = listOf(
|
||||
TesterFeatureToggle(name = "FEATURE_TOGGLE_1", isEnabled = true),
|
||||
TesterFeatureToggle(name = "FEATURE_TOGGLE_2", isEnabled = false),
|
||||
),
|
||||
onToggleValueChange = { _, _ -> },
|
||||
onBackClicked = {},
|
||||
onBackClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -106,13 +99,13 @@ private fun PreviewFeatureTogglesScreen_InLightTheme() {
|
|||
private fun PreviewFeatureTogglesScreen_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
FeatureTogglesScreen(
|
||||
stateHolder = FeatureTogglesStateHolder.Content(
|
||||
state = FeatureTogglesContentState(
|
||||
featureToggles = listOf(
|
||||
TesterFeatureToggle(name = "FEATURE_TOGGLE_1", isEnabled = true),
|
||||
TesterFeatureToggle(name = "FEATURE_TOGGLE_2", isEnabled = false),
|
||||
),
|
||||
onToggleValueChange = { _, _ -> },
|
||||
onBackClicked = {},
|
||||
onBackClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.feature.tester.presentation.featuretoggles.viewmodels
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.core.featuretoggle.manager.MutableFeatureTogglesManager
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesContentState
|
||||
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* ViewModel for screen with list of feature toggles
|
||||
*
|
||||
* @property featureTogglesManager manager for getting information about the availability of feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@HiltViewModel
|
||||
internal class FeatureTogglesViewModel @Inject constructor(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : ViewModel() {
|
||||
|
||||
/** Current ui state */
|
||||
var uiState: FeatureTogglesContentState by mutableStateOf(initState())
|
||||
private set
|
||||
|
||||
private val mutableFeatureTogglesManager: MutableFeatureTogglesManager
|
||||
get() = requireNotNull(featureTogglesManager as? MutableFeatureTogglesManager) {
|
||||
"Feature toggle manager must be mutable (debug build type)"
|
||||
}
|
||||
|
||||
/** Setup navigation state property by router [router] */
|
||||
fun setupNavigation(router: InnerTesterRouter) {
|
||||
uiState = uiState.copy(onBackClick = router::back)
|
||||
}
|
||||
|
||||
private fun initState(): FeatureTogglesContentState {
|
||||
return FeatureTogglesContentState(
|
||||
featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles(),
|
||||
onBackClick = {},
|
||||
onToggleValueChange = ::onToggleValueChange,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onToggleValueChange(name: String, isEnabled: Boolean) {
|
||||
mutableFeatureTogglesManager.changeToggle(name = name, isEnabled = isEnabled)
|
||||
|
||||
uiState = uiState.copy(featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles())
|
||||
}
|
||||
|
||||
private fun MutableFeatureTogglesManager.getTesterFeatureToggles(): List<TesterFeatureToggle> {
|
||||
return this
|
||||
.getFeatureToggles()
|
||||
.map { TesterFeatureToggle(it.key, it.value) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.feature.tester.presentation.menu.state
|
||||
|
||||
/**
|
||||
* Content state of tester menu screen
|
||||
*
|
||||
* @property onBackClick the lambda to be invoked when back button is pressed
|
||||
* @property onFeatureTogglesClick the lambda to be invoked when feature toggles button is pressed
|
||||
*/
|
||||
data class TesterMenuContentState(
|
||||
val onBackClick: () -> Unit,
|
||||
val onFeatureTogglesClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.feature.tester.presentation.menu.state
|
||||
|
||||
/**
|
||||
* State holder of tester menu screen
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed interface TesterMenuStateHolder {
|
||||
|
||||
/**
|
||||
* Content
|
||||
*
|
||||
* @property onBackClicked the lambda to be invoked when back button is pressed
|
||||
* @property onFeatureTogglesClicked the lambda to be invoked when feature toggles button is pressed
|
||||
*/
|
||||
data class Content(
|
||||
val onBackClicked: () -> Unit,
|
||||
val onFeatureTogglesClicked: () -> Unit
|
||||
) : TesterMenuStateHolder
|
||||
}
|
||||
|
|
@ -16,29 +16,22 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.tester.impl.R
|
||||
import com.tangem.feature.tester.presentation.menu.state.TesterMenuStateHolder
|
||||
import com.tangem.feature.tester.presentation.menu.state.TesterMenuContentState
|
||||
|
||||
/**
|
||||
* Screen with functionality for testers
|
||||
*
|
||||
* @param stateHolder screen state
|
||||
* @param state screen state
|
||||
*/
|
||||
@Composable
|
||||
fun TesterMenuScreen(stateHolder: TesterMenuStateHolder) {
|
||||
when (stateHolder) {
|
||||
is TesterMenuStateHolder.Content -> TesterMenuContent(content = stateHolder)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TesterMenuContent(content: TesterMenuStateHolder.Content) {
|
||||
internal fun TesterMenuScreen(state: TesterMenuContentState) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
) {
|
||||
AppBarWithBackButton(
|
||||
onBackClick = content.onBackClicked,
|
||||
onBackClick = state.onBackClick,
|
||||
text = stringResource(id = R.string.tester_menu),
|
||||
)
|
||||
Column(
|
||||
|
|
@ -52,12 +45,12 @@ private fun TesterMenuContent(content: TesterMenuStateHolder.Content) {
|
|||
) {
|
||||
PrimaryButton(
|
||||
text = stringResource(R.string.feature_toggles),
|
||||
onClick = content.onFeatureTogglesClicked,
|
||||
onClick = state.onFeatureTogglesClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
PrimaryButton(
|
||||
text = stringResource(R.string.stand_toggles),
|
||||
onClick = content.onFeatureTogglesClicked,
|
||||
onClick = state.onFeatureTogglesClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = false,
|
||||
)
|
||||
|
|
@ -70,9 +63,9 @@ private fun TesterMenuContent(content: TesterMenuStateHolder.Content) {
|
|||
private fun PreviewTesterMenuScreen_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
TesterMenuScreen(
|
||||
stateHolder = TesterMenuStateHolder.Content(
|
||||
onBackClicked = {},
|
||||
onFeatureTogglesClicked = {},
|
||||
state = TesterMenuContentState(
|
||||
onBackClick = {},
|
||||
onFeatureTogglesClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -83,9 +76,9 @@ private fun PreviewTesterMenuScreen_InLightTheme() {
|
|||
private fun PreviewTesterMenuScreen_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
TesterMenuScreen(
|
||||
stateHolder = TesterMenuStateHolder.Content(
|
||||
onBackClicked = {},
|
||||
onFeatureTogglesClicked = {},
|
||||
state = TesterMenuContentState(
|
||||
onBackClick = {},
|
||||
onFeatureTogglesClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.feature.tester.presentation.navigation
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.navigation.NavController
|
||||
import com.tangem.feature.tester.presentation.TesterActivity
|
||||
import dagger.hilt.android.qualifiers.ActivityContext
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Implementation of router for tester feature
|
||||
*
|
||||
* @property context activity context
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@ActivityScoped
|
||||
internal class DefaultTesterRouter @Inject constructor(
|
||||
@ActivityContext private val context: Context,
|
||||
) : InnerTesterRouter {
|
||||
|
||||
private var navController: NavController? = null
|
||||
|
||||
override fun startTesterScreen() {
|
||||
context.startActivity(
|
||||
Intent(context, TesterActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
)
|
||||
}
|
||||
|
||||
override fun setNavController(navController: NavController) {
|
||||
this.navController = navController
|
||||
}
|
||||
|
||||
override fun open(screen: TesterScreen) {
|
||||
navController?.navigate(screen.name)
|
||||
}
|
||||
|
||||
override fun back() {
|
||||
navController?.popBackStack()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.feature.tester.presentation.navigation
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
|
||||
/**
|
||||
* Inner feature router
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal interface InnerTesterRouter : TesterRouter {
|
||||
|
||||
/** Set up a navigation controller that bound to tester navigation graph */
|
||||
fun setNavController(navController: NavController)
|
||||
|
||||
/** Open specified screen [screen] */
|
||||
fun open(screen: TesterScreen)
|
||||
|
||||
/** Open last screen */
|
||||
fun back()
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.feature.tester.presentation.navigation
|
||||
|
||||
/**
|
||||
* Tester feature screens
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal enum class TesterScreen {
|
||||
MENU, FEATURE_TOGGLES
|
||||
}
|
||||
|
|
@ -24,5 +24,6 @@ include(":features:swap:data")
|
|||
include(":features:swap:domain")
|
||||
include(":features:swap:presentation")
|
||||
|
||||
include(":features:tester:api")
|
||||
include(":features:tester:impl")
|
||||
// endregion Feature modules
|
||||
Loading…
Add table
Add a link
Reference in a new issue