@@ -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
@@ -157,26 +170,23 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
157
170
}
158
171
}
159
172
160
- fun onCloseButtonPressed (url : String? ) {
161
- terminateFlowAsCancellation(url ? : " unknown " )
173
+ fun onCloseButtonPressed () {
174
+ terminateFlowAsCancellation(latestStepInWebFlow )
162
175
}
163
176
164
- fun onBackButtonPressed (
165
- url : String? = null,
166
- canGoBack : Boolean = false,
167
- ) {
177
+ fun onBackButtonPressed (canGoBack : Boolean = false) {
168
178
// 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
179
if (! canGoBack) {
170
- terminateFlowAsCancellation(url ? : " unknown " )
180
+ terminateFlowAsCancellation(latestStepInWebFlow )
171
181
return
172
182
}
173
183
174
184
_viewState .value = ViewState .NavigatingBack
175
185
}
176
186
177
- private fun terminateFlowAsCancellation (url : String ) {
187
+ private fun terminateFlowAsCancellation (stage : String ) {
178
188
viewModelScope.launch {
179
- _viewState .value = ViewState .UserCancelledImportFlow (stage = " unknown " )
189
+ _viewState .value = ViewState .UserCancelledImportFlow (stage)
180
190
}
181
191
}
182
192
@@ -251,12 +261,63 @@ class ImportGoogleBookmarksWebFlowViewModel @Inject constructor(
251
261
fun onPageStarted (url : String? ) {
252
262
val host = url?.toUri()?.host ? : return
253
263
_viewState .value = if (host.contains(" takeout.google.com" , ignoreCase = true )) {
264
+ updateLatestStepSpecificStage(GOOGLE_TAKEOUT_PAGE_REACHED )
254
265
HideWebPage
266
+ } else if (host.contains(" accounts.google.com" , ignoreCase = true )) {
267
+ updateLatestStepLoginPage()
268
+ ShowWebPage
255
269
} else {
256
270
ShowWebPage
257
271
}
258
272
}
259
273
274
+ private fun updateLatestStepLoginPage () {
275
+ // if we already have the login page as the current step, do nothing
276
+ if (latestStepInWebFlow == GOOGLE_ACCOUNTS_PAGE_FIRST || latestStepInWebFlow == GOOGLE_ACCOUNTS_REPEATED ) {
277
+ return
278
+ }
279
+
280
+ // if uninitialized, this is the first time seeing the login page
281
+ if (latestStepInWebFlow == STEP_UNINITIALIZED ) {
282
+ updateLatestStepSpecificStage(GOOGLE_ACCOUNTS_PAGE_FIRST )
283
+ return
284
+ }
285
+
286
+ // this must be a repeated visit to the login page
287
+ updateLatestStepSpecificStage(GOOGLE_ACCOUNTS_REPEATED )
288
+ }
289
+
290
+ private fun updateLatestStepSpecificStage (step : String ) {
291
+ latestStepInWebFlow = step
292
+ logcat { " cdr latest step is: $step " }
293
+ }
294
+
295
+ fun onWebMessageReceived (data : String ) {
296
+ viewModelScope.launch {
297
+ when (val result = takeoutWebMessageParser.parseMessage(data)) {
298
+ is TakeoutActionSuccess -> {
299
+ logcat { " cdr successfully parsed message: $result " }
300
+ updateLatestStepSpecificStage(result.actionID)
301
+ }
302
+
303
+ is TakeoutActionError -> {
304
+ logcat { " cdr experienced an error in the step: $result " }
305
+ }
306
+
307
+ UnknownMessageFormat -> {
308
+ logcat(WARN ) { " cdr failed to parse message, unknown format: $data " }
309
+ }
310
+ }
311
+ }
312
+ }
313
+
314
+ companion object {
315
+ private const val GOOGLE_TAKEOUT_PAGE_REACHED = " takeout"
316
+ private const val GOOGLE_ACCOUNTS_PAGE_FIRST = " login-first"
317
+ private const val GOOGLE_ACCOUNTS_REPEATED = " login-repeat"
318
+ private const val STEP_UNINITIALIZED = " uninitialized"
319
+ }
320
+
260
321
sealed interface Command {
261
322
data class InjectCredentialsFromReauth (val url : String? = null , val username : String = " " , val password : String? ) : Command
262
323
data class PromptUserToSelectFromStoredCredentials (
0 commit comments