Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-09 18:25:59 +04:00
parent e4d637927a
commit 2a021f7d03
14 changed files with 155 additions and 76 deletions

View file

@ -0,0 +1,20 @@
package com.tangem.feature.tester.di
import android.content.Context
import com.tangem.feature.tester.presentation.navigation.DefaultTesterMenuLauncher
import com.tangem.features.tester.api.TesterMenuLauncher
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
internal object TesterMenuLauncherModule {
@Provides
fun provideTesterMenuLauncher(@ApplicationContext context: Context): TesterMenuLauncher {
return DefaultTesterMenuLauncher(context = context)
}
}

View file

@ -1,7 +1,7 @@
package com.tangem.feature.tester.di
import com.tangem.feature.tester.presentation.navigation.DefaultTesterRouter
import com.tangem.features.tester.api.TesterRouter
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@ -14,5 +14,5 @@ internal interface TesterRouterModule {
@Binds
@ActivityScoped
fun bindTesterRouter(defaultTesterRouter: DefaultTesterRouter): TesterRouter
fun bindTesterRouter(defaultTesterRouter: DefaultTesterRouter): InnerTesterRouter
}

View file

@ -32,7 +32,6 @@ import com.tangem.feature.tester.presentation.providers.ui.BlockchainProvidersSc
import com.tangem.feature.tester.presentation.providers.viewmodel.BlockchainProvidersViewModel
import com.tangem.feature.tester.presentation.testpush.ui.TestPushScreen
import com.tangem.feature.tester.presentation.testpush.viewmodel.TestPushViewModel
import com.tangem.features.tester.api.TesterRouter
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.collections.immutable.persistentSetOf
import javax.inject.Inject
@ -44,9 +43,8 @@ internal class TesterActivity : ComposeActivity() {
@Inject
override lateinit var uiDependencies: UiDependencies
/** Router for inner feature navigation */
@Inject
lateinit var testerRouter: TesterRouter
lateinit var innerTesterRouter: InnerTesterRouter
@Inject
lateinit var appFinisher: AppFinisher
@ -54,11 +52,6 @@ internal class TesterActivity : ComposeActivity() {
@Inject
lateinit var appRouter: AppRouter
private val innerTesterRouter: InnerTesterRouter
get() = requireNotNull(testerRouter as? InnerTesterRouter) {
"TesterRouter must be InnerTesterRouter for tester feature"
}
@Composable
override fun ScreenContent(modifier: Modifier) {
val systemBarsColor = TangemTheme.colors.background.secondary

View file

@ -0,0 +1,53 @@
package com.tangem.feature.tester.presentation.navigation
import android.content.Context
import android.content.Intent
import android.hardware.Sensor
import android.hardware.SensorManager
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.tangem.feature.tester.presentation.TesterActivity
import com.tangem.features.tester.api.TesterMenuLauncher
/**
* Default implementation of [TesterMenuLauncher] that listens for shake events using the device's accelerometer.
* When a shake is detected, it opens the tester menu.
*
* @param context the application context used to access system services
*
[REDACTED_AUTHOR]
*/
internal class DefaultTesterMenuLauncher(private val context: Context) : TesterMenuLauncher {
override val launchOnShakeObserver: DefaultLifecycleObserver by lazy(LazyThreadSafetyMode.NONE) {
createObserver(context)
}
private fun createObserver(context: Context): DefaultLifecycleObserver {
return object : DefaultLifecycleObserver {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
private val shakeEventListener = ShakeEventListener(action = ::openTesterMenu)
override fun onResume(owner: LifecycleOwner) {
accelerometer?.let {
sensorManager.registerListener(
/* listener = */ shakeEventListener,
/* sensor = */ it,
/* samplingPeriodUs = */ SensorManager.SENSOR_DELAY_NORMAL,
)
}
}
override fun onPause(owner: LifecycleOwner) {
sensorManager.unregisterListener(shakeEventListener)
}
}
}
private fun openTesterMenu() {
val intent = Intent(context, TesterActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
}

View file

@ -1,31 +1,19 @@
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 {
internal class DefaultTesterRouter @Inject constructor() : InnerTesterRouter {
private var navController: NavController? = null
override fun getEntryIntent(): Intent {
return Intent(context, TesterActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
override fun setNavController(navController: NavController) {
this.navController = navController
}

View file

@ -1,14 +1,13 @@
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 {
internal interface InnerTesterRouter {
/** Set up a navigation controller that bound to tester navigation graph */
fun setNavController(navController: NavController)

View file

@ -0,0 +1,51 @@
package com.tangem.feature.tester.presentation.navigation
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import kotlin.math.sqrt
/**
* Listener for device shake events.
*
* This class implements [SensorEventListener] and is used to detect device shaking based on accelerometer data.
* When a shake is detected, the provided action is invoked.
*
* @property action lambda function to be called when a shake is detected
*
[REDACTED_AUTHOR]
*/
internal class ShakeEventListener(private val action: () -> Unit) : SensorEventListener {
private var lastShakeTime = 0L
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
override fun onSensorChanged(event: SensorEvent?) {
if (event?.sensor?.type != Sensor.TYPE_ACCELEROMETER) return
val acceleration = calculateAcceleration(event = event)
val currentTime = System.currentTimeMillis()
val currentShakeInterval = currentTime - lastShakeTime
if (acceleration > SHAKE_THRESHOLD && currentShakeInterval > SHAKE_INTERVAL_MS) {
lastShakeTime = currentTime
action()
}
}
private fun calculateAcceleration(event: SensorEvent): Float {
val (x, y, z) = event.toXYZ()
return sqrt(x * x + y * y + z * z) - SensorManager.GRAVITY_EARTH
}
private fun SensorEvent.toXYZ() = Triple(values[0], values[1], values[2])
private companion object {
private const val SHAKE_THRESHOLD: Float = 12f
private const val SHAKE_INTERVAL_MS: Long = 1000
}
}