summaryrefslogtreecommitdiffstats
path: root/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/blocklist/BlocklistMiddleware.kt
blob: 1b65af86fa3866aa0afa8d3988fe8409193aaba0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/* 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.blocklist

import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.home.recenttabs.RecentTab

/**
 * This [Middleware] reacts to item removals from the home screen, adding them to a blocklist.
 * Additionally, it reacts to state changes in order to filter them by the blocklist.
 *
 * @param blocklistHandler An instance of [BlocklistHandler] for interacting with the blocklist
 * stored in shared preferences.
 */
class BlocklistMiddleware(
    private val blocklistHandler: BlocklistHandler,
) : Middleware<AppState, AppAction> {

    /**
     * Will filter "Change" actions using the blocklist and use "Remove" actions to update
     * the blocklist.
     */
    override fun invoke(
        context: MiddlewareContext<AppState, AppAction>,
        next: (AppAction) -> Unit,
        action: AppAction,
    ) {
        next(getUpdatedAction(context.state, action))
    }

    @Suppress("ComplexMethod")
    private fun getUpdatedAction(
        state: AppState,
        action: AppAction,
    ) = with(blocklistHandler) {
        when (action) {
            is AppAction.Change -> {
                action.copy(
                    bookmarks = action.bookmarks.filteredByBlocklist(),
                    recentTabs = action.recentTabs.filteredByBlocklist().filterContile(),
                    recentHistory = action.recentHistory.filteredByBlocklist().filterContile(),
                    recentSyncedTabState = action.recentSyncedTabState.filteredByBlocklist().filterContile(),
                )
            }
            is AppAction.RecentTabsChange -> {
                action.copy(
                    recentTabs = action.recentTabs.filteredByBlocklist().filterContile(),
                )
            }
            is AppAction.BookmarksChange -> {
                action.copy(
                    bookmarks = action.bookmarks.filteredByBlocklist(),
                )
            }
            is AppAction.RecentHistoryChange -> {
                action.copy(recentHistory = action.recentHistory.filteredByBlocklist().filterContile())
            }
            is AppAction.RecentSyncedTabStateChange -> {
                action.copy(
                    state = action.state.filteredByBlocklist().filterContile(),
                )
            }
            is AppAction.RemoveRecentTab -> {
                if (action.recentTab is RecentTab.Tab) {
                    addUrlToBlocklist(action.recentTab.state.content.url)
                    state.toActionFilteringAllState(this)
                } else {
                    action
                }
            }
            is AppAction.RemoveBookmark -> {
                action.bookmark.url?.let { url ->
                    addUrlToBlocklist(url)
                    state.toActionFilteringAllState(this)
                } ?: action
            }
            is AppAction.RemoveRecentHistoryHighlight -> {
                addUrlToBlocklist(action.highlightUrl)
                state.toActionFilteringAllState(this)
            }
            is AppAction.RemoveRecentSyncedTab -> {
                addUrlToBlocklist(action.syncedTab.url)
                state.toActionFilteringAllState(this)
            }
            else -> action
        }
    }

    // When an item is removed from any part of the state, it should also be removed from any other
    // relevant parts that contain it.
    // This is a candidate for refactoring once context receivers lands in Kotlin 1.6.20
    // https://blog.jetbrains.com/kotlin/2022/02/kotlin-1-6-20-m1-released/#prototype-of-context-receivers-for-kotlin-jvm
    private fun AppState.toActionFilteringAllState(blocklistHandler: BlocklistHandler) =
        with(blocklistHandler) {
            AppAction.Change(
                recentTabs = recentTabs.filteredByBlocklist().filterContile(),
                bookmarks = bookmarks.filteredByBlocklist(),
                recentHistory = recentHistory.filteredByBlocklist().filterContile(),
                topSites = topSites,
                mode = mode,
                collections = collections,
                showCollectionPlaceholder = showCollectionPlaceholder,
                recentSyncedTabState = recentSyncedTabState.filteredByBlocklist().filterContile(),
            )
        }
}