SessionControlController.kt 15.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.home.sessioncontrol

import androidx.navigation.NavController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.browser.session.SessionManager
12
import mozilla.components.concept.engine.Engine
13
14
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.tab.collections.TabCollection
15
import mozilla.components.feature.tab.collections.ext.restore
16
import mozilla.components.feature.tabs.TabsUseCases
17
import mozilla.components.feature.top.sites.TopSite
18
import mozilla.components.support.ktx.kotlin.isUrl
19
20
21
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
22
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
23
24
25
26
import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
27
import org.mozilla.fenix.components.metrics.MetricsUtils
28
import org.mozilla.fenix.components.tips.Tip
29
30
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.metrics
31
import org.mozilla.fenix.ext.nav
32
import org.mozilla.fenix.ext.sessionsOfType
33
import org.mozilla.fenix.ext.settings
34
35
36
37
38
import org.mozilla.fenix.home.HomeFragment
import org.mozilla.fenix.home.HomeFragmentAction
import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.home.HomeFragmentStore
import org.mozilla.fenix.settings.SupportUtils
39
import org.mozilla.fenix.utils.Settings
ekager's avatar
ekager committed
40
import mozilla.components.feature.tab.collections.Tab as ComponentTab
41
42
43
44
45

/**
 * [HomeFragment] controller. An interface that handles the view manipulation of the Tabs triggered
 * by the Interactor.
 */
46
@Suppress("TooManyFunctions")
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
interface SessionControlController {
    /**
     * @see [CollectionInteractor.onCollectionAddTabTapped]
     */
    fun handleCollectionAddTabTapped(collection: TabCollection)

    /**
     * @see [CollectionInteractor.onCollectionOpenTabClicked]
     */
    fun handleCollectionOpenTabClicked(tab: ComponentTab)

    /**
     * @see [CollectionInteractor.onCollectionOpenTabsTapped]
     */
    fun handleCollectionOpenTabsTapped(collection: TabCollection)

    /**
     * @see [CollectionInteractor.onCollectionRemoveTab]
     */
66
    fun handleCollectionRemoveTab(collection: TabCollection, tab: ComponentTab, wasSwiped: Boolean)
67
68
69
70
71
72
73
74
75
76
77

    /**
     * @see [CollectionInteractor.onCollectionShareTabsClicked]
     */
    fun handleCollectionShareTabsClicked(collection: TabCollection)

    /**
     * @see [CollectionInteractor.onDeleteCollectionTapped]
     */
    fun handleDeleteCollectionTapped(collection: TabCollection)

78
79
80
81
82
    /**
     * @see [TopSiteInteractor.onOpenInPrivateTabClicked]
     */
    fun handleOpenInPrivateTabClicked(topSite: TopSite)

83
84
85
86
87
    /**
     * @see [TabSessionInteractor.onPrivateBrowsingLearnMoreClicked]
     */
    fun handlePrivateBrowsingLearnMoreClicked()

88
89
90
91
92
    /**
     * @see [TopSiteInteractor.onRemoveTopSiteClicked]
     */
    fun handleRemoveTopSiteClicked(topSite: TopSite)

93
94
95
96
97
    /**
     * @see [CollectionInteractor.onRenameCollectionTapped]
     */
    fun handleRenameCollectionTapped(collection: TabCollection)

98
99
100
    /**
     * @see [TopSiteInteractor.onSelectTopSite]
     */
101
    fun handleSelectTopSite(url: String, type: TopSite.Type)
102

103
104
105
106
107
    /**
     * @see [OnboardingInteractor.onStartBrowsingClicked]
     */
    fun handleStartBrowsingClicked()

108
109
110
111
112
    /**
     * @see [OnboardingInteractor.onOpenSettingsClicked]
     */
    fun handleOpenSettingsClicked()

113
114
115
116
117
118
119
120
121
122
    /**
     * @see [OnboardingInteractor.onWhatsNewGetAnswersClicked]
     */
    fun handleWhatsNewGetAnswersClicked()

    /**
     * @see [OnboardingInteractor.onReadPrivacyNoticeClicked]
     */
    fun handleReadPrivacyNoticeClicked()

123
124
125
126
    /**
     * @see [CollectionInteractor.onToggleCollectionExpanded]
     */
    fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean)
