1
0
Fork 0

Update to new feature-media API.

master
Sebastian Kaspari 2020-03-26 19:20:59 +01:00 committed by Emily Kager
parent cd7c2c4bd5
commit 02aabea14a
15 changed files with 139 additions and 90 deletions

View File

@ -213,6 +213,9 @@
android:exported="false" android:exported="false"
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"/> android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"/>
<service android:name=".media.MediaService"
android:exported="false" />
<service <service
android:name=".customtabs.CustomTabsService" android:name=".customtabs.CustomTabsService"
android:exported="true" android:exported="true"

View File

@ -10,20 +10,6 @@ object FeatureFlags {
*/ */
const val pullToRefreshEnabled = false const val pullToRefreshEnabled = false
/**
* Integration of media features provided by `feature-media` component:
* - Background playback without the app getting killed
* - Media notification with play/pause controls
* - Audio Focus handling (pausing/resuming in agreement with other media apps)
* - Support for hardware controls to toggle play/pause (e.g. buttons on a headset)
*
* Behind nightly flag until all related Android Components issues are fixed and QA has signed
* off.
*
* https://github.com/mozilla-mobile/fenix/issues/4431
*/
const val mediaIntegration = true
/** /**
* Allows Progressive Web Apps to be installed to the device home screen. * Allows Progressive Web Apps to be installed to the device home screen.
*/ */

View File

@ -16,6 +16,7 @@ import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.fragment_create_collection.view.* import kotlinx.android.synthetic.main.fragment_create_collection.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.lib.publicsuffixlist.PublicSuffixList
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -45,10 +46,11 @@ class CollectionCreationFragment : DialogFragment() {
val args: CollectionCreationFragmentArgs by navArgs() val args: CollectionCreationFragmentArgs by navArgs()
val sessionManager = requireComponents.core.sessionManager val sessionManager = requireComponents.core.sessionManager
val store = requireComponents.core.store
val publicSuffixList = requireComponents.publicSuffixList val publicSuffixList = requireComponents.publicSuffixList
val tabs = sessionManager.getTabs(args.tabIds, publicSuffixList) val tabs = sessionManager.getTabs(args.tabIds, store, publicSuffixList)
val selectedTabs = if (args.selectedTabIds != null) { val selectedTabs = if (args.selectedTabIds != null) {
sessionManager.getTabs(args.selectedTabIds, publicSuffixList).toSet() sessionManager.getTabs(args.selectedTabIds, store, publicSuffixList).toSet()
} else { } else {
if (tabs.size == 1) setOf(tabs.first()) else emptySet() if (tabs.size == 1) setOf(tabs.first()) else emptySet()
} }
@ -80,7 +82,10 @@ class CollectionCreationFragment : DialogFragment() {
viewLifecycleOwner.lifecycleScope viewLifecycleOwner.lifecycleScope
) )
) )
collectionCreationView = CollectionCreationView(view.createCollectionWrapper, collectionCreationInteractor) collectionCreationView = CollectionCreationView(
view.createCollectionWrapper,
collectionCreationInteractor
)
return view return view
} }
@ -108,9 +113,13 @@ class CollectionCreationFragment : DialogFragment() {
} }
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun SessionManager.getTabs(tabIds: Array<String>?, publicSuffixList: PublicSuffixList): List<Tab> { fun SessionManager.getTabs(
tabIds: Array<String>?,
store: BrowserStore,
publicSuffixList: PublicSuffixList
): List<Tab> {
return tabIds return tabIds
?.mapNotNull { this.findSessionById(it) } ?.mapNotNull { this.findSessionById(it) }
?.map { it.toTab(publicSuffixList) } ?.map { it.toTab(store, publicSuffixList) }
?: emptyList() ?: emptyList()
} }

View File

