@@ -28,6 +28,9 @@ import com.duckduckgo.autofill.impl.importing.takeout.processor.TakeoutBookmarkI
28
28
import com.duckduckgo.autofill.impl.importing.takeout.store.BookmarkImportConfigStore
29
29
import com.duckduckgo.autofill.impl.importing.takeout.webflow.ImportGoogleBookmarksWebFlowViewModel.ViewState.HideWebPage
30
30
import com.duckduckgo.autofill.impl.importing.takeout.webflow.ImportGoogleBookmarksWebFlowViewModel.ViewState.ShowWebPage
31
+ import com.duckduckgo.autofill.impl.importing.takeout.webflow.TakeoutMessageResult.TakeoutActionError
32
+ import com.duckduckgo.autofill.impl.importing.takeout.webflow.TakeoutMessageResult.TakeoutActionSuccess
33
+ import com.duckduckgo.autofill.impl.importing.takeout.webflow.TakeoutMessageResult.UnknownMessageFormat
31
34
import com.duckduckgo.autofill.impl.importing.takeout.zip.TakeoutBookmarkExtractor
32
35
import com.duckduckgo.autofill.impl.importing.takeout.zip.TakeoutBookmarkExtractor.ExtractionResult
33
36
import com.duckduckgo.autofill.impl.importing.takeout.zip.TakeoutZipDownloader
@@ -57,6 +60,7 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
57
60
private val takeoutBookmarkImporter : TakeoutBookmarkImporter ,
58
61
private val takeoutZipDownloader : TakeoutZipDownloader ,
59
62
private val bookmarkImportConfigStore : BookmarkImportConfigStore ,
63
+ private val takeoutWebMessageParser : TakeoutWebMessageParser ,
60
64
) : ViewModel() {
61
65
62
66
private val _viewState = MutableStateFlow <ViewState >(ViewState .Initializing )
@@ -65,6 +69,8 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
65
69
private val _commands = MutableSharedFlow <Command >(replay = 0 , extraBufferCapacity = 1 )
66
70
val commands: SharedFlow <Command > = _commands
67
71
72
+ private var latestStepInWebFlow: String = STEP_UNINITIALIZED
73
+
68
74
suspend fun loadInitialWebpage () {
69
75
withContext(dispatchers.io()) {
70
76
val initialUrl = bookmarkImportConfigStore.getConfig().launchUrlGoogleTakeout
@@ -132,7 +138,10 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
132
138
}
133
139
}
134
140
135
- private suspend fun onBookmarksExtracted (extractionResult : ExtractionResult , folderName : String ) {
141
+ private suspend fun onBookmarksExtracted (
142
+ extractionResult : ExtractionResult ,
143
+ folderName : String ,
144
+ ) {
136
145
when (extractionResult) {
137
146
is ExtractionResult .Success -> {
138
147
val importResult = takeoutBookmarkImporter.importBookmarks(
@@ -149,7 +158,10 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
149
158
}
150
159
}
151
160
152
- private suspend fun onBookmarksImported (importResult : ImportSavedSitesResult , folderName : String ) {
161
+ private suspend fun onBookmarksImported (
162
+ importResult : ImportSavedSitesResult ,
163
+ folderName : String ,
164
+ ) {
153
165
when (importResult) {
154
166
is ImportSavedSitesResult .Success -> {
155
167
val importedCount = importResult.savedSites.size
@@ -164,26 +176,23 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
164
176
}
165
177
}
166
178
167
- fun onCloseButtonPressed (url : String? ) {
168
- terminateFlowAsCancellation(url ? : " unknown " )
179
+ fun onCloseButtonPressed () {
180
+ terminateFlowAsCancellation(latestStepInWebFlow )
169
181
}
170
182
171
- fun onBackButtonPressed (
172
- url : String? = null,
173
- canGoBack : Boolean = false,
174
- ) {
183
+ fun onBackButtonPressed (canGoBack : Boolean = false) {
175
184
// if WebView can't go back, then we're at the first stage or something's gone wrong. Either way, time to cancel out of the screen.
176
185
if (! canGoBack) {
177
- terminateFlowAsCancellation(url ? : " unknown " )
186
+ terminateFlowAsCancellation(latestStepInWebFlow )
178
187
return
179
188
}
180
189
181
190
_viewState .value = ViewState .NavigatingBack
182
191
}
183
192
184
- private fun terminateFlowAsCancellation (url : String ) {
193
+ private fun terminateFlowAsCancellation (stage : String ) {
185
194
viewModelScope.launch {
186
- _viewState .value = ViewState .UserCancelledImportFlow (stage = " unknown " )
195
+ _viewState .value = ViewState .UserCancelledImportFlow (stage)
187
196
}
188
197
}
189
198
@@ -258,12 +267,63 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
258
267
fun onPageStarted (url : String? ) {
259
268
val host = url?.toUri()?.host ? : return
260
269
_viewState .value = if (host.contains(" takeout.google.com" , ignoreCase = true )) {
270
+ updateLatestStepSpecificStage(GOOGLE_TAKEOUT_PAGE_REACHED )
261
271
HideWebPage
272
+ } else if (host.contains(" accounts.google.com" , ignoreCase = true )) {
273
+ updateLatestStepLoginPage()
274
+ ShowWebPage
262
275
} else {
263
276
ShowWebPage
264
277
}
265
278
}
266
279
280
+ private fun updateLatestStepLoginPage () {
281
+ // if we already have the login page as the current step, do nothing
282
+ if (latestStepInWebFlow == GOOGLE_ACCOUNTS_PAGE_FIRST || latestStepInWebFlow == GOOGLE_ACCOUNTS_REPEATED ) {
283
+ return
284
+ }
285
+
286
+ // if uninitialized, this is the first time seeing the login page
287
+ if (latestStepInWebFlow == STEP_UNINITIALIZED ) {
288
+ updateLatestStepSpecificStage(GOOGLE_ACCOUNTS_PAGE_FIRST )
289
+ return
290
+ }
291
+
292
+ // this must be a repeated visit to the login page
293
+ updateLatestStepSpecificStage(GOOGLE_ACCOUNTS_REPEATED )
294
+ }
295
+
296
+ private fun updateLatestStepSpecificStage (step : String ) {
297
+ latestStepInWebFlow = step
298
+ logcat { " cdr latest step is: $step " }
299
+ }
300
+
301
+ fun onWebMessageReceived (data : String ) {
302
+ viewModelScope.launch {
303
+ when (val result = takeoutWebMessageParser.parseMessage(data)) {
304
+ is TakeoutActionSuccess -> {
305
+ logcat { " cdr successfully parsed message: $result " }
306
+ updateLatestStepSpecificStage(result.actionID)
307
+ }
308
+
309
+ is TakeoutActionError -> {
310
+ logcat { " cdr experienced an error in the step: $result " }
311
+ }
312
+
313
+ UnknownMessageFormat -> {
314
+ logcat(WARN ) { " cdr failed to parse message, unknown format: $data " }
315
+ }
316
+ }
317
+ }
318
+ }
319
+
320
+ companion object {
321
+ private const val GOOGLE_TAKEOUT_PAGE_REACHED = " takeout"
322
+ private const val GOOGLE_ACCOUNTS_PAGE_FIRST = " login-first"
323
+ private const val GOOGLE_ACCOUNTS_REPEATED = " login-repeat"
324
+ private const val STEP_UNINITIALIZED = " uninitialized"
325
+ }
326
+
267
327
sealed interface Command {
268
328
data class InjectCredentialsFromReauth (val url : String? = null , val username : String = " " , val password : String? ) : Command
269
329
data class PromptUserToSelectFromStoredCredentials (
0 commit comments