127

128
129
130
    /**
     * @see [TipInteractor.onCloseTip]
     */
131
    fun handleCloseTip(tip: Tip)
132

133
134
135
136
137
138
139
140
141
142
    /**
     * @see [ToolbarInteractor.onPasteAndGo]
     */
    fun handlePasteAndGo(clipboardText: String)

    /**
     * @see [ToolbarInteractor.onPaste]
     */
    fun handlePaste(clipboardText: String)

143
144
145
146
    /**
     * @see [CollectionInteractor.onAddTabsToCollectionTapped]
     */
    fun handleCreateCollection()
147
148
149
150
151

    /**
     * @see [CollectionInteractor.onRemoveCollectionsPlaceholder]
     */
    fun handleRemoveCollectionsPlaceholder()
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171

    /**
     * @see [TorBootstrapInteractor.onTorBootstrapConnectClicked]
     */
    fun handleTorBootstrapConnectClicked()

    /**
     * @see [TorBootstrapInteractor.onTorBootstrapConnectingClicked]
     */
    fun handleTorBootstrapConnectingClicked()

    /**
     * @see [TorBootstrapInteractor.onTorStartBootstrapping]
     */
    fun handleTorStartBootstrapping()

    /**
     * @see [TorBootstrapInteractor.onTorStartDebugBootstrapping]
     */
    fun handleTorStartDebugBootstrapping()
172
173
}

174
@Suppress("TooManyFunctions", "LargeClass")
175
176
class DefaultSessionControlController(
    private val activity: HomeActivity,
177
    private val settings: Settings,
178
179
180
181
182
    private val engine: Engine,
    private val metrics: MetricController,
    private val sessionManager: SessionManager,
    private val tabCollectionStorage: TabCollectionStorage,
    private val addTabUseCase: TabsUseCases.AddNewTabUseCase,
183
    private val fragmentStore: HomeFragmentStore,
184
    private val navController: NavController,
185
    private val viewLifecycleScope: CoroutineScope,
186
187
    private val hideOnboarding: () -> Unit,
    private val registerCollectionStorageObserver: () -> Unit,
188
189
190
191
192
193
194
195
    private val showDeleteCollectionPrompt: (
        tabCollection: TabCollection,
        title: String?,
        message: String,
        wasSwiped: Boolean,
        handleSwipedItemDeletionCancel: () -> Unit
    ) -> Unit,
    private val showTabTray: () -> Unit,
196
197
198
199
    private val handleSwipedItemDeletionCancel: () -> Unit,
    private val handleTorBootstrapConnect: () -> Unit,
    private val initiateTorBootstrap: (Boolean) -> Unit,
    private val cancelTorBootstrap: () -> Unit
200
201
202
203
204
205
206
207
208
209
210
) : SessionControlController {

    override fun handleCollectionAddTabTapped(collection: TabCollection) {
        metrics.track(Event.CollectionAddTabPressed)
        showCollectionCreationFragment(
            step = SaveCollectionStep.SelectTabs,
            selectedTabCollectionId = collection.id
        )
    }

    override fun handleCollectionOpenTabClicked(tab: ComponentTab) {
211
212
        sessionManager.restore(
            activity,
213
            engine,
214
215
216
217
218
219
220
221
222
223
224
            tab,
            onTabRestored = {
                activity.openToBrowser(BrowserDirection.FromHome)
            },
            onFailure = {
                activity.openToBrowserAndLoad(
                    searchTermOrURL = tab.url,
                    newTab = true,
                    from = BrowserDirection.FromHome
                )
            }
225
226
227
228
229
230
        )

        metrics.track(Event.CollectionTabRestored)
    }

    override fun handleCollectionOpenTabsTapped(collection: TabCollection) {
231
232
        sessionManager.restore(
            activity,
233
            engine,
234
235
            collection,
            onFailure = { url ->
236
                addTabUseCase.invoke(url)
237
            }
238
        )
239

240
        showTabTray()
241
242
243
        metrics.track(Event.CollectionAllTabsRestored)
    }

244
245
246
247
248
    override fun handleCollectionRemoveTab(
        collection: TabCollection,
        tab: ComponentTab,
        wasSwiped: Boolean
    ) {
249
250
        metrics.track(Event.CollectionTabRemoved)

251
        if (collection.tabs.size == 1) {
252
253
254
255
256
257
            val title = activity.resources.getString(
                R.string.delete_tab_and_collection_dialog_title,
                collection.title
            )
            val message =
                activity.resources.getString(R.string.delete_tab_and_collection_dialog_message)
258
259
260
261
262
263
264
            showDeleteCollectionPrompt(
                collection,
                title,
                message,
                wasSwiped,
                handleSwipedItemDeletionCancel
            )
265
        } else {
266
            viewLifecycleScope.launch {
267
268
                tabCollectionStorage.removeTabFromCollection(collection, tab)
            }
269
270
271
272
        }
    }

    override fun handleCollectionShareTabsClicked(collection: TabCollection) {
273
274
275
276
        showShareFragment(
            collection.title,
            collection.tabs.map { ShareData(url = it.url, title = it.title) }
        )
277
278
279
280
        metrics.track(Event.CollectionShared)
    }

    override fun handleDeleteCollectionTapped(collection: TabCollection) {
281
282
        val message =
            activity.resources.getString(R.string.tab_collection_dialog_message, collection.title)
283
        showDeleteCollectionPrompt(collection, null, message, false, handleSwipedItemDeletionCancel)
284
285
    }

286
    override fun handleOpenInPrivateTabClicked(topSite: TopSite) {
287
        metrics.track(Event.TopSiteOpenInPrivateTab)
288
289
290
291
292
293
294
295
296
297
        with(activity) {
            browsingModeManager.mode = BrowsingMode.Private
            openToBrowserAndLoad(
                searchTermOrURL = topSite.url,
                newTab = true,
                from = BrowserDirection.FromHome
            )
        }
    }

298
299
300
301
302
303
304
305
306
    override fun handlePrivateBrowsingLearnMoreClicked() {
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
                (SupportUtils.SumoTopic.PRIVATE_BROWSING_MYTHS),
            newTab = true,
            from = BrowserDirection.FromHome
        )
    }

