Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
For #19933 - Show a media tab item on homescreen for the last tab wit…
Browse files Browse the repository at this point in the history
…h media
  • Loading branch information
Mugurell committed Jul 15, 2021
1 parent 68b56ff commit 1251894
Show file tree
Hide file tree
Showing 12 changed files with 671 additions and 70 deletions.
4 changes: 3 additions & 1 deletion app/src/main/java/org/mozilla/fenix/components/Core.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
import mozilla.components.feature.downloads.DownloadMiddleware
import mozilla.components.feature.logins.exceptions.LoginExceptionStorage
import mozilla.components.feature.media.MediaSessionFeature
import mozilla.components.feature.media.middleware.LastMediaAccessMiddleware
import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
import mozilla.components.feature.prompts.PromptMiddleware
import mozilla.components.feature.pwa.ManifestStorage
Expand Down Expand Up @@ -208,7 +209,8 @@ class Core(
),
RecordingDevicesMiddleware(context),
PromptMiddleware(),
AdsTelemetryMiddleware(adsTelemetry)
AdsTelemetryMiddleware(adsTelemetry),
LastMediaAccessMiddleware()
)

if (FeatureFlags.historyMetadataFeature) {
Expand Down
37 changes: 28 additions & 9 deletions app/src/main/java/org/mozilla/fenix/ext/BrowserState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,40 @@

package org.mozilla.fenix.ext

import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.selector.selectedNormalTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState

/**
* Returns the currently selected tab if there's one as a list.
* Get the last opened normal tab and the last tab with in progress media, if available.
*
* @return A list of the currently selected tab or an empty list.
* @return A list of the last opened tab and the last tab with in progress media
* if distinct and available or an empty list.
*/
fun BrowserState.asRecentTabs(): List<TabSessionState> {
val tab = selectedTab

return if (tab != null && !tab.content.private) {
listOfNotNull(tab)
} else {
emptyList()
return mutableListOf<TabSessionState>().apply {
val lastOpenedNormalTab = lastOpenedNormalTab
lastOpenedNormalTab?.let { add(it) }
inProgressMediaTab
?.takeUnless { it == lastOpenedNormalTab }
?.let {
add(it)
}
}
}

/**
* Get the selected normal tab or the last accessed normal tab
* if there is no selected tab or the selected tab is a private one.
*/
val BrowserState.lastOpenedNormalTab: TabSessionState?
get() = selectedNormalTab ?: normalTabs.maxByOrNull { it.lastAccess }

/**
* Get the last tab with in progress media.
*/
val BrowserState.inProgressMediaTab: TabSessionState?
get() = normalTabs
.filter { it.lastMediaAccess > 0 }
.maxByOrNull { it.lastMediaAccess }
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ package org.mozilla.fenix.home.recenttabs
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.selector.selectedNormalTab
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.state.helpers.AbstractBinding
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.ext.asRecentTabs
import org.mozilla.fenix.ext.lastOpenedNormalTab
import org.mozilla.fenix.home.HomeFragmentAction
import org.mozilla.fenix.home.HomeFragmentStore

Expand All @@ -29,20 +28,20 @@ class RecentTabsListFeature(
) : AbstractBinding<BrowserState>(browserStore) {

override suspend fun onState(flow: Flow<BrowserState>) {
flow.map { it.selectedTab }
.ifAnyChanged { arrayOf(it?.id, it?.content?.title, it?.content?.icon) }
.collect { _ ->
// Attempt to get the selected normal tab or the last accessed normal tab
// if there is no selected tab or the selected tab is a private one.
val selectedTab = browserStore.state.selectedNormalTab
?: browserStore.state.normalTabs.maxByOrNull { it.lastAccess }
val recentTabsList = if (selectedTab != null) {
listOf(selectedTab)
} else {
emptyList()
}

homeStore.dispatch(HomeFragmentAction.RecentTabsChange(recentTabsList))
flow
// Listen for changes regarding the currently selected tab and the in progress media tab
// and also for changes (close, undo) in normal tabs that could involve these.
.ifAnyChanged {
val lastOpenedNormalTab = it.lastOpenedNormalTab
arrayOf(
lastOpenedNormalTab?.id,
lastOpenedNormalTab?.content?.title,
lastOpenedNormalTab?.content?.icon,
it.normalTabs
)
}
.collect {
homeStore.dispatch(HomeFragmentAction.RecentTabsChange(browserStore.state.asRecentTabs()))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/* 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.recenttabs.view

import android.view.View
import android.view.ViewGroup
import androidx.appcompat.content.res.AppCompatResources
import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.util.dpToPx
import org.mozilla.fenix.R

private const val TOP_MARGIN_DP = 1

/**
* All possible positions of a recent tab in relation to others when shown in the "Jump back in" section.
*/
enum class RecentTabsItemPosition {
/**
* This is the only tab to be shown in this section.
*/
SINGLE,

/**
* This item is to be shown at the top of the section with others below it.
*/
TOP,

/**
* This item is to be shown between others in this section.
*/
MIDDLE,

/**
* This item is to be shown at the bottom of the section with others above it.
*/
BOTTOM
}

/**
* Helpers for setting various layout properties for the view from a [RecentTabViewHolder].
*
* Depending on the provided [RecentTabsItemPosition]:
* - sets a different background so that the entire section possibly containing
* more such items would have rounded corners but sibling items not.
* - sets small margins for the items so that there's a clear separation between siblings
*/
sealed class RecentTabViewDecorator {
/**
* Apply the decoration to [itemView].
*/
abstract operator fun invoke(itemView: View): View

companion object {
/**
* Get the appropriate decorator to set view background / margins depending on the position
* of that view in the recent tabs section.
*/
fun forPosition(position: RecentTabsItemPosition) = when (position) {
RecentTabsItemPosition.SINGLE -> SingleTabDecoration
RecentTabsItemPosition.TOP -> TopTabDecoration
RecentTabsItemPosition.MIDDLE -> MiddleTabDecoration
RecentTabsItemPosition.BOTTOM -> BottomTabDecoration
}
}

/**
* Decorator for a view shown in the recent tabs section that will update it to express
* that that item is the single one shown in this section.
*/
object SingleTabDecoration : RecentTabViewDecorator() {
override fun invoke(itemView: View): View {
val context = itemView.context

itemView.background =
AppCompatResources.getDrawable(context, R.drawable.home_list_row_background)

return itemView
}
}

/**
* Decorator for a view shown in the recent tabs section that will update it to express
* that this is an item shown at the top of the section and there are others below it.
*/
object TopTabDecoration : RecentTabViewDecorator() {
override fun invoke(itemView: View): View {
val context = itemView.context

itemView.background =
AppCompatResources.getDrawable(context, R.drawable.rounded_top_corners)

return itemView
}
}

/**
* Decorator for a view shown in the recent tabs section that will update it to express
* that this is an item shown has other recents tabs to be shown on top or below it.
*/
object MiddleTabDecoration : RecentTabViewDecorator() {
override fun invoke(itemView: View): View {
val context = itemView.context

itemView.setBackgroundColor(context.getColorFromAttr(R.attr.above))

(itemView.layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin =
TOP_MARGIN_DP.dpToPx(context.resources.displayMetrics)

return itemView
}
}

/**
* Decorator for a view shown in the recent tabs section that will update it to express
* that this is an item shown at the bottom of the section and there are others above it.
*/
object BottomTabDecoration : RecentTabViewDecorator() {
override fun invoke(itemView: View): View {
val context = itemView.context

itemView.background =
AppCompatResources.getDrawable(context, R.drawable.rounded_bottom_corners)

(itemView.layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin =
TOP_MARGIN_DP.dpToPx(context.resources.displayMetrics)

return itemView
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class RecentTabViewHolder(
private val icons: BrowserIcons = view.context.components.core.icons
) : ViewHolder(view) {

fun bindTab(tab: TabSessionState) {
fun bindTab(tab: TabSessionState): View {
// A page may take a while to retrieve a title, so let's show the url until we get one.
recent_tab_title.text = if (tab.content.title.isNotEmpty()) {
tab.content.title
Expand All @@ -46,6 +46,8 @@ class RecentTabViewHolder(
itemView.setOnClickListener {
interactor.onRecentTabClicked(tab.id)
}

return itemView
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import mozilla.components.ui.widgets.WidgetSiteItemView
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.components.tips.Tip
import org.mozilla.fenix.home.OnboardingState
import org.mozilla.fenix.home.recenttabs.view.RecentTabViewDecorator
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageViewHolder
Expand All @@ -41,6 +42,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingWh
import org.mozilla.fenix.home.recenttabs.view.RecentTabViewHolder
import org.mozilla.fenix.home.recenttabs.view.RecentTabsHeaderViewHolder
import org.mozilla.fenix.home.recentbookmarks.view.RecentBookmarksViewHolder
import org.mozilla.fenix.home.recenttabs.view.RecentTabsItemPosition
import org.mozilla.fenix.home.tips.ButtonTipViewHolder
import mozilla.components.feature.tab.collections.Tab as ComponentTab

Expand Down Expand Up @@ -141,8 +143,12 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
object OnboardingWhatsNew : AdapterItem(OnboardingWhatsNewViewHolder.LAYOUT_ID)

object RecentTabsHeader : AdapterItem(RecentTabsHeaderViewHolder.LAYOUT_ID)
data class RecentTabItem(val tab: TabSessionState) : AdapterItem(RecentTabViewHolder.LAYOUT_ID) {
override fun sameAs(other: AdapterItem) = other is RecentTabItem && tab.id == other.tab.id
data class RecentTabItem(
val tab: TabSessionState,
val position: RecentTabsItemPosition
) : AdapterItem(RecentTabViewHolder.LAYOUT_ID) {
override fun sameAs(other: AdapterItem) = other is RecentTabItem && tab.id == other.tab.id &&
position == other.position

override fun contentsSameAs(other: AdapterItem): Boolean {
val otherItem = other as RecentTabItem
Expand Down Expand Up @@ -316,7 +322,10 @@ class SessionControlAdapter(
(item as AdapterItem.OnboardingAutomaticSignIn).state.withAccount
)
is RecentTabViewHolder -> {
holder.bindTab((item as AdapterItem.RecentTabItem).tab)
val (tab, tabPosition) = item as AdapterItem.RecentTabItem
holder.bindTab(tab).apply {
RecentTabViewDecorator.forPosition(tabPosition).invoke(this)
}
}
is RecentBookmarksViewHolder -> {
holder.bind(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package org.mozilla.fenix.home.sessioncontrol

import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
Expand All @@ -20,6 +21,7 @@ import org.mozilla.fenix.home.HomeFragmentState
import org.mozilla.fenix.home.HomeScreenViewModel
import org.mozilla.fenix.home.Mode
import org.mozilla.fenix.home.OnboardingState
import org.mozilla.fenix.home.recenttabs.view.RecentTabsItemPosition

// This method got a little complex with the addition of the tab tray feature flag
// When we remove the tabs from the home screen this will get much simpler again.
Expand Down Expand Up @@ -65,13 +67,42 @@ private fun normalModeAdapterItems(
return items
}

private fun showRecentTabs(
/**
* Constructs the list of items to be shown in the recent tabs section.
*
* This section's structure is:
* - section header
* - one or more normal tabs
* - zero or one media tab (if there is a tab opened on which media started playing.
* This may be a duplicate of one of the normal tabs shown above).
*/
@VisibleForTesting
internal fun showRecentTabs(
recentTabs: List<TabSessionState>,
items: MutableList<AdapterItem>
) {
items.add(AdapterItem.RecentTabsHeader)
recentTabs.forEach {
items.add(AdapterItem.RecentTabItem(it))

recentTabs.forEachIndexed { index, recentTab ->
// If this is the first tab to be shown but more will follow.
if (index == 0 && recentTabs.size > 1) {
items.add(AdapterItem.RecentTabItem(recentTab, RecentTabsItemPosition.TOP))
}

// if this is the only tab to be shown.
else if (index == 0 && recentTabs.size == 1) {
items.add(AdapterItem.RecentTabItem(recentTab, RecentTabsItemPosition.SINGLE))
}

// If there are items above and below.
else if (index < recentTabs.size - 1) {
items.add(AdapterItem.RecentTabItem(recentTab, RecentTabsItemPosition.MIDDLE))
}

// If this is the last recent tab to be shown.
else if (index < recentTabs.size) {
items.add(AdapterItem.RecentTabItem(recentTab, RecentTabsItemPosition.BOTTOM))
}
}
}

Expand Down
1 change: 0 additions & 1 deletion app/src/main/res/layout/recent_tabs_list_row.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/home_list_row_background"
android:clipToPadding="false"
android:elevation="@dimen/home_item_elevation"
android:foreground="?android:attr/selectableItemBackground">
Expand Down
Loading

0 comments on commit 1251894

Please sign in to comment.