SessionControlController.kt 14.3 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
@Suppress("TooManyFunctions", "LargeClass")
155
156
class DefaultSessionControlController(
    private val activity: HomeActivity,
157
    private val settings: Settings,
158
159
160
161
162
    private val engine: Engine,
    private val metrics: MetricController,
    private val sessionManager: SessionManager,
    private val tabCollectionStorage: TabCollectionStorage,
    private val addTabUseCase: TabsUseCases.AddNewTabUseCase,
163
    private val fragmentStore: HomeFragmentStore,
164
    private val navController: NavController,
165
    private val viewLifecycleScope: CoroutineScope,
166
167
    private val hideOnboarding: () -> Unit,
    private val registerCollectionStorageObserver: () -> Unit,
168
169
170
171
172
173
174
175
176
    private val showDeleteCollectionPrompt: (
        tabCollection: TabCollection,
        title: String?,
        message: String,
        wasSwiped: Boolean,
        handleSwipedItemDeletionCancel: () -> Unit
    ) -> Unit,
    private val showTabTray: () -> Unit,
    private val handleSwipedItemDeletionCancel: () -> Unit
177
178
179
180
181
182
183
184
185
186
187
) : SessionControlController {

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

    override fun handleCollectionOpenTabClicked(tab: ComponentTab) {
188
189
        sessionManager.restore(
            activity,
190
            engine,
191
192
193
194
195
196
197
198
199
200
201
            tab,
            onTabRestored = {
                activity.openToBrowser(BrowserDirection.FromHome)
            },
            onFailure = {
                activity.openToBrowserAndLoad(
                    searchTermOrURL = tab.url,
                    newTab = true,
                    from = BrowserDirection.FromHome
                )
            }
202
203
204
205
206
207
        )

        metrics.track(Event.CollectionTabRestored)
    }

    override fun handleCollectionOpenTabsTapped(collection: TabCollection) {
208
209
        sessionManager.restore(
            activity,
210
            engine,
211
212
            collection,
            onFailure = { url ->
213
                addTabUseCase.invoke(url)
214
            }
215
        )
216

217
        showTabTray()
218
219
220
        metrics.track(Event.CollectionAllTabsRestored)
    }

221
222
223
224
225
    override fun handleCollectionRemoveTab(
        collection: TabCollection,
        tab: ComponentTab,
        wasSwiped: Boolean
    ) {
226
227
        metrics.track(Event.CollectionTabRemoved)

228
        if (collection.tabs.size == 1) {
229
230
231
232
233
234
            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)
235
236
237
238
239
240
241
            showDeleteCollectionPrompt(
                collection,
                title,
                message,
                wasSwiped,
                handleSwipedItemDeletionCancel
            )
242
        } else {
243
            viewLifecycleScope.launch {
244
245
                tabCollectionStorage.removeTabFromCollection(collection, tab)
            }
246
247
248
249
        }
    }

    override fun handleCollectionShareTabsClicked(collection: TabCollection) {
250
251
252
253
        showShareFragment(
            collection.title,
            collection.tabs.map { ShareData(url = it.url, title = it.title) }
        )
254
255
256
257
        metrics.track(Event.CollectionShared)
    }

    override fun handleDeleteCollectionTapped(collection: TabCollection) {
258
259
        val message =
            activity.resources.getString(R.string.tab_collection_dialog_message, collection.title)
260
        showDeleteCollectionPrompt(collection, null, message, false, handleSwipedItemDeletionCancel)
261
262
    }

263
    override fun handleOpenInPrivateTabClicked(topSite: TopSite) {
264
        metrics.track(Event.TopSiteOpenInPrivateTab)
265
266
267
268
269
270
271
272
273
274
        with(activity) {
            browsingModeManager.mode = BrowsingMode.Private
            openToBrowserAndLoad(
                searchTermOrURL = topSite.url,
                newTab = true,
                from = BrowserDirection.FromHome
            )
        }
    }

275
276
277
278
279
280
281
282
283
    override fun handlePrivateBrowsingLearnMoreClicked() {
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
                (SupportUtils.SumoTopic.PRIVATE_BROWSING_MYTHS),
            newTab = true,
            from = BrowserDirection.FromHome
        )
    }

284
    override fun handleRemoveTopSiteClicked(topSite: TopSite) {
285
        metrics.track(Event.TopSiteRemoved)
ekager's avatar
ekager committed
286
287
288
        if (topSite.url == SupportUtils.POCKET_TRENDING_URL) {
            metrics.track(Event.PocketTopSiteRemoved)
        }
289

290
        viewLifecycleScope.launch(Dispatchers.IO) {
291
292
293
            with(activity.components.useCases.topSitesUseCase) {
                removeTopSites(topSite)
            }
294
295
296
        }
    }

