SessionControlController.kt 14.2 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
import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.components.TabCollectionStorage
25
import org.mozilla.fenix.components.TopSiteStorage
26
27
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
28
import org.mozilla.fenix.components.metrics.MetricsUtils
29
import org.mozilla.fenix.components.tips.Tip
30
31
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.metrics
32
import org.mozilla.fenix.ext.nav
33
import org.mozilla.fenix.ext.sessionsOfType
34
import org.mozilla.fenix.ext.settings
35
36
37
38
39
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
40
import org.mozilla.fenix.utils.Settings
ekager's avatar
ekager committed
41
import mozilla.components.feature.tab.collections.Tab as ComponentTab
42
43
44
45
46

/**
 * [HomeFragment] controller. An interface that handles the view manipulation of the Tabs triggered
 * by the Interactor.
 */
47
@Suppress("TooManyFunctions")
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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]
     */
67
    fun handleCollectionRemoveTab(collection: TabCollection, tab: ComponentTab, wasSwiped: Boolean)
68
69
70
71
72
73
74
75
76
77
78

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

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

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

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

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

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

99
100
101
    /**
     * @see [TopSiteInteractor.onSelectTopSite]
     */
102
    fun handleSelectTopSite(url: String, isDefault: Boolean)
103

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

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

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

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

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

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

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

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

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

    /**
     * @see [CollectionInteractor.onRemoveCollectionsPlaceholder]
     */
    fun handleRemoveCollectionsPlaceholder()
153
154
}

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

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

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

        metrics.track(Event.CollectionTabRestored)
    }

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

219
        showTabTray()
220
221
222
        metrics.track(Event.CollectionAllTabsRestored)
    }

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

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

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

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

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

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

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

292
        viewLifecycleScope.launch(Dispatchers.IO) {
293
294
295
296
            topSiteStorage.removeTopSite(topSite)
        }
    }

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, isDefault: Boolean) {
306
        metrics.track(Event.TopSiteOpenInNewTab)
307
308
309
310
311
312
        if (isDefault) {
            metrics.track(Event.TopSiteOpenDefault)
        }
        if (url == SupportUtils.POCKET_TRENDING_URL) {
            metrics.track(Event.PocketTopSiteClicked)
        }
313
        addTabUseCase.invoke(
314
315
316
            url = url,
            selectTab = true,
            startLoading = true
317
        )
318
        activity.openToBrowser(BrowserDirection.FromHome)
319
320
    }

321
322
323
324
    override fun handleStartBrowsingClicked() {
        hideOnboarding()
    }

325
    override fun handleOpenSettingsClicked() {
326
327
        val directions = HomeFragmentDirections.actionGlobalPrivateBrowsingFragment()
        navController.nav(R.id.homeFragment, directions)
328
329
    }

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

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

346
    override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
347
        fragmentStore.dispatch(HomeFragmentAction.CollectionExpanded(collection, expand))
348
349
    }

350
351
352
353
    override fun handleCloseTip(tip: Tip) {
        fragmentStore.dispatch(HomeFragmentAction.RemoveTip(tip))
    }

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

361
362
363
364
365
366
367
368
369
370
    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()

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

385
    override fun handleCreateCollection() {
386
        showTabTrayCollectionCreation()
387
388
    }

389
390
391
392
393
    override fun handleRemoveCollectionsPlaceholder() {
        settings.showCollectionsPlaceholderOnHome = false
        fragmentStore.dispatch(HomeFragmentAction.RemoveCollectionsPlaceholder)
    }

394
    private fun showShareFragment(shareSubject: String, data: List<ShareData>) {
Jeff Boek's avatar
Jeff Boek committed
395
        val directions = HomeFragmentDirections.actionGlobalShareFragment(
396
            shareSubject = shareSubject,
397
398
399
400
            data = data.toTypedArray()
        )
        navController.nav(R.id.homeFragment, directions)
    }
401
402
403
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
430
431
432
433

    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)
    }
434
}