307
    override fun handleRemoveTopSiteClicked(topSite: TopSite) {
308
        metrics.track(Event.TopSiteRemoved)
ekager's avatar
ekager committed
309
310
311
        if (topSite.url == SupportUtils.POCKET_TRENDING_URL) {
            metrics.track(Event.PocketTopSiteRemoved)
        }
312

313
        viewLifecycleScope.launch(Dispatchers.IO) {
314
315
316
            with(activity.components.useCases.topSitesUseCase) {
                removeTopSites(topSite)
            }
317
318
319
        }
    }

320
321
322
323
324
325
326
327
    override fun handleRenameCollectionTapped(collection: TabCollection) {
        showCollectionCreationFragment(
            step = SaveCollectionStep.RenameCollection,
            selectedTabCollectionId = collection.id
        )
        metrics.track(Event.CollectionRenamePressed)
    }

328
    override fun handleSelectTopSite(url: String, type: TopSite.Type) {
329
        metrics.track(Event.TopSiteOpenInNewTab)
330
331
332
333
        when (type) {
            TopSite.Type.DEFAULT -> metrics.track(Event.TopSiteOpenDefault)
            TopSite.Type.FRECENT -> metrics.track(Event.TopSiteOpenFrecent)
            TopSite.Type.PINNED -> metrics.track(Event.TopSiteOpenPinned)
334
        }
335

336
337
338
        if (url == SupportUtils.POCKET_TRENDING_URL) {
            metrics.track(Event.PocketTopSiteClicked)
        }
339
        addTabUseCase.invoke(
340
341
342
            url = url,
            selectTab = true,
            startLoading = true
343
        )
344
        activity.openToBrowser(BrowserDirection.FromHome)
345
346
    }

347
348
349
350
    override fun handleStartBrowsingClicked() {
        hideOnboarding()
    }

351
    override fun handleOpenSettingsClicked() {
352
353
        val directions = HomeFragmentDirections.actionGlobalPrivateBrowsingFragment()
        navController.nav(R.id.homeFragment, directions)
354
355
    }

