SessionControlController.kt 13.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
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
66
67
68
69
70
71
72
73
74
75
76
77
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]
     */
    fun handleCollectionRemoveTab(collection: TabCollection, tab: ComponentTab)

    /**
     * @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
    private val showDeleteCollectionPrompt: (tabCollection: TabCollection, title: String?, message: String) -> Unit,
164
    private val showTabTray: () -> Unit
165
166
167
168
169
170
171
172
173
174
175
) : SessionControlController {

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

    override fun handleCollectionOpenTabClicked(tab: ComponentTab) {
176
177
        sessionManager.restore(
            activity,
178
            engine,
179
180
181
182
183
184
185
186
187
188
189
            tab,
            onTabRestored = {
                activity.openToBrowser(BrowserDirection.FromHome)
            },
            onFailure = {
                activity.openToBrowserAndLoad(
                    searchTermOrURL = tab.url,
                    newTab = true,
                    from = BrowserDirection.FromHome
                )
            }
190
191
192
193
194
195
        )

        metrics.track(Event.CollectionTabRestored)
    }

    override fun handleCollectionOpenTabsTapped(collection: TabCollection) {
196
197
        sessionManager.restore(
            activity,
198
            engine,
199
200
            collection,
            onFailure = { url ->
201
                addTabUseCase.invoke(url)
202
            }
203
        )
204

205
        showTabTray()
206
207
208
209
210
211
        metrics.track(Event.CollectionAllTabsRestored)
    }

    override fun handleCollectionRemoveTab(collection: TabCollection, tab: ComponentTab) {
        metrics.track(Event.CollectionTabRemoved)

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

    override fun handleCollectionShareTabsClicked(collection: TabCollection) {
        showShareFragment(collection.tabs.map { ShareData(url = it.url, title = it.title) })
        metrics.track(Event.CollectionShared)
    }

    override fun handleDeleteCollectionTapped(collection: TabCollection) {
233
234
        val message =
            activity.resources.getString(R.string.tab_collection_dialog_message, collection.title)
235
        showDeleteCollectionPrompt(collection, null, message)
236
237
    }

238
    override fun handleOpenInPrivateTabClicked(topSite: TopSite) {
239
        metrics.track(Event.TopSiteOpenInPrivateTab)
240
241
242
243
244
245
246
247
248
249
        with(activity) {
            browsingModeManager.mode = BrowsingMode.Private
            openToBrowserAndLoad(
                searchTermOrURL = topSite.url,
                newTab = true,
                from = BrowserDirection.FromHome
            )
        }
    }

250
251
252
253
254
255
256
257
258
    override fun handlePrivateBrowsingLearnMoreClicked() {
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
                (SupportUtils.SumoTopic.PRIVATE_BROWSING_MYTHS),
            newTab = true,
            from = BrowserDirection.FromHome
        )
    }

259
    override fun handleRemoveTopSiteClicked(topSite: TopSite) {
260
        metrics.track(Event.TopSiteRemoved)
ekager's avatar
ekager committed
261
262
263
        if (topSite.url == SupportUtils.POCKET_TRENDING_URL) {
            metrics.track(Event.PocketTopSiteRemoved)
        }
264

265
        viewLifecycleScope.launch(Dispatchers.IO) {
266
267
268
269
            topSiteStorage.removeTopSite(topSite)
        }
    }

270
271
272
273
274
275
276
277
    override fun handleRenameCollectionTapped(collection: TabCollection) {
        showCollectionCreationFragment(
            step = SaveCollectionStep.RenameCollection,
            selectedTabCollectionId = collection.id
        )
        metrics.track(Event.CollectionRenamePressed)
    }

278
    override fun handleSelectTopSite(url: String, isDefault: Boolean) {
279
        metrics.track(Event.TopSiteOpenInNewTab)
280
281
282
283
284
285
        if (isDefault) {
            metrics.track(Event.TopSiteOpenDefault)
        }
        if (url == SupportUtils.POCKET_TRENDING_URL) {
            metrics.track(Event.PocketTopSiteClicked)
        }
286
        addTabUseCase.invoke(
287
288
289
            url = url,
            selectTab = true,
            startLoading = true
290
        )
291
        activity.openToBrowser(BrowserDirection.FromHome)
292
293
    }

294
295
296
297
    override fun handleStartBrowsingClicked() {
        hideOnboarding()
    }

298
    override fun handleOpenSettingsClicked() {
299
300
        val directions = HomeFragmentDirections.actionGlobalPrivateBrowsingFragment()
        navController.nav(R.id.homeFragment, directions)
301
302
    }

303
    override fun handleWhatsNewGetAnswersClicked() {
304
305
306
307
308
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getWhatsNewUrl(activity),
            newTab = true,
            from = BrowserDirection.FromHome
        )
309
310
311
    }

    override fun handleReadPrivacyNoticeClicked() {
312
313
314
315
316
        activity.openToBrowserAndLoad(
            searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE),
            newTab = true,
            from = BrowserDirection.FromHome
        )
317
318
    }

319
    override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
320
        fragmentStore.dispatch(HomeFragmentAction.CollectionExpanded(collection, expand))
321
322
    }

323
324
325
326
    override fun handleCloseTip(tip: Tip) {
        fragmentStore.dispatch(HomeFragmentAction.RemoveTip(tip))
    }

327
328
329
330
331
332
333
    private fun showTabTrayCollectionCreation() {
        val directions = HomeFragmentDirections.actionGlobalTabTrayDialogFragment(
            enterMultiselect = true
        )
        navController.nav(R.id.homeFragment, directions)
    }

334
335
336
337
338
339
340
341
342
343
    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()

344
345
346
347
348
        val tabIds = sessionManager
            .sessionsOfType(private = activity.browsingModeManager.mode.isPrivate)
            .map { session -> session.id }
            .toList()
            .toTypedArray()
349
        val directions = HomeFragmentDirections.actionGlobalCollectionCreationFragment(
350
351
352
353
354
355
356
357
            tabIds = tabIds,
            saveCollectionStep = step,
            selectedTabIds = selectedTabIds,
            selectedTabCollectionId = selectedTabCollectionId ?: -1
        )
        navController.nav(R.id.homeFragment, directions)
    }

358
    override fun handleCreateCollection() {
359
        showTabTrayCollectionCreation()
360
361
    }

362
    private fun showShareFragment(data: List<ShareData>) {
Jeff Boek's avatar
Jeff Boek committed
363
        val directions = HomeFragmentDirections.actionGlobalShareFragment(
364
365
366
367
            data = data.toTypedArray()
        )
        navController.nav(R.id.homeFragment, directions)
    }
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400

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