Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mobile/android/fenix/app/longfox/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
implementation libs.androidx.compose.ui.tooling.preview
implementation libs.androidx.compose.ui.graphics
implementation libs.androidx.core.ktx
implementation libs.androidx.lifecycle.runtime
implementation libs.androidx.datastore.core
implementation libs.androidx.datastore.preferences
implementation libs.google.material
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@

package org.mozilla.fenix.longfox

import android.content.Context
import android.content.ContextWrapper
import android.view.ViewGroup
import androidx.activity.ComponentDialog
import androidx.activity.compose.BackHandler
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import org.mozilla.fenix.longfox.GleanMetrics.Longfox

/**
Expand All @@ -19,10 +24,10 @@ import org.mozilla.fenix.longfox.GleanMetrics.Longfox
interface LongFoxFeatureApi {

/**
* If you want to include the game somewhere, call this with a view you want to attach it to.
* @param container the view that you want to put the game in
* Shows the game in its own window, hosted on the activity backing [context].
* @param context an activity [Context] the game should be shown over
*/
fun start(container: ViewGroup)
fun start(context: Context)

/**
* Call this if you want to send a telemetry event when the entry point is shown.
Expand All @@ -37,27 +42,49 @@ interface LongFoxFeatureApi {
class LongFoxFeature : LongFoxFeatureApi {

/**
* Adds a compose view with the game to whichever view is passed in.
* When back is pressed, gets rid of this game ComposeView.
* @param container the view that you want to put the game in.
* Shows the game in its own window, hosted on the activity backing [context].
* Using a separate window keeps accessibility services (TalkBack, the Accessibility Scanner)
* scoped to the game rather than reading through the opaque canvas to the UI behind it.
* When back is pressed, the game window is dismissed.
* @param context an activity [Context] the game should be shown over.
*/
override fun start(container: ViewGroup) {
val context = container.context ?: return
override fun start(context: Context) {
Longfox.gameLaunched.record()
container.addView(

val dialog = ComponentDialog(context, android.R.style.Theme_Translucent_NoTitleBar)
dialog.window?.apply {
setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
)
}

dialog.setContentView(
ComposeView(context).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
MaterialTheme {
LongFoxGameScreen()
}
BackHandler {
container.removeView(this)
disposeComposition()
dialog.dismiss()
}
}
},
)

// On destroy, dismiss the window with the host so it can't leak if the activity goes away mid-game.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌🏾

context.findLifecycleOwner()?.lifecycle?.let { lifecycle ->
val observer = object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
dialog.dismiss()
}
}
lifecycle.addObserver(observer)
dialog.setOnDismissListener { lifecycle.removeObserver(observer) }
}

dialog.show()
}

/**
Expand All @@ -67,3 +94,9 @@ class LongFoxFeature : LongFoxFeatureApi {
Longfox.entryPointShown.record()
}
}

private tailrec fun Context.findLifecycleOwner(): LifecycleOwner? = when (this) {
is LifecycleOwner -> this
is ContextWrapper -> baseContext.findLifecycleOwner()
else -> null
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.compose.snackbar.SnackbarState
import org.mozilla.fenix.ext.application
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.ext.hideToolbar
import org.mozilla.fenix.ext.isOnline
import org.mozilla.fenix.ext.nav
Expand Down Expand Up @@ -1308,7 +1307,7 @@ class HomeFragment : Fragment() {
),
logoController = LogoController(
longFoxFeature = requireComponents.core.longFoxFeature,
container = requireActivity().getRootView() as? ViewGroup,
context = requireActivity(),
longFoxEnabled = requireComponents.settings.longfoxEnabled,
),
sportsController = DefaultSportsController(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@

package org.mozilla.fenix.home.logo

import android.view.ViewGroup
import android.content.Context
import org.mozilla.fenix.longfox.LongFoxFeatureApi

/**
* Controller for launching the LongFox feature.
*/
class LogoController(
private val longFoxFeature: LongFoxFeatureApi,
private val container: ViewGroup?,
private val context: Context,
private val longFoxEnabled: Boolean,
) {

/**
* When the longfox entry point text is clicked, launch the LongFox feature.
*/
fun handleLongfoxEntryPointClicked() {
if (container != null && longFoxEnabled) longFoxFeature.start(container = container)
if (longFoxEnabled) longFoxFeature.start(context)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

package org.mozilla.fenix.home.logo

import android.view.ViewGroup
import android.content.Context
import mozilla.components.support.test.fakes.android.FakeContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
Expand All @@ -19,47 +19,32 @@ class LogoControllerTest {
class FakeLongFoxFeature : LongFoxFeatureApi {
var started = false
var entryPointShownCount = 0
override fun start(container: ViewGroup) {
override fun start(context: Context) {
started = true
}
override fun onEntryPointShown() {
entryPointShownCount++
}
}

class FakeViewGroup : ViewGroup(FakeContext()) {
override fun onLayout(p0: Boolean, p1: Int, p2: Int, p3: Int, p4: Int) { }
}

val fakeLongFoxFeature = FakeLongFoxFeature()

@Test
fun `if longfox is disabled, do nothing when entry point clicked`() {
val logoController = LogoController(
longFoxFeature = fakeLongFoxFeature,
container = FakeViewGroup(),
context = FakeContext(),
longFoxEnabled = false,
)
logoController.handleLongfoxEntryPointClicked()
assertFalse(fakeLongFoxFeature.started)
}

@Test
fun `if longfox is enabled but no container exists, do nothing when entry point clicked`() {
val logoController = LogoController(
longFoxFeature = fakeLongFoxFeature,
container = null,
longFoxEnabled = true,
)
logoController.handleLongfoxEntryPointClicked()
assertFalse(fakeLongFoxFeature.started)
}

@Test
fun `if longfox is enabled and container exists, launch game when entry point clicked`() {
fun `if longfox is enabled, launch game when entry point clicked`() {
val logoController = LogoController(
longFoxFeature = fakeLongFoxFeature,
container = FakeViewGroup(),
context = FakeContext(),
longFoxEnabled = true,
)
logoController.handleLongfoxEntryPointClicked()
Expand All @@ -70,7 +55,7 @@ class LogoControllerTest {
fun `record telemetry when entry point shown`() {
val logoController = LogoController(
longFoxFeature = fakeLongFoxFeature,
container = FakeViewGroup(),
context = FakeContext(),
longFoxEnabled = true,
)
logoController.handleLongfoxEntryPointShown()
Expand Down