[fenix] For https://github.com/mozilla-mobile/fenix/issues/24333 - Replace the xml based CollectionViewHolder with a composable

pull/600/head
Mugurell 2 years ago committed by mergify[bot]
parent d92adcff12
commit 7185eee3df

@ -21,9 +21,10 @@ import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry import mozilla.components.support.base.observer.ObserverRegistry
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
import org.mozilla.fenix.perf.StrictModeManager import org.mozilla.fenix.perf.StrictModeManager
private const val COLLECTION_MAX_TITLE_LENGTH = 20
class TabCollectionStorage( class TabCollectionStorage(
private val context: Context, private val context: Context,
strictMode: StrictModeManager, strictMode: StrictModeManager,
@ -106,10 +107,10 @@ fun TabCollection.description(context: Context): String {
return this.tabs return this.tabs
.map { it.url.toShortUrl(context.components.publicSuffixList) } .map { it.url.toShortUrl(context.components.publicSuffixList) }
.map { .map {
if (it.length > CollectionViewHolder.maxTitleLength) { if (it.length > COLLECTION_MAX_TITLE_LENGTH) {
it.substring( it.substring(
0, 0,
CollectionViewHolder.maxTitleLength COLLECTION_MAX_TITLE_LENGTH
) + "" ) + ""
} else { } else {
it 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 * 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/. */ * 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 android.view.View
import androidx.core.graphics.BlendModeColorFilterCompat.createBlendModeColorFilterCompat import androidx.compose.foundation.layout.Column
import androidx.core.graphics.BlendModeCompat.SRC_IN import androidx.compose.foundation.layout.Spacer
import mozilla.components.browser.menu.BrowserMenuBuilder import androidx.compose.foundation.layout.height
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem import androidx.compose.runtime.Composable
import mozilla.components.browser.state.selector.normalTabs 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 mozilla.components.feature.tab.collections.TabCollection
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.CollectionHomeListRowBinding import org.mozilla.fenix.compose.ComposeViewHolder
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.home.sessioncontrol.CollectionInteractor 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( class CollectionViewHolder(
view: View, composeView: ComposeView,
val interactor: CollectionInteractor viewLifecycleOwner: LifecycleOwner,
) : ViewHolder(view) { private val interactor: CollectionInteractor,
) : ComposeViewHolder(composeView, viewLifecycleOwner) {
private lateinit var collection: TabCollection private var collectionData = CollectionInfo()
private var expanded = false
private var collectionMenu: CollectionItemMenu
private var binding: CollectionHomeListRowBinding
init { init {
binding = CollectionHomeListRowBinding.bind(view) val horizontalPadding =
composeView.resources.getDimensionPixelSize(R.dimen.home_item_horizontal_margin)
composeView.setPadding(horizontalPadding, 0, horizontalPadding, 0)
}
collectionMenu = CollectionItemMenu( @Composable
view.context, override fun Content() {
{ view.context.components.core.store.state.normalTabs.isNotEmpty() } val collectionInfo by remember { mutableStateOf(collectionData) }
) {
when (it) { collectionInfo.collection?.let { collection ->
is CollectionItemMenu.Item.DeleteCollection -> interactor.onDeleteCollectionTapped(collection) val menuItems = getMenuItems(
is CollectionItemMenu.Item.AddTab -> interactor.onCollectionAddTabTapped(collection) collection = collection,
is CollectionItemMenu.Item.RenameCollection -> interactor.onRenameCollectionTapped(collection) onOpenTabsTapped = interactor::onCollectionOpenTabsTapped,
is CollectionItemMenu.Item.OpenTabs -> interactor.onCollectionOpenTabsTapped(collection) 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) { fun bindSession(collection: TabCollection, expanded: Boolean) {
this.collection = collection collectionData = CollectionInfo(
this.expanded = expanded collection = collection,
updateCollectionUI() isExpanded = expanded
}
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
) )
} }
companion object { companion object {
const val buttonIncreaseDps = 16 val LAYOUT_ID = View.generateViewId()
const val LAYOUT_ID = R.layout.collection_home_list_row
const val maxTitleLength = 20
} }
} }
class CollectionItemMenu( /**
private val context: Context, * Wrapper over a [TabCollection] adding information about whether it should be shown as expanded or collapsed.
private val shouldShowAddTab: () -> Boolean, *
private val onItemTapped: (Item) -> Unit = {} * @property collection [TabCollection] to display.
) { * @property isExpanded Whether the collection is expanded to show it's containing tabs or not.
sealed class Item { */
object DeleteCollection : Item() @Stable
object AddTab : Item() private data class CollectionInfo(
object RenameCollection : Item() val collection: TabCollection? = null,
object OpenTabs : Item() val isExpanded: Boolean = false,
} )
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)
}
)
}
}

@ -20,6 +20,7 @@ import org.mozilla.fenix.components.Components
import org.mozilla.fenix.gleanplumb.Message import org.mozilla.fenix.gleanplumb.Message
import org.mozilla.fenix.home.BottomSpacerViewHolder import org.mozilla.fenix.home.BottomSpacerViewHolder
import org.mozilla.fenix.home.TopPlaceholderViewHolder 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.PocketCategoriesViewHolder
import org.mozilla.fenix.home.pocket.PocketRecommendationsHeaderViewHolder import org.mozilla.fenix.home.pocket.PocketRecommendationsHeaderViewHolder
import org.mozilla.fenix.home.pocket.PocketStoriesViewHolder 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.RecentVisitsHeaderViewHolder
import org.mozilla.fenix.home.recentvisits.view.RecentlyVisitedViewHolder import org.mozilla.fenix.home.recentvisits.view.RecentlyVisitedViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHolder 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.CustomizeHomeButtonViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.PrivateBrowsingDescriptionViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.PrivateBrowsingDescriptionViewHolder
@ -278,6 +278,11 @@ class SessionControlAdapter(
viewLifecycleOwner = viewLifecycleOwner, viewLifecycleOwner = viewLifecycleOwner,
interactor = interactor interactor = interactor
) )
CollectionViewHolder.LAYOUT_ID -> return CollectionViewHolder(
composeView = ComposeView(parent.context),
viewLifecycleOwner,
interactor = interactor
)
} }
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
@ -288,7 +293,6 @@ class SessionControlAdapter(
viewLifecycleOwner = viewLifecycleOwner, viewLifecycleOwner = viewLifecycleOwner,
interactor = interactor interactor = interactor
) )
CollectionViewHolder.LAYOUT_ID -> CollectionViewHolder(view, interactor)
TabInCollectionViewHolder.LAYOUT_ID -> TabInCollectionViewHolder( TabInCollectionViewHolder.LAYOUT_ID -> TabInCollectionViewHolder(
view as WidgetSiteItemView, view as WidgetSiteItemView,
interactor interactor
@ -335,6 +339,11 @@ class SessionControlAdapter(
// This View already listens and maps store updates. Avoid creating and binding new Views. // 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. // 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) 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…
Cancel
Save