@ -20,8 +20,20 @@ import androidx.transition.AutoTransition
import androidx.transition.Transition import androidx.transition.Transition
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_collection_creation.* import kotlinx.android.synthetic.main.component_collection_creation.back_button
import kotlinx.android.synthetic.main.component_collection_creation.view.* import kotlinx.android.synthetic.main.component_collection_creation.collection_constraint_layout
import kotlinx.android.synthetic.main.component_collection_creation.name_collection_edittext
import kotlinx.android.synthetic.main.component_collection_creation.save_button
import kotlinx.android.synthetic.main.component_collection_creation.select_all_button
import kotlinx.android.synthetic.main.component_collection_creation.view.bottom_bar_icon_button
import kotlinx.android.synthetic.main.component_collection_creation.view.bottom_bar_text
import kotlinx.android.synthetic.main.component_collection_creation.view.bottom_button_bar_layout
import kotlinx.android.synthetic.main.component_collection_creation.view.collection_constraint_layout
import kotlinx.android.synthetic.main.component_collection_creation.view.collections_list
import kotlinx.android.synthetic.main.component_collection_creation.view.name_collection_edittext
import kotlinx.android.synthetic.main.component_collection_creation.view.select_all_button
import kotlinx.android.synthetic.main.component_collection_creation.view.tab_list
import mozilla.components.browser.state.state.MediaState
import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.support.ktx.android.view.hideKeyboard import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.support.ktx.android.view.showKeyboard import mozilla.components.support.ktx.android.view.showKeyboard
@ -253,7 +265,8 @@ class CollectionCreationView(
tab.id.toString(), tab.id.toString(),
tab.url, tab.url,
tab.url.toShortUrl(view.context.components.publicSuffixList), tab.url.toShortUrl(view.context.components.publicSuffixList),
tab.title tab.title,
mediaState = MediaState.State.NONE
) )
}.let { tabs -> }.let { tabs ->
collectionCreationTabListAdapter.updateData(tabs, tabs.toSet(), true) collectionCreationTabListAdapter.updateData(tabs, tabs.toSet(), true)

View File

@ -27,9 +27,8 @@ import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.mediaquery.PreferredColorScheme import mozilla.components.concept.engine.mediaquery.PreferredColorScheme
import mozilla.components.concept.fetch.Client import mozilla.components.concept.fetch.Client
import mozilla.components.feature.customtabs.store.CustomTabsServiceStore import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
import mozilla.components.feature.media.MediaFeature
import mozilla.components.feature.media.RecordingDevicesNotificationFeature import mozilla.components.feature.media.RecordingDevicesNotificationFeature
import mozilla.components.feature.media.state.MediaStateMachine import mozilla.components.feature.media.middleware.MediaMiddleware
import mozilla.components.feature.pwa.ManifestStorage import mozilla.components.feature.pwa.ManifestStorage
import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.pwa.WebAppShortcutManager
import mozilla.components.feature.session.HistoryDelegate import mozilla.components.feature.session.HistoryDelegate
@ -44,6 +43,7 @@ import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.media.MediaService
import org.mozilla.fenix.test.Mockable import org.mozilla.fenix.test.Mockable
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -66,7 +66,7 @@ class Core(private val context: Context) {
preferredColorScheme = getPreferredColorScheme(), preferredColorScheme = getPreferredColorScheme(),
automaticFontSizeAdjustment = context.settings().shouldUseAutoSize, automaticFontSizeAdjustment = context.settings().shouldUseAutoSize,
fontInflationEnabled = context.settings().shouldUseAutoSize, fontInflationEnabled = context.settings().shouldUseAutoSize,
suspendMediaWhenInactive = !FeatureFlags.mediaIntegration, suspendMediaWhenInactive = false,
forceUserScalableContent = context.settings().forceEnableZoom forceUserScalableContent = context.settings().forceEnableZoom
) )
@ -97,7 +97,11 @@ class Core(private val context: Context) {
* The [BrowserStore] holds the global [BrowserState]. * The [BrowserStore] holds the global [BrowserState].
*/ */
val store by lazy { val store by lazy {
BrowserStore() BrowserStore(
middleware = listOf(
MediaMiddleware(context, MediaService::class.java)
)
)
} }
/** /**
@ -150,14 +154,6 @@ class Core(private val context: Context) {
.whenSessionsChange() .whenSessionsChange()
} }
if (FeatureFlags.mediaIntegration) {
MediaStateMachine.start(sessionManager)
// Enable media features like showing an ongoing notification with media controls when
// media in web content is playing.
MediaFeature(context).enable()
}
WebNotificationFeature( WebNotificationFeature(
context, engine, icons, R.drawable.ic_status_logo, context, engine, icons, R.drawable.ic_status_logo,
HomeActivity::class.java HomeActivity::class.java

View File

@ -6,21 +6,36 @@ package org.mozilla.fenix.ext
import android.content.Context import android.content.Context
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.feature.media.state.MediaState import mozilla.components.browser.state.state.MediaState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.lib.publicsuffixlist.PublicSuffixList
import org.mozilla.fenix.home.Tab import org.mozilla.fenix.home.Tab
fun Session.toTab(context: Context, selected: Boolean? = null, mediaState: MediaState? = null): Tab = fun Session.toTab(context: Context, selected: Boolean? = null): Tab =
this.toTab(context.components.publicSuffixList, selected, mediaState) this.toTab(
context.components.core.store,
context.components.publicSuffixList,
selected
)
fun Session.toTab(publicSuffixList: PublicSuffixList, selected: Boolean? = null, mediaState: MediaState? = null): Tab { fun Session.toTab(store: BrowserStore, publicSuffixList: PublicSuffixList, selected: Boolean? = null): Tab {
return Tab( return Tab(
sessionId = this.id, sessionId = this.id,
url = this.url, url = this.url,
hostname = this.url.toShortUrl(publicSuffixList), hostname = this.url.toShortUrl(publicSuffixList),
title = this.title, title = this.title,
selected = selected, selected = selected,
mediaState = mediaState, icon = this.icon,
icon = this.icon mediaState = getMediaStateForSession(store, this)
) )
} }
private fun getMediaStateForSession(store: BrowserStore, session: Session): MediaState.State {
// For now we are looking up the media state for this session in the BrowserStore. Eventually
// we will migrate away from Session(Manager) and can use BrowserStore and BrowserState directly.
return if (store.state.media.aggregate.activeTabId == session.id) {
store.state.media.aggregate.state
} else {
MediaState.State.NONE
}
}

View File

@ -56,25 +56,27 @@ import kotlinx.android.synthetic.main.fragment_home.view.sessionControlRecyclerV
import kotlinx.android.synthetic.main.fragment_home.view.toolbar import kotlinx.android.synthetic.main.fragment_home.view.toolbar
import kotlinx.android.synthetic.main.fragment_home.view.toolbarLayout import kotlinx.android.synthetic.main.fragment_home.view.toolbarLayout
import kotlinx.android.synthetic.main.fragment_home.view.toolbar_wrapper import kotlinx.android.synthetic.main.fragment_home.view.toolbar_wrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.menu.ext.getHighlight import mozilla.components.browser.menu.ext.getHighlight
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.feature.media.ext.getSession
import mozilla.components.feature.media.state.MediaState
import mozilla.components.feature.media.state.MediaStateMachine
import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite import mozilla.components.feature.top.sites.TopSite
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.ktx.android.util.dpToPx import mozilla.components.support.ktx.android.util.dpToPx
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -108,7 +110,6 @@ import org.mozilla.fenix.whatsnew.WhatsNew
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.min import kotlin.math.min
@ExperimentalCoroutinesApi
@SuppressWarnings("TooManyFunctions", "LargeClass") @SuppressWarnings("TooManyFunctions", "LargeClass")
class HomeFragment : Fragment() { class HomeFragment : Fragment() {
private val homeViewModel: HomeScreenViewModel by viewModels { private val homeViewModel: HomeScreenViewModel by viewModels {
@ -165,7 +166,11 @@ class HomeFragment : Fragment() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
postponeEnterTransition() postponeEnterTransition()
val sessionObserver = BrowserSessionsObserver(sessionManager, singleSessionObserver) { val sessionObserver = BrowserSessionsObserver(
sessionManager,
requireComponents.core.store,
singleSessionObserver
) {
emitSessionChanges() emitSessionChanges()
} }
@ -205,8 +210,9 @@ class HomeFragment : Fragment() {
sessionControlInteractor = SessionControlInteractor( sessionControlInteractor = SessionControlInteractor(
DefaultSessionControlController( DefaultSessionControlController(
store = requireComponents.core.store,
activity = activity, activity = activity,
store = homeFragmentStore, fragmentStore = homeFragmentStore,
navController = findNavController(), navController = findNavController(),
browsingModeManager = browsingModeManager, browsingModeManager = browsingModeManager,
lifecycleScope = viewLifecycleOwner.lifecycleScope, lifecycleScope = viewLifecycleOwner.lifecycleScope,
@ -277,7 +283,6 @@ class HomeFragment : Fragment() {
} }
} }
@ExperimentalCoroutinesApi
@SuppressWarnings("LongMethod") @SuppressWarnings("LongMethod")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -895,16 +900,9 @@ class HomeFragment : Fragment() {
private fun List<Session>.toTabs(): List<Tab> { private fun List<Session>.toTabs(): List<Tab> {
val selected = sessionManager.selectedSession val selected = sessionManager.selectedSession
val mediaStateSession = MediaStateMachine.state.getSession()
return this.map { return map {
val mediaState = if (mediaStateSession?.id == it.id) { it.toTab(requireContext(), it == selected)
MediaStateMachine.state
} else {
null
}
it.toTab(requireContext(), it == selected, mediaState)
} }
} }
@ -973,18 +971,24 @@ class HomeFragment : Fragment() {
*/ */
private class BrowserSessionsObserver( private class BrowserSessionsObserver(
private val manager: SessionManager, private val manager: SessionManager,
private val store: BrowserStore,
private val observer: Session.Observer, private val observer: Session.Observer,
private val onChanged: () -> Unit private val onChanged: () -> Unit
) : LifecycleObserver { ) : LifecycleObserver {
private var scope: CoroutineScope? = null
/** /**
* Start observing * Start observing
*/ */
@OnLifecycleEvent(Lifecycle.Event.ON_START) @OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart() { fun onStart() {
MediaStateMachine.register(managerObserver)
manager.register(managerObserver) manager.register(managerObserver)
subscribeToAll() subscribeToAll()
scope = store.flowScoped { flow ->
flow.ifChanged { it.media.aggregate }
.collect { onChanged() }
}
} }
/** /**
@ -992,7 +996,7 @@ private class BrowserSessionsObserver(
*/ */
@OnLifecycleEvent(Lifecycle.Event.ON_STOP) @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStop() { fun onStop() {
MediaStateMachine.unregister(managerObserver) scope?.cancel()
manager.unregister(managerObserver) manager.unregister(managerObserver)
unsubscribeFromAll() unsubscribeFromAll()
} }
@ -1013,11 +1017,7 @@ private class BrowserSessionsObserver(
session.unregister(observer) session.unregister(observer)
} }
private val managerObserver = object : SessionManager.Observer, MediaStateMachine.Observer { private val managerObserver = object : SessionManager.Observer {
override fun onStateChanged(state: MediaState) {
onChanged()
}
override fun onSessionAdded(session: Session) { override fun onSessionAdded(session: Session) {
subscribeTo(session) subscribeTo(session)
onChanged() onChanged()

View File

@ -7,7 +7,7 @@ package org.mozilla.fenix.home
import android.graphics.Bitmap import android.graphics.Bitmap
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.feature.media.state.MediaState import mozilla.components.browser.state.state.MediaState
import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite import mozilla.components.feature.top.sites.TopSite
import mozilla.components.lib.state.Action import mozilla.components.lib.state.Action
@ -29,8 +29,8 @@ data class Tab(
val hostname: String, val hostname: String,
val title: String, val title: String,
val selected: Boolean? = null, val selected: Boolean? = null,
var mediaState: MediaState? = null, val icon: Bitmap? = null,
val icon: Bitmap? = null val mediaState: MediaState.State
) )
data class TopSiteItem( data class TopSiteItem(

View File

@ -15,7 +15,6 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.tab_list_row.* import kotlinx.android.synthetic.main.tab_list_row.*
import mozilla.components.feature.media.state.MediaState
import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.home.OnboardingState import org.mozilla.fenix.home.OnboardingState
@ -256,7 +255,7 @@ class SessionControlAdapter(
} }
if (it.shouldUpdateSelected) { holder.updateSelected(it.tab.selected ?: false) } if (it.shouldUpdateSelected) { holder.updateSelected(it.tab.selected ?: false) }
if (it.shouldUpdateMediaState) { if (it.shouldUpdateMediaState) {
holder.updatePlayPauseButton(it.tab.mediaState ?: MediaState.None) holder.updatePlayPauseButton(it.tab.mediaState)
} }
} }
} }

View File

@ -10,10 +10,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.prompt.ShareData import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.media.ext.pauseIfPlaying import mozilla.components.feature.media.ext.pauseIfPlaying
import mozilla.components.feature.media.ext.playIfPaused import mozilla.components.feature.media.ext.playIfPaused
import mozilla.components.feature.media.state.MediaStateMachine
import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tab.collections.ext.restore import mozilla.components.feature.tab.collections.ext.restore
import mozilla.components.feature.top.sites.TopSite import mozilla.components.feature.top.sites.TopSite
@ -157,8 +157,9 @@ interface SessionControlController {
@SuppressWarnings("TooManyFunctions", "LargeClass") @SuppressWarnings("TooManyFunctions", "LargeClass")
class DefaultSessionControlController( class DefaultSessionControlController(
private val store: BrowserStore,
private val activity: HomeActivity, private val activity: HomeActivity,
private val store: HomeFragmentStore, private val fragmentStore: HomeFragmentStore,
private val navController: NavController, private val navController: NavController,
private val browsingModeManager: BrowsingModeManager, private val browsingModeManager: BrowsingModeManager,
private val lifecycleScope: CoroutineScope, private val lifecycleScope: CoroutineScope,
@ -266,11 +267,11 @@ class DefaultSessionControlController(
} }
override fun handlePauseMediaClicked() { override fun handlePauseMediaClicked() {
MediaStateMachine.state.pauseIfPlaying() store.state.media.pauseIfPlaying()
} }
override fun handlePlayMediaClicked() { override fun handlePlayMediaClicked() {
MediaStateMachine.state.playIfPaused() store.state.media.playIfPaused()
} }
override fun handlePrivateBrowsingLearnMoreClicked() { override fun handlePrivateBrowsingLearnMoreClicked() {
@ -358,7 +359,7 @@ class DefaultSessionControlController(
} }
override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) { override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
store.dispatch(HomeFragmentAction.CollectionExpanded(collection, expand)) fragmentStore.dispatch(HomeFragmentAction.CollectionExpanded(collection, expand))
} }
override fun handleonOpenNewTabClicked() { override fun handleonOpenNewTabClicked() {

View File

@ -12,7 +12,7 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.tab_list_row.* import kotlinx.android.synthetic.main.tab_list_row.*
import mozilla.components.feature.media.state.MediaState import mozilla.components.browser.state.state.MediaState
import mozilla.components.support.ktx.android.util.dpToFloat import mozilla.components.support.ktx.android.util.dpToFloat
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
@ -50,15 +50,19 @@ class TabViewHolder(
play_pause_button.setOnClickListener { play_pause_button.setOnClickListener {
when (tab?.mediaState) { when (tab?.mediaState) {
is MediaState.Playing -> { MediaState.State.PLAYING -> {
it.context.components.analytics.metrics.track(Event.TabMediaPause) it.context.components.analytics.metrics.track(Event.TabMediaPause)
interactor.onPauseMediaClicked() interactor.onPauseMediaClicked()
} }
is MediaState.Paused -> { MediaState.State.PAUSED -> {
it.context.components.analytics.metrics.track(Event.TabMediaPlay) it.context.components.analytics.metrics.track(Event.TabMediaPlay)
interactor.onPlayMediaClicked() interactor.onPlayMediaClicked()
} }
MediaState.State.NONE -> throw AssertionError(
"Play/Pause button clicked without play/pause state."
)
} }
} }
@ -82,20 +86,20 @@ class TabViewHolder(
updateHostname(tab.hostname) updateHostname(tab.hostname)
updateFavIcon(tab.url, tab.icon) updateFavIcon(tab.url, tab.icon)
updateSelected(tab.selected ?: false) updateSelected(tab.selected ?: false)
updatePlayPauseButton(tab.mediaState ?: MediaState.None) updatePlayPauseButton(tab.mediaState)
item_tab.transitionName = "$TAB_ITEM_TRANSITION_NAME${tab.sessionId}" item_tab.transitionName = "$TAB_ITEM_TRANSITION_NAME${tab.sessionId}"
updateCloseButtonDescription(tab.title) updateCloseButtonDescription(tab.title)
} }
internal fun updatePlayPauseButton(mediaState: MediaState) { internal fun updatePlayPauseButton(mediaState: MediaState.State) {
with(play_pause_button) { with(play_pause_button) {
visibility = if (mediaState is MediaState.Playing || mediaState is MediaState.Paused) { visibility = if (mediaState == MediaState.State.PLAYING || mediaState == MediaState.State.PAUSED) {
View.VISIBLE View.VISIBLE
} else { } else {
View.GONE View.GONE
} }
if (mediaState is MediaState.Playing) { if (mediaState == MediaState.State.PLAYING) {
play_pause_button.contentDescription = play_pause_button.contentDescription =
context.getString(R.string.mozac_feature_media_notification_action_pause) context.getString(R.string.mozac_feature_media_notification_action_pause)
setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.pause_with_background)) setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.pause_with_background))

View File

@ -0,0 +1,16 @@
/* 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.media
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.media.service.AbstractMediaService
import org.mozilla.fenix.ext.components
/**
* [AbstractMediaService] implementation for injecting [BrowserStore] singleton.
*/
class MediaService : AbstractMediaService() {
override val store: BrowserStore by lazy { components.core.store }
}

View File

@ -19,6 +19,8 @@ import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.tab.collections.Tab import mozilla.components.feature.tab.collections.Tab
import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.lib.publicsuffixlist.PublicSuffixList
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -44,6 +46,7 @@ class CollectionCreationFragmentTest {
@MockK private lateinit var sessionManager: SessionManager @MockK private lateinit var sessionManager: SessionManager
@MockK private lateinit var publicSuffixList: PublicSuffixList @MockK private lateinit var publicSuffixList: PublicSuffixList
@MockK private lateinit var store: BrowserStore
private val sessionMozilla = Session(initialUrl = URL_MOZILLA, id = SESSION_ID_MOZILLA) private val sessionMozilla = Session(initialUrl = URL_MOZILLA, id = SESSION_ID_MOZILLA)
private val sessionBcc = Session(initialUrl = URL_BCC, id = SESSION_ID_BCC) private val sessionBcc = Session(initialUrl = URL_BCC, id = SESSION_ID_BCC)
@ -57,6 +60,7 @@ class CollectionCreationFragmentTest {
every { sessionManager.findSessionById(SESSION_ID_BAD_2) } answers { null } every { sessionManager.findSessionById(SESSION_ID_BAD_2) } answers { null }
every { publicSuffixList.stripPublicSuffix(URL_MOZILLA) } answers { GlobalScope.async { URL_MOZILLA } } every { publicSuffixList.stripPublicSuffix(URL_MOZILLA) } answers { GlobalScope.async { URL_MOZILLA } }
every { publicSuffixList.stripPublicSuffix(URL_BCC) } answers { GlobalScope.async { URL_BCC } } every { publicSuffixList.stripPublicSuffix(URL_BCC) } answers { GlobalScope.async { URL_BCC } }
every { store.state } answers { BrowserState() }
} }
@Test @Test
@ -80,7 +84,7 @@ class CollectionCreationFragmentTest {
@Test @Test
fun `GIVEN tabs are present in session manager WHEN getTabs is called THEN tabs will be returned`() { fun `GIVEN tabs are present in session manager WHEN getTabs is called THEN tabs will be returned`() {
val tabs = sessionManager val tabs = sessionManager
.getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BCC), publicSuffixList) .getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BCC), store, publicSuffixList)
val hosts = tabs.map { it.hostname } val hosts = tabs.map { it.hostname }
@ -91,7 +95,7 @@ class CollectionCreationFragmentTest {
@Test @Test
fun `GIVEN some tabs are present in session manager WHEN getTabs is called THEN only valid tabs will be returned`() { fun `GIVEN some tabs are present in session manager WHEN getTabs is called THEN only valid tabs will be returned`() {
val tabs = sessionManager val tabs = sessionManager
.getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BAD_1), publicSuffixList) .getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BAD_1), store, publicSuffixList)
val hosts = tabs.map { it.hostname } val hosts = tabs.map { it.hostname }
@ -102,7 +106,7 @@ class CollectionCreationFragmentTest {
@Test @Test
fun `GIVEN tabs are not present in session manager WHEN getTabs is called THEN an empty list will be returned`() { fun `GIVEN tabs are not present in session manager WHEN getTabs is called THEN an empty list will be returned`() {
val tabs = sessionManager val tabs = sessionManager
.getTabs(arrayOf(SESSION_ID_BAD_1, SESSION_ID_BAD_2), publicSuffixList) .getTabs(arrayOf(SESSION_ID_BAD_1, SESSION_ID_BAD_2), store, publicSuffixList)
assertEquals(emptyList<Tab>(), tabs) assertEquals(emptyList<Tab>(), tabs)
} }
@ -110,7 +114,7 @@ class CollectionCreationFragmentTest {
@Test @Test
fun `WHEN getTabs is called will null tabIds THEN an empty list will be returned`() { fun `WHEN getTabs is called will null tabIds THEN an empty list will be returned`() {
val tabs = sessionManager val tabs = sessionManager
.getTabs(null, publicSuffixList) .getTabs(null, store, publicSuffixList)
assertEquals(emptyList<Tab>(), tabs) assertEquals(emptyList<Tab>(), tabs)
} }

View File

@ -17,6 +17,7 @@ import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain import kotlinx.coroutines.test.setMain
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.Engine
import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.feature.tabs.TabsUseCases
@ -38,8 +39,9 @@ import mozilla.components.feature.tab.collections.Tab as ComponentTab
class DefaultSessionControlControllerTest { class DefaultSessionControlControllerTest {
private val mainThreadSurrogate = newSingleThreadContext("UI thread") private val mainThreadSurrogate = newSingleThreadContext("UI thread")
private val store: BrowserStore = mockk(relaxed = true)
private val activity: HomeActivity = mockk(relaxed = true) private val activity: HomeActivity = mockk(relaxed = true)
private val store: HomeFragmentStore = mockk(relaxed = true) private val fragmentStore: HomeFragmentStore = mockk(relaxed = true)
private val navController: NavController = mockk(relaxed = true) private val navController: NavController = mockk(relaxed = true)
private val browsingModeManager: BrowsingModeManager = mockk(relaxed = true) private val browsingModeManager: BrowsingModeManager = mockk(relaxed = true)
private val closeTab: (sessionId: String) -> Unit = mockk(relaxed = true) private val closeTab: (sessionId: String) -> Unit = mockk(relaxed = true)
@ -71,7 +73,7 @@ class DefaultSessionControlControllerTest {
every { activity.components.core.tabCollectionStorage } returns tabCollectionStorage every { activity.components.core.tabCollectionStorage } returns tabCollectionStorage
every { activity.components.useCases.tabsUseCases } returns tabsUseCases every { activity.components.useCases.tabsUseCases } returns tabsUseCases
every { store.state } returns state every { fragmentStore.state } returns state
every { state.collections } returns emptyList() every { state.collections } returns emptyList()
every { state.expandedCollections } returns emptySet() every { state.expandedCollections } returns emptySet()
every { state.mode } returns Mode.Normal every { state.mode } returns Mode.Normal
@ -79,8 +81,9 @@ class DefaultSessionControlControllerTest {
every { activity.components.analytics.metrics } returns metrics every { activity.components.analytics.metrics } returns metrics
controller = DefaultSessionControlController( controller = DefaultSessionControlController(
activity = activity,
store = store, store = store,
activity = activity,
fragmentStore = fragmentStore,
navController = navController, navController = navController,
browsingModeManager = browsingModeManager, browsingModeManager = browsingModeManager,
lifecycleScope = MainScope(), lifecycleScope = MainScope(),
@ -235,6 +238,6 @@ class DefaultSessionControlControllerTest {
fun handleToggleCollectionExpanded() { fun handleToggleCollectionExpanded() {
val collection: TabCollection = mockk(relaxed = true) val collection: TabCollection = mockk(relaxed = true)
controller.handleToggleCollectionExpanded(collection, true) controller.handleToggleCollectionExpanded(collection, true)
verify { store.dispatch(HomeFragmentAction.CollectionExpanded(collection, true)) } verify { fragmentStore.dispatch(HomeFragmentAction.CollectionExpanded(collection, true)) }
} }
} }

View File

@ -3,5 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
object AndroidComponents { object AndroidComponents {
const val VERSION = "38.0.20200326160141" const val VERSION = "38.0.20200327101347"
} }