297
298
299
300
301
302
303
304
    override fun handleRenameCollectionTapped(collection: TabCollection) {
        showCollectionCreationFragment(
            step = SaveCollectionStep.RenameCollection,
            selectedTabCollectionId = collection.id
        )
        metrics.track(Event.CollectionRenamePressed)
    }

305
    override fun handleSelectTopSite(url: String, type: TopSite.Type) {
306
        metrics.track(Event.TopSiteOpenInNewTab)
307
308
309
310
        when (type) {
            TopSite.Type.DEFAULT -> metrics.track(Event.TopSiteOpenDefault)
            TopSite.Type.FRECENT -> metrics.track(Event.TopSiteOpenFrecent)
            TopSite.Type.PINNED -> metrics.track(Event.TopSiteOpenPinned)
311
        }
312

313
314
315
        if (url == SupportUtils.POCKET_TRENDING_URL) {
            metrics.track(Event.PocketTopSiteClicked)
        }
316
        addTabUseCase.invoke(
317
318
319
            url = url,
            selectTab = true,
            startLoading = true
320
        )
321
        activity.openToBrowser(BrowserDirection.FromHome)
322
323
    }

324
325
326
327
    override fun handleStartBrowsingClicked() {
        hideOnboarding()
    }

328
    override fun handleOpenSettingsClicked() {
329
330
        val directions = HomeFragmentDirections.actionGlobalPrivateBrowsingFragment()
        navController.nav(R.id.homeFragment, directions)
331
332
    }

333
    override fun handleWhatsNewGetAnswersClicked() {
334
335
336
337
338
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getWhatsNewUrl(activity),
            newTab = true,
            from = BrowserDirection.FromHome
        )
339
340
341
    }

    override fun handleReadPrivacyNoticeClicked() {
342
343
344
345
346
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE),
            newTab = true,
            from = BrowserDirection.FromHome
        )
347
348
    }

349
    override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
350
        fragmentStore.dispatch(HomeFragmentAction.CollectionExpanded(collection, expand))
351
352
    }

353
354
355
356
    override fun handleCloseTip(tip: Tip) {
        fragmentStore.dispatch(HomeFragmentAction.RemoveTip(tip))
    }

357
358
359
360
361
362
363
    private fun showTabTrayCollectionCreation() {
        val directions = HomeFragmentDirections.actionGlobalTabTrayDialogFragment(
            enterMultiselect = true
        )
        navController.nav(R.id.homeFragment, directions)
    }

364
365
366
367
368
369
370
371
372
373
    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()

374
375
376
377
378
        val tabIds = sessionManager
            .sessionsOfType(private = activity.browsingModeManager.mode.isPrivate)
            .map { session -> session.id }
            .toList()
            .toTypedArray()
379
        val directions = HomeFragmentDirections.actionGlobalCollectionCreationFragment(
380
381
382
383
384
385
386
387
            tabIds = tabIds,
            saveCollectionStep = step,
            selectedTabIds = selectedTabIds,
            selectedTabCollectionId = selectedTabCollectionId ?: -1
        )
        navController.nav(R.id.homeFragment, directions)
    }

388
    override fun handleCreateCollection() {
389
        showTabTrayCollectionCreation()
390
391
    }

392
393
394
395
396
    override fun handleRemoveCollectionsPlaceholder() {
        settings.showCollectionsPlaceholderOnHome = false
        fragmentStore.dispatch(HomeFragmentAction.RemoveCollectionsPlaceholder)
    }

397
    private fun showShareFragment(shareSubject: String, data: List<ShareData>) {
Jeff Boek's avatar
Jeff Boek committed
398
        val directions = HomeFragmentDirections.actionGlobalShareFragment(
399
            shareSubject = shareSubject,
400
401
402
403
            data = data.toTypedArray()
        )
        navController.nav(R.id.homeFragment, directions)
    }
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429

    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
            searchAccessPoint.let { sap ->
                MetricsUtils.createSearchEvent(
                    activity.components.search.provider.getDefaultEngine(activity),
                    activity,
                    sap
                )
            }
        }

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

    override fun handlePaste(clipboardText: String) {
430
        val directions = HomeFragmentDirections.actionGlobalSearchDialog(
431
432
433
434
435
            sessionId = null,
            pastedText = clipboardText
        )
        navController.nav(R.id.homeFragment, directions)
    }
436
}