Revision control
Copy as Markdown
/* 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
package org.mozilla.tv.firefox.navigationoverlay
import androidx.annotation.StringRes
import androidx.annotation.UiThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.LiveDataReactiveStreams
import androidx.lifecycle.ViewModel
import io.reactivex.BackpressureStrategy
import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import io.reactivex.subjects.BehaviorSubject
import mozilla.components.support.base.observer.Consumable
import org.mozilla.tv.firefox.R
import org.mozilla.tv.firefox.channels.pinnedtile.PinnedTileRepo
import org.mozilla.tv.firefox.session.SessionRepo
import org.mozilla.tv.firefox.telemetry.TelemetryIntegration
import org.mozilla.tv.firefox.utils.URLs
import org.mozilla.tv.firefox.utils.UrlUtils
class ToolbarViewModel(
private val sessionRepo: SessionRepo,
private val pinnedTileRepo: PinnedTileRepo,
private val telemetryIntegration: TelemetryIntegration = TelemetryIntegration.INSTANCE
) : ViewModel() {
data class State(
val backEnabled: Boolean,
val forwardEnabled: Boolean,
val refreshEnabled: Boolean,
val pinEnabled: Boolean,
val pinChecked: Boolean,
val turboChecked: Boolean,
val desktopModeEnabled: Boolean,
val desktopModeChecked: Boolean,
val urlBarText: String
)
sealed class Action {
data class ShowTopToast(@StringRes val textId: Int) : Action()
data class ShowBottomToast(@StringRes val textId: Int) : Action()
data class SetOverlayVisible(val visible: Boolean) : Action()
object ExitFirefox : Action()
}
// We use events in order to decouple the ViewModel from holding a reference to a context
private val _events = BehaviorSubject.create<Consumable<Action>>()
val events = _events.hide()
val state: Observable<State> = Observables.combineLatest(sessionRepo.state, pinnedTileRepo.pinnedTiles) { sessionState, pinnedTiles ->
fun isCurrentURLPinned() = pinnedTiles.containsKey(sessionState.currentUrl)
ToolbarViewModel.State(
backEnabled = sessionState.backEnabled,
forwardEnabled = sessionState.forwardEnabled,
refreshEnabled = !sessionState.currentUrl.isEqualToHomepage(),
pinEnabled = !sessionState.currentUrl.isEqualToHomepage(),
pinChecked = isCurrentURLPinned(),
turboChecked = sessionState.turboModeActive,
desktopModeEnabled = !sessionState.currentUrl.isEqualToHomepage(),
desktopModeChecked = sessionState.desktopModeActive,
urlBarText = UrlUtils.toUrlBarDisplay(sessionState.currentUrl)
)
}
@Deprecated(message = "Use ToolbarViewModel.state for new code")
val legacyState: LiveData<ToolbarViewModel.State> = LiveDataReactiveStreams
.fromPublisher(state.toFlowable(BackpressureStrategy.LATEST))
@UiThread
fun backButtonClicked() {
sendOverlayClickTelemetry(NavigationEvent.BACK)
sessionRepo.attemptBack(forceYouTubeExit = true)
hideOverlay()
}
@UiThread
fun forwardButtonClicked() {
sendOverlayClickTelemetry(NavigationEvent.FORWARD)
sessionRepo.goForward()
hideOverlay()
}
@UiThread
fun reloadButtonClicked() {
sendOverlayClickTelemetry(NavigationEvent.RELOAD)
sessionRepo.reload()
sessionRepo.pushCurrentValue()
hideOverlay()
}
@UiThread
fun pinButtonClicked() {
val pinChecked = state.blockingFirst().pinChecked
val url = sessionRepo.state.blockingFirst().currentUrl
sendOverlayClickTelemetry(NavigationEvent.PIN_ACTION, pinChecked = !pinChecked)
if (pinChecked) {
pinnedTileRepo.removePinnedTile(url)
_events.onNext(Consumable.from(Action.ShowTopToast(R.string.notification_unpinned_site)))
} else {
pinnedTileRepo.addPinnedTile(url, sessionRepo.currentURLScreenshot())
_events.onNext(Consumable.from(Action.ShowTopToast(R.string.notification_pinned_site)))
}
hideOverlay()
}
@UiThread
fun turboButtonClicked() {
val currentUrl = sessionRepo.state.blockingFirst().currentUrl
val turboModeActive = sessionRepo.state.blockingFirst().turboModeActive
sessionRepo.setTurboModeEnabled(!turboModeActive)
sessionRepo.reload()
sendOverlayClickTelemetry(NavigationEvent.TURBO, turboChecked = !turboModeActive)
currentUrl.let { if (!it.isEqualToHomepage()) hideOverlay() }
}
@UiThread
fun desktopModeButtonClicked() {
val desktopModeChecked = state.blockingFirst().desktopModeChecked
sendOverlayClickTelemetry(NavigationEvent.DESKTOP_MODE, desktopModeChecked = !desktopModeChecked)
sessionRepo.setDesktopMode(!desktopModeChecked)
val textId = when {
desktopModeChecked -> R.string.notification_request_non_desktop_site
else -> R.string.notification_request_desktop_site
}
_events.onNext(Consumable.from(Action.ShowBottomToast(textId)))
hideOverlay()
}
@UiThread
fun exitFirefoxButtonClicked() {
sendOverlayClickTelemetry(NavigationEvent.EXIT_FIREFOX)
_events.onNext(Consumable.from(Action.ExitFirefox))
}
private fun sendOverlayClickTelemetry(
event: NavigationEvent,
turboChecked: Boolean? = null,
pinChecked: Boolean? = null,
desktopModeChecked: Boolean? = null
) {
@Suppress("DEPRECATION")
legacyState.value?.let {
telemetryIntegration.overlayClickEvent(
event,
turboChecked ?: it.turboChecked,
pinChecked ?: it.pinChecked,
desktopModeChecked ?: it.desktopModeChecked
)
}
}
private fun String.isEqualToHomepage() = this == URLs.APP_URL_HOME
private fun hideOverlay() {
_events.onNext(Consumable.from(Action.SetOverlayVisible(false)))
}
}