added support for translations and printing
This commit is contained in:
+2
-2
@@ -227,8 +227,8 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
||||
|
||||
val engineView = createEngine(components)
|
||||
fragmentEngineView = engineView // Track for lifecycle management
|
||||
val originalContext = ActivityContextWrapper.getOriginalContext(requireActivity())
|
||||
?.let { ProfileContext(it, components.profileApplicationContext.relativePath) }
|
||||
val originalContext =
|
||||
ActivityContextWrapper.getOriginalContext(requireActivity()) ?: requireActivity()
|
||||
val engineNativeView = engineView.asView()
|
||||
engineNativeView.layoutParams = layoutParams
|
||||
|
||||
|
||||
+80
@@ -27,6 +27,9 @@ import eu.weblibre.flutter_mozilla_components.pigeons.ReaderableState
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.RestoreLocation
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.SecurityInfoState
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.TabContentState
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.TabTranslationStateData
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.TranslationEngineStateData
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.TranslationLanguage
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
@@ -39,6 +42,7 @@ import mozilla.components.browser.icons.IconRequest
|
||||
import mozilla.components.browser.session.storage.RecoverableBrowserState
|
||||
import mozilla.components.browser.state.action.EngineAction
|
||||
import mozilla.components.browser.state.action.TabListAction
|
||||
import mozilla.components.browser.state.action.TranslationsAction
|
||||
import mozilla.components.browser.state.selector.findTab
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.browser.state.state.LastMediaAccessState
|
||||
@@ -55,6 +59,7 @@ import org.json.JSONObject
|
||||
import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
|
||||
import mozilla.components.feature.addons.logger
|
||||
import mozilla.components.concept.engine.EngineSession
|
||||
import mozilla.components.concept.engine.translate.TranslationOperation
|
||||
|
||||
class GeckoTabsApiImpl : GeckoTabsApi {
|
||||
companion object {
|
||||
@@ -185,6 +190,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
|
||||
onPageExtensionsChange: Boolean,
|
||||
onBrowserExtensionIcons: Boolean,
|
||||
onPageExtensionIcons: Boolean,
|
||||
onTranslationStateChange: Boolean,
|
||||
) {
|
||||
try {
|
||||
val tabs = components.core.store.state.tabs.map { it.copy() }
|
||||
@@ -293,11 +299,85 @@ class GeckoTabsApiImpl : GeckoTabsApi {
|
||||
onPageExtensionIcons
|
||||
)
|
||||
}
|
||||
|
||||
// Sync translation events
|
||||
if (onTranslationStateChange) {
|
||||
syncTranslationEvents(tabs)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("$TAG: Failed to sync events", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun syncTranslationEvents(tabs: List<mozilla.components.browser.state.state.TabSessionState>) {
|
||||
try {
|
||||
val browserState = components.core.store.state
|
||||
|
||||
// Retry initialization when support status is still unknown.
|
||||
if (browserState.translationEngine.isEngineSupported == null) {
|
||||
components.core.store.dispatch(TranslationsAction.InitTranslationsBrowserState)
|
||||
}
|
||||
|
||||
// Fetch supported languages when engine is supported but languages are still missing.
|
||||
if (
|
||||
browserState.translationEngine.isEngineSupported == true &&
|
||||
(
|
||||
browserState.translationEngine.supportedLanguages?.fromLanguages == null ||
|
||||
browserState.translationEngine.supportedLanguages?.toLanguages == null
|
||||
)
|
||||
) {
|
||||
components.core.store.dispatch(
|
||||
TranslationsAction.OperationRequestedAction(
|
||||
tabId = browserState.selectedTabId,
|
||||
operation = TranslationOperation.FETCH_SUPPORTED_LANGUAGES,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val translationEngine = browserState.translationEngine
|
||||
|
||||
// Emit browser-level engine state
|
||||
val fromLanguages = translationEngine.supportedLanguages?.fromLanguages?.map { lang ->
|
||||
TranslationLanguage(code = lang.code, localizedDisplayName = lang.localizedDisplayName ?: lang.code)
|
||||
}
|
||||
val toLanguages = translationEngine.supportedLanguages?.toLanguages?.map { lang ->
|
||||
TranslationLanguage(code = lang.code, localizedDisplayName = lang.localizedDisplayName ?: lang.code)
|
||||
}
|
||||
|
||||
components.flutterEvents.onTranslationEngineStateChange(
|
||||
EventSequence.next(),
|
||||
TranslationEngineStateData(
|
||||
isEngineSupported = translationEngine.isEngineSupported,
|
||||
fromLanguages = fromLanguages,
|
||||
toLanguages = toLanguages,
|
||||
)
|
||||
) { }
|
||||
|
||||
// Emit per-tab translation state
|
||||
tabs.forEach { tab ->
|
||||
val translationsState = tab.translationsState
|
||||
components.flutterEvents.onTabTranslationStateChange(
|
||||
EventSequence.next(),
|
||||
TabTranslationStateData(
|
||||
tabId = tab.id,
|
||||
isTranslated = translationsState.isTranslated,
|
||||
isTranslateProcessing = translationsState.isTranslateProcessing,
|
||||
isOfferTranslate = translationsState.isOfferTranslate,
|
||||
isExpectedTranslate = translationsState.isExpectedTranslate,
|
||||
detectedLanguageCode = translationsState.translationEngineState?.detectedLanguages?.documentLangTag,
|
||||
userPreferredLanguageCode = translationsState.translationEngineState?.detectedLanguages?.userPreferredLangTag,
|
||||
requestedFromLanguage = translationsState.translationEngineState?.requestedTranslationPair?.fromLanguage,
|
||||
requestedToLanguage = translationsState.translationEngineState?.requestedTranslationPair?.toLanguage,
|
||||
translationErrorName = translationsState.translationError?.errorName,
|
||||
displayError = translationsState.translationError?.displayError,
|
||||
)
|
||||
) { }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("$TAG: Failed to sync translation events", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun syncExtensionEvents(
|
||||
onBrowserExtensionsChange: Boolean,
|
||||
onPageExtensionsChange: Boolean,
|
||||
|
||||
+5
@@ -30,6 +30,7 @@ import mozilla.components.browser.icons.BrowserIcons
|
||||
import mozilla.components.browser.session.storage.SessionStorage
|
||||
import mozilla.components.browser.state.engine.EngineMiddleware
|
||||
import mozilla.components.browser.state.engine.middleware.SessionPrioritizationMiddleware
|
||||
import mozilla.components.browser.state.engine.middleware.TranslationsMiddleware
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
|
||||
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
|
||||
@@ -67,6 +68,7 @@ import mozilla.components.concept.base.crash.Breadcrumb
|
||||
import mozilla.components.concept.base.crash.CrashReporting
|
||||
import mozilla.components.support.base.worker.Frequency
|
||||
import org.mozilla.geckoview.GeckoRuntime
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.Job
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -207,6 +209,9 @@ class Core(
|
||||
SaveToPDFMiddleware(),
|
||||
FileUploadsDirCleanerMiddleware(fileUploadsDirCleaner),
|
||||
LastMediaAccessMiddleware(),
|
||||
// Keep parity with Fenix: do not initialize translations on store init.
|
||||
// EngineObserver will dispatch InitTranslationsBrowserState after first completed page load.
|
||||
TranslationsMiddleware(engine, MainScope(), false),
|
||||
) + EngineMiddleware.create(
|
||||
engine,
|
||||
// We are disabling automatic suspending of engine sessions under memory pressure.
|
||||
|
||||
+68
@@ -23,6 +23,9 @@ import eu.weblibre.flutter_mozilla_components.pigeons.ShareTarget
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.ShareTargetFiles
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.ShareTargetParams
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.TabContentState
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.TabTranslationStateData
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.TranslationEngineStateData
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.TranslationLanguage
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
@@ -222,6 +225,71 @@ class Events(
|
||||
}
|
||||
}
|
||||
|
||||
// Translation engine state (browser-level)
|
||||
stateFlow.flowScoped { flow ->
|
||||
flow.map { state -> state.translationEngine }
|
||||
.distinctUntilChanged { old, new ->
|
||||
old.isEngineSupported == new.isEngineSupported &&
|
||||
old.supportedLanguages == new.supportedLanguages
|
||||
}
|
||||
.debounce(50)
|
||||
.collect { translationEngine ->
|
||||
val fromLanguages = translationEngine.supportedLanguages?.fromLanguages?.map { lang ->
|
||||
TranslationLanguage(code = lang.code, localizedDisplayName = lang.localizedDisplayName ?: lang.code)
|
||||
}
|
||||
val toLanguages = translationEngine.supportedLanguages?.toLanguages?.map { lang ->
|
||||
TranslationLanguage(code = lang.code, localizedDisplayName = lang.localizedDisplayName ?: lang.code)
|
||||
}
|
||||
|
||||
flutterEvents.onTranslationEngineStateChange(
|
||||
EventSequence.next(),
|
||||
TranslationEngineStateData(
|
||||
isEngineSupported = translationEngine.isEngineSupported,
|
||||
fromLanguages = fromLanguages,
|
||||
toLanguages = toLanguages,
|
||||
)
|
||||
) { _ -> }
|
||||
}
|
||||
}
|
||||
|
||||
// Per-tab translation state
|
||||
stateFlow.flowScoped { flow ->
|
||||
flow.mapNotNull { state -> state.tabs }
|
||||
.filterChanged {
|
||||
it.translationsState
|
||||
}
|
||||
.ifAnyChanged {
|
||||
arrayOf(
|
||||
it.translationsState.isTranslated,
|
||||
it.translationsState.isTranslateProcessing,
|
||||
it.translationsState.isOfferTranslate,
|
||||
it.translationsState.isExpectedTranslate,
|
||||
it.translationsState.translationEngineState,
|
||||
it.translationsState.translationError,
|
||||
)
|
||||
}
|
||||
.debounce(25)
|
||||
.collect { tab ->
|
||||
val ts = tab.translationsState
|
||||
flutterEvents.onTabTranslationStateChange(
|
||||
EventSequence.next(),
|
||||
TabTranslationStateData(
|
||||
tabId = tab.id,
|
||||
isTranslated = ts.isTranslated,
|
||||
isTranslateProcessing = ts.isTranslateProcessing,
|
||||
isOfferTranslate = ts.isOfferTranslate,
|
||||
isExpectedTranslate = ts.isExpectedTranslate,
|
||||
detectedLanguageCode = ts.translationEngineState?.detectedLanguages?.documentLangTag,
|
||||
userPreferredLanguageCode = ts.translationEngineState?.detectedLanguages?.userPreferredLangTag,
|
||||
requestedFromLanguage = ts.translationEngineState?.requestedTranslationPair?.fromLanguage,
|
||||
requestedToLanguage = ts.translationEngineState?.requestedTranslationPair?.toLanguage,
|
||||
translationErrorName = ts.translationError?.errorName,
|
||||
displayError = ts.translationError?.displayError,
|
||||
)
|
||||
) { _ -> }
|
||||
}
|
||||
}
|
||||
|
||||
// PWA manifest availability events - following Fenix MenuPresenter pattern
|
||||
stateFlow.flowScoped { flow ->
|
||||
flow.mapNotNull { state -> state.selectedTab }
|
||||
|
||||
+9
@@ -24,6 +24,15 @@ class SaveToPDFMiddleware : Middleware<BrowserState, BrowserAction> {
|
||||
is EngineAction.SaveToPdfExceptionAction -> {
|
||||
logger.error("Unable to save PDF", action.throwable)
|
||||
}
|
||||
is EngineAction.PrintContentAction -> {
|
||||
next(action)
|
||||
}
|
||||
is EngineAction.PrintContentCompletedAction -> {
|
||||
logger.info("Print completed successfully")
|
||||
}
|
||||
is EngineAction.PrintContentExceptionAction -> {
|
||||
logger.error("Unable to print content", action.throwable)
|
||||
}
|
||||
else -> {
|
||||
next(action)
|
||||
}
|
||||
|
||||
+419
-131
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user