For #24333 - Replace the xml based CollectionViewHolder with a composable
This commit is contained in:
parent
2126c7eeb4
commit
1e3319f0e8
|
@ -21,9 +21,10 @@ import mozilla.components.support.base.observer.Observable
|
|||
import mozilla.components.support.base.observer.ObserverRegistry
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.toShortUrl
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
|
||||
import org.mozilla.fenix.perf.StrictModeManager
|
||||
|
||||
private const val COLLECTION_MAX_TITLE_LENGTH = 20
|
||||
|
||||
class TabCollectionStorage(
|
||||
private val context: Context,
|
||||
strictMode: StrictModeManager,
|
||||
|
@ -106,10 +107,10 @@ fun TabCollection.description(context: Context): String {
|
|||
return this.tabs
|
||||
.map { it.url.toShortUrl(context.components.publicSuffixList) }
|
||||
.map {
|
||||
if (it.length > CollectionViewHolder.maxTitleLength) {
|
||||
if (it.length > COLLECTION_MAX_TITLE_LENGTH) {
|
||||
it.substring(
|
||||
0,
|
||||
CollectionViewHolder.maxTitleLength
|
||||
COLLECTION_MAX_TITLE_LENGTH
|
||||
) + "…"
|
||||
} else {
|
||||
it
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
/* 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.collections
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Paint
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import mozilla.components.browser.state.state.recover.RecoverableTab
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import mozilla.components.feature.tab.collections.Tab
|
||||
import mozilla.components.feature.tab.collections.TabCollection
|
||||
import org.mozilla.fenix.R.drawable
|
||||
import org.mozilla.fenix.R.string
|
||||
import org.mozilla.fenix.compose.list.ExpandableListHeader
|
||||
import org.mozilla.fenix.ext.getIconColor
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
import org.mozilla.fenix.theme.Theme
|
||||
|
||||
/**
|
||||
* Rectangular shape with all corners rounded used to display a collapsed collection.
|
||||
*/
|
||||
private val collapsedCollectionShape = RoundedCornerShape(8.dp)
|
||||
|
||||
/**
|
||||
* Rectangular shape with only the top corners rounded used to display an expanded collection with other views
|
||||
* placed immediately below this which can be shown immediately next to it, with no visible separation.
|
||||
*/
|
||||
private val expandedCollectionShape = RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp)
|
||||
|
||||
/**
|
||||
* Displays an individual [TabCollection].
|
||||
*
|
||||
* @param collection [TabCollection] to display.
|
||||
* @param expanded Whether the collection is expanded to show it's containing tabs or not.
|
||||
* @param menuItems List of [CollectionMenuItem] to be shown in a menu.
|
||||
* @param onToggleCollectionExpanded Invoked when the user clicks on the collection.
|
||||
* @param onCollectionShareTabsClicked Invoked when the user clicks to share the collection.
|
||||
* @param onCollectionMenuOpened Invoked when the user clicks to open a menu for the collection.
|
||||
*/
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
fun Collection(
|
||||
collection: TabCollection,
|
||||
expanded: Boolean,
|
||||
menuItems: List<CollectionMenuItem>,
|
||||
onToggleCollectionExpanded: (TabCollection, Boolean) -> Unit,
|
||||
onCollectionShareTabsClicked: (TabCollection) -> Unit,
|
||||
onCollectionMenuOpened: () -> Unit,
|
||||
) {
|
||||
var isMenuExpanded by remember(collection) { mutableStateOf(false) }
|
||||
val isExpanded by remember(collection) { mutableStateOf(expanded) }
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.semantics(mergeDescendants = true) {}
|
||||
.clickable { onToggleCollectionExpanded(collection, !isExpanded) }
|
||||
.height(48.dp),
|
||||
shape = if (isExpanded) expandedCollectionShape else collapsedCollectionShape,
|
||||
backgroundColor = FirefoxTheme.colors.layer2,
|
||||
elevation = 5.dp, // This needs to match the elevation of TabInCollection for matching shadows.
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(drawable.ic_tab_collection),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(
|
||||
start = 16.dp,
|
||||
end = 8.dp // (24.dp - 16.dp) hardcoded in ExpandableListHeader
|
||||
),
|
||||
tint = Paint().apply {
|
||||
color = Color(collection.getIconColor(LocalContext.current))
|
||||
blendMode = BlendMode.SrcIn
|
||||
}.color,
|
||||
)
|
||||
|
||||
ExpandableListHeader(
|
||||
headerText = collection.title,
|
||||
expanded = isExpanded,
|
||||
) {
|
||||
if (isExpanded) {
|
||||
Row {
|
||||
IconButton(
|
||||
onClick = { onCollectionShareTabsClicked(collection) }
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(drawable.ic_share),
|
||||
contentDescription = stringResource(string.share_button_content_description),
|
||||
tint = FirefoxTheme.colors.iconPrimary,
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
isMenuExpanded = !isMenuExpanded
|
||||
onCollectionMenuOpened()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(drawable.ic_menu),
|
||||
contentDescription = stringResource(
|
||||
string.collection_menu_button_content_description
|
||||
),
|
||||
tint = FirefoxTheme.colors.iconPrimary,
|
||||
)
|
||||
|
||||
CollectionMenu(
|
||||
showMenu = isMenuExpanded,
|
||||
menuItems = menuItems,
|
||||
onDismissRequest = { isMenuExpanded = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun CollectionDarkPreview() {
|
||||
FirefoxTheme(Theme.Dark) {
|
||||
Collection(
|
||||
collection = collectionPreview,
|
||||
expanded = false,
|
||||
menuItems = emptyList(),
|
||||
onToggleCollectionExpanded = { _, _ -> },
|
||||
onCollectionShareTabsClicked = {},
|
||||
onCollectionMenuOpened = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun CollectionDarkExpandedPreview() {
|
||||
FirefoxTheme(Theme.Dark) {
|
||||
Collection(
|
||||
collection = collectionPreview,
|
||||
expanded = true,
|
||||
menuItems = emptyList(),
|
||||
onToggleCollectionExpanded = { _, _ -> },
|
||||
onCollectionShareTabsClicked = {},
|
||||
onCollectionMenuOpened = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
private fun CollectionLightPreview() {
|
||||
FirefoxTheme(Theme.Light) {
|
||||
Collection(
|
||||
collection = collectionPreview,
|
||||
expanded = false,
|
||||
menuItems = emptyList(),
|
||||
onToggleCollectionExpanded = { _, _ -> },
|
||||
onCollectionShareTabsClicked = {},
|
||||
onCollectionMenuOpened = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
private fun CollectionLightExpandedPreview() {
|
||||
FirefoxTheme(Theme.Light) {
|
||||
Collection(
|
||||
collection = collectionPreview,
|
||||
expanded = true,
|
||||
menuItems = emptyList(),
|
||||
onToggleCollectionExpanded = { _, _ -> },
|
||||
onCollectionShareTabsClicked = {},
|
||||
onCollectionMenuOpened = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val collectionPreview = object : TabCollection {
|
||||
override val id: Long = 1L
|
||||
override val tabs: List<Tab> = emptyList()
|
||||
override val title: String = "Collection 1"
|
||||
|
||||
override fun restore(
|
||||
context: Context,
|
||||
engine: Engine,
|
||||
restoreSessionId: Boolean,
|
||||
): List<RecoverableTab> = emptyList()
|
||||
|
||||
override fun restoreSubset(
|
||||
context: Context,
|
||||
engine: Engine,
|
||||
tabs: List<Tab>,
|
||||
restoreSessionId: Boolean,
|
||||
): List<RecoverableTab> = emptyList()
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
/* 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.collections
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import mozilla.components.browser.state.selector.normalTabs
|
||||
import mozilla.components.browser.state.state.recover.RecoverableTab
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import mozilla.components.feature.tab.collections.Tab
|
||||
import mozilla.components.feature.tab.collections.TabCollection
|
||||
import org.mozilla.fenix.R.string
|
||||
import org.mozilla.fenix.compose.inComposePreview
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
import org.mozilla.fenix.theme.Theme
|
||||
|
||||
/**
|
||||
* Menu shown for a [org.mozilla.fenix.home.collections.Collection].
|
||||
*
|
||||
* @see [DropdownMenu]
|
||||
*
|
||||
* @param showMenu Whether this is currently open and visible to the user.
|
||||
* @param menuItems List of options shown.
|
||||
* @param onDismissRequest Called when the user chooses a menu option or requests to dismiss the menu.
|
||||
*/
|
||||
@Composable
|
||||
fun CollectionMenu(
|
||||
showMenu: Boolean,
|
||||
menuItems: List<CollectionMenuItem>,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
DisposableEffect(LocalConfiguration.current.orientation) {
|
||||
onDispose { onDismissRequest() }
|
||||
}
|
||||
|
||||
// DropdownMenu uses the medium shape from MaterialTheme.
|
||||
// Override it's corner radius to be the same 8.dp as in mozac_browser_menu_corner_radius
|
||||
MaterialTheme(shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(8.dp))) {
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { onDismissRequest() },
|
||||
modifier = Modifier
|
||||
.background(color = FirefoxTheme.colors.layer2),
|
||||
) {
|
||||
for (item in menuItems) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
item.onClick()
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
text = item.title,
|
||||
color = item.color,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A menu item for collections.
|
||||
*
|
||||
* @property title The menu item title.
|
||||
* @property color The color that should be set for the title.
|
||||
* @property onClick Invoked when the user clicks on the menu item.
|
||||
*/
|
||||
@Immutable
|
||||
data class CollectionMenuItem(
|
||||
val title: String,
|
||||
val color: Color,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs and returns the default list of menu options for a [TabCollection].
|
||||
*
|
||||
* @param collection [TabCollection] for which the menu will be shown.
|
||||
* Might serve as an argument for the callbacks for when the user interacts with certain menu options.
|
||||
* @param onOpenTabsTapped Invoked when the user chooses to open the tabs from [collection].
|
||||
* @param onRenameCollectionTapped Invoked when the user chooses to rename the [collection].
|
||||
* @param onAddTabTapped Invoked when the user chooses to add tabs to [collection].
|
||||
* @param onDeleteCollectionTapped Invoked when the user chooses to delete [collection].
|
||||
*/
|
||||
@Composable
|
||||
fun getMenuItems(
|
||||
collection: TabCollection,
|
||||
onOpenTabsTapped: (TabCollection) -> Unit,
|
||||
onRenameCollectionTapped: (TabCollection) -> Unit,
|
||||
onAddTabTapped: (TabCollection) -> Unit,
|
||||
onDeleteCollectionTapped: (TabCollection) -> Unit,
|
||||
): List<CollectionMenuItem> {
|
||||
return listOfNotNull(
|
||||
CollectionMenuItem(
|
||||
title = stringResource(string.collection_open_tabs),
|
||||
color = FirefoxTheme.colors.textPrimary
|
||||
) {
|
||||
onOpenTabsTapped(collection)
|
||||
},
|
||||
CollectionMenuItem(
|
||||
title = stringResource(string.collection_rename),
|
||||
color = FirefoxTheme.colors.textPrimary
|
||||
) {
|
||||
onRenameCollectionTapped(collection)
|
||||
},
|
||||
|
||||
if (hasOpenTabs()) {
|
||||
CollectionMenuItem(
|
||||
title = stringResource(string.add_tab),
|
||||
color = FirefoxTheme.colors.textPrimary
|
||||
) {
|
||||
onAddTabTapped(collection)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
|
||||
CollectionMenuItem(
|
||||
title = stringResource(string.collection_delete),
|
||||
color = FirefoxTheme.colors.textWarning
|
||||
) {
|
||||
onDeleteCollectionTapped(collection)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun hasOpenTabs() = when (inComposePreview) {
|
||||
true -> true
|
||||
false -> LocalContext.current.components.core.store.state.normalTabs.isNotEmpty()
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun CollectionMenuDarkPreview() {
|
||||
FirefoxTheme(Theme.Dark) {
|
||||
CollectionMenu(
|
||||
showMenu = true,
|
||||
menuItems = getMenuItems(
|
||||
collection = collectionPreview,
|
||||
onOpenTabsTapped = {},
|
||||
onRenameCollectionTapped = {},
|
||||
onAddTabTapped = {},
|
||||
onDeleteCollectionTapped = {}
|
||||
),
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
private fun CollectionMenuLightPreview() {
|
||||
FirefoxTheme(Theme.Light) {
|
||||
CollectionMenu(
|
||||
showMenu = true,
|
||||
menuItems = getMenuItems(
|
||||
collection = collectionPreview,
|
||||
onOpenTabsTapped = {},
|
||||
onRenameCollectionTapped = {},
|
||||
onAddTabTapped = {},
|
||||
onDeleteCollectionTapped = {}
|
||||
),
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
private val collectionPreview = object : TabCollection {
|
||||
override val id: Long = 1L
|
||||
override val tabs: List<Tab> = emptyList()
|
||||
override val title: String = "Collection 1"
|
||||
|
||||
override fun restore(
|
||||
context: Context,
|
||||
engine: Engine,
|
||||
restoreSessionId: Boolean,
|
||||
): List<RecoverableTab> = emptyList()
|
||||
|
||||
override fun restoreSubset(
|
||||
context: Context,
|
||||
engine: Engine,
|
||||
tabs: List<Tab>,
|
||||
restoreSessionId: Boolean,
|
||||
): List<RecoverableTab> = emptyList()
|
||||
}
|
|
@ -2,154 +2,103 @@
|
|||
* 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.viewholders
|
||||
package org.mozilla.fenix.home.collections
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat.createBlendModeColorFilterCompat
|
||||
import androidx.core.graphics.BlendModeCompat.SRC_IN
|
||||
import mozilla.components.browser.menu.BrowserMenuBuilder
|
||||
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
|
||||
import mozilla.components.browser.state.selector.normalTabs
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import mozilla.components.feature.tab.collections.TabCollection
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.databinding.CollectionHomeListRowBinding
|
||||
import org.mozilla.fenix.utils.view.ViewHolder
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.getIconColor
|
||||
import org.mozilla.fenix.ext.increaseTapArea
|
||||
import org.mozilla.fenix.ext.removeAndDisable
|
||||
import org.mozilla.fenix.ext.removeTouchDelegate
|
||||
import org.mozilla.fenix.ext.showAndEnable
|
||||
import org.mozilla.fenix.compose.ComposeViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.CollectionInteractor
|
||||
import org.mozilla.fenix.theme.ThemeManager
|
||||
|
||||
/**
|
||||
* [RecyclerView.ViewHolder] for displaying an individual [TabCollection].
|
||||
* Clients are expected to use [bindSession] to link a particular [TabCollection] to be displayed
|
||||
* otherwise this will be an empty, 0 size View.
|
||||
*
|
||||
* @param composeView [ComposeView] which will be populated with Jetpack Compose UI content.
|
||||
* @param viewLifecycleOwner [LifecycleOwner] to which this Composable will be tied to.
|
||||
* @param interactor [CollectionInteractor] callback for user interactions.
|
||||
*/
|
||||
class CollectionViewHolder(
|
||||
view: View,
|
||||
val interactor: CollectionInteractor
|
||||
) : ViewHolder(view) {
|
||||
|
||||
private lateinit var collection: TabCollection
|
||||
private var expanded = false
|
||||
private var collectionMenu: CollectionItemMenu
|
||||
private var binding: CollectionHomeListRowBinding
|
||||
composeView: ComposeView,
|
||||
viewLifecycleOwner: LifecycleOwner,
|
||||
private val interactor: CollectionInteractor,
|
||||
) : ComposeViewHolder(composeView, viewLifecycleOwner) {
|
||||
private var collectionData = CollectionInfo()
|
||||
|
||||
init {
|
||||
binding = CollectionHomeListRowBinding.bind(view)
|
||||
val horizontalPadding =
|
||||
composeView.resources.getDimensionPixelSize(R.dimen.home_item_horizontal_margin)
|
||||
composeView.setPadding(horizontalPadding, 0, horizontalPadding, 0)
|
||||
}
|
||||
|
||||
collectionMenu = CollectionItemMenu(
|
||||
view.context,
|
||||
{ view.context.components.core.store.state.normalTabs.isNotEmpty() }
|
||||
) {
|
||||
when (it) {
|
||||
is CollectionItemMenu.Item.DeleteCollection -> interactor.onDeleteCollectionTapped(collection)
|
||||
is CollectionItemMenu.Item.AddTab -> interactor.onCollectionAddTabTapped(collection)
|
||||
is CollectionItemMenu.Item.RenameCollection -> interactor.onRenameCollectionTapped(collection)
|
||||
is CollectionItemMenu.Item.OpenTabs -> interactor.onCollectionOpenTabsTapped(collection)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val collectionInfo by remember { mutableStateOf(collectionData) }
|
||||
|
||||
collectionInfo.collection?.let { collection ->
|
||||
val menuItems = getMenuItems(
|
||||
collection = collection,
|
||||
onOpenTabsTapped = interactor::onCollectionOpenTabsTapped,
|
||||
onRenameCollectionTapped = interactor::onRenameCollectionTapped,
|
||||
onAddTabTapped = interactor::onCollectionAddTabTapped,
|
||||
onDeleteCollectionTapped = interactor::onDeleteCollectionTapped,
|
||||
)
|
||||
|
||||
Column {
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
Collection(
|
||||
collection = collection,
|
||||
expanded = collectionInfo.isExpanded,
|
||||
menuItems = menuItems,
|
||||
onToggleCollectionExpanded = interactor::onToggleCollectionExpanded,
|
||||
onCollectionShareTabsClicked = interactor::onCollectionShareTabsClicked,
|
||||
onCollectionMenuOpened = interactor::onCollectionMenuOpened,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
binding.collectionOverflowButton.setOnClickListener {
|
||||
interactor.onCollectionMenuOpened()
|
||||
collectionMenu.menuBuilder
|
||||
.build(view.context)
|
||||
.show(anchor = it)
|
||||
}
|
||||
|
||||
binding.collectionShareButton.setOnClickListener {
|
||||
interactor.onCollectionShareTabsClicked(collection)
|
||||
}
|
||||
|
||||
view.clipToOutline = true
|
||||
view.setOnClickListener {
|
||||
interactor.onToggleCollectionExpanded(collection, !expanded)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically replace the current [TabCollection] shown in this `ViewHolder`.
|
||||
*
|
||||
* @param collection [TabCollection] to be shown
|
||||
* @param expanded Whether to show the collection as expanded or collapsed.
|
||||
*/
|
||||
fun bindSession(collection: TabCollection, expanded: Boolean) {
|
||||
this.collection = collection
|
||||
this.expanded = expanded
|
||||
updateCollectionUI()
|
||||
}
|
||||
|
||||
private fun updateCollectionUI() {
|
||||
binding.collectionTitle.text = collection.title
|
||||
|
||||
itemView.isActivated = expanded
|
||||
if (expanded) {
|
||||
binding.collectionShareButton.apply {
|
||||
showAndEnable()
|
||||
increaseTapArea(buttonIncreaseDps)
|
||||
}
|
||||
binding.collectionOverflowButton.apply {
|
||||
showAndEnable()
|
||||
increaseTapArea(buttonIncreaseDps)
|
||||
}
|
||||
} else {
|
||||
|
||||
binding.collectionShareButton.apply {
|
||||
removeAndDisable()
|
||||
removeTouchDelegate()
|
||||
}
|
||||
binding.collectionOverflowButton.apply {
|
||||
removeAndDisable()
|
||||
removeTouchDelegate()
|
||||
}
|
||||
}
|
||||
|
||||
binding.collectionIcon.colorFilter = createBlendModeColorFilterCompat(
|
||||
collection.getIconColor(itemView.context),
|
||||
SRC_IN
|
||||
collectionData = CollectionInfo(
|
||||
collection = collection,
|
||||
isExpanded = expanded
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val buttonIncreaseDps = 16
|
||||
const val LAYOUT_ID = R.layout.collection_home_list_row
|
||||
const val maxTitleLength = 20
|
||||
val LAYOUT_ID = View.generateViewId()
|
||||
}
|
||||
}
|
||||
|
||||
class CollectionItemMenu(
|
||||
private val context: Context,
|
||||
private val shouldShowAddTab: () -> Boolean,
|
||||
private val onItemTapped: (Item) -> Unit = {}
|
||||
) {
|
||||
sealed class Item {
|
||||
object DeleteCollection : Item()
|
||||
object AddTab : Item()
|
||||
object RenameCollection : Item()
|
||||
object OpenTabs : Item()
|
||||
}
|
||||
|
||||
val menuBuilder by lazy { BrowserMenuBuilder(menuItems) }
|
||||
|
||||
private val menuItems by lazy {
|
||||
listOf(
|
||||
SimpleBrowserMenuItem(
|
||||
context.getString(R.string.collection_open_tabs)
|
||||
) {
|
||||
onItemTapped.invoke(Item.OpenTabs)
|
||||
},
|
||||
|
||||
SimpleBrowserMenuItem(
|
||||
context.getString(R.string.collection_rename)
|
||||
) {
|
||||
onItemTapped.invoke(Item.RenameCollection)
|
||||
},
|
||||
|
||||
SimpleBrowserMenuItem(
|
||||
context.getString(R.string.add_tab)
|
||||
) {
|
||||
onItemTapped.invoke(Item.AddTab)
|
||||
}.apply { visible = shouldShowAddTab },
|
||||
|
||||
SimpleBrowserMenuItem(
|
||||
context.getString(R.string.collection_delete),
|
||||
textColorResource = ThemeManager.resolveAttribute(R.attr.textWarning, context)
|
||||
) {
|
||||
onItemTapped.invoke(Item.DeleteCollection)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Wrapper over a [TabCollection] adding information about whether it should be shown as expanded or collapsed.
|
||||
*
|
||||
* @property collection [TabCollection] to display.
|
||||
* @property isExpanded Whether the collection is expanded to show it's containing tabs or not.
|
||||
*/
|
||||
@Stable
|
||||
private data class CollectionInfo(
|
||||
val collection: TabCollection? = null,
|
||||
val isExpanded: Boolean = false,
|
||||
)
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.mozilla.fenix.components.Components
|
|||
import org.mozilla.fenix.gleanplumb.Message
|
||||
import org.mozilla.fenix.home.BottomSpacerViewHolder
|
||||
import org.mozilla.fenix.home.TopPlaceholderViewHolder
|
||||
import org.mozilla.fenix.home.collections.CollectionViewHolder
|
||||
import org.mozilla.fenix.home.pocket.PocketCategoriesViewHolder
|
||||
import org.mozilla.fenix.home.pocket.PocketRecommendationsHeaderViewHolder
|
||||
import org.mozilla.fenix.home.pocket.PocketStoriesViewHolder
|
||||
|
@ -30,7 +31,6 @@ import org.mozilla.fenix.home.recenttabs.view.RecentTabsHeaderViewHolder
|
|||
import org.mozilla.fenix.home.recentvisits.view.RecentVisitsHeaderViewHolder
|
||||
import org.mozilla.fenix.home.recentvisits.view.RecentlyVisitedViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.CustomizeHomeButtonViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.PrivateBrowsingDescriptionViewHolder
|
||||
|
@ -278,6 +278,11 @@ class SessionControlAdapter(
|
|||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
interactor = interactor
|
||||
)
|
||||
CollectionViewHolder.LAYOUT_ID -> return CollectionViewHolder(
|
||||
composeView = ComposeView(parent.context),
|
||||
viewLifecycleOwner,
|
||||
interactor = interactor
|
||||
)
|
||||
}
|
||||
|
||||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||
|
@ -288,7 +293,6 @@ class SessionControlAdapter(
|
|||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
interactor = interactor
|
||||
)
|
||||
CollectionViewHolder.LAYOUT_ID -> CollectionViewHolder(view, interactor)
|
||||
TabInCollectionViewHolder.LAYOUT_ID -> TabInCollectionViewHolder(
|
||||
view as WidgetSiteItemView,
|
||||
interactor
|
||||
|
@ -335,6 +339,11 @@ class SessionControlAdapter(
|
|||
// This View already listens and maps store updates. Avoid creating and binding new Views.
|
||||
// The composition will live until the ViewTreeLifecycleOwner to which it's attached to is destroyed.
|
||||
}
|
||||
is CollectionViewHolder -> {
|
||||
// Dispose the underlying composition immediately.
|
||||
// This ViewHolder can be removed / re-added and we need it to show a fresh new composition.
|
||||
holder.composeView.disposeComposition()
|
||||
}
|
||||
else -> super.onViewRecycled(holder)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/item_collection"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginHorizontal="@dimen/home_item_horizontal_margin"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/card_list_row_background"
|
||||
android:clickable="true"
|
||||
android:clipToPadding="false"
|
||||
android:elevation="@dimen/home_item_elevation"
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/collection_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:importantForAccessibility="no"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_tab_collection"
|
||||
app:tint="@null" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/collection_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:minLines="1"
|
||||
android:textAppearance="@style/Header14TextStyle"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/collection_icon"
|
||||
app:layout_constraintEnd_toStartOf="@id/chevron"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@+id/collection_icon"
|
||||
app:layout_constraintTop_toTopOf="@id/collection_icon"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
<ImageView
|
||||
android:id="@+id/chevron"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:srcCompat="@drawable/ic_chevron"
|
||||
android:contentDescription="@string/tab_menu"
|
||||
app:layout_constraintBottom_toBottomOf="@id/collection_icon"
|
||||
app:layout_constraintEnd_toStartOf="@+id/collection_share_button"
|
||||
app:layout_constraintStart_toEndOf="@+id/collection_title"
|
||||
app:layout_constraintTop_toTopOf="@id/collection_icon" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/collection_share_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/share_button_content_description"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/collection_icon"
|
||||
app:layout_constraintEnd_toStartOf="@id/collection_overflow_button"
|
||||
app:layout_constraintTop_toTopOf="@id/collection_icon"
|
||||
app:srcCompat="@drawable/ic_share"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/collection_overflow_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/collection_menu_button_content_description"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/collection_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/collection_icon"
|
||||
app:srcCompat="@drawable/ic_menu"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<View
|
||||
android:id="@+id/selected_border"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:alpha="0"
|
||||
android:background="@drawable/session_border"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in New Issue
Block a user