@@ -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
@@ -87,6 +93,7 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
87
93
url.contains(" .zip" ) ||
88
94
url.contains(" takeout.google.com" ) ||
89
95
contentDisposition?.contains(" attachment" ) == true -> true
96
+
90
97
else -> false
91
98
}
92
99
@@ -125,7 +132,10 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
125
132
}
126
133
}
127
134
128
- private suspend fun onBookmarksExtracted (extractionResult : ExtractionResult , folderName : String ) {
135
+ private suspend fun onBookmarksExtracted (
136
+ extractionResult : ExtractionResult ,
137
+ folderName : String ,
138
+ ) {
129
139
when (extractionResult) {
130
140
is ExtractionResult .Success -> {
131
141
val importResult = takeoutBookmarkImporter.importBookmarks(
@@ -142,7 +152,10 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
142
152
}
143
153
}
144
154
145
- private suspend fun onBookmarksImported (importResult : ImportSavedSitesResult , folderName : String ) {
155
+ private suspend fun onBookmarksImported (
156
+ importResult : ImportSavedSitesResult ,
157
+ folderName : String ,
158
+ ) {
146
159
when (importResult) {
147
160
is ImportSavedSitesResult .Success -> {
148
161
val importedCount = importResult.savedSites.size
@@ -158,7 +171,7 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
158
171
}
159
172
160
173
fun onCloseButtonPressed (url : String? ) {
161
- terminateFlowAsCancellation(url ? : " unknown " )
174
+ terminateFlowAsCancellation(latestStepInWebFlow )
162
175
}
163
176
164
177
fun onBackButtonPressed (
@@ -167,16 +180,16 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
167
180
) {
168
181
// 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.
169
182
if (! canGoBack) {
170
- terminateFlowAsCancellation(url ? : " unknown " )
183
+ terminateFlowAsCancellation(latestStepInWebFlow )
171
184
return
172
185
}
173
186
174
187
_viewState .value = ViewState .NavigatingBack
175
188
}
176
189
177
- private fun terminateFlowAsCancellation (url : String ) {
190
+ private fun terminateFlowAsCancellation (stage : String ) {
178
191
viewModelScope.launch {
179
- _viewState .value = ViewState .UserCancelledImportFlow (stage = " unknown " )
192
+ _viewState .value = ViewState .UserCancelledImportFlow (stage)
180
193
}
181
194
}
182
195
@@ -251,12 +264,63 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
251
264
fun onPageStarted (url : String? ) {
252
265
val host = url?.toUri()?.host ? : return
253
266
_viewState .value = if (host.contains(" takeout.google.com" , ignoreCase = true )) {
267
+ updateLatestStepSpecificStage(GOOGLE_TAKEOUT_PAGE_REACHED )
254
268
HideWebPage
269
+ } else if (host.contains(" accounts.google.com" , ignoreCase = true )) {
270
+ updateLatestStepLoginPage()
271
+ ShowWebPage
255
272
} else {
256
273
ShowWebPage
257
274
}
258
275
}
259
276
277
+ private fun updateLatestStepLoginPage () {
278
+ // if we already have the login page as the current step, do nothing
279
+ if (latestStepInWebFlow == GOOGLE_ACCOUNTS_PAGE_FIRST || latestStepInWebFlow == GOOGLE_ACCOUNTS_REPEATED ) {
280
+ return
281
+ }
282
+
283
+ // if uninitialized, this is the first time seeing the login page
284
+ if (latestStepInWebFlow == STEP_UNINITIALIZED ) {
285
+ updateLatestStepSpecificStage(GOOGLE_ACCOUNTS_PAGE_FIRST )
286
+ return
287
+ }
288
+
289
+ // this must be a repeated visit to the login page
290
+ updateLatestStepSpecificStage(GOOGLE_ACCOUNTS_REPEATED )
291
+ }
292
+
293
+ private fun updateLatestStepSpecificStage (step : String ) {
294
+ latestStepInWebFlow = step
295
+ logcat { " cdr latest step is: $step " }
296
+ }
297
+
298
+ fun onWebMessageReceived (data : String ) {
299
+ viewModelScope.launch {
300
+ when (val result = takeoutWebMessageParser.parseMessage(data)) {
301
+ is TakeoutActionSuccess -> {
302
+ logcat { " cdr successfully parsed message: $result " }
303
+ updateLatestStepSpecificStage(result.actionID)
304
+ }
305
+
306
+ is TakeoutActionError -> {
307
+ logcat { " cdr experienced an error in the step: $result " }
308
+ }
309
+
310
+ UnknownMessageFormat -> {
311
+ logcat(WARN ) { " cdr failed to parse message, unknown format: $data " }
312
+ }
313
+ }
314
+ }
315
+ }
316
+
317
+ companion object {
318
+ private const val GOOGLE_TAKEOUT_PAGE_REACHED = " takeout"
319
+ private const val GOOGLE_ACCOUNTS_PAGE_FIRST = " login-first"
320
+ private const val GOOGLE_ACCOUNTS_REPEATED = " login-repeat"
321
+ private const val STEP_UNINITIALIZED = " uninitialized"
322
+ }
323
+
260
324
sealed interface Command {
261
325
data class InjectCredentialsFromReauth (val url : String? = null , val username : String = " " , val password : String? ) : Command
262
326
data class PromptUserToSelectFromStoredCredentials (
0 commit comments