SessionControlController.kt 15.6 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

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

    /**
159
     * @see [TorBootstrapInteractor.onTorStopBootstrapping]
160
     */
161
    fun handleTorStopBootstrapping()
162
163
164
165
166
167
168
169
170
171

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

    /**
     * @see [TorBootstrapInteractor.onTorStartDebugBootstrapping]
     */
    fun handleTorStartDebugBootstrapping()
172
173
174
175
176

    /**
     * @see [TorBootstrapInteractor.onTorBootstrapNetworkSettingsClicked]
     */
    fun handleTorNetworkSettingsClicked()
177
178
}

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

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

    override fun handleCollectionOpenTabClicked(tab: ComponentTab) {
217
218
        sessionManager.restore(
            activity,
219
            engine,
220
221
222
223
224
225
226
227
228
229
230
            tab,
            onTabRestored = {
                activity.openToBrowser(BrowserDirection.FromHome)
            },
            onFailure = {
                activity.openToBrowserAndLoad(
                    searchTermOrURL = tab.url,
                    newTab = true,
                    from = BrowserDirection.FromHome
                )
            }
231
232
233
234
235
236
        )

        metrics.track(Event.CollectionTabRestored)
    }

    override fun handleCollectionOpenTabsTapped(collection: TabCollection) {
237
238
        sessionManager.restore(
            activity,
239
            engine,
240
241
            collection,
            onFailure = { url ->
242
                addTabUseCase.invoke(url)
243
            }
244
        )
245

246
        showTabTray()
247
248
249
        metrics.track(Event.CollectionAllTabsRestored)
    }

250
251
252
253
254
    override fun handleCollectionRemoveTab(
        collection: TabCollection,
        tab: ComponentTab,
        wasSwiped: Boolean
    ) {
255
256
        metrics.track(Event.CollectionTabRemoved)

257
        if (collection.tabs.size == 1) {
258
259
260
261
262
263
            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)
264
265
266
267
268
269
270
            showDeleteCollectionPrompt(
                collection,
                title,
                message,
                wasSwiped,
                handleSwipedItemDeletionCancel
            )
271
        } else {
272
            viewLifecycleScope.launch {
273
274
                tabCollectionStorage.removeTabFromCollection(collection, tab)
            }
275
276
277
278
        }
    }

    override fun handleCollectionShareTabsClicked(collection: TabCollection) {
279
280
281
282
        showShareFragment(
            collection.title,
            collection.tabs.map { ShareData(url = it.url, title = it.title) }
        )
283
284
285
286
        metrics.track(Event.CollectionShared)
    }

    override fun handleDeleteCollectionTapped(collection: TabCollection) {
287
288
        val message =
            activity.resources.getString(R.string.tab_collection_dialog_message, collection.title)
289
        showDeleteCollectionPrompt(collection, null, message, false, handleSwipedItemDeletionCancel)
290
291
    }

292
    override fun handleOpenInPrivateTabClicked(topSite: TopSite) {
293
        metrics.track(Event.TopSiteOpenInPrivateTab)
294
295
296
297
298
299
300
301
302
303
        with(activity) {
            browsingModeManager.mode = BrowsingMode.Private
            openToBrowserAndLoad(
                searchTermOrURL = topSite.url,
                newTab = true,
                from = BrowserDirection.FromHome
            )
        }
    }

304
305
306
307
308
309
310
311
312
    override fun handlePrivateBrowsingLearnMoreClicked() {
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
                (SupportUtils.SumoTopic.PRIVATE_BROWSING_MYTHS),
            newTab = true,
            from = BrowserDirection.FromHome
        )
    }

313
    override fun handleRemoveTopSiteClicked(topSite: TopSite) {
314
        metrics.track(Event.TopSiteRemoved)
ekager's avatar
ekager committed
315
316
317
        if (topSite.url == SupportUtils.POCKET_TRENDING_URL) {
            metrics.track(Event.PocketTopSiteRemoved)
        }
318

319
        viewLifecycleScope.launch(Dispatchers.IO) {
320
321
322
            with(activity.components.useCases.topSitesUseCase) {
                removeTopSites(topSite)
            }
323
324
325
        }
    }

326
327
328
329
330
331
332
333
    override fun handleRenameCollectionTapped(collection: TabCollection) {
        showCollectionCreationFragment(
            step = SaveCollectionStep.RenameCollection,
            selectedTabCollectionId = collection.id
        )
        metrics.track(Event.CollectionRenamePressed)
    }