356
    override fun handleWhatsNewGetAnswersClicked() {
357
358
359
360
361
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getWhatsNewUrl(activity),
            newTab = true,
            from = BrowserDirection.FromHome
        )
362
363
364
    }

    override fun handleReadPrivacyNoticeClicked() {
365
366
367
368
369
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE),
            newTab = true,
            from = BrowserDirection.FromHome
        )
370
371
    }

372
    override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
373
        fragmentStore.dispatch(HomeFragmentAction.CollectionExpanded(collection, expand))
374
375
    }

376
377
378
379
    override fun handleCloseTip(tip: Tip) {
        fragmentStore.dispatch(HomeFragmentAction.RemoveTip(tip))
    }

380
381
382
383
384
385
386
    private fun showTabTrayCollectionCreation() {
        val directions = HomeFragmentDirections.actionGlobalTabTrayDialogFragment(
            enterMultiselect = true
        )
        navController.nav(R.id.homeFragment, directions)
    }

387
388
389
390
391
392
393
394
395
396
    private fun showCollectionCreationFragment(
        step: SaveCollectionStep,
        selectedTabIds: Array<String>? = null,
        selectedTabCollectionId: Long? = null
    ) {
        if (navController.currentDestination?.id == R.id.collectionCreationFragment) return

        // Only register the observer right before moving to collection creation
        registerCollectionStorageObserver()

397
398
399
400
401
        val tabIds = sessionManager
            .sessionsOfType(private = activity.browsingModeManager.mode.isPrivate)
            .map { session -> session.id }
            .toList()
            .toTypedArray()
402
        val directions = HomeFragmentDirections.actionGlobalCollectionCreationFragment(
403
404
405
406
407
408
409
410
            tabIds = tabIds,
            saveCollectionStep = step,
            selectedTabIds = selectedTabIds,
            selectedTabCollectionId = selectedTabCollectionId ?: -1
        )
        navController.nav(R.id.homeFragment, directions)
    }

411
    override fun handleCreateCollection() {
412
        showTabTrayCollectionCreation()
413
414
    }

415
416
417
418
419
    override fun handleRemoveCollectionsPlaceholder() {
        settings.showCollectionsPlaceholderOnHome = false
        fragmentStore.dispatch(HomeFragmentAction.RemoveCollectionsPlaceholder)
    }

420
    private fun showShareFragment(shareSubject: String, data: List<ShareData>) {
Jeff Boek's avatar
Jeff Boek committed
421
        val directions = HomeFragmentDirections.actionGlobalShareFragment(
422
            shareSubject = shareSubject,
423
424
425
426
            data = data.toTypedArray()
        )
        navController.nav(R.id.homeFragment, directions)
    }
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453

    override fun handlePasteAndGo(clipboardText: String) {
        activity.openToBrowserAndLoad(
            searchTermOrURL = clipboardText,
            newTab = true,
            from = BrowserDirection.FromHome,
            engine = activity.components.search.provider.getDefaultEngine(activity)
        )

        val event = if (clipboardText.isUrl()) {
            Event.EnteredUrl(false)
        } else {
            val searchAccessPoint = Event.PerformedSearch.SearchAccessPoint.ACTION
            activity.settings().incrementActiveSearchCount()
            searchAccessPoint.let { sap ->
                MetricsUtils.createSearchEvent(
                    activity.components.search.provider.getDefaultEngine(activity),
                    activity,
                    sap
                )
            }
        }

        event?.let { activity.metrics.track(it) }
    }

    override fun handlePaste(clipboardText: String) {
454
        val directions = HomeFragmentDirections.actionGlobalSearchDialog(
455
456
457
458
459
            sessionId = null,
            pastedText = clipboardText
        )
        navController.nav(R.id.homeFragment, directions)
    }
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475

    override fun handleTorBootstrapConnectClicked() {
        handleTorBootstrapConnect()
    }

    override fun handleTorBootstrapConnectingClicked() {
        cancelTorBootstrap()
    }

    override fun handleTorStartBootstrapping() {
        initiateTorBootstrap(false)
    }

    override fun handleTorStartDebugBootstrapping() {
        initiateTorBootstrap(true)
    }
476
}