Bug 1855979 - Basic Translation Beginning

The goal of this bug is to get a very basic initial `translate`
interaction using the default determined page language and default
determined user language and begin starting `Controller` and `Interactor`
files.

This bug also sets up `sessionId` as a navigation parameter for
translations.
fenix/123.0
ohall-m 5 months ago committed by mergify[bot]
parent 0613321c15
commit 67d60af2e3

@ -225,7 +225,9 @@ class DefaultBrowserToolbarController(
override fun handleTranslationsButtonClick() {
val directions =
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment()
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment(
sessionId = currentSession?.id,
)
navController.navigateSafe(R.id.browserFragment, directions)
}

@ -409,7 +409,9 @@ class DefaultBrowserToolbarMenuController(
ToolbarMenu.Item.Translate -> {
val directions =
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment()
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment(
sessionId = currentSession?.id,
)
navController.navigateSafe(R.id.browserFragment, directions)
}
}

@ -11,6 +11,7 @@ import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import mozilla.components.support.base.feature.UserInteractionHandler
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.showToolbar
@ -20,6 +21,7 @@ import org.mozilla.fenix.theme.FirefoxTheme
* A fragment displaying the Firefox Translation settings screen.
*/
class TranslationSettingsFragment : Fragment(), UserInteractionHandler {
private val args by navArgs<TranslationSettingsFragmentArgs>()
override fun onResume() {
super.onResume()
showToolbar(getString(R.string.translation_settings_toolbar_title))
@ -60,7 +62,8 @@ class TranslationSettingsFragment : Fragment(), UserInteractionHandler {
override fun onBackPressed(): Boolean {
findNavController().navigate(
TranslationSettingsFragmentDirections.actionTranslationSettingsFragmentToTranslationsDialogFragment(
TranslationsDialogAccessPoint.TranslationsOptions,
sessionId = args.sessionId,
translationsDialogAccessPoint = TranslationsDialogAccessPoint.TranslationsOptions,
),
)
return true

@ -0,0 +1,84 @@
/* 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.translations
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.translate.DetectedLanguages
import mozilla.components.concept.engine.translate.TranslationOptions
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.support.base.log.logger.Logger
/**
* Manages requests to complete operations with other components.
*
* @param translationUseCase The use case for performing a translation.
* @param browserStore The browser store to use for this controller.
* @param tabId The tab to perform operations or complete requests for.
*/
class TranslationsController(
private val translationUseCase: SessionUseCases.TranslateUseCase,
private val browserStore: BrowserStore,
private val tabId: String,
) {
private val logger = Logger("TranslationsController")
/**
* Retrieves detected information about the language on the browser page, the user's preferred
* language, and if the detected page language is supported.
*
* @return The [DetectedLanguages] object that contains page and user preference information.
*/
fun getDetectedLanguages(): DetectedLanguages? {
logger.info("Retrieving translations language support from the browser store.")
return browserStore.state.findTab(tabId)?.translationsState?.translationEngineState?.detectedLanguages
}
/**
* Translates the page on the given [tabId]. Will fallback to default expectations if
* [fromLanguage] and [toLanguage] are not provided.
*
* @param tabId The ID of the tab to translate.
* @param fromLanguage The BCP-47 language code to translate from. If null, the default will
* be set to the page language.
* @param toLanguage The BCP-47 language code to translate to. If null, the default will
* be set to the user's preferred language.
* @param options Optional options to specify and additional criteria for the translation.
*/
fun translate(
tabId: String?,
fromLanguage: String?,
toLanguage: String?,
options: TranslationOptions?,
) {
if (fromLanguage != null && toLanguage != null) {
logger.info("Requesting a translation.")
translationUseCase.invoke(tabId, fromLanguage, toLanguage, options)
return
}
// Fallback to find defaults
var defaultFromLanguage = fromLanguage
var defaultToLanguage = toLanguage
val detectedLanguages = getDetectedLanguages()
if (defaultFromLanguage == null) {
defaultFromLanguage = detectedLanguages?.documentLangTag
logger.info("Setting translating to use a default 'from' of $defaultFromLanguage.")
}
if (defaultToLanguage == null) {
defaultToLanguage = detectedLanguages?.userPreferredLangTag
logger.info("Setting translating to use a default 'to' of $defaultToLanguage.")
}
if (defaultFromLanguage != null && defaultToLanguage != null) {
logger.info("Requesting a translation based on defaults.")
translationUseCase.invoke(tabId, defaultFromLanguage, defaultToLanguage, options)
return
}
}
}

@ -26,6 +26,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.theme.FirefoxTheme
@ -44,6 +45,7 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() {
private var behavior: BottomSheetBehavior<View>? = null
private val args by navArgs<TranslationsDialogFragmentArgs>()
private lateinit var interactor: TranslationsInteractor
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
super.onCreateDialog(savedInstanceState).apply {
@ -61,6 +63,14 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() {
container: ViewGroup?,
savedInstanceState: Bundle?,
): View = ComposeView(requireContext()).apply {
interactor = TranslationsInteractor(
translationsController = TranslationsController(
translationUseCase = requireComponents.useCases.sessionUseCases.translate,
browserStore = requireComponents.core.store,
tabId = args.sessionId,
),
)
setContent {
FirefoxTheme {
var translationsVisibility by remember {
@ -115,7 +125,14 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() {
from = BrowserDirection.FromTranslationsDialogFragment,
)
},
onTranslateButtonClick = {},
onTranslateButtonClick = {
interactor.onTranslate(
tabId = args.sessionId,
fromLanguage = null,
toLanguage = null,
null,
)
},
onNotNowButtonClick = { dismiss() },
)
}
@ -143,7 +160,9 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() {
onTranslationSettingsClicked = {
findNavController().navigate(
TranslationsDialogFragmentDirections
.actionTranslationsDialogFragmentToTranslationSettingsFragment(),
.actionTranslationsDialogFragmentToTranslationSettingsFragment(
sessionId = args.sessionId,
),
)
},
)

@ -0,0 +1,54 @@
/* 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.translations
import mozilla.components.concept.engine.translate.DetectedLanguages
import mozilla.components.concept.engine.translate.TranslationOptions
import mozilla.components.support.base.log.logger.Logger
/**
* Manages coordinating the functionality interactions for use in the views.
*
* @param translationsController The translations controller that requests data and
* interactions.
*/
class TranslationsInteractor(
private val translationsController: TranslationsController,
) {
private val logger = Logger("TranslationsInteractor")
/**
* Retrieves the [DetectedLanguages] applicable to the page.
*
* @return The [DetectedLanguages] object that contains page and user preference information.
*/
fun detectedLanguages(): DetectedLanguages? {
logger.info("Requesting translations language support from the controller.")
return translationsController.getDetectedLanguages()
}
/**
* Translates the page on the given [tabId].
* If null is provided for [fromLanguage] or [toLanguage], the engine will attempt to
* find a sensible default.
*
* @param tabId The ID of the tab to translate.
* @param fromLanguage The BCP-47 language code to translate from. Usually will be the detected
* page language. If set as null, will revert to a default page language, if known.
* @param toLanguage The BCP-47 language code to translate to. Usually will be the user's
* preferred language. If set as null, will revert to a default of the user's preferred
* language, if known.
* @param options Optional options to specify and additional criteria for the translation.
*/
fun onTranslate(
tabId: String?,
fromLanguage: String?,
toLanguage: String?,
options: TranslationOptions?,
) {
logger.info("Requesting a translation from the controller.")
translationsController.translate(tabId, fromLanguage, toLanguage, options)
}
}

@ -1424,9 +1424,16 @@
<navigation
android:id="@+id/translations_graph"
app:startDestination="@id/translationsDialogFragment">
<argument
android:name="sessionId"
app:argType="string"
app:nullable="true" />
<dialog
android:id="@+id/translationsDialogFragment"
android:name="org.mozilla.fenix.translations.TranslationsDialogFragment">
<argument
android:name="sessionId"
app:argType="string" />
<argument
android:name="translationsDialogAccessPoint"
android:defaultValue="Translations"
@ -1438,6 +1445,9 @@
<fragment
android:id="@+id/translationSettingsFragment"
android:name="org.mozilla.fenix.translations.TranslationSettingsFragment">
<argument
android:name="sessionId"
app:argType="string" />
<action
android:id="@+id/action_translationSettingsFragment_to_translationsDialogFragment"
app:destination="@id/translationsDialogFragment"

@ -456,7 +456,11 @@ class DefaultBrowserToolbarControllerTest {
controller.handleTranslationsButtonClick()
verify {
navController.navigate(BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment())
navController.navigate(
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment(
sessionId = "1",
),
)
}
}

@ -835,7 +835,9 @@ class DefaultBrowserToolbarMenuControllerTest {
verify {
navController.navigate(
directionsEq(
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment(),
BrowserFragmentDirections.actionBrowserFragmentToTranslationsDialogFragment(
sessionId = selectedTab.id,
),
),
)
}

@ -0,0 +1,94 @@
/* 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.components.translations
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.TranslationsState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.translate.DetectedLanguages
import mozilla.components.concept.engine.translate.TranslationEngineState
import mozilla.components.concept.engine.translate.TranslationOptions
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.support.test.mock
import mozilla.components.support.test.whenever
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.translations.TranslationsController
@RunWith(FenixRobolectricTestRunner::class)
class TranslationsControllerTest {
val tab: TabSessionState = spy(
createTab(
url = "https://www.firefox.com",
title = "Firefox",
id = "1",
),
)
private val tabs = spy(listOf(tab))
private val browserState = spy(BrowserState(tabs = tabs))
private val browserStore = spy(BrowserStore(browserState))
private val translationsUseCase: SessionUseCases.TranslateUseCase = mock()
private val translationsController = spy(TranslationsController(translationUseCase = translationsUseCase, browserStore = browserStore, tabId = tab.id))
@Test
fun `Controller translate called the translate use case as expected`() {
val from = "en"
val to = "es"
val options = TranslationOptions(false)
translationsController.translate(tab.id, from, to, options)
verify(translationsUseCase).invoke(tab.id, from, to, options)
}
@Test
fun `Controller translate called the translate use case as expected when languages were null`() {
val mockFrom = "es"
val mockTo = "en"
val mockDetectedLanguages = DetectedLanguages(
documentLangTag = mockFrom,
supportedDocumentLang = true,
userPreferredLangTag = mockTo,
)
whenever(translationsController.getDetectedLanguages()).thenReturn(mockDetectedLanguages)
val from = null
val to = null
val options = TranslationOptions(false)
translationsController.translate(tab.id, from, to, options)
verify(translationsUseCase).invoke(tab.id, mockFrom, mockTo, options)
}
@Test
fun `Controller detectedLanguages retrieved the languages as expected`() {
val mockFrom = "es"
val mockTo = "en"
val mockDetectedLanguages = DetectedLanguages(
documentLangTag = mockFrom,
supportedDocumentLang = true,
userPreferredLangTag = mockTo,
)
val mockState = TranslationsState(
translationEngineState = TranslationEngineState(mockDetectedLanguages),
)
whenever(browserState.findTab(tab.id)?.translationsState).thenReturn(mockState)
val test = translationsController.getDetectedLanguages()
assertNotNull(test)
assertTrue(test?.documentLangTag == mockDetectedLanguages.documentLangTag)
assertTrue(test?.userPreferredLangTag == mockDetectedLanguages.userPreferredLangTag)
}
}

@ -0,0 +1,78 @@
/* 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.components.translations
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.translate.DetectedLanguages
import mozilla.components.concept.engine.translate.TranslationOptions
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.support.test.mock
import mozilla.components.support.test.whenever
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.translations.TranslationsController
import org.mozilla.fenix.translations.TranslationsInteractor
@RunWith(FenixRobolectricTestRunner::class)
class TranslationsInteractorTest {
val tab: TabSessionState = spy(
createTab(
url = "https://www.firefox.com",
title = "Firefox",
id = "1",
),
)
private val tabs = spy(listOf(tab))
private val browserState = spy(BrowserState(tabs = tabs))
private val browserStore = spy(BrowserStore(browserState))
private val translationsUseCase: SessionUseCases.TranslateUseCase = mock()
private val translationsController = spy(TranslationsController(translationUseCase = translationsUseCase, browserStore = browserStore, tabId = tab.id))
private val interactor = TranslationsInteractor(translationsController)
@Test
fun `Interactor onTranslate called the translate controller as expected`() {
val from = "en"
val to = "es"
val options = TranslationOptions(false)
interactor.onTranslate(tab.id, from, to, options)
verify(translationsController).translate(tab.id, from, to, options)
verify(translationsController, never()).getDetectedLanguages()
}
@Test
fun `Interactor onTranslate called the translate controller as expected when languages are null`() {
val mockFrom = "es"
val mockTo = "en"
val mockDetectedLanguages = DetectedLanguages(
documentLangTag = mockFrom,
supportedDocumentLang = true,
userPreferredLangTag = mockTo,
)
whenever(translationsController.getDetectedLanguages()).thenReturn(mockDetectedLanguages)
val from = null
val to = null
val options = TranslationOptions(false)
interactor.onTranslate(tab.id, from, to, options)
verify(translationsController).translate(tab.id, from, to, options)
verify(translationsController).getDetectedLanguages()
verify(translationsUseCase).invoke(tab.id, mockFrom, mockTo, options)
}
@Test
fun `Interactor detectedLanguages called the controller to pull detected languages`() {
interactor.detectedLanguages()
verify(translationsController).getDetectedLanguages()
}
}
Loading…
Cancel
Save