Small refactor before we add onboarding cards (#2541)
* For #2390 - Cleans up the toAdapterList method before we add onboarding * For #2514 - Hide tabs menu when no tabs are openmaster
parent
c34946b88f
commit
b3a3c94169
|
@ -22,7 +22,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.TabInCollectionViewHold
|
||||||
import java.lang.IllegalStateException
|
import java.lang.IllegalStateException
|
||||||
|
|
||||||
sealed class AdapterItem {
|
sealed class AdapterItem {
|
||||||
object TabHeader : AdapterItem()
|
data class TabHeader(val isPrivate: Boolean, val hasTabs: Boolean) : AdapterItem()
|
||||||
object NoTabMessage : AdapterItem()
|
object NoTabMessage : AdapterItem()
|
||||||
data class TabItem(val tab: Tab) : AdapterItem()
|
data class TabItem(val tab: Tab) : AdapterItem()
|
||||||
object PrivateBrowsingDescription : AdapterItem()
|
object PrivateBrowsingDescription : AdapterItem()
|
||||||
|
@ -35,7 +35,7 @@ sealed class AdapterItem {
|
||||||
|
|
||||||
val viewType: Int
|
val viewType: Int
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
TabHeader -> TabHeaderViewHolder.LAYOUT_ID
|
is TabHeader -> TabHeaderViewHolder.LAYOUT_ID
|
||||||
NoTabMessage -> NoTabMessageViewHolder.LAYOUT_ID
|
NoTabMessage -> NoTabMessageViewHolder.LAYOUT_ID
|
||||||
is TabItem -> TabViewHolder.LAYOUT_ID
|
is TabItem -> TabViewHolder.LAYOUT_ID
|
||||||
SaveTabGroup -> SaveTabGroupViewHolder.LAYOUT_ID
|
SaveTabGroup -> SaveTabGroupViewHolder.LAYOUT_ID
|
||||||
|
@ -52,7 +52,7 @@ class SessionControlAdapter(
|
||||||
private val actionEmitter: Observer<SessionControlAction>
|
private val actionEmitter: Observer<SessionControlAction>
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
var items: List<AdapterItem> = listOf()
|
private var items: List<AdapterItem> = listOf()
|
||||||
private lateinit var job: Job
|
private lateinit var job: Job
|
||||||
|
|
||||||
fun reloadData(items: List<AdapterItem>) {
|
fun reloadData(items: List<AdapterItem>) {
|
||||||
|
@ -69,15 +69,10 @@ class SessionControlAdapter(
|
||||||
NoTabMessageViewHolder.LAYOUT_ID -> NoTabMessageViewHolder(view)
|
NoTabMessageViewHolder.LAYOUT_ID -> NoTabMessageViewHolder(view)
|
||||||
TabViewHolder.LAYOUT_ID -> TabViewHolder(view, actionEmitter, job)
|
TabViewHolder.LAYOUT_ID -> TabViewHolder(view, actionEmitter, job)
|
||||||
SaveTabGroupViewHolder.LAYOUT_ID -> SaveTabGroupViewHolder(view, actionEmitter)
|
SaveTabGroupViewHolder.LAYOUT_ID -> SaveTabGroupViewHolder(view, actionEmitter)
|
||||||
PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> PrivateBrowsingDescriptionViewHolder(
|
PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> PrivateBrowsingDescriptionViewHolder(view, actionEmitter)
|
||||||
view,
|
|
||||||
actionEmitter
|
|
||||||
)
|
|
||||||
DeleteTabsViewHolder.LAYOUT_ID -> DeleteTabsViewHolder(view, actionEmitter)
|
DeleteTabsViewHolder.LAYOUT_ID -> DeleteTabsViewHolder(view, actionEmitter)
|
||||||
CollectionHeaderViewHolder.LAYOUT_ID -> CollectionHeaderViewHolder(view)
|
CollectionHeaderViewHolder.LAYOUT_ID -> CollectionHeaderViewHolder(view)
|
||||||
NoCollectionMessageViewHolder.LAYOUT_ID -> NoCollectionMessageViewHolder(
|
NoCollectionMessageViewHolder.LAYOUT_ID -> NoCollectionMessageViewHolder(view)
|
||||||
view
|
|
||||||
)
|
|
||||||
CollectionViewHolder.LAYOUT_ID -> CollectionViewHolder(view, actionEmitter, job)
|
CollectionViewHolder.LAYOUT_ID -> CollectionViewHolder(view, actionEmitter, job)
|
||||||
TabInCollectionViewHolder.LAYOUT_ID -> TabInCollectionViewHolder(view, actionEmitter, job)
|
TabInCollectionViewHolder.LAYOUT_ID -> TabInCollectionViewHolder(view, actionEmitter, job)
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
|
@ -100,6 +95,10 @@ class SessionControlAdapter(
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
when (holder) {
|
when (holder) {
|
||||||
|
is TabHeaderViewHolder -> {
|
||||||
|
val tabHeader = items[position] as AdapterItem.TabHeader
|
||||||
|
holder.bind(tabHeader.isPrivate, tabHeader.hasTabs)
|
||||||
|
}
|
||||||
is TabViewHolder -> holder.bindSession(
|
is TabViewHolder -> holder.bindSession(
|
||||||
(items[position] as AdapterItem.TabItem).tab
|
(items[position] as AdapterItem.TabItem).tab
|
||||||
)
|
)
|
||||||
|
@ -107,7 +106,7 @@ class SessionControlAdapter(
|
||||||
(items[position] as AdapterItem.CollectionItem).collection
|
(items[position] as AdapterItem.CollectionItem).collection
|
||||||
)
|
)
|
||||||
is TabInCollectionViewHolder -> {
|
is TabInCollectionViewHolder -> {
|
||||||
val item = (items[position] as AdapterItem.TabInCollectionItem)
|
val item = items[position] as AdapterItem.TabInCollectionItem
|
||||||
holder.bindSession(item.collection, item.tab, item.isLastTab)
|
holder.bindSession(item.collection, item.tab, item.isLastTab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,9 +58,16 @@ data class TabCollection(
|
||||||
var expanded: Boolean = false
|
var expanded: Boolean = false
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
sealed class FirefoxAcocuntsState {
|
||||||
|
object SignedOut : FirefoxAcocuntsState()
|
||||||
|
data class AutoSignedIn(val email: String) : FirefoxAcocuntsState()
|
||||||
|
data class SignedIn(val email: String) : FirefoxAcocuntsState()
|
||||||
|
}
|
||||||
|
|
||||||
sealed class Mode {
|
sealed class Mode {
|
||||||
object Normal : Mode()
|
object Normal : Mode()
|
||||||
object Private : Mode()
|
object Private : Mode()
|
||||||
|
data class Onboarding(val firefoxAccountsState: FirefoxAcocuntsState) : Mode()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SessionControlState(
|
data class SessionControlState(
|
||||||
|
@ -110,12 +117,9 @@ sealed class SessionControlChange : Change {
|
||||||
data class CollectionsChange(val collections: List<TabCollection>) : SessionControlChange()
|
data class CollectionsChange(val collections: List<TabCollection>) : SessionControlChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
class SessionControlViewModel(initialState: SessionControlState) :
|
class SessionControlViewModel(
|
||||||
UIComponentViewModelBase<SessionControlState, SessionControlChange>(
|
initialState: SessionControlState
|
||||||
initialState,
|
) : UIComponentViewModelBase<SessionControlState, SessionControlChange>(initialState, reducer) {
|
||||||
reducer
|
|
||||||
) {
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val reducer: (SessionControlState, SessionControlChange) -> SessionControlState = { state, change ->
|
val reducer: (SessionControlState, SessionControlChange) -> SessionControlState = { state, change ->
|
||||||
when (change) {
|
when (change) {
|
||||||
|
|
|
@ -16,60 +16,60 @@ import org.mozilla.fenix.mvi.UIView
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import org.mozilla.fenix.BuildConfig
|
import org.mozilla.fenix.BuildConfig
|
||||||
|
|
||||||
// Convert HomeState into a data structure HomeAdapter understands
|
private fun normalModeAdapterItems(tabs: List<Tab>, collections: List<TabCollection>): List<AdapterItem> {
|
||||||
@SuppressWarnings("ComplexMethod", "NestedBlockDepth")
|
|
||||||
private fun SessionControlState.toAdapterList(): List<AdapterItem> {
|
|
||||||
val items = mutableListOf<AdapterItem>()
|
val items = mutableListOf<AdapterItem>()
|
||||||
items.add(AdapterItem.TabHeader)
|
items.add(AdapterItem.TabHeader(false, tabs.isNotEmpty()))
|
||||||
|
|
||||||
// Populate tabs
|
|
||||||
if (tabs.isNotEmpty()) {
|
if (tabs.isNotEmpty()) {
|
||||||
tabs.reversed().map(AdapterItem::TabItem).forEach { items.add(it) }
|
items.addAll(tabs.reversed().map(AdapterItem::TabItem))
|
||||||
if (mode == Mode.Private) {
|
if (BuildConfig.COLLECTIONS_ENABLED) {
|
||||||
items.add(AdapterItem.DeleteTabs)
|
|
||||||
} else if (BuildConfig.COLLECTIONS_ENABLED) {
|
|
||||||
items.add(AdapterItem.SaveTabGroup)
|
items.add(AdapterItem.SaveTabGroup)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val item = if (mode == Mode.Private) AdapterItem.PrivateBrowsingDescription
|
items.add(AdapterItem.NoTabMessage)
|
||||||
else AdapterItem.NoTabMessage
|
|
||||||
|
|
||||||
items.add(item)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate collections
|
items.add(AdapterItem.CollectionHeader)
|
||||||
if (mode == Mode.Normal) {
|
if (collections.isNotEmpty()) {
|
||||||
items.add(AdapterItem.CollectionHeader)
|
|
||||||
if (collections.isNotEmpty()) {
|
|
||||||
|
|
||||||
// If the collection is expanded, we want to add all of its tabs beneath it in the adapter
|
// If the collection is expanded, we want to add all of its tabs beneath it in the adapter
|
||||||
collections.reversed().map(AdapterItem::CollectionItem).forEach {
|
collections.reversed().map(AdapterItem::CollectionItem).forEach {
|
||||||
if (it.collection.expanded) {
|
items.add(it)
|
||||||
items.add(it)
|
if (it.collection.expanded) {
|
||||||
addCollectionTabItems(it.collection, it.collection.tabs, items)
|
items.addAll(collectionTabItems(it.collection))
|
||||||
} else {
|
|
||||||
items.add(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
items.add(AdapterItem.NoCollectionMessage)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
items.add(AdapterItem.NoCollectionMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addCollectionTabItems(
|
private fun privateModeAdapterItems(tabs: List<Tab>): List<AdapterItem> {
|
||||||
collection: TabCollection,
|
val items = mutableListOf<AdapterItem>()
|
||||||
tabs: MutableList<Tab>,
|
items.add(AdapterItem.TabHeader(true, tabs.isNotEmpty()))
|
||||||
itemList: MutableList<AdapterItem>
|
|
||||||
) {
|
if (tabs.isNotEmpty()) {
|
||||||
for (tabIndex in 0 until tabs.size) {
|
items.addAll(tabs.reversed().map(AdapterItem::TabItem))
|
||||||
itemList.add(AdapterItem.TabInCollectionItem
|
items.add(AdapterItem.DeleteTabs)
|
||||||
(collection, collection.tabs[tabIndex], tabIndex == collection.tabs.size - 1))
|
} else {
|
||||||
|
items.add(AdapterItem.PrivateBrowsingDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun SessionControlState.toAdapterList(): List<AdapterItem> = when (mode) {
|
||||||
|
is Mode.Normal -> normalModeAdapterItems(tabs, collections)
|
||||||
|
is Mode.Private -> privateModeAdapterItems(tabs)
|
||||||
|
is Mode.Onboarding -> listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectionTabItems(collection: TabCollection) = collection.tabs.mapIndexed { index, tab ->
|
||||||
|
AdapterItem.TabInCollectionItem(collection, tab, index == collection.tabs.lastIndex)
|
||||||
|
}
|
||||||
|
|
||||||
class SessionControlUIView(
|
class SessionControlUIView(
|
||||||
container: ViewGroup,
|
container: ViewGroup,
|
||||||
actionEmitter: Observer<SessionControlAction>,
|
actionEmitter: Observer<SessionControlAction>,
|
||||||
|
|
|
@ -19,14 +19,14 @@ import org.mozilla.fenix.home.sessioncontrol.TabAction
|
||||||
import org.mozilla.fenix.home.sessioncontrol.onNext
|
import org.mozilla.fenix.home.sessioncontrol.onNext
|
||||||
|
|
||||||
class TabHeaderViewHolder(
|
class TabHeaderViewHolder(
|
||||||
view: View,
|
private val view: View,
|
||||||
private val actionEmitter: Observer<SessionControlAction>
|
private val actionEmitter: Observer<SessionControlAction>
|
||||||
) : RecyclerView.ViewHolder(view) {
|
) : RecyclerView.ViewHolder(view) {
|
||||||
private var isPrivate = false
|
private var isPrivate = false
|
||||||
private var tabsMenu: TabHeaderMenu
|
private var tabsMenu: TabHeaderMenu
|
||||||
|
|
||||||
init {
|
init {
|
||||||
tabsMenu = TabHeaderMenu(view.context) {
|
tabsMenu = TabHeaderMenu(view.context, isPrivate) {
|
||||||
when (it) {
|
when (it) {
|
||||||
is TabHeaderMenu.Item.Share -> actionEmitter.onNext(TabAction.ShareTabs)
|
is TabHeaderMenu.Item.Share -> actionEmitter.onNext(TabAction.ShareTabs)
|
||||||
is TabHeaderMenu.Item.CloseAll -> actionEmitter.onNext(TabAction.CloseAll(isPrivate))
|
is TabHeaderMenu.Item.CloseAll -> actionEmitter.onNext(TabAction.CloseAll(isPrivate))
|
||||||
|
@ -37,11 +37,8 @@ class TabHeaderViewHolder(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
view.apply {
|
|
||||||
val headerTextResourceId =
|
|
||||||
if (isPrivate) R.string.tabs_header_private_title else R.string.tab_header_label
|
|
||||||
header_text.text = context.getString(headerTextResourceId)
|
|
||||||
|
|
||||||
|
view.apply {
|
||||||
add_tab_button.run {
|
add_tab_button.run {
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
actionEmitter.onNext(TabAction.Add)
|
actionEmitter.onNext(TabAction.Add)
|
||||||
|
@ -58,8 +55,19 @@ class TabHeaderViewHolder(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun bind(isPrivate: Boolean, hasTabs: Boolean) {
|
||||||
|
this.isPrivate = isPrivate
|
||||||
|
tabsMenu.isPrivate = isPrivate
|
||||||
|
|
||||||
|
val headerTextResourceId =
|
||||||
|
if (isPrivate) R.string.tabs_header_private_title else R.string.tab_header_label
|
||||||
|
view.header_text.text = view.context.getString(headerTextResourceId)
|
||||||
|
view.tabs_overflow_button.visibility = if (hasTabs) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
class TabHeaderMenu(
|
class TabHeaderMenu(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
|
var isPrivate: Boolean,
|
||||||
private val onItemTapped: (Item) -> Unit = {}
|
private val onItemTapped: (Item) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
sealed class Item {
|
sealed class Item {
|
||||||
|
@ -81,20 +89,13 @@ class TabHeaderViewHolder(
|
||||||
context.getString(R.string.tabs_menu_share_tabs)
|
context.getString(R.string.tabs_menu_share_tabs)
|
||||||
) {
|
) {
|
||||||
onItemTapped.invoke(Item.Share)
|
onItemTapped.invoke(Item.Share)
|
||||||
}
|
},
|
||||||
).let {
|
SimpleBrowserMenuItem(
|
||||||
val list = it.toMutableList()
|
context.getString(R.string.tabs_menu_save_to_collection)
|
||||||
if (BuildConfig.COLLECTIONS_ENABLED) {
|
) {
|
||||||
list.add(
|
onItemTapped.invoke(Item.SaveToCollection)
|
||||||
SimpleBrowserMenuItem(
|
}.apply { visible = { !isPrivate && BuildConfig.COLLECTIONS_ENABLED } }
|
||||||
context.getString(R.string.tabs_menu_save_to_collection)
|
)
|
||||||
) {
|
|
||||||
onItemTapped.invoke(Item.SaveToCollection)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
list
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,6 @@ class TabInCollectionViewHolder(
|
||||||
var isLastTab = false
|
var isLastTab = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
||||||
collection_tab_icon.clipToOutline = true
|
collection_tab_icon.clipToOutline = true
|
||||||
collection_tab_icon.outlineProvider = object : ViewOutlineProvider() {
|
collection_tab_icon.outlineProvider = object : ViewOutlineProvider() {
|
||||||
override fun getOutline(view: View?, outline: Outline?) {
|
override fun getOutline(view: View?, outline: Outline?) {
|
||||||
|
|
|
@ -21,27 +21,30 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<ImageButton
|
<LinearLayout
|
||||||
android:id="@+id/add_tab_button"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="20dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="20dp"
|
android:orientation="horizontal"
|
||||||
android:layout_marginEnd="30dp"
|
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/add_tab"
|
|
||||||
android:src="@drawable/ic_new"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/tabs_overflow_button"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/tabs_overflow_button"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_marginEnd="4.5dp"
|
android:layout_marginEnd="4.5dp"
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/open_tabs_menu"
|
|
||||||
android:src="@drawable/ic_menu"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/add_tab_button"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/add_tab"
|
||||||
|
android:src="@drawable/ic_new"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/tabs_overflow_button"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/open_tabs_menu"
|
||||||
|
android:src="@drawable/ic_menu" />
|
||||||
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in New Issue