SessionControlController.kt 13.7 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
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, isDefault: Boolean)
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
@Suppress("TooManyFunctions", "LargeClass")
150
151
class DefaultSessionControlController(
    private val activity: HomeActivity,
152
153
154
155
156
157
    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,
158
    private val fragmentStore: HomeFragmentStore,
159
    private val navController: NavController,
160
    private val viewLifecycleScope: CoroutineScope,
161
162
    private val hideOnboarding: () -> Unit,
    private val registerCollectionStorageObserver: () -> Unit,
163
164
165
166
167
168
169
170
171
    private val showDeleteCollectionPrompt: (
        tabCollection: TabCollection,
        title: String?,
        message: String,
        wasSwiped: Boolean,
        handleSwipedItemDeletionCancel: () -> Unit
    ) -> Unit,
    private val showTabTray: () -> Unit,
    private val handleSwipedItemDeletionCancel: () -> Unit
172
173
174
175
176
177
178
179
180
181
182
) : SessionControlController {

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

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

        metrics.track(Event.CollectionTabRestored)
    }

    override fun handleCollectionOpenTabsTapped(collection: TabCollection) {
203
204
        sessionManager.restore(
            activity,
205
            engine,
206
207
            collection,
            onFailure = { url ->
208
                addTabUseCase.invoke(url)
209
            }
210
        )
211

212
        showTabTray()
213
214
215
        metrics.track(Event.CollectionAllTabsRestored)
    }

216
    override fun handleCollectionRemoveTab(collection: TabCollection, tab: ComponentTab, wasSwiped: Boolean) {
217
218
        metrics.track(Event.CollectionTabRemoved)

219
        if (collection.tabs.size == 1) {
220
221
222
223
224
225
            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)
226
            showDeleteCollectionPrompt(collection, title, message, wasSwiped, handleSwipedItemDeletionCancel)
227
228
229
230
        } else {
            viewLifecycleScope.launch(Dispatchers.IO) {
                tabCollectionStorage.removeTabFromCollection(collection, tab)
            }
231
232
233
234
        }
    }

    override fun handleCollectionShareTabsClicked(collection: TabCollection) {
235
236
237
238
        showShareFragment(
            collection.title,
            collection.tabs.map { ShareData(url = it.url, title = it.title) }
        )
239
240
241
242
        metrics.track(Event.CollectionShared)
    }

    override fun handleDeleteCollectionTapped(collection: TabCollection) {
243
244
        val message =
            activity.resources.getString(R.string.tab_collection_dialog_message, collection.title)
245
        showDeleteCollectionPrompt(collection, null, message, false, handleSwipedItemDeletionCancel)
246
247
    }

248
    override fun handleOpenInPrivateTabClicked(topSite: TopSite) {
249
        metrics.track(Event.TopSiteOpenInPrivateTab)
250
251
252
253
254
255
256
257
258
259
        with(activity) {
            browsingModeManager.mode = BrowsingMode.Private
            openToBrowserAndLoad(
                searchTermOrURL = topSite.url,
                newTab = true,
                from = BrowserDirection.FromHome
            )
        }
    }

260
261
262
263
264
265
266
267
268
    override fun handlePrivateBrowsingLearnMoreClicked() {
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
                (SupportUtils.SumoTopic.PRIVATE_BROWSING_MYTHS),
            newTab = true,
            from = BrowserDirection.FromHome
        )
    }

269
    override fun handleRemoveTopSiteClicked(topSite: TopSite) {
270
        metrics.track(Event.TopSiteRemoved)
ekager's avatar
ekager committed
271
272
273
        if (topSite.url == SupportUtils.POCKET_TRENDING_URL) {
            metrics.track(Event.PocketTopSiteRemoved)
        }
274

275
        viewLifecycleScope.launch(Dispatchers.IO) {
276
277
278
279
            topSiteStorage.removeTopSite(topSite)
        }
    }

280
281
282
283
284
285
286
287
    override fun handleRenameCollectionTapped(collection: TabCollection) {
        showCollectionCreationFragment(
            step = SaveCollectionStep.RenameCollection,
            selectedTabCollectionId = collection.id
        )
        metrics.track(Event.CollectionRenamePressed)
    }

288
    override fun handleSelectTopSite(url: String, isDefault: Boolean) {
289
        metrics.track(Event.TopSiteOpenInNewTab)
290
291
292
293
294
295
        if (isDefault) {
            metrics.track(Event.TopSiteOpenDefault)
        }
        if (url == SupportUtils.POCKET_TRENDING_URL) {
            metrics.track(Event.PocketTopSiteClicked)
        }
296
        addTabUseCase.invoke(
297
298
299
            url = url,
            selectTab = true,
            startLoading = true
300
        )
301
        activity.openToBrowser(BrowserDirection.FromHome)
302
303
    }

304
305
306
307
    override fun handleStartBrowsingClicked() {
        hideOnboarding()
    }

308
    override fun handleOpenSettingsClicked() {
309
310
        val directions = HomeFragmentDirections.actionGlobalPrivateBrowsingFragment()
        navController.nav(R.id.homeFragment, directions)
311
312
    }

313
    override fun handleWhatsNewGetAnswersClicked() {
314
315
316
317
318
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getWhatsNewUrl(activity),
            newTab = true,
            from = BrowserDirection.FromHome
        )
319
320
321
    }

    override fun handleReadPrivacyNoticeClicked() {
322
323
324
325
326
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE),
            newTab = true,
            from = BrowserDirection.FromHome
        )
327
328
    }

329
    override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
330
        fragmentStore.dispatch(HomeFragmentAction.CollectionExpanded(collection, expand))
331
332
    }

333
334
335
336
    override fun handleCloseTip(tip: Tip) {
        fragmentStore.dispatch(HomeFragmentAction.RemoveTip(tip))
    }

337
338
339
340
341
342
343
    private fun showTabTrayCollectionCreation() {
        val directions = HomeFragmentDirections.actionGlobalTabTrayDialogFragment(
            enterMultiselect = true
        )
        navController.nav(R.id.homeFragment, directions)
    }

344
345
346
347
348
349
350
351
352
353
    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()

354
355
356
357
358
        val tabIds = sessionManager
            .sessionsOfType(private = activity.browsingModeManager.mode.isPrivate)
            .map { session -> session.id }
            .toList()
            .toTypedArray()
359
        val directions = HomeFragmentDirections.actionGlobalCollectionCreationFragment(
360
361
362
363
364
365
366
367
            tabIds = tabIds,
            saveCollectionStep = step,
            selectedTabIds = selectedTabIds,
            selectedTabCollectionId = selectedTabCollectionId ?: -1
        )
        navController.nav(R.id.homeFragment, directions)
    }

368
    override fun handleCreateCollection() {
369
        showTabTrayCollectionCreation()
370
371
    }

372
    private fun showShareFragment(shareSubject: String, data: List<ShareData>) {
Jeff Boek's avatar
Jeff Boek committed
373
        val directions = HomeFragmentDirections.actionGlobalShareFragment(
374
            shareSubject = shareSubject,
375
376
377
378
            data = data.toTypedArray()
        )
        navController.nav(R.id.homeFragment, directions)
    }
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411

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