Skip to content

Commit

Permalink
For mozilla-mobile#1114: Show playing tab
Browse files Browse the repository at this point in the history
  • Loading branch information
sblatz committed Sep 11, 2019
1 parent 2e2bac4 commit aa9661b
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 47 deletions.
7 changes: 5 additions & 2 deletions app/src/main/java/org/mozilla/fenix/ext/Session.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ package org.mozilla.fenix.ext

import android.content.Context
import mozilla.components.browser.session.Session
import mozilla.components.feature.media.state.MediaState
import org.mozilla.fenix.home.sessioncontrol.Tab

fun Session.toTab(context: Context, selected: Boolean? = null): Tab {
fun Session.toTab(context: Context, selected: Boolean? = null, mediaState: MediaState? = null): Tab {
return Tab(
this.id,
this.url,
this.url.urlToTrimmedHost(context),
this.title,
selected)
selected,
mediaState
)
}
34 changes: 30 additions & 4 deletions app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile
import mozilla.components.feature.media.ext.getSession
import mozilla.components.feature.media.ext.pauseIfPlaying
import mozilla.components.feature.media.ext.playIfPaused
import mozilla.components.feature.media.state.MediaState
import mozilla.components.feature.media.state.MediaStateMachine
import mozilla.components.feature.tab.collections.TabCollection
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.BOTTOM
import org.jetbrains.anko.constraint.layout.ConstraintSetBuilder.Side.END
Expand Down Expand Up @@ -144,6 +149,7 @@ class HomeFragment : Fragment(), AccountObserver {
val sessionObserver = BrowserSessionsObserver(sessionManager, singleSessionObserver) {
emitSessionChanges()
}

lifecycle.addObserver(sessionObserver)

if (!onboarding.userHasBeenOnboarded()) {
Expand Down Expand Up @@ -372,6 +378,12 @@ class HomeFragment : Fragment(), AccountObserver {
share(session.url)
}
}
is TabAction.PauseMedia -> {
MediaStateMachine.state.pauseIfPlaying()
}
is TabAction.PlayMedia -> {
MediaStateMachine.state.playIfPaused()
}
is TabAction.CloseAll -> {
if (pendingSessionDeletion?.deletionJob == null) {
removeAllTabsWithUndo(
Expand Down Expand Up @@ -926,7 +938,17 @@ class HomeFragment : Fragment(), AccountObserver {

private fun List<Session>.toTabs(): List<Tab> {
val selected = sessionManager.selectedSession
return this.map { it.toTab(requireContext(), it == selected) }
val mediaStateSession = MediaStateMachine.state.getSession()

return this.map {
val mediaState = if (mediaStateSession?.id == it.id) {
MediaStateMachine.state
} else {
null
}

it.toTab(requireContext(), it == selected, mediaState)
}
}

companion object {
Expand All @@ -935,8 +957,6 @@ class HomeFragment : Fragment(), AccountObserver {
private const val ANIM_ON_SCREEN_DELAY = 200L
private const val FADE_ANIM_DURATION = 150L
private const val ANIM_SNACKBAR_DELAY = 100L
private const val ACCESSIBILITY_FOCUS_DELAY = 2000L
private const val TELEMETRY_HOME_IDENITIFIER = "home"
private const val SHARED_TRANSITION_MS = 200L
private const val TAB_ITEM_TRANSITION_NAME = "tab_item"
private const val CFR_WIDTH_DIVIDER = 1.7
Expand Down Expand Up @@ -966,6 +986,7 @@ private class BrowserSessionsObserver(
*/
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart() {
MediaStateMachine.register(managerObserver)
manager.register(managerObserver)
subscribeToAll()
}
Expand All @@ -975,6 +996,7 @@ private class BrowserSessionsObserver(
*/
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStop() {
MediaStateMachine.unregister(managerObserver)
manager.unregister(managerObserver)
unsubscribeFromAll()
}
Expand All @@ -995,7 +1017,11 @@ private class BrowserSessionsObserver(
session.unregister(observer)
}

private val managerObserver = object : SessionManager.Observer {
private val managerObserver = object : SessionManager.Observer, MediaStateMachine.Observer {
override fun onStateChanged(state: MediaState) {
onChanged()
}

override fun onSessionAdded(session: Session) {
subscribeTo(session)
onChanged()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package org.mozilla.fenix.home.sessioncontrol

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.DrawableRes
import androidx.annotation.LayoutRes
Expand All @@ -14,6 +15,8 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observer
import kotlinx.android.synthetic.main.tab_list_row.*
import mozilla.components.feature.media.state.MediaState
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.NoContentMessageViewHolder
Expand All @@ -37,7 +40,28 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
data class TabHeader(val isPrivate: Boolean, val hasTabs: Boolean) : AdapterItem(TabHeaderViewHolder.LAYOUT_ID)
data class TabItem(val tab: Tab) : AdapterItem(TabViewHolder.LAYOUT_ID) {
override fun sameAs(other: AdapterItem) = other is TabItem && tab.sessionId == other.tab.sessionId

// Tell the adapter exactly what values have changed so it only has to draw those
override fun getChangePayload(newItem: AdapterItem): Any? {
(newItem as TabItem).let {
val shouldUpdateUrl = newItem.tab.url != this.tab.url
val shouldUpdateHostname = newItem.tab.hostname != this.tab.hostname
val shouldUpdateTitle = newItem.tab.title != this.tab.title
val shouldUpdateSelected = newItem.tab.selected != this.tab.selected
val shouldUpdateMediaState = newItem.tab.mediaState != this.tab.mediaState

return AdapterItemDiffCallback.TabChangePayload(
tab = newItem.tab,
shouldUpdateUrl = shouldUpdateUrl,
shouldUpdateHostname = shouldUpdateHostname,
shouldUpdateTitle = shouldUpdateTitle,
shouldUpdateSelected = shouldUpdateSelected,
shouldUpdateMediaState = shouldUpdateMediaState
)
}
}
}

object SaveTabGroup : AdapterItem(SaveTabGroupViewHolder.LAYOUT_ID)

object PrivateBrowsingDescription : AdapterItem(PrivateBrowsingDescriptionViewHolder.LAYOUT_ID)
Expand Down Expand Up @@ -85,13 +109,31 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
* True if this item represents the same value as other. Used by [AdapterItemDiffCallback].
*/
open fun sameAs(other: AdapterItem) = this::class == other::class

/**
* Returns a payload if there's been a change, or null if not
*/
open fun getChangePayload(newItem: AdapterItem): Any? = null
}

class AdapterItemDiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = oldItem.sameAs(newItem)

@Suppress("DiffUtilEquals")
override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = oldItem == newItem

override fun getChangePayload(oldItem: AdapterItem, newItem: AdapterItem): Any? {
return oldItem.getChangePayload(newItem) ?: return super.getChangePayload(oldItem, newItem)
}

data class TabChangePayload(
val tab: Tab,
val shouldUpdateUrl: Boolean,
val shouldUpdateHostname: Boolean,
val shouldUpdateTitle: Boolean,
val shouldUpdateSelected: Boolean,
val shouldUpdateMediaState: Boolean
)
}

class SessionControlAdapter(
Expand Down Expand Up @@ -133,9 +175,9 @@ class SessionControlAdapter(
val tabHeader = item as AdapterItem.TabHeader
holder.bind(tabHeader.isPrivate, tabHeader.hasTabs)
}
is TabViewHolder -> holder.bindSession(
(item as AdapterItem.TabItem).tab
)
is TabViewHolder -> {
holder.bindSession((item as AdapterItem.TabItem).tab)
}
is NoContentMessageViewHolder -> {
val (icon, header, description) = item as AdapterItem.NoContentMessage
holder.bind(icon, header, description)
Expand All @@ -152,13 +194,36 @@ class SessionControlAdapter(
(item as AdapterItem.OnboardingSectionHeader).labelBuilder
)
is OnboardingManualSignInViewHolder -> holder.bind()
is OnboardingAutomaticSignInViewHolder -> holder.bind(
(
(
item as AdapterItem.OnboardingAutomaticSignIn
).state as OnboardingState.SignedOutCanAutoSignIn
).withAccount
is OnboardingAutomaticSignInViewHolder -> holder.bind((
(item as AdapterItem.OnboardingAutomaticSignIn).state
as OnboardingState.SignedOutCanAutoSignIn).withAccount
)
}
}

override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int,
payloads: MutableList<Any>
) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position)
return
}

(payloads[0] as AdapterItemDiffCallback.TabChangePayload).let {
(holder as TabViewHolder).updateTab(it.tab)

// Always set the visibility to GONE to avoid the play button sticking around from previous draws
holder.play_pause_button.visibility = View.GONE

if (it.shouldUpdateHostname) { holder.updateHostname(it.tab.hostname) }
if (it.shouldUpdateTitle) { holder.updateTitle(it.tab.title) }
if (it.shouldUpdateUrl) { holder.updateFavIcon(it.tab.url) }
if (it.shouldUpdateSelected) { holder.updateSelected(it.tab.selected ?: false) }
if (it.shouldUpdateMediaState) {
holder.updatePlayPauseButton(it.tab.mediaState ?: MediaState.None)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
package org.mozilla.fenix.home.sessioncontrol

import android.content.Context
import android.os.Parcelable
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observer
import kotlinx.android.parcel.Parcelize
import mozilla.components.browser.session.Session
import mozilla.components.feature.media.state.MediaState
import mozilla.components.service.fxa.sharing.ShareableAccount
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.ext.components
Expand Down Expand Up @@ -46,14 +45,14 @@ class SessionControlComponent(
}
}

@Parcelize
data class Tab(
val sessionId: String,
val url: String,
val hostname: String,
val title: String,
val selected: Boolean? = null
) : Parcelable
val selected: Boolean? = null,
var mediaState: MediaState? = null
)

fun List<Tab>.toSessionBundle(context: Context): MutableList<Session> {
val sessionBundle = mutableListOf<Session>()
Expand All @@ -62,7 +61,6 @@ fun List<Tab>.toSessionBundle(context: Context): MutableList<Session> {
sessionBundle.add(session)
}
}

return sessionBundle
}

Expand Down Expand Up @@ -108,6 +106,8 @@ sealed class TabAction : Action {
data class Select(val tabView: View, val sessionId: String) : TabAction()
data class Close(val sessionId: String) : TabAction()
data class Share(val sessionId: String) : TabAction()
data class PauseMedia(val sessionId: String) : TabAction()
data class PlayMedia(val sessionId: String) : TabAction()
object PrivateBrowsingLearnMore : TabAction()
}

Expand Down
Loading

0 comments on commit aa9661b

Please sign in to comment.