11package com.darkrockstudios.app.securecamera.introduction
22
3- import android.content.Context
4- import androidx.compose.material.icons.Icons
5- import androidx.compose.material.icons.automirrored.filled.Send
6- import androidx.compose.material.icons.filled.Camera
7- import androidx.compose.material.icons.filled.LocationOff
8- import androidx.compose.material.icons.filled.Lock
9- import androidx.compose.material.icons.filled.MyLocation
10- import androidx.compose.material.icons.filled.PrivacyTip
11- import androidx.lifecycle.viewModelScope
12- import com.darkrockstudios.app.securecamera.BaseViewModel
13- import com.darkrockstudios.app.securecamera.R
14- import com.darkrockstudios.app.securecamera.security.HardwareSchemeConfig
153import com.darkrockstudios.app.securecamera.security.SecurityLevel
16- import com.darkrockstudios.app.securecamera.security.SecurityLevelDetector
17- import com.darkrockstudios.app.securecamera.security.SoftwareSchemeConfig
18- import com.darkrockstudios.app.securecamera.usecases.CreatePinUseCase
19- import com.darkrockstudios.app.securecamera.usecases.PinSizeUseCase
20- import com.darkrockstudios.app.securecamera.usecases.PinStrengthCheckUseCase
21- import kotlinx.coroutines.Dispatchers
22- import kotlinx.coroutines.flow.MutableSharedFlow
23- import kotlinx.coroutines.flow.asSharedFlow
24- import kotlinx.coroutines.flow.update
25- import kotlinx.coroutines.launch
26- import kotlin.time.Duration.Companion.minutes
4+ import kotlinx.coroutines.flow.SharedFlow
5+ import kotlinx.coroutines.flow.StateFlow
276
28- class IntroductionViewModel (
29- private val appContext : Context ,
30- private val securityLevelDetector : SecurityLevelDetector ,
31- private val pinStrengthCheck : PinStrengthCheckUseCase ,
32- private val createPinUseCase : CreatePinUseCase ,
33- private val pinSizeUseCase : PinSizeUseCase ,
34- ) : BaseViewModel<IntroductionUiState>() {
7+ interface IntroductionViewModel {
8+ val uiState: StateFlow <IntroductionUiState >
9+ val skipToPage: SharedFlow <Int >
3510
36- override fun createState () = IntroductionUiState (
37- securityLevel = securityLevelDetector.detectSecurityLevel(),
38- slides = createSlides(),
39- pinSize = pinSizeUseCase.getPinSizeRange()
40- )
11+ fun setPage (page : Int )
12+ suspend fun navigateToNextPage ()
13+ suspend fun navigateToSecurity ()
4114
42- private val _skipToPage = MutableSharedFlow <Int >()
43- val skipToPage = _skipToPage .asSharedFlow()
44-
45- private fun createSlides (): List <IntroductionSlide > {
46- return listOf (
47- IntroductionSlide (
48- icon = Icons .Filled .Camera ,
49- title = appContext.getString(R .string.intro_slide0_title),
50- description = appContext.getString(R .string.intro_slide0_description)
51- ),
52- IntroductionSlide (
53- icon = Icons .Filled .PrivacyTip ,
54- title = appContext.getString(R .string.intro_slide1_title),
55- description = appContext.getString(R .string.intro_slide1_description)
56- ),
57- IntroductionSlide (
58- icon = Icons .Filled .Lock ,
59- title = appContext.getString(R .string.intro_slide2_title),
60- description = appContext.getString(R .string.intro_slide2_description)
61- ),
62- IntroductionSlide (
63- icon = Icons .AutoMirrored .Filled .Send ,
64- title = appContext.getString(R .string.intro_slide3_title),
65- description = appContext.getString(R .string.intro_slide3_description),
66- ),
67- IntroductionSlide (
68- icon = Icons .Filled .LocationOff ,
69- title = appContext.getString(R .string.intro_slide4_title),
70- description = appContext.getString(R .string.intro_slide4_description),
71- ),
72- IntroductionSlide (
73- icon = Icons .Filled .MyLocation ,
74- title = appContext.getString(R .string.intro_slide5_title),
75- description = appContext.getString(R .string.intro_slide5_description),
76- ),
77- )
78- }
79-
80- fun setPage (page : Int ) {
81- _uiState .update { it.copy(currentPage = page) }
82- }
83-
84- suspend fun navigateToNextPage () {
85- val currentPage = uiState.value.currentPage
86- val totalPages = uiState.value.slides.size + 2
87- if (currentPage < totalPages - 1 ) {
88- _skipToPage .emit(currentPage + 1 )
89- }
90- }
91-
92- suspend fun navigateToSecurity () {
93- _skipToPage .emit(uiState.value.slides.size)
94- }
95-
96- fun createPin (pin : String , confirmPin : String ) {
97- val config = when (uiState.value.securityLevel) {
98- SecurityLevel .TEE , SecurityLevel .STRONGBOX -> HardwareSchemeConfig (
99- requireBiometricAttestation = uiState.value.requireBiometrics,
100- authTimeout = 5 .minutes, // Hard coded for now
101- ephemeralKey = uiState.value.ephemeralKey
102- )
103-
104- SecurityLevel .SOFTWARE -> SoftwareSchemeConfig
105- }
106-
107- val pinSize = pinSizeUseCase.getPinSizeRange()
108- if (pin != confirmPin || (pin.length in pinSize).not ()) {
109- _uiState .update { it.copy(errorMessage = appContext.getString(R .string.pin_creation_error)) }
110- return
111- }
112-
113- val strongPin = pinStrengthCheck.isPinStrongEnough(pin)
114- if (strongPin.not ()) {
115- _uiState .update { it.copy(errorMessage = appContext.getString(R .string.pin_creation_error_weak_pin)) }
116- return
117- }
118-
119- // Set loading state to true before starting PIN creation
120- _uiState .update { it.copy(isCreatingPin = true , errorMessage = null ) }
121-
122- viewModelScope.launch(Dispatchers .Default ) {
123- if (createPinUseCase.createPin(pin, config)) {
124- _uiState .update { it.copy(pinCreated = true , isCreatingPin = false ) }
125- } else {
126- _uiState .update { it.copy(isCreatingPin = false ) }
127- }
128- }
129- }
130-
131- fun toggleBiometricsRequired () {
132- _uiState .update { it.copy(requireBiometrics = it.requireBiometrics.not ()) }
133- }
134-
135- fun toggleEphemeralKey () {
136- _uiState .update { it.copy(ephemeralKey = it.ephemeralKey.not ()) }
137- }
15+ fun createPin (pin : String , confirmPin : String )
16+ fun toggleBiometricsRequired ()
17+ fun toggleEphemeralKey ()
13818}
13919
14020data class IntroductionUiState (
141- val slides : List <IntroductionSlide > = emptyList(),
142- val errorMessage : String? = null ,
143- val pinCreated : Boolean = false ,
144- val securityLevel : SecurityLevel ,
145- val requireBiometrics : Boolean = false ,
146- val ephemeralKey : Boolean = false ,
147- val currentPage : Int = 0 ,
148- val isCreatingPin : Boolean = false ,
149- val pinSize : IntRange ,
150- )
21+ val slides : List <IntroductionSlide > = emptyList(),
22+ val errorMessage : String? = null ,
23+ val pinCreated : Boolean = false ,
24+ val securityLevel : SecurityLevel ,
25+ val requireBiometrics : Boolean = false ,
26+ val ephemeralKey : Boolean = false ,
27+ val currentPage : Int = 0 ,
28+ val isCreatingPin : Boolean = false ,
29+ val pinSize : IntRange ,
30+ )
0 commit comments