generated from bitwarden/template
-
Notifications
You must be signed in to change notification settings - Fork 9
/
ManualCodeEntryViewModel.kt
266 lines (231 loc) · 8.02 KB
/
ManualCodeEntryViewModel.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
package com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.authenticator.R
import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemEntity
import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemType
import com.bitwarden.authenticator.data.authenticator.repository.AuthenticatorRepository
import com.bitwarden.authenticator.data.authenticator.repository.model.CreateItemResult
import com.bitwarden.authenticator.ui.platform.base.BaseViewModel
import com.bitwarden.authenticator.ui.platform.base.util.Text
import com.bitwarden.authenticator.ui.platform.base.util.asText
import com.bitwarden.authenticator.ui.platform.base.util.isBase32
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import java.util.UUID
import javax.inject.Inject
private const val KEY_STATE = "state"
/**
* The ViewModel for handling user interactions in the manual code entry screen.
*
*/
@HiltViewModel
class ManualCodeEntryViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val authenticatorRepository: AuthenticatorRepository,
) : BaseViewModel<ManualCodeEntryState, ManualCodeEntryEvent, ManualCodeEntryAction>(
initialState = savedStateHandle[KEY_STATE]
?: ManualCodeEntryState(code = "", issuer = "", dialog = null),
) {
override fun handleAction(action: ManualCodeEntryAction) {
when (action) {
is ManualCodeEntryAction.CloseClick -> handleCloseClick()
is ManualCodeEntryAction.CodeTextChange -> handleCodeTextChange(action)
is ManualCodeEntryAction.IssuerTextChange -> handleIssuerTextChange(action)
is ManualCodeEntryAction.CodeSubmit -> handleCodeSubmit()
is ManualCodeEntryAction.ScanQrCodeTextClick -> handleScanQrCodeTextClick()
is ManualCodeEntryAction.SettingsClick -> handleSettingsClick()
is ManualCodeEntryAction.Internal.CreateItemResultReceive -> {
handleCreateItemReceive(action)
}
ManualCodeEntryAction.DismissDialog -> {
handleDialogDismiss()
}
}
}
private fun handleDialogDismiss() {
mutableStateFlow.update { it.copy(dialog = null) }
}
private fun handleIssuerTextChange(action: ManualCodeEntryAction.IssuerTextChange) {
mutableStateFlow.update {
it.copy(issuer = action.issuer)
}
}
private fun handleCloseClick() {
sendEvent(ManualCodeEntryEvent.NavigateBack)
}
private fun handleCodeTextChange(action: ManualCodeEntryAction.CodeTextChange) {
mutableStateFlow.update {
it.copy(code = action.code)
}
}
private fun handleCodeSubmit() {
if (state.code.isBlank()) {
mutableStateFlow.update {
it.copy(
dialog = ManualCodeEntryState.DialogState.Error(
message = R.string.key_is_required.asText()
)
)
}
return
}
if (!state.code.isBase32()) {
mutableStateFlow.update {
it.copy(
dialog = ManualCodeEntryState.DialogState.Error(
message = R.string.key_is_invalid.asText()
)
)
}
return
}
if (state.issuer.isBlank()) {
mutableStateFlow.update {
it.copy(
dialog = ManualCodeEntryState.DialogState.Error(
message = R.string.name_is_required.asText()
)
)
}
return
}
viewModelScope.launch {
val result = authenticatorRepository.createItem(
AuthenticatorItemEntity(
id = UUID.randomUUID().toString(),
key = state.code,
issuer = state.issuer,
accountName = "",
userId = null,
type = if (state.code.startsWith("steam://")) {
AuthenticatorItemType.STEAM
} else {
AuthenticatorItemType.TOTP
}
)
)
sendAction(ManualCodeEntryAction.Internal.CreateItemResultReceive(result))
}
}
private fun handleScanQrCodeTextClick() {
sendEvent(ManualCodeEntryEvent.NavigateToQrCodeScreen)
}
private fun handleSettingsClick() {
sendEvent(ManualCodeEntryEvent.NavigateToAppSettings)
}
private fun handleCreateItemReceive(
action: ManualCodeEntryAction.Internal.CreateItemResultReceive,
) {
when (action.result) {
CreateItemResult.Error -> {
mutableStateFlow.update {
it.copy(
dialog = ManualCodeEntryState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(),
)
)
}
}
CreateItemResult.Success -> {
sendEvent(
event = ManualCodeEntryEvent.ShowToast(
message = R.string.verification_code_added.asText(),
),
)
sendEvent(
event = ManualCodeEntryEvent.NavigateBack,
)
}
}
}
}
/**
* Models state of the manual entry screen.
*/
@Parcelize
data class ManualCodeEntryState(
val code: String,
val issuer: String,
val dialog: DialogState?,
) : Parcelable {
@Parcelize
sealed class DialogState : Parcelable {
@Parcelize
data class Error(
val title: Text? = null,
val message: Text,
) : DialogState()
@Parcelize
data class Loading(
val message: Text,
) : DialogState()
}
}
/**
* Models events for the [ManualCodeEntryScreen].
*/
sealed class ManualCodeEntryEvent {
/**
* Navigate back.
*/
data object NavigateBack : ManualCodeEntryEvent()
/**
* Navigate to the Qr code screen.
*/
data object NavigateToQrCodeScreen : ManualCodeEntryEvent()
/**
* Navigate to the app settings.
*/
data object NavigateToAppSettings : ManualCodeEntryEvent()
/**
* Show a toast with the given [message].
*/
data class ShowToast(val message: Text) : ManualCodeEntryEvent()
}
/**
* Models actions for the [ManualCodeEntryScreen].
*/
sealed class ManualCodeEntryAction {
/**
* User clicked close.
*/
data object CloseClick : ManualCodeEntryAction()
/**
* The user has submitted a code.
*/
data object CodeSubmit : ManualCodeEntryAction()
/**
* The user has changed the code text.
*/
data class CodeTextChange(val code: String) : ManualCodeEntryAction()
/**
* The use has changed the issuer text.
*/
data class IssuerTextChange(val issuer: String) : ManualCodeEntryAction()
/**
* Models actions that the [ManualCodeEntryViewModel] itself might send.
*/
sealed class Internal : ManualCodeEntryAction() {
/**
* Indicates a result for creating an item has been received.
*/
data class CreateItemResultReceive(val result: CreateItemResult) : Internal()
}
/**
* The text to switch to QR code scanning is clicked.
*/
data object ScanQrCodeTextClick : ManualCodeEntryAction()
/**
* The action for the user clicking the settings button.
*/
data object SettingsClick : ManualCodeEntryAction()
/**
* The user has dismissed the dialog.
*/
data object DismissDialog : ManualCodeEntryAction()
}