TabGroupReducer.kt
/* 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 mozilla.components.browser.state.reducer
import mozilla.components.browser.state.action.TabGroupAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabGroup
import mozilla.components.browser.state.state.TabPartition
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.getGroupById
internal object TabGroupReducer {
/**
* [TabGroupAction] reducer function for modifying tab groups in [BrowserState.tabPartitions].
*/
fun reduce(state: BrowserState, action: TabGroupAction): BrowserState {
return when (action) {
is TabGroupAction.AddTabGroupAction -> {
action.group.tabIds.forEach { state.assertTabExists(it) }
state.addTabGroup(action.partition, action.group)
}
is TabGroupAction.RemoveTabGroupAction -> {
state.removeTabGroup(action.partition, action.group)
}
is TabGroupAction.AddTabAction -> {
state.assertTabExists(action.tabId)
if (!state.groupExists(action.partition, action.group)) {
state.addTabGroup(action.partition, TabGroup(action.group, tabIds = listOf(action.tabId)))
} else {
state.updateTabGroup(action.partition, action.group) {
it.copy(tabIds = (it.tabIds + action.tabId).distinct())
}
}
}
is TabGroupAction.AddTabsAction -> {
action.tabIds.forEach { state.assertTabExists(it) }
if (!state.groupExists(action.partition, action.group)) {
state.addTabGroup(action.partition, TabGroup(action.group, tabIds = action.tabIds.distinct()))
} else {
state.updateTabGroup(action.partition, action.group) {
it.copy(tabIds = (it.tabIds + action.tabIds).distinct())
}
}
}
is TabGroupAction.RemoveTabAction -> {
state.updateTabGroup(action.partition, action.group) {
it.copy(tabIds = it.tabIds - action.tabId)
}
}
is TabGroupAction.RemoveTabsAction -> {
state.updateTabGroup(action.partition, action.group) {
it.copy(tabIds = it.tabIds - action.tabIds)
}
}
}
}
}
/**
* Adds the provided tab group and creates the partition if needed.
*/
private fun BrowserState.addTabGroup(partitionId: String, group: TabGroup): BrowserState {
val partition = tabPartitions[partitionId]
val updatedPartition = if (partition != null) {
require(partition.getGroupById(group.id) == null) {
"Tab group with same ID already exists"
}
partition.copy(tabGroups = partition.tabGroups + group)
} else {
TabPartition(partitionId, tabGroups = listOf(group))
}
return copy(tabPartitions = tabPartitions + (partitionId to updatedPartition))
}
/**
* Removes a tab group from the provided partition.
*/
private fun BrowserState.removeTabGroup(partitionId: String, groupId: String): BrowserState {
val partition = tabPartitions[partitionId]
val group = partition?.getGroupById(groupId)
return if (group != null) {
val updatedPartition = partition.copy(tabGroups = partition.tabGroups - group)
if (updatedPartition.tabGroups.isEmpty()) {
copy(tabPartitions = tabPartitions - partitionId)
} else {
copy(tabPartitions = tabPartitions + (partitionId to updatedPartition))
}
} else {
this
}
}
/**
* Checks if a tab group exists in the provided partition.
*/
private fun BrowserState.groupExists(partitionId: String, groupId: String): Boolean {
return tabPartitions[partitionId]?.getGroupById(groupId) != null
}
/**
* Checks that the provided tab exists and throws an
* [IllegalArgumentException] otherwise.
*
* @param tabId the id of the [TabSessionState] to check.
*/
private fun BrowserState.assertTabExists(tabId: String) {
require(tabs.find { it.id == tabId } != null) {
"Tab does not exist"
}
}
/**
* Utility function to update a [TabGroup] within a [TabPartition] in [BrowserState].
*/
private fun BrowserState.updateTabGroup(
partitionId: String,
groupId: String,
update: (TabGroup) -> TabGroup,
): BrowserState {
return updateTabPartition(partitionId) { partition ->
partition.updateTabGroup(groupId, update)
}
}
/**
* Updates the specified tab partition by invoking [update].
*/
private inline fun BrowserState.updateTabPartition(
partitionId: String,
crossinline update: (TabPartition) -> TabPartition,
): BrowserState {
val partition = tabPartitions[partitionId] ?: return this
return copy(tabPartitions = tabPartitions + (partitionId to update(partition)))
}
/**
* Updates the specified tab group within this partition by invoking [update].
*/
private inline fun TabPartition.updateTabGroup(
groupId: String,
crossinline update: (TabGroup) -> TabGroup,
): TabPartition {
return tabGroups.update(groupId, update)?.let {
copy(tabGroups = it)
} ?: this
}
/**
* Updates the provided tab group by invoking [update].
*/
private inline fun List<TabGroup>.update(
groupId: String,
crossinline update: (TabGroup) -> TabGroup,
): List<TabGroup>? {
val groupIndex = indexOfFirst { it.id == groupId }
if (groupIndex == -1) return null
return subList(0, groupIndex) + update(get(groupIndex)) + subList(groupIndex + 1, size)
}