334
    override fun handleSelectTopSite(url: String, type: TopSite.Type) {
335
        metrics.track(Event.TopSiteOpenInNewTab)
336
337
338
339
        when (type) {
            TopSite.Type.DEFAULT -> metrics.track(Event.TopSiteOpenDefault)
            TopSite.Type.FRECENT -> metrics.track(Event.TopSiteOpenFrecent)
            TopSite.Type.PINNED -> metrics.track(Event.TopSiteOpenPinned)
340
        }
341

342
343
344
        if (url == SupportUtils.POCKET_TRENDING_URL) {
            metrics.track(Event.PocketTopSiteClicked)
        }
345
        addTabUseCase.invoke(
346
347
348
            url = url,
            selectTab = true,
            startLoading = true
349
        )
350
        activity.openToBrowser(BrowserDirection.FromHome)
351
352
    }

353
354
355
356
    override fun handleStartBrowsingClicked() {
        hideOnboarding()
    }

357
    override fun handleOpenSettingsClicked() {
358
359
        val directions = HomeFragmentDirections.actionGlobalPrivateBrowsingFragment()
        navController.nav(R.id.homeFragment, directions)
360
361
    }

362
    override fun handleWhatsNewGetAnswersClicked() {
363
364
365
366
367
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getWhatsNewUrl(activity),
            newTab = true,
            from = BrowserDirection.FromHome
        )
368
369
370
    }

    override fun handleReadPrivacyNoticeClicked() {
371
372
373
374
375
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE),
            newTab = true,
            from = BrowserDirection.FromHome
        )
376
377
    }

378
    override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
379
        fragmentStore.dispatch(HomeFragmentAction.CollectionExpanded(collection, expand))
380
381
    }

382
383
384
385
    override fun handleCloseTip(tip: Tip) {
        fragmentStore.dispatch(HomeFragmentAction.RemoveTip(tip))
    }

386
387
388
389
390
391
392
    private fun showTabTrayCollectionCreation() {
        val directions = HomeFragmentDirections.actionGlobalTabTrayDialogFragment(
            enterMultiselect = true
        )
        navController.nav(R.id.homeFragment, directions)
    }

393
394
395
396
397
398
399
400
401
402
    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()

403
404
405
406
407
        val tabIds = sessionManager
            .sessionsOfType(private = activity.browsingModeManager.mode.isPrivate)
            .map { session -> session.id }
            .toList()
            .toTypedArray()
408
        val directions = HomeFragmentDirections.actionGlobalCollectionCreationFragment(
409
410
411
412
413
414
415
416
            tabIds = tabIds,
            saveCollectionStep = step,
            selectedTabIds = selectedTabIds,
            selectedTabCollectionId = selectedTabCollectionId ?: -1
        )
        navController.nav(R.id.homeFragment, directions)
    }

417
    override fun handleCreateCollection() {
418
        showTabTrayCollectionCreation()
419
420
    }

421
422
423
424
425
    override fun handleRemoveCollectionsPlaceholder() {
        settings.showCollectionsPlaceholderOnHome = false
        fragmentStore.dispatch(HomeFragmentAction.RemoveCollectionsPlaceholder)
    }

426
    private fun showShareFragment(shareSubject: String, data: List<ShareData>) {
Jeff Boek's avatar
Jeff Boek committed
427
        val directions = HomeFragmentDirections.actionGlobalShareFragment(
428
            shareSubject = shareSubject,
429
430
431
432
            data = data.toTypedArray()
        )
        navController.nav(R.id.homeFragment, directions)
    }
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465

    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) {
        val directions = HomeFragmentDirections.actionGlobalSearch(
            sessionId = null,
            pastedText = clipboardText
        )
        navController.nav(R.id.homeFragment, directions)
    }
466
467
468
469
470

    override fun handleTorBootstrapConnectClicked() {
        handleTorBootstrapConnect()
    }

471
    override fun handleTorStopBootstrapping() {
472
473
474
475
476
477
478
479
480
481
        cancelTorBootstrap()
    }

    override fun handleTorStartBootstrapping() {
        initiateTorBootstrap(false)
    }

    override fun handleTorStartDebugBootstrapping() {
        initiateTorBootstrap(true)
    }
482
483
484
485

    override fun handleTorNetworkSettingsClicked() {
        openTorNetworkSettings()
    }
486
}