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