migrate mozilla components into project
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
build/
|
||||
@@ -0,0 +1,30 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "6fe09872b12acb5747d6e01e84987120cabf31df"
|
||||
channel: "master"
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 6fe09872b12acb5747d6e01e84987120cabf31df
|
||||
base_revision: 6fe09872b12acb5747d6e01e84987120cabf31df
|
||||
- platform: android
|
||||
create_revision: 6fe09872b12acb5747d6e01e84987120cabf31df
|
||||
base_revision: 6fe09872b12acb5747d6e01e84987120cabf31df
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
@@ -0,0 +1,3 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -0,0 +1 @@
|
||||
TODO: Add your license here.
|
||||
@@ -0,0 +1,15 @@
|
||||
# flutter_mozilla_components
|
||||
|
||||
A new Flutter plugin project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter
|
||||
[plug-in package](https://flutter.dev/to/develop-plugins),
|
||||
a specialized package that includes platform-specific implementation code for
|
||||
Android and/or iOS.
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -0,0 +1,9 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
@@ -0,0 +1,112 @@
|
||||
group = "eu.lensai.flutter_mozilla_components"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.9.20"
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.6.0")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://maven.mozilla.org/maven2/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "com.android.library"
|
||||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
if (project.android.hasProperty("namespace")) {
|
||||
namespace = "eu.lensai.flutter_mozilla_components"
|
||||
}
|
||||
|
||||
compileSdk = 35
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += "src/main/kotlin"
|
||||
test.java.srcDirs += "src/test/kotlin"
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.mozilla.components:support-utils:131.0b1'
|
||||
implementation 'org.mozilla.components:support-ktx:131.0b1'
|
||||
implementation 'org.mozilla.components:support-webextensions:131.0b1'
|
||||
implementation 'org.mozilla.components:support-locale:131.0b1'
|
||||
implementation 'org.mozilla.components:lib-fetch-httpurlconnection:131.0b1'
|
||||
implementation 'org.mozilla.components:lib-crash:131.0b1'
|
||||
implementation 'org.mozilla.components:lib-publicsuffixlist:131.0b1'
|
||||
implementation 'org.mozilla.components:lib-dataprotect:131.0b1'
|
||||
implementation 'org.mozilla.components:concept-engine:131.0b1'
|
||||
implementation 'org.mozilla.components:concept-fetch:131.0b1'
|
||||
implementation 'org.mozilla.components:browser-engine-gecko:131.0b1'
|
||||
implementation 'org.mozilla.components:browser-state:131.0b1'
|
||||
implementation 'org.mozilla.components:browser-session-storage:131.0b1'
|
||||
implementation 'org.mozilla.components:browser-icons:131.0b1'
|
||||
//implementation 'org.mozilla.components:browser-storage-sync:131.0b1'
|
||||
implementation 'org.mozilla.components:browser-thumbnails:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-addons:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-app-links:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-autofill:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-downloads:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-media:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-tabs:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-prompts:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-session:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-readerview:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-privatemode:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-sitepermissions:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-webcompat:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-webnotifications:131.0b1'
|
||||
implementation 'org.mozilla.components:service-digitalassetlinks:131.0b1'
|
||||
implementation 'org.mozilla.components:ui-widgets:131.0b1'
|
||||
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
//https://stackoverflow.com/questions/73782320/onbackinvokedcallback-is-not-enabled-for-the-application-in-set-androidenableo
|
||||
implementation 'androidx.activity:activity:1.9.1'
|
||||
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.mockito:mockito-core:5.0.0")
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
outputs.upToDateWhen {false}
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = 'flutter_mozilla_components'
|
||||
@@ -0,0 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="eu.lensai.flutter_mozilla_components">
|
||||
</manifest>
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import mozilla.components.browser.errorpages.ErrorPages
|
||||
import mozilla.components.browser.errorpages.ErrorType
|
||||
import mozilla.components.concept.engine.EngineSession
|
||||
import mozilla.components.concept.engine.request.RequestInterceptor
|
||||
|
||||
class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
|
||||
override fun onLoadRequest(
|
||||
engineSession: EngineSession,
|
||||
uri: String,
|
||||
lastUri: String?,
|
||||
hasUserGesture: Boolean,
|
||||
isSameDomain: Boolean,
|
||||
isRedirect: Boolean,
|
||||
isDirectNavigation: Boolean,
|
||||
isSubframeRequest: Boolean,
|
||||
): RequestInterceptor.InterceptionResponse? {
|
||||
val components = GlobalComponents.components!!
|
||||
|
||||
return when (uri) {
|
||||
// "about:privatebrowsing" -> {
|
||||
// val page = PrivatePage.createPrivateBrowsingPage(context, uri)
|
||||
// RequestInterceptor.InterceptionResponse.Content(page, encoding = "base64")
|
||||
// }
|
||||
|
||||
// "about:crashes" -> {
|
||||
// val intent = Intent(context, CrashListActivity::class.java)
|
||||
// intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
|
||||
// context.startActivity(intent)
|
||||
//
|
||||
// RequestInterceptor.InterceptionResponse.Url("about:blank")
|
||||
// }
|
||||
|
||||
else -> {
|
||||
components.appLinksInterceptor.onLoadRequest(
|
||||
engineSession,
|
||||
uri,
|
||||
lastUri,
|
||||
hasUserGesture,
|
||||
isSameDomain,
|
||||
isRedirect,
|
||||
isDirectNavigation,
|
||||
isSubframeRequest,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onErrorRequest(
|
||||
session: EngineSession,
|
||||
errorType: ErrorType,
|
||||
uri: String?,
|
||||
): RequestInterceptor.ErrorResponse {
|
||||
val errorPage = ErrorPages.createUrlEncodedErrorPage(context, errorType, uri)
|
||||
return RequestInterceptor.ErrorResponse(errorPage)
|
||||
}
|
||||
|
||||
override fun interceptsAppInitiatedRequests() = true
|
||||
}
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.fragment.app.Fragment
|
||||
import eu.lensai.flutter_mozilla_components.databinding.FragmentBrowserBinding
|
||||
import mozilla.components.concept.engine.EngineView
|
||||
import mozilla.components.feature.app.links.AppLinksFeature
|
||||
import mozilla.components.feature.downloads.DownloadsFeature
|
||||
import mozilla.components.feature.downloads.manager.FetchDownloadManager
|
||||
import mozilla.components.feature.privatemode.feature.SecureWindowFeature
|
||||
import mozilla.components.feature.prompts.PromptFeature
|
||||
import mozilla.components.feature.session.SessionFeature
|
||||
import mozilla.components.feature.session.SwipeRefreshFeature
|
||||
import mozilla.components.feature.sitepermissions.SitePermissionsFeature
|
||||
import mozilla.components.feature.sitepermissions.SitePermissionsRules
|
||||
import mozilla.components.feature.sitepermissions.SitePermissionsRules.AutoplayAction
|
||||
import mozilla.components.support.base.feature.ActivityResultHandler
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import mozilla.components.support.ktx.android.arch.lifecycle.addObservers
|
||||
import mozilla.components.support.locale.ActivityContextWrapper
|
||||
import mozilla.components.support.utils.ext.requestInPlacePermissions
|
||||
|
||||
/**
|
||||
* Base fragment extended by [BrowserFragment] and [ExternalAppBrowserFragment].
|
||||
* This class only contains shared code focused on the main browsing content.
|
||||
* UI code specific to the app or to custom tabs can be found in the subclasses.
|
||||
*/
|
||||
@SuppressWarnings("LargeClass")
|
||||
abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, ActivityResultHandler {
|
||||
private val sessionFeature = ViewBoundFeatureWrapper<SessionFeature>()
|
||||
private val downloadsFeature = ViewBoundFeatureWrapper<DownloadsFeature>()
|
||||
private val appLinksFeature = ViewBoundFeatureWrapper<AppLinksFeature>()
|
||||
private val promptFeature = ViewBoundFeatureWrapper<PromptFeature>()
|
||||
private val sitePermissionsFeature = ViewBoundFeatureWrapper<SitePermissionsFeature>()
|
||||
private val swipeRefreshFeature = ViewBoundFeatureWrapper<SwipeRefreshFeature>()
|
||||
|
||||
protected val sessionId: String?
|
||||
get() = arguments?.getString(SESSION_ID_KEY)
|
||||
|
||||
private val activityResultHandler: List<ViewBoundFeatureWrapper<*>> = listOf(
|
||||
promptFeature,
|
||||
)
|
||||
|
||||
private var _engineView: EngineView? = null
|
||||
private var _binding: FragmentBrowserBinding? = null
|
||||
private var _components: Components? = null
|
||||
|
||||
val binding get() = _binding!!
|
||||
val components get() = _components!!
|
||||
val engineView get() = _engineView!!
|
||||
|
||||
protected abstract fun createEngine(components: Components) : EngineView
|
||||
|
||||
@CallSuper
|
||||
@Suppress("LongMethod")
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentBrowserBinding.inflate(inflater, container, false)
|
||||
_components = GlobalComponents.components
|
||||
|
||||
_engineView = createEngine(components)
|
||||
|
||||
val engineNativeView = engineView.asView()
|
||||
// Set layout parameters
|
||||
val layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
engineNativeView.layoutParams = layoutParams
|
||||
|
||||
binding.swipeToRefresh.addView(engineNativeView)
|
||||
|
||||
val originalContext = ActivityContextWrapper.getOriginalContext(requireActivity())
|
||||
engineView.setActivityContext(originalContext)
|
||||
|
||||
sessionFeature.set(
|
||||
feature = SessionFeature(
|
||||
components.store,
|
||||
components.sessionUseCases.goBack,
|
||||
components.sessionUseCases.goForward,
|
||||
engineView,
|
||||
sessionId,
|
||||
),
|
||||
owner = this,
|
||||
view = binding.root,
|
||||
)
|
||||
|
||||
swipeRefreshFeature.set(
|
||||
feature = SwipeRefreshFeature(
|
||||
components.store,
|
||||
components.sessionUseCases.reload,
|
||||
binding.swipeToRefresh,
|
||||
),
|
||||
owner = this,
|
||||
view = binding.root,
|
||||
)
|
||||
|
||||
downloadsFeature.set(
|
||||
feature = DownloadsFeature(
|
||||
requireContext().applicationContext,
|
||||
store = components.store,
|
||||
useCases = components.downloadsUseCases,
|
||||
fragmentManager = childFragmentManager,
|
||||
onDownloadStopped = { download, id, status ->
|
||||
Logger.debug("Download done. ID#$id $download with status $status")
|
||||
},
|
||||
downloadManager = FetchDownloadManager(
|
||||
requireContext().applicationContext,
|
||||
components.store,
|
||||
DownloadService::class,
|
||||
notificationsDelegate = components.notificationsDelegate,
|
||||
),
|
||||
tabId = sessionId,
|
||||
onNeedToRequestPermissions = { permissions ->
|
||||
requestInPlacePermissions(REQUEST_KEY_DOWNLOAD_PERMISSIONS, permissions) { result ->
|
||||
downloadsFeature.get()?.onPermissionsResult(
|
||||
result.keys.toTypedArray(),
|
||||
result.values.map {
|
||||
when (it) {
|
||||
true -> PackageManager.PERMISSION_GRANTED
|
||||
false -> PackageManager.PERMISSION_DENIED
|
||||
}
|
||||
}.toIntArray(),
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
owner = this,
|
||||
view = binding.root,
|
||||
)
|
||||
|
||||
appLinksFeature.set(
|
||||
feature = AppLinksFeature(
|
||||
context = requireContext(),
|
||||
store = components.store,
|
||||
sessionId = sessionId,
|
||||
fragmentManager = parentFragmentManager,
|
||||
launchInApp = { components.preferences.getBoolean(DefaultComponents.PREF_LAUNCH_EXTERNAL_APP, false) },
|
||||
loadUrlUseCase = components.sessionUseCases.loadUrl,
|
||||
),
|
||||
owner = this,
|
||||
view = binding.root,
|
||||
)
|
||||
|
||||
promptFeature.set(
|
||||
feature = PromptFeature(
|
||||
fragment = this,
|
||||
store = components.store,
|
||||
customTabId = sessionId,
|
||||
tabsUseCases = components.tabsUseCases,
|
||||
fragmentManager = parentFragmentManager,
|
||||
fileUploadsDirCleaner = components.fileUploadsDirCleaner,
|
||||
onNeedToRequestPermissions = { permissions ->
|
||||
requestInPlacePermissions(REQUEST_KEY_PROMPT_PERMISSIONS, permissions) { result ->
|
||||
promptFeature.get()?.onPermissionsResult(
|
||||
result.keys.toTypedArray(),
|
||||
result.values.map {
|
||||
when (it) {
|
||||
true -> PackageManager.PERMISSION_GRANTED
|
||||
false -> PackageManager.PERMISSION_DENIED
|
||||
}
|
||||
}.toIntArray(),
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
owner = this,
|
||||
view = binding.root,
|
||||
)
|
||||
|
||||
sitePermissionsFeature.set(
|
||||
feature = SitePermissionsFeature(
|
||||
context = requireContext(),
|
||||
sessionId = sessionId,
|
||||
storage = components.permissionStorage,
|
||||
fragmentManager = parentFragmentManager,
|
||||
sitePermissionsRules = SitePermissionsRules(
|
||||
autoplayAudible = AutoplayAction.BLOCKED,
|
||||
autoplayInaudible = AutoplayAction.BLOCKED,
|
||||
camera = SitePermissionsRules.Action.ASK_TO_ALLOW,
|
||||
location = SitePermissionsRules.Action.ASK_TO_ALLOW,
|
||||
notification = SitePermissionsRules.Action.ASK_TO_ALLOW,
|
||||
microphone = SitePermissionsRules.Action.ASK_TO_ALLOW,
|
||||
persistentStorage = SitePermissionsRules.Action.ASK_TO_ALLOW,
|
||||
mediaKeySystemAccess = SitePermissionsRules.Action.ASK_TO_ALLOW,
|
||||
crossOriginStorageAccess = SitePermissionsRules.Action.ASK_TO_ALLOW,
|
||||
),
|
||||
onNeedToRequestPermissions = { permissions ->
|
||||
requestInPlacePermissions(REQUEST_KEY_SITE_PERMISSIONS, permissions) { result ->
|
||||
sitePermissionsFeature.get()?.onPermissionsResult(
|
||||
result.keys.toTypedArray(),
|
||||
result.values.map {
|
||||
when (it) {
|
||||
true -> PackageManager.PERMISSION_GRANTED
|
||||
false -> PackageManager.PERMISSION_DENIED
|
||||
}
|
||||
}.toIntArray(),
|
||||
)
|
||||
}
|
||||
},
|
||||
onShouldShowRequestPermissionRationale = { shouldShowRequestPermissionRationale(it) },
|
||||
store = components.store,
|
||||
),
|
||||
owner = this,
|
||||
view = binding.root,
|
||||
)
|
||||
|
||||
val secureWindowFeature = SecureWindowFeature(
|
||||
window = requireActivity().window,
|
||||
store = components.store,
|
||||
customTabId = sessionId,
|
||||
)
|
||||
|
||||
// Observe the lifecycle for supported features
|
||||
lifecycle.addObservers(
|
||||
secureWindowFeature,
|
||||
)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
// consumeFlow(components.store) { flow ->
|
||||
// flow.mapNotNull { state -> state.findCustomTabOrSelectedTab(sessionId) }
|
||||
// .ifAnyChanged { tab ->
|
||||
// arrayOf(
|
||||
// tab.content.loading,
|
||||
// tab.content.canGoBack,
|
||||
// tab.content.canGoForward,
|
||||
// )
|
||||
// }
|
||||
// .collect {
|
||||
// binding.toolbar.invalidateActions()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onBackPressed(): Boolean =
|
||||
listOf(sessionFeature).any { it.onBackPressed() }
|
||||
|
||||
@CallSuper
|
||||
override fun onActivityResult(requestCode: Int, data: Intent?, resultCode: Int): Boolean {
|
||||
return activityResultHandler.any { it.onActivityResult(requestCode, data, resultCode) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SESSION_ID_KEY = "session_id"
|
||||
|
||||
private const val REQUEST_KEY_DOWNLOAD_PERMISSIONS = "downloadFeature"
|
||||
private const val REQUEST_KEY_PROMPT_PERMISSIONS = "promptFeature"
|
||||
private const val REQUEST_KEY_SITE_PERMISSIONS = "sitePermissionsFeature"
|
||||
|
||||
@JvmStatic
|
||||
protected fun Bundle.putSessionId(sessionId: String?) {
|
||||
putString(SESSION_ID_KEY, sessionId)
|
||||
}
|
||||
}
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
engineView.setActivityContext(null)
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import mozilla.components.browser.thumbnails.BrowserThumbnails
|
||||
import mozilla.components.concept.engine.EngineView
|
||||
import mozilla.components.feature.media.fullscreen.MediaSessionFullscreenFeature
|
||||
import mozilla.components.feature.session.FullScreenFeature
|
||||
import mozilla.components.feature.tabs.WindowFeature
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
||||
import mozilla.components.support.ktx.android.view.enterImmersiveMode
|
||||
import mozilla.components.support.ktx.android.view.exitImmersiveMode
|
||||
|
||||
/**
|
||||
* Fragment used for browsing the web within the main app.
|
||||
*/
|
||||
class BrowserFragment(private val context: Context) : BaseBrowserFragment(), UserInteractionHandler {
|
||||
private val thumbnailsFeature = ViewBoundFeatureWrapper<BrowserThumbnails>()
|
||||
private val fullScreenFeature = ViewBoundFeatureWrapper<FullScreenFeature>()
|
||||
private val mediaSessionFullscreenFeature =
|
||||
ViewBoundFeatureWrapper<MediaSessionFullscreenFeature>()
|
||||
|
||||
override fun createEngine(components: Components): EngineView {
|
||||
return components.engine.createView(context).apply {
|
||||
// selectionActionDelegate = DefaultSelectionActionDelegate(
|
||||
// store = components.store,
|
||||
// context = context,
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
||||
val binding = super.binding
|
||||
|
||||
fullScreenFeature.set(
|
||||
feature = FullScreenFeature(
|
||||
components.store,
|
||||
components.sessionUseCases,
|
||||
sessionId,
|
||||
) { inFullScreen ->
|
||||
if (inFullScreen) {
|
||||
activity?.enterImmersiveMode()
|
||||
} else {
|
||||
activity?.exitImmersiveMode()
|
||||
}
|
||||
},
|
||||
owner = this,
|
||||
view = binding.root,
|
||||
)
|
||||
|
||||
mediaSessionFullscreenFeature.set(
|
||||
feature = MediaSessionFullscreenFeature(
|
||||
requireActivity(),
|
||||
components.store,
|
||||
sessionId,
|
||||
),
|
||||
owner = this,
|
||||
view = binding.root,
|
||||
)
|
||||
|
||||
thumbnailsFeature.set(
|
||||
feature = BrowserThumbnails(requireContext(), engineView, components.store),
|
||||
owner = this,
|
||||
view = binding.root,
|
||||
)
|
||||
|
||||
val windowFeature = WindowFeature(components.store, components.tabsUseCases)
|
||||
lifecycle.addObserver(windowFeature)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
return when {
|
||||
fullScreenFeature.onBackPressed() -> true
|
||||
else -> super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(context: Context, sessionId: String? = null) = BrowserFragment(context).apply {
|
||||
arguments = Bundle().apply {
|
||||
putSessionId(sessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import eu.lensai.flutter_mozilla_components.middleware.FlutterEventMiddleware
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.engine.gecko.GeckoEngine
|
||||
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.selector.selectedTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.browser.thumbnails.ThumbnailsMiddleware
|
||||
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
|
||||
import mozilla.components.concept.base.crash.Breadcrumb
|
||||
import mozilla.components.concept.engine.DefaultSettings
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import mozilla.components.concept.engine.mediaquery.PreferredColorScheme
|
||||
import mozilla.components.concept.fetch.Client
|
||||
import mozilla.components.feature.addons.AddonManager
|
||||
import mozilla.components.feature.addons.amo.AMOAddonsProvider
|
||||
import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker
|
||||
import mozilla.components.feature.addons.update.DefaultAddonUpdater
|
||||
import mozilla.components.feature.app.links.AppLinksInterceptor
|
||||
import mozilla.components.feature.app.links.AppLinksUseCases
|
||||
import mozilla.components.feature.downloads.DownloadMiddleware
|
||||
import mozilla.components.feature.downloads.DownloadsUseCases
|
||||
import mozilla.components.feature.media.MediaSessionFeature
|
||||
import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
|
||||
import mozilla.components.feature.prompts.PromptMiddleware
|
||||
import mozilla.components.feature.prompts.file.FileUploadsDirCleaner
|
||||
import mozilla.components.feature.readerview.ReaderViewMiddleware
|
||||
import mozilla.components.feature.session.SessionUseCases
|
||||
import mozilla.components.feature.tabs.TabsUseCases
|
||||
import mozilla.components.feature.session.middleware.LastAccessMiddleware
|
||||
import mozilla.components.feature.session.middleware.undo.UndoMiddleware
|
||||
import mozilla.components.feature.sitepermissions.OnDiskSitePermissionsStorage
|
||||
import mozilla.components.feature.webnotifications.WebNotificationFeature
|
||||
import mozilla.components.lib.crash.Crash
|
||||
import mozilla.components.lib.crash.CrashReporter
|
||||
import mozilla.components.lib.crash.service.CrashReporterService
|
||||
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
|
||||
import mozilla.components.lib.fetch.httpurlconnection.HttpURLConnectionClient
|
||||
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||
import mozilla.components.lib.state.ext.flowScoped
|
||||
import mozilla.components.service.digitalassetlinks.local.StatementApi
|
||||
import mozilla.components.service.digitalassetlinks.local.StatementRelationChecker
|
||||
import mozilla.components.support.base.android.NotificationsDelegate
|
||||
import mozilla.components.support.base.worker.Frequency
|
||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.filterChanged
|
||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val DAY_IN_MINUTES = 24 * 60L
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Suppress("LargeClass")
|
||||
open class DefaultComponents(private val applicationContext: Context) {
|
||||
companion object {
|
||||
const val SAMPLE_BROWSER_PREFERENCES = "sample_browser_preferences"
|
||||
const val PREF_LAUNCH_EXTERNAL_APP = "sample_browser_launch_external_app"
|
||||
const val PREF_GLOBAL_PRIVACY_CONTROL = "sample_browser_global_privacy_control"
|
||||
}
|
||||
|
||||
val preferences: SharedPreferences =
|
||||
applicationContext.getSharedPreferences(SAMPLE_BROWSER_PREFERENCES, Context.MODE_PRIVATE)
|
||||
|
||||
private val securePreferences by lazy { SecureAbove22Preferences(applicationContext, "key_store") }
|
||||
|
||||
val publicSuffixList by lazy { PublicSuffixList(applicationContext) }
|
||||
|
||||
// Engine Settings
|
||||
val engineSettings by lazy {
|
||||
DefaultSettings().apply {
|
||||
//historyTrackingDelegate = HistoryDelegate(lazyHistoryStorage)
|
||||
requestInterceptor = AppRequestInterceptor(applicationContext)
|
||||
remoteDebuggingEnabled = true
|
||||
supportMultipleWindows = true
|
||||
preferredColorScheme = PreferredColorScheme.Dark
|
||||
httpsOnlyMode = Engine.HttpsOnlyMode.ENABLED
|
||||
globalPrivacyControlEnabled = preferences.getBoolean(
|
||||
PREF_GLOBAL_PRIVACY_CONTROL,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val notificationManagerCompat = NotificationManagerCompat.from(applicationContext)
|
||||
|
||||
val notificationsDelegate: NotificationsDelegate by lazy {
|
||||
NotificationsDelegate(
|
||||
notificationManagerCompat,
|
||||
)
|
||||
}
|
||||
|
||||
val addonUpdater =
|
||||
DefaultAddonUpdater(applicationContext, Frequency(1, TimeUnit.DAYS), notificationsDelegate)
|
||||
|
||||
// Engine
|
||||
open val engine: Engine by lazy {
|
||||
GeckoEngine(applicationContext, engineSettings)
|
||||
}
|
||||
|
||||
val icons by lazy { BrowserIcons(applicationContext, client) }
|
||||
|
||||
open val client: Client by lazy { HttpURLConnectionClient() }
|
||||
|
||||
// Storage
|
||||
//private val lazyHistoryStorage = lazy { PlacesHistoryStorage(applicationContext) }
|
||||
//val historyStorage by lazy { lazyHistoryStorage.value }
|
||||
|
||||
val sessionStorage by lazy { SessionStorage(applicationContext, engine) }
|
||||
|
||||
val permissionStorage by lazy { OnDiskSitePermissionsStorage(applicationContext) }
|
||||
|
||||
val thumbnailStorage by lazy { ThumbnailStorage(applicationContext) }
|
||||
|
||||
val fileUploadsDirCleaner: FileUploadsDirCleaner by lazy {
|
||||
FileUploadsDirCleaner { applicationContext.cacheDir }
|
||||
}
|
||||
|
||||
val store by lazy {
|
||||
BrowserStore(
|
||||
middleware = listOf(
|
||||
FlutterEventMiddleware(),
|
||||
DownloadMiddleware(applicationContext, DownloadService::class.java),
|
||||
ReaderViewMiddleware(),
|
||||
ThumbnailsMiddleware(thumbnailStorage),
|
||||
UndoMiddleware(),
|
||||
RecordingDevicesMiddleware(applicationContext, notificationsDelegate),
|
||||
LastAccessMiddleware(),
|
||||
PromptMiddleware(),
|
||||
SessionPrioritizationMiddleware(),
|
||||
) + EngineMiddleware.create(engine),
|
||||
).apply {
|
||||
this.flowScoped { flow ->
|
||||
flow.mapNotNull { state -> state.selectedTab }
|
||||
.distinctUntilChangedBy {
|
||||
it.content.url
|
||||
}
|
||||
.collect { tab ->
|
||||
Log.d("URL_CHANGE", tab.content.url)
|
||||
}
|
||||
}
|
||||
|
||||
this.flowScoped { flow ->
|
||||
flow.mapNotNull { state -> state.tabs }
|
||||
.filterChanged {
|
||||
it.content
|
||||
}
|
||||
.ifAnyChanged { arrayOf(it.content.url) }
|
||||
.collect { tab ->
|
||||
Log.d("URL_CHANGE2", tab.content.url)
|
||||
}
|
||||
}
|
||||
|
||||
WebNotificationFeature(
|
||||
applicationContext,
|
||||
engine,
|
||||
icons,
|
||||
R.drawable.ic_launcher_foreground,
|
||||
permissionStorage,
|
||||
NotificationActivity::class.java,
|
||||
notificationsDelegate = notificationsDelegate,
|
||||
)
|
||||
|
||||
MediaSessionFeature(applicationContext, MediaSessionService::class.java, this).start()
|
||||
}
|
||||
}
|
||||
|
||||
val sessionUseCases by lazy { SessionUseCases(store) }
|
||||
val tabsUseCases by lazy { TabsUseCases(store) }
|
||||
|
||||
// Addons
|
||||
val addonManager by lazy {
|
||||
AddonManager(store, engine, addonsProvider, addonUpdater)
|
||||
}
|
||||
|
||||
val addonsProvider by lazy {
|
||||
AMOAddonsProvider(
|
||||
applicationContext,
|
||||
client,
|
||||
collectionName = "7dfae8669acc4312a65e8ba5553036",
|
||||
maxCacheAgeInMinutes = DAY_IN_MINUTES,
|
||||
)
|
||||
}
|
||||
|
||||
val supportedAddonsChecker by lazy {
|
||||
DefaultSupportedAddonsChecker(applicationContext, Frequency(1, TimeUnit.DAYS))
|
||||
}
|
||||
|
||||
val appLinksUseCases by lazy { AppLinksUseCases(applicationContext) }
|
||||
|
||||
val appLinksInterceptor by lazy {
|
||||
AppLinksInterceptor(
|
||||
applicationContext,
|
||||
interceptLinkClicks = true,
|
||||
launchInApp = {
|
||||
preferences.getBoolean(PREF_LAUNCH_EXTERNAL_APP, false)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Digital Asset Links checking
|
||||
val relationChecker by lazy {
|
||||
StatementRelationChecker(StatementApi(client))
|
||||
}
|
||||
|
||||
val downloadsUseCases: DownloadsUseCases by lazy { DownloadsUseCases(store) }
|
||||
|
||||
val crashReporter: CrashReporter by lazy {
|
||||
CrashReporter(
|
||||
applicationContext,
|
||||
services = listOf(
|
||||
object : CrashReporterService {
|
||||
override val id: String
|
||||
get() = "xxx"
|
||||
override val name: String
|
||||
get() = "Test"
|
||||
|
||||
override fun createCrashReportUrl(identifier: String): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun report(crash: Crash.UncaughtExceptionCrash): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun report(crash: Crash.NativeCodeCrash): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun report(
|
||||
throwable: Throwable,
|
||||
breadcrumbs: ArrayList<Breadcrumb>,
|
||||
): String? {
|
||||
return null
|
||||
}
|
||||
},
|
||||
),
|
||||
notificationsDelegate = notificationsDelegate,
|
||||
).install(applicationContext)
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.feature.downloads.AbstractFetchDownloadService
|
||||
import mozilla.components.support.base.android.NotificationsDelegate
|
||||
|
||||
class DownloadService : AbstractFetchDownloadService() {
|
||||
override val httpClient by lazy { GlobalComponents.components!!.client }
|
||||
override val store: BrowserStore by lazy { GlobalComponents.components!!.store }
|
||||
override val notificationsDelegate: NotificationsDelegate by lazy { GlobalComponents.components!!.notificationsDelegate }
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
import GeckoBrowserApi
|
||||
import GeckoSessionApi
|
||||
import GeckoTabsApi
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.annotation.NonNull
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import eu.lensai.flutter_mozilla_components.api.GeckoBrowserApiImpl
|
||||
import eu.lensai.flutter_mozilla_components.api.GeckoSessionApiImpl
|
||||
import eu.lensai.flutter_mozilla_components.api.GeckoTabsApiImpl
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import mozilla.components.browser.engine.gecko.GeckoEngine
|
||||
import mozilla.components.browser.engine.gecko.fetch.GeckoViewFetchClient
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import mozilla.components.experiment.NimbusExperimentDelegate
|
||||
import mozilla.components.feature.webcompat.WebCompatFeature
|
||||
import mozilla.components.lib.crash.handler.CrashHandlerService
|
||||
import mozilla.components.support.base.log.Log
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import mozilla.components.support.base.log.sink.AndroidLogSink
|
||||
import org.mozilla.geckoview.GeckoRuntime
|
||||
import org.mozilla.geckoview.GeckoRuntimeSettings
|
||||
|
||||
/**
|
||||
* Helper class for lazily instantiating components needed by the application.
|
||||
*/
|
||||
class Components(private val applicationContext: Context) : DefaultComponents(applicationContext) {
|
||||
private val runtime by lazy {
|
||||
// Allow for exfiltrating Gecko metrics through the Glean SDK.
|
||||
val builder = GeckoRuntimeSettings.Builder().aboutConfigEnabled(true)
|
||||
builder.experimentDelegate(NimbusExperimentDelegate())
|
||||
builder.crashHandler(CrashHandlerService::class.java)
|
||||
GeckoRuntime.create(applicationContext, builder.build())
|
||||
}
|
||||
|
||||
override val engine: Engine by lazy {
|
||||
GeckoEngine(applicationContext, engineSettings, runtime).also {
|
||||
// it.installBuiltInWebExtension("borderify@mozac.org", "resource://android/assets/extensions/borderify/") {
|
||||
// throwable ->
|
||||
// Log.log(Log.Priority.ERROR, "SampleBrowser", throwable, "Failed to install borderify")
|
||||
// }
|
||||
//
|
||||
// it.installBuiltInWebExtension("testext@mozac.org", "resource://android/assets/extensions/test/") {
|
||||
// throwable ->
|
||||
// Log.log(Log.Priority.ERROR, "SampleBrowser", throwable, "Failed to install testext")
|
||||
// }
|
||||
|
||||
WebCompatFeature.install(it)
|
||||
//WebCompatReporterFeature.install(it)
|
||||
}
|
||||
}
|
||||
|
||||
override val client by lazy { GeckoViewFetchClient(applicationContext, runtime) }
|
||||
}
|
||||
|
||||
|
||||
/** FlutterMozillaComponentsPlugin */
|
||||
class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
|
||||
private var activity: Activity? = null
|
||||
|
||||
private lateinit var _flutterPluginBinding: FlutterPlugin.FlutterPluginBinding;
|
||||
|
||||
init {
|
||||
Log.addSink(AndroidLogSink())
|
||||
}
|
||||
|
||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
_flutterPluginBinding = flutterPluginBinding
|
||||
|
||||
GlobalComponents.setUp(flutterPluginBinding.applicationContext)
|
||||
|
||||
val intent = Intent(flutterPluginBinding.applicationContext, NotificationActivity::class.java)
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
flutterPluginBinding.applicationContext.startActivity(intent)
|
||||
|
||||
GeckoBrowserApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoBrowserApiImpl {
|
||||
showNativeFragment()
|
||||
})
|
||||
|
||||
GeckoSessionApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoSessionApiImpl())
|
||||
GeckoTabsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoTabsApiImpl())
|
||||
}
|
||||
|
||||
private fun showNativeFragment() {
|
||||
if (activity == null) {
|
||||
//result.error("ACTIVITY_NOT_ATTACHED", "Activity is not attached", null)
|
||||
return
|
||||
}
|
||||
|
||||
// Replace this with your actual Fragment
|
||||
val nativeFragment = BrowserFragment.create(activity!!)
|
||||
|
||||
val fm = (activity as FragmentActivity).supportFragmentManager
|
||||
fm.beginTransaction()
|
||||
.replace(FRAGMENT_CONTAINER_ID, nativeFragment)
|
||||
.commitAllowingStateLoss()
|
||||
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
|
||||
}
|
||||
|
||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||
this.activity = binding.activity
|
||||
|
||||
_flutterPluginBinding.platformViewRegistry.registerViewFactory(
|
||||
"eu.lensai/gecko", GeckoViewFactory(binding.activity, FRAGMENT_CONTAINER_ID))
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivityForConfigChanges() {
|
||||
onDetachedFromActivity()
|
||||
}
|
||||
|
||||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
||||
onAttachedToActivity(binding)
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivity() {
|
||||
this.activity = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FRAGMENT_CONTAINER_ID = 0xBEEF
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
import android.view.View
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import io.flutter.plugin.common.StandardMessageCodec
|
||||
import io.flutter.plugin.platform.PlatformView
|
||||
import io.flutter.plugin.platform.PlatformViewFactory
|
||||
|
||||
class GeckoViewFactory(private val activity: Activity, private val containerId: Int) : PlatformViewFactory(
|
||||
StandardMessageCodec.INSTANCE) {
|
||||
override fun create(context: Context?, id: Int, args: Any?): PlatformView {
|
||||
return NativeFragmentView(this.activity, this.containerId)
|
||||
}
|
||||
}
|
||||
|
||||
private class NativeFragmentView(
|
||||
private val activity: Activity?,
|
||||
private val containerId: Int
|
||||
) : PlatformView {
|
||||
private val container: View
|
||||
|
||||
init {
|
||||
val vParams: ViewGroup.LayoutParams =
|
||||
FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
container = FrameLayout(activity!!)
|
||||
container.layoutParams = vParams
|
||||
container.id = containerId
|
||||
}
|
||||
|
||||
override fun getView(): View {
|
||||
return container
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
// Clean up if needed
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.feature.addons.update.GlobalAddonDependencyProvider
|
||||
import mozilla.components.support.base.facts.Facts
|
||||
import mozilla.components.support.base.facts.processor.LogFactProcessor
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import mozilla.components.support.webextensions.WebExtensionSupport
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object GlobalComponents {
|
||||
private var _components: Components? = null
|
||||
|
||||
val components: Components?
|
||||
get() = _components
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
private fun restoreBrowserState(newComponents: Components) = GlobalScope.launch(Dispatchers.Main) {
|
||||
newComponents.tabsUseCases.restore(newComponents.sessionStorage)
|
||||
|
||||
newComponents.sessionStorage.autoSave(newComponents.store)
|
||||
.periodicallyInForeground(interval = 30, unit = TimeUnit.SECONDS)
|
||||
.whenGoingToBackground()
|
||||
.whenSessionsChange()
|
||||
}
|
||||
|
||||
fun setUp(applicationContext: Context) {
|
||||
val newComponents = Components(applicationContext)
|
||||
|
||||
newComponents.crashReporter.install(applicationContext)
|
||||
Facts.registerProcessor(LogFactProcessor())
|
||||
|
||||
newComponents.engine.warmUp()
|
||||
|
||||
restoreBrowserState(newComponents)
|
||||
newComponents.downloadsUseCases.restoreDownloads()
|
||||
|
||||
try {
|
||||
GlobalAddonDependencyProvider.initialize(
|
||||
newComponents.addonManager,
|
||||
newComponents.addonUpdater,
|
||||
)
|
||||
|
||||
WebExtensionSupport.initialize(
|
||||
newComponents.engine,
|
||||
newComponents.store,
|
||||
onNewTabOverride = {
|
||||
_, engineSession, url ->
|
||||
newComponents.tabsUseCases.addTab(url, selectTab = true, engineSession = engineSession)
|
||||
},
|
||||
onCloseTabOverride = {
|
||||
_, sessionId ->
|
||||
newComponents.tabsUseCases.removeTab(sessionId)
|
||||
},
|
||||
onSelectTabOverride = {
|
||||
_, sessionId ->
|
||||
newComponents.tabsUseCases.selectTab(sessionId)
|
||||
},
|
||||
onUpdatePermissionRequest = newComponents.addonUpdater::onUpdatePermissionRequest,
|
||||
onExtensionsLoaded = { extensions ->
|
||||
newComponents.addonUpdater.registerForFutureUpdates(extensions)
|
||||
newComponents.supportedAddonsChecker.registerForChecks()
|
||||
},
|
||||
)
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
// Web extension support is only available for engine gecko
|
||||
Logger.error("Failed to initialize web extension support", e)
|
||||
}
|
||||
|
||||
newComponents.tabsUseCases.addTab.invoke("https://google.com", selectTab = true)
|
||||
|
||||
_components = newComponents
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.base.crash.CrashReporting
|
||||
import mozilla.components.feature.media.service.AbstractMediaSessionService
|
||||
import mozilla.components.support.base.android.NotificationsDelegate
|
||||
|
||||
/**
|
||||
* See [AbstractMediaSessionService].
|
||||
*/
|
||||
class MediaSessionService : AbstractMediaSessionService() {
|
||||
override val crashReporter: CrashReporting? by lazy { GlobalComponents.components!!.crashReporter }
|
||||
override val store: BrowserStore by lazy { GlobalComponents.components!!.store }
|
||||
override val notificationsDelegate: NotificationsDelegate by lazy { GlobalComponents.components!!.notificationsDelegate }
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import eu.lensai.flutter_mozilla_components.GlobalComponents
|
||||
|
||||
class NotificationActivity: AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
GlobalComponents.components!!.notificationsDelegate.bindToActivity(this)
|
||||
|
||||
finish()
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package eu.lensai.flutter_mozilla_components.api
|
||||
|
||||
import GeckoBrowserApi
|
||||
|
||||
class GeckoBrowserApiImpl(
|
||||
private val showFragmentCallback: () -> Unit
|
||||
) : GeckoBrowserApi {
|
||||
override fun showNativeFragment() {
|
||||
showFragmentCallback.invoke();
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package eu.lensai.flutter_mozilla_components.api
|
||||
|
||||
import GeckoSessionApi
|
||||
import LoadUrlFlagsValue
|
||||
import android.util.Log
|
||||
import eu.lensai.flutter_mozilla_components.GlobalComponents
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.concept.engine.EngineSession
|
||||
import mozilla.components.concept.engine.translate.TranslationOptions
|
||||
import TranslationOptions as PigeonTranslationOptions
|
||||
import mozilla.components.feature.session.SessionUseCases
|
||||
|
||||
class GeckoSessionApiImpl() : GeckoSessionApi {
|
||||
private val sessionUseCases: SessionUseCases by lazy { GlobalComponents.components!!.sessionUseCases }
|
||||
private val state: BrowserState by lazy { GlobalComponents.components!!.store.state }
|
||||
|
||||
override fun loadUrl(
|
||||
tabId: String?,
|
||||
url: String,
|
||||
flags: LoadUrlFlagsValue,
|
||||
additionalHeaders: Map<String, String>?
|
||||
) {
|
||||
sessionUseCases.loadUrl(
|
||||
url = url,
|
||||
sessionId = tabId,
|
||||
flags = EngineSession.LoadUrlFlags.select(flags.value.toInt()),
|
||||
additionalHeaders = additionalHeaders
|
||||
)
|
||||
}
|
||||
|
||||
override fun loadData(tabId: String?, data: String, mimeType: String, encoding: String) {
|
||||
sessionUseCases.loadData(
|
||||
data = data,
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
mimeType = mimeType,
|
||||
encoding = encoding,
|
||||
)
|
||||
}
|
||||
|
||||
override fun reload(tabId: String?, flags: LoadUrlFlagsValue) {
|
||||
sessionUseCases.reload(
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
flags = EngineSession.LoadUrlFlags.select(flags.value.toInt()),
|
||||
)
|
||||
}
|
||||
|
||||
override fun stopLoading(tabId: String?) {
|
||||
sessionUseCases.stopLoading(
|
||||
tabId = tabId
|
||||
)
|
||||
}
|
||||
|
||||
override fun goBack(tabId: String?, userInteraction: Boolean) {
|
||||
sessionUseCases.goBack(
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
userInteraction = userInteraction
|
||||
)
|
||||
}
|
||||
|
||||
override fun goForward(tabId: String?, userInteraction: Boolean) {
|
||||
sessionUseCases.goForward(
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
userInteraction = userInteraction
|
||||
)
|
||||
}
|
||||
|
||||
override fun goToHistoryIndex(index: Long, tabId: String?) {
|
||||
sessionUseCases.goToHistoryIndex(
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
index = index.toInt()
|
||||
)
|
||||
}
|
||||
|
||||
override fun requestDesktopSite(tabId: String?, enable: Boolean) {
|
||||
sessionUseCases.requestDesktopSite(
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
enable = enable,
|
||||
)
|
||||
}
|
||||
|
||||
override fun exitFullscreen(tabId: String?) {
|
||||
sessionUseCases.exitFullscreen(
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
)
|
||||
}
|
||||
|
||||
override fun saveToPdf(tabId: String?) {
|
||||
sessionUseCases.saveToPdf(
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
)
|
||||
}
|
||||
|
||||
override fun printContent(tabId: String?) {
|
||||
sessionUseCases.printContent(
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
)
|
||||
}
|
||||
|
||||
override fun translate(
|
||||
tabId: String?,
|
||||
fromLanguage: String,
|
||||
toLanguage: String,
|
||||
options: PigeonTranslationOptions?
|
||||
) {
|
||||
sessionUseCases.translate(
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
fromLanguage = fromLanguage,
|
||||
toLanguage = toLanguage,
|
||||
options = if (options != null) TranslationOptions(downloadModel = options.downloadModel) else null,
|
||||
)
|
||||
}
|
||||
|
||||
override fun translateRestore(tabId: String?) {
|
||||
sessionUseCases.translateRestore(
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
)
|
||||
}
|
||||
|
||||
override fun crashRecovery(tabIds: List<String>?) {
|
||||
if(tabIds != null) {
|
||||
sessionUseCases.crashRecovery.invoke(tabIds = tabIds)
|
||||
} else {
|
||||
sessionUseCases.crashRecovery.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
override fun purgeHistory() {
|
||||
sessionUseCases.purgeHistory()
|
||||
}
|
||||
|
||||
override fun updateLastAccess(tabId: String?, lastAccess: Long?) {
|
||||
sessionUseCases.updateLastAccess(
|
||||
tabId = tabId ?: state.selectedTabId,
|
||||
lastAccess = lastAccess ?: System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
package eu.lensai.flutter_mozilla_components.api
|
||||
|
||||
import GeckoTabsApi
|
||||
import HistoryMetadataKey as PigeonHistoryMetadataKey
|
||||
import LoadUrlFlagsValue
|
||||
import RecoverableBrowserState as PigeonRecoverableBrowserState
|
||||
import RestoreLocation as PigeonRestoreLocation
|
||||
import RecoverableTab as PigeonRecoverableTab
|
||||
import SourceValue
|
||||
import eu.lensai.flutter_mozilla_components.GlobalComponents
|
||||
import mozilla.components.browser.session.storage.RecoverableBrowserState
|
||||
import mozilla.components.browser.state.action.TabListAction
|
||||
import mozilla.components.browser.state.selector.findTab
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.browser.state.state.LastMediaAccessState
|
||||
import mozilla.components.browser.state.state.ReaderState
|
||||
import mozilla.components.browser.state.state.SessionState
|
||||
import mozilla.components.browser.state.state.recover.RecoverableTab
|
||||
import mozilla.components.browser.state.state.recover.TabState
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import mozilla.components.concept.engine.EngineSession
|
||||
import mozilla.components.concept.storage.HistoryMetadataKey
|
||||
import mozilla.components.feature.tabs.TabsUseCases
|
||||
import org.json.JSONObject
|
||||
|
||||
class GeckoTabsApiImpl() : GeckoTabsApi {
|
||||
private val tabsUseCases: TabsUseCases by lazy { GlobalComponents.components!!.tabsUseCases }
|
||||
private val engine: Engine by lazy { GlobalComponents.components!!.engine }
|
||||
private val state: BrowserState by lazy { GlobalComponents.components!!.store.state }
|
||||
|
||||
private fun restoreSource(source: SourceValue ) : SessionState.Source {
|
||||
return SessionState.Source.restore(
|
||||
source.id.toInt(),
|
||||
source.caller?.packageId,
|
||||
source.caller?.category?.value?.toInt()
|
||||
)
|
||||
}
|
||||
private fun mapTab(t: PigeonRecoverableTab) : RecoverableTab {
|
||||
return RecoverableTab(
|
||||
engineSessionState = if(t.engineSessionStateJson != null)
|
||||
engine.createSessionState(JSONObject(t.engineSessionStateJson))
|
||||
else
|
||||
null,
|
||||
state = TabState(
|
||||
id = t.state.id,
|
||||
url = t.state.url,
|
||||
parentId = t.state.parentId,
|
||||
title = t.state.title,
|
||||
searchTerm = t.state.searchTerm,
|
||||
contextId = t.state.contextId,
|
||||
readerState = ReaderState(
|
||||
readerable = t.state.readerState.readerable,
|
||||
active = t.state.readerState.active,
|
||||
checkRequired = t.state.readerState.checkRequired,
|
||||
connectRequired = t.state.readerState.connectRequired,
|
||||
baseUrl = t.state.readerState.baseUrl,
|
||||
activeUrl = t.state.readerState.activeUrl,
|
||||
scrollY = t.state.readerState.scrollY?.toInt(),
|
||||
),
|
||||
lastAccess = t.state.lastAccess,
|
||||
createdAt = t.state.createdAt,
|
||||
lastMediaAccessState = LastMediaAccessState(
|
||||
lastMediaUrl = t.state.lastMediaAccessState.lastMediaUrl,
|
||||
lastMediaAccess = t.state.lastMediaAccessState.lastMediaAccess,
|
||||
mediaSessionActive = t.state.lastMediaAccessState.mediaSessionActive
|
||||
),
|
||||
private = t.state.private,
|
||||
historyMetadata = if(t.state.historyMetadata != null)
|
||||
HistoryMetadataKey(
|
||||
url = t.state.historyMetadata.url,
|
||||
searchTerm = t.state.historyMetadata.searchTerm,
|
||||
referrerUrl = t.state.historyMetadata.referrerUrl
|
||||
)
|
||||
else null,
|
||||
source = restoreSource(t.state.source),
|
||||
index = t.state.index.toInt(),
|
||||
hasFormData = t.state.hasFormData,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapRestoreLocation(t: PigeonRestoreLocation) : TabListAction.RestoreAction.RestoreLocation {
|
||||
return TabListAction.RestoreAction.RestoreLocation.entries.first { it.ordinal == t.ordinal }
|
||||
}
|
||||
|
||||
override fun selectTab(tabId: String) {
|
||||
tabsUseCases.selectTab(tabId = tabId)
|
||||
}
|
||||
|
||||
override fun removeTab(tabId: String) {
|
||||
tabsUseCases.removeTab(tabId = tabId)
|
||||
}
|
||||
|
||||
override fun addTab(
|
||||
url: String,
|
||||
selectTab: Boolean,
|
||||
startLoading: Boolean,
|
||||
parentId: String?,
|
||||
flags: LoadUrlFlagsValue,
|
||||
contextId: String?,
|
||||
source: SourceValue,
|
||||
private: Boolean,
|
||||
historyMetadata: PigeonHistoryMetadataKey?,
|
||||
additionalHeaders: Map<String, String>?
|
||||
): String {
|
||||
return tabsUseCases.addTab(
|
||||
url = url,
|
||||
selectTab = selectTab,
|
||||
startLoading = startLoading,
|
||||
parentId = parentId,
|
||||
flags = EngineSession.LoadUrlFlags.select(flags.value.toInt()),
|
||||
contextId = contextId,
|
||||
source = restoreSource(source),
|
||||
private = private,
|
||||
historyMetadata = if(historyMetadata != null)
|
||||
HistoryMetadataKey(
|
||||
url = historyMetadata.url,
|
||||
searchTerm = historyMetadata.searchTerm,
|
||||
referrerUrl = historyMetadata.referrerUrl
|
||||
)
|
||||
else null,
|
||||
additionalHeaders = additionalHeaders
|
||||
)
|
||||
}
|
||||
|
||||
override fun removeAllTabs(recoverable: Boolean) {
|
||||
tabsUseCases.removeAllTabs(recoverable = recoverable)
|
||||
}
|
||||
|
||||
override fun removeTabs(ids: List<String>) {
|
||||
tabsUseCases.removeTabs(ids = ids)
|
||||
}
|
||||
|
||||
override fun removeNormalTabs() {
|
||||
tabsUseCases.removeNormalTabs()
|
||||
}
|
||||
|
||||
override fun removePrivateTabs() {
|
||||
tabsUseCases.removePrivateTabs()
|
||||
}
|
||||
|
||||
override fun undo() {
|
||||
tabsUseCases.undo()
|
||||
}
|
||||
|
||||
override fun restoreTabsByList(
|
||||
tabs: List<PigeonRecoverableTab>,
|
||||
selectTabId: String?,
|
||||
restoreLocation: PigeonRestoreLocation
|
||||
) {
|
||||
tabsUseCases.restore(
|
||||
tabs = tabs.map { t -> mapTab(t) },
|
||||
restoreLocation = mapRestoreLocation(restoreLocation),
|
||||
selectTabId = selectTabId,
|
||||
)
|
||||
}
|
||||
|
||||
override fun restoreTabsByBrowserState(
|
||||
state: PigeonRecoverableBrowserState,
|
||||
restoreLocation: PigeonRestoreLocation
|
||||
) {
|
||||
tabsUseCases.restore(
|
||||
state = RecoverableBrowserState(
|
||||
selectedTabId = state.selectedTabId,
|
||||
tabs = state.tabs.filterNotNull().map { t -> mapTab(t) },
|
||||
),
|
||||
restoreLocation = mapRestoreLocation(restoreLocation),
|
||||
)
|
||||
}
|
||||
|
||||
override fun selectOrAddTabByHistory(url: String, historyMetadata: PigeonHistoryMetadataKey): String {
|
||||
return tabsUseCases.selectOrAddTab(
|
||||
url = url,
|
||||
historyMetadata = HistoryMetadataKey(
|
||||
url = historyMetadata.url,
|
||||
searchTerm = historyMetadata.searchTerm,
|
||||
referrerUrl = historyMetadata.referrerUrl
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun selectOrAddTabByUrl(
|
||||
url: String,
|
||||
private: Boolean,
|
||||
source: SourceValue,
|
||||
flags: LoadUrlFlagsValue,
|
||||
ignoreFragment: Boolean
|
||||
): String {
|
||||
return tabsUseCases.selectOrAddTab(
|
||||
url = url,
|
||||
private = private,
|
||||
source = restoreSource(source),
|
||||
flags = EngineSession.LoadUrlFlags.select(flags.value.toInt()),
|
||||
ignoreFragment = ignoreFragment,
|
||||
)
|
||||
}
|
||||
|
||||
override fun duplicateTab(selectTabId: String?, selectNewTab: Boolean): String {
|
||||
val tabState = if(selectTabId != null) state.findTab(selectTabId) else null
|
||||
|
||||
return tabsUseCases.duplicateTab(
|
||||
tab = tabState!!,
|
||||
selectNewTab = selectNewTab,
|
||||
)
|
||||
}
|
||||
|
||||
override fun moveTabs(tabIds: List<String>, targetTabId: String, placeAfter: Boolean) {
|
||||
tabsUseCases.moveTabs(
|
||||
tabIds = tabIds,
|
||||
targetTabId = targetTabId,
|
||||
placeAfter = placeAfter,
|
||||
)
|
||||
}
|
||||
|
||||
override fun migratePrivateTabUseCase(tabId: String, alternativeUrl: String?): String {
|
||||
return tabsUseCases.migratePrivateTabUseCase(
|
||||
tabId = tabId,
|
||||
alternativeUrl = alternativeUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/* 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 eu.lensai.flutter_mozilla_components.middleware
|
||||
|
||||
import android.util.Log
|
||||
import mozilla.components.browser.state.action.BrowserAction
|
||||
import mozilla.components.browser.state.action.ContentAction
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
|
||||
/**
|
||||
* [Middleware] implementation for handling [ContentAction.UpdateThumbnailAction] and storing
|
||||
* the thumbnail to the disk cache.
|
||||
*/
|
||||
class FlutterEventMiddleware() : Middleware<BrowserState, BrowserAction> {
|
||||
@Suppress("ComplexMethod")
|
||||
override fun invoke(
|
||||
context: MiddlewareContext<BrowserState, BrowserAction>,
|
||||
next: (BrowserAction) -> Unit,
|
||||
action: BrowserAction,
|
||||
) {
|
||||
when (action) {
|
||||
is ContentAction.UpdateThumbnailAction -> {
|
||||
Log.d("UpdateThumbnailAction", action.sessionId)
|
||||
}
|
||||
is ContentAction.UpdateTitleAction -> {
|
||||
Log.d("UpdateTitleAction", action.title)
|
||||
}
|
||||
else -> {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
+1258
File diff suppressed because it is too large
Load Diff
+19
@@ -0,0 +1,19 @@
|
||||
<!-- 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/. -->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108"
|
||||
android:tint="#FFFFFF">
|
||||
<group android:scaleX="2.9232"
|
||||
android:scaleY="2.9232"
|
||||
android:translateX="18.9216"
|
||||
android:translateY="18.9216">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<mozilla.components.ui.widgets.VerticalSwipeRefreshLayout
|
||||
android:id="@+id/swipeToRefresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<!-- Inject engine here -->
|
||||
</mozilla.components.ui.widgets.VerticalSwipeRefreshLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.AppCompat.Translucent" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation</item>
|
||||
</style>
|
||||
</resources>
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package eu.lensai.flutter_mozilla_components
|
||||
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import kotlin.test.Test
|
||||
import org.mockito.Mockito
|
||||
|
||||
/*
|
||||
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
|
||||
*
|
||||
* Once you have built the plugin's example app, you can run these tests from the command
|
||||
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
|
||||
* you can run them directly from IDEs that support JUnit such as Android Studio.
|
||||
*/
|
||||
|
||||
internal class FlutterMozillaComponentsPluginTest {
|
||||
@Test
|
||||
fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
|
||||
val plugin = FlutterMozillaComponentsPlugin()
|
||||
|
||||
val call = MethodCall("getPlatformVersion", null)
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
@@ -0,0 +1,16 @@
|
||||
# flutter_mozilla_components_example
|
||||
|
||||
Demonstrates how to use the flutter_mozilla_components plugin.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
@@ -0,0 +1,28 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -0,0 +1,13 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@@ -0,0 +1,44 @@
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "eu.lensai.flutter_mozilla_components_example"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "eu.lensai.flutter_mozilla_components_example"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,93 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<!-- This is needed because the android.permission.CAMERA above automatically
|
||||
adds a requirements for camera hardware and we don't want add those restrictions -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<!-- Needed for uploading media files on devices with Android 13 and later. -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
|
||||
|
||||
<application
|
||||
android:label="flutter_mozilla_components_example"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity android:name="eu.lensai.flutter_mozilla_components.NotificationActivity" android:theme="@style/Theme.AppCompat.Translucent" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name="eu.lensai.flutter_mozilla_components.DownloadService"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
|
||||
<service android:name="eu.lensai.flutter_mozilla_components.MediaSessionService"
|
||||
android:foregroundServiceType="mediaPlayback"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package eu.lensai.flutter_mozilla_components_example
|
||||
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
|
||||
class MainActivity: FlutterFragmentActivity()
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 442 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 721 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,18 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
|
||||
@@ -0,0 +1,25 @@
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.1.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.9.20" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
MyAppState createState() => MyAppState();
|
||||
}
|
||||
|
||||
class MyAppState extends State<MyApp> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Native Fragment Example'),
|
||||
actions: [
|
||||
MenuAnchor(
|
||||
builder: (context, controller, child) => IconButton(
|
||||
onPressed: () {
|
||||
controller.open();
|
||||
},
|
||||
icon: const Icon(Icons.menu)),
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await GeckoSessionService.forCurrentTab().reload();
|
||||
},
|
||||
child: const Text('Reload'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await GeckoSessionService.forCurrentTab().goBack();
|
||||
},
|
||||
child: const Text('Back'),
|
||||
)
|
||||
])
|
||||
],
|
||||
),
|
||||
body: const SafeArea(
|
||||
child: Center(
|
||||
child: GeckoView(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
name: flutter_mozilla_components_example
|
||||
description: "Demonstrates how to use the flutter_mozilla_components plugin."
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
environment:
|
||||
sdk: ^3.6.0-198.0.dev
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||
# dependencies can be manually updated by changing the version numbers below to
|
||||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
flutter_mozilla_components:
|
||||
# When depending on this package from a real application you should use:
|
||||
# flutter_mozilla_components: ^x.y.z
|
||||
# See https://dart.dev/tools/pub/dependencies#version-constraints
|
||||
# The example app is bundled with the plugin so we use a path dependency on
|
||||
# the parent directory to use the current plugin's version.
|
||||
path: ../
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^4.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/to/asset-from-package
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/to/font-from-package
|
||||
@@ -0,0 +1,4 @@
|
||||
# melos_managed_dependency_overrides: flutter_mozilla_components
|
||||
dependency_overrides:
|
||||
flutter_mozilla_components:
|
||||
path: ..
|
||||
@@ -0,0 +1,27 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:flutter_mozilla_components_example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that platform version is retrieved.
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(Widget widget) => widget is Text &&
|
||||
widget.data!.startsWith('Running on:'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export 'src/geckoview_widget.dart';
|
||||
//export 'src/domain/services/gecko_browser.dart';
|
||||
export 'src/domain/services/gecko_session.dart';
|
||||
export 'src/domain/services/gecko_tabs.dart';
|
||||
@@ -0,0 +1,46 @@
|
||||
// ignore_for_file: constant_identifier_names, non_constant_identifier_names
|
||||
|
||||
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
|
||||
|
||||
/// Describes a combination of flags provided to the engine when loading a URL.
|
||||
class LoadUrlFlags {
|
||||
final int value;
|
||||
|
||||
const LoadUrlFlags._(this.value);
|
||||
|
||||
LoadUrlFlagsValue toValue() {
|
||||
return LoadUrlFlagsValue(value: value);
|
||||
}
|
||||
|
||||
static const LoadUrlFlags NONE = LoadUrlFlags._(0);
|
||||
static const LoadUrlFlags BYPASS_CACHE = LoadUrlFlags._(1 << 0);
|
||||
static const LoadUrlFlags BYPASS_PROXY = LoadUrlFlags._(1 << 1);
|
||||
static const LoadUrlFlags EXTERNAL = LoadUrlFlags._(1 << 2);
|
||||
static const LoadUrlFlags ALLOW_POPUPS = LoadUrlFlags._(1 << 3);
|
||||
static const LoadUrlFlags BYPASS_CLASSIFIER = LoadUrlFlags._(1 << 4);
|
||||
static const LoadUrlFlags LOAD_FLAGS_FORCE_ALLOW_DATA_URI =
|
||||
LoadUrlFlags._(1 << 5);
|
||||
static const LoadUrlFlags LOAD_FLAGS_REPLACE_HISTORY = LoadUrlFlags._(1 << 6);
|
||||
static const LoadUrlFlags LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE =
|
||||
LoadUrlFlags._(1 << 7);
|
||||
static const LoadUrlFlags ALLOW_ADDITIONAL_HEADERS = LoadUrlFlags._(1 << 15);
|
||||
static const LoadUrlFlags ALLOW_JAVASCRIPT_URL = LoadUrlFlags._(1 << 16);
|
||||
|
||||
static LoadUrlFlags ALL = LoadUrlFlags._(
|
||||
BYPASS_CACHE.value |
|
||||
BYPASS_PROXY.value |
|
||||
EXTERNAL.value |
|
||||
ALLOW_POPUPS.value |
|
||||
BYPASS_CLASSIFIER.value |
|
||||
LOAD_FLAGS_FORCE_ALLOW_DATA_URI.value |
|
||||
LOAD_FLAGS_REPLACE_HISTORY.value |
|
||||
LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE.value |
|
||||
ALLOW_ADDITIONAL_HEADERS.value |
|
||||
ALLOW_JAVASCRIPT_URL.value,
|
||||
);
|
||||
|
||||
static LoadUrlFlags select(List<LoadUrlFlags> flags) {
|
||||
int combinedValue = flags.fold(0, (sum, flag) => sum | flag.value);
|
||||
return LoadUrlFlags._(combinedValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import 'package:flutter_mozilla_components/src/helper/helper.dart';
|
||||
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
|
||||
|
||||
/// Describes a category of an external package.
|
||||
enum PackageCategory {
|
||||
unknown(-1),
|
||||
game(0),
|
||||
audio(1),
|
||||
video(2),
|
||||
image(3),
|
||||
social(4),
|
||||
news(5),
|
||||
maps(6),
|
||||
productivity(7);
|
||||
|
||||
final int id;
|
||||
const PackageCategory(this.id);
|
||||
|
||||
PackageCategoryValue toValue() {
|
||||
return PackageCategoryValue(value: id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the origin of a session to describe how and why it was created.
|
||||
abstract class Source {
|
||||
final int id;
|
||||
|
||||
const Source(this.id);
|
||||
|
||||
SourceValue toValue();
|
||||
|
||||
/// Initializes a [Source] of a correct type from its component properties.
|
||||
/// Intended use is for restoring persisted state.
|
||||
static Source restore(
|
||||
int? sourceId, String? packageId, int? packageCategory) {
|
||||
final caller = packageId != null
|
||||
? ExternalPackage(
|
||||
packageId: packageId,
|
||||
category: packageCategoryfromInt(packageCategory).toValue())
|
||||
: null;
|
||||
|
||||
switch (sourceId) {
|
||||
case 1:
|
||||
return External.actionSend(caller);
|
||||
case 2:
|
||||
return External.actionView(caller);
|
||||
case 3:
|
||||
return External.actionSearch(caller);
|
||||
case 4:
|
||||
return External.customTab(caller);
|
||||
case 5:
|
||||
return Internal.homeScreen;
|
||||
case 6:
|
||||
return Internal.menu;
|
||||
case 7:
|
||||
return Internal.newTab;
|
||||
case 8:
|
||||
return Internal.none;
|
||||
case 9:
|
||||
return Internal.textSelection;
|
||||
case 10:
|
||||
return Internal.userEntered;
|
||||
case 11:
|
||||
return Internal.customTab;
|
||||
default:
|
||||
return Internal.none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes sessions of external origins, i.e. from outside of the application.
|
||||
abstract class External extends Source {
|
||||
final ExternalPackage? caller;
|
||||
|
||||
const External(super.id, this.caller);
|
||||
|
||||
@override
|
||||
SourceValue toValue() {
|
||||
return SourceValue(id: id, caller: caller);
|
||||
}
|
||||
|
||||
/// Created to handle an ACTION_SEND (share) intent.
|
||||
factory External.actionSend(ExternalPackage? caller) = ActionSend;
|
||||
|
||||
/// Created to handle an ACTION_VIEW intent.
|
||||
factory External.actionView(ExternalPackage? caller) = ActionView;
|
||||
|
||||
/// Created to handle an ACTION_SEARCH and ACTION_WEB_SEARCH intent.
|
||||
factory External.actionSearch(ExternalPackage? caller) = ActionSearch;
|
||||
|
||||
/// Created to handle a CustomTabs intent of external origin.
|
||||
factory External.customTab(ExternalPackage? caller) = CustomTab;
|
||||
}
|
||||
|
||||
class ActionSend extends External {
|
||||
const ActionSend(ExternalPackage? caller) : super(1, caller);
|
||||
}
|
||||
|
||||
class ActionView extends External {
|
||||
const ActionView(ExternalPackage? caller) : super(2, caller);
|
||||
}
|
||||
|
||||
class ActionSearch extends External {
|
||||
const ActionSearch(ExternalPackage? caller) : super(3, caller);
|
||||
}
|
||||
|
||||
class CustomTab extends External {
|
||||
const CustomTab(ExternalPackage? caller) : super(4, caller);
|
||||
}
|
||||
|
||||
/// Describes sessions of internal origin, i.e. from within of the application.
|
||||
abstract class Internal extends Source {
|
||||
const Internal(super.id);
|
||||
|
||||
@override
|
||||
SourceValue toValue() {
|
||||
return SourceValue(id: id);
|
||||
}
|
||||
|
||||
/// User interacted with the home screen.
|
||||
static const homeScreen = _HomeScreen();
|
||||
|
||||
/// User interacted with a menu.
|
||||
static const menu = _Menu();
|
||||
|
||||
/// User opened a new tab.
|
||||
static const newTab = _NewTab();
|
||||
|
||||
/// Default value and for testing purposes.
|
||||
static const none = _None();
|
||||
|
||||
/// User selected text.
|
||||
static const textSelection = _TextSelection();
|
||||
|
||||
/// User entered a URL or search term.
|
||||
static const userEntered = _UserEntered();
|
||||
|
||||
/// Created to handle a CustomTabs intent of internal origin.
|
||||
static const customTab = _CustomTab();
|
||||
}
|
||||
|
||||
class _HomeScreen extends Internal {
|
||||
const _HomeScreen() : super(5);
|
||||
}
|
||||
|
||||
class _Menu extends Internal {
|
||||
const _Menu() : super(6);
|
||||
}
|
||||
|
||||
class _NewTab extends Internal {
|
||||
const _NewTab() : super(7);
|
||||
}
|
||||
|
||||
class _None extends Internal {
|
||||
const _None() : super(8);
|
||||
}
|
||||
|
||||
class _TextSelection extends Internal {
|
||||
const _TextSelection() : super(9);
|
||||
}
|
||||
|
||||
class _UserEntered extends Internal {
|
||||
const _UserEntered() : super(10);
|
||||
}
|
||||
|
||||
class _CustomTab extends Internal {
|
||||
const _CustomTab() : super(11);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
|
||||
|
||||
class GeckoBrowserService {
|
||||
final GeckoBrowserApi _api;
|
||||
|
||||
GeckoBrowserService({GeckoBrowserApi? api}) : _api = api ?? GeckoBrowserApi();
|
||||
|
||||
Future<void> showNativeFragment() {
|
||||
return _api.showNativeFragment();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import 'package:flutter_mozilla_components/src/data/models/load_url_flags.dart';
|
||||
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
|
||||
|
||||
class GeckoSessionService {
|
||||
final String? tabId;
|
||||
final GeckoSessionApi _api;
|
||||
|
||||
GeckoSessionService.forCurrentTab({GeckoSessionApi? api})
|
||||
: _api = api ?? GeckoSessionApi(),
|
||||
tabId = null;
|
||||
|
||||
GeckoSessionService({required String this.tabId, GeckoSessionApi? api})
|
||||
: _api = api ?? GeckoSessionApi();
|
||||
|
||||
Future<void> loadUrl({
|
||||
required Uri url,
|
||||
LoadUrlFlags flags = LoadUrlFlags.NONE,
|
||||
Map<String, String>? additionalHeaders,
|
||||
}) {
|
||||
return _api.loadUrl(
|
||||
tabId: tabId,
|
||||
url: url.toString(),
|
||||
flags: flags.toValue(),
|
||||
additionalHeaders: additionalHeaders,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> loadData({
|
||||
required String data,
|
||||
required String mimeType,
|
||||
String encoding = "UTF-8",
|
||||
}) {
|
||||
return _api.loadData(
|
||||
tabId: tabId,
|
||||
data: data,
|
||||
mimeType: mimeType,
|
||||
encoding: encoding,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reload({LoadUrlFlags flags = LoadUrlFlags.NONE}) {
|
||||
return _api.reload(
|
||||
tabId: tabId,
|
||||
flags: flags.toValue(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> stopLoading() {
|
||||
return _api.stopLoading(tabId: tabId);
|
||||
}
|
||||
|
||||
Future<void> goBack({bool userInteraction = true}) {
|
||||
return _api.goBack(
|
||||
tabId: tabId,
|
||||
userInteraction: userInteraction,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> goForward({bool userInteraction = true}) {
|
||||
return _api.goForward(
|
||||
tabId: tabId,
|
||||
userInteraction: userInteraction,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> goToHistoryIndex({required int index}) {
|
||||
return _api.goToHistoryIndex(
|
||||
index: index,
|
||||
tabId: tabId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> requestDesktopSite({required bool enable}) {
|
||||
return _api.requestDesktopSite(
|
||||
enable: enable,
|
||||
tabId: tabId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> exitFullscreen() {
|
||||
return _api.exitFullscreen(tabId: tabId);
|
||||
}
|
||||
|
||||
Future<void> saveToPdf() {
|
||||
return _api.saveToPdf(tabId: tabId);
|
||||
}
|
||||
|
||||
Future<void> printContent() {
|
||||
return _api.printContent(tabId: tabId);
|
||||
}
|
||||
|
||||
Future<void> translate({
|
||||
required String fromLanguage,
|
||||
required String toLanguage,
|
||||
TranslationOptions? options,
|
||||
}) {
|
||||
return _api.translate(
|
||||
tabId: tabId,
|
||||
fromLanguage: fromLanguage,
|
||||
toLanguage: toLanguage,
|
||||
options: options,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> translateRestore() {
|
||||
return _api.translateRestore(tabId: tabId);
|
||||
}
|
||||
|
||||
Future<void> crashRecovery({
|
||||
List<String>? tabIds,
|
||||
}) {
|
||||
return _api.crashRecovery(tabIds: tabIds);
|
||||
}
|
||||
|
||||
Future<void> purgeHistory() {
|
||||
return _api.purgeHistory();
|
||||
}
|
||||
|
||||
Future<void> updateLastAccess({
|
||||
String? tabId, //If null = current tab
|
||||
DateTime? lastAccess, //If null datetime.now
|
||||
}) {
|
||||
return _api.updateLastAccess(
|
||||
tabId: tabId,
|
||||
lastAccess: (lastAccess ?? DateTime.now()).millisecondsSinceEpoch,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import 'package:flutter_mozilla_components/src/data/models/load_url_flags.dart';
|
||||
import 'package:flutter_mozilla_components/src/data/models/source.dart';
|
||||
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
|
||||
|
||||
class GeckoTabsService {
|
||||
final GeckoTabsApi _api;
|
||||
|
||||
GeckoTabsService({GeckoTabsApi? api}) : _api = api ?? GeckoTabsApi();
|
||||
|
||||
Future<void> selectTab({required String tabId}) {
|
||||
return _api.selectTab(tabId: tabId);
|
||||
}
|
||||
|
||||
Future<void> removeTab({required String tabId}) {
|
||||
return _api.removeTab(tabId: tabId);
|
||||
}
|
||||
|
||||
Future<String> addTab({
|
||||
Uri? url,
|
||||
bool selectTab = true,
|
||||
bool startLoading = true,
|
||||
String? parentId,
|
||||
LoadUrlFlags flags = LoadUrlFlags.NONE,
|
||||
String? contextId,
|
||||
Source source = Internal.newTab,
|
||||
bool private = false,
|
||||
HistoryMetadataKey? historyMetadata,
|
||||
Map<String, String>? additionalHeaders,
|
||||
}) {
|
||||
return _api.addTab(
|
||||
url: (url ?? Uri.parse('about:blank')).toString(),
|
||||
selectTab: selectTab,
|
||||
startLoading: startLoading,
|
||||
parentId: parentId,
|
||||
flags: flags.toValue(),
|
||||
contextId: contextId,
|
||||
source: source.toValue(),
|
||||
private: private,
|
||||
historyMetadata: historyMetadata,
|
||||
additionalHeaders: additionalHeaders,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeAllTabs({required bool recoverable}) {
|
||||
return _api.removeAllTabs(recoverable: recoverable);
|
||||
}
|
||||
|
||||
Future<void> removeTabs({required List<String> ids}) {
|
||||
return _api.removeTabs(ids: ids);
|
||||
}
|
||||
|
||||
Future<void> removeNormalTabs() {
|
||||
return _api.removeNormalTabs();
|
||||
}
|
||||
|
||||
Future<void> removePrivateTabs() {
|
||||
return _api.removePrivateTabs();
|
||||
}
|
||||
|
||||
Future<void> undo() {
|
||||
return _api.undo();
|
||||
}
|
||||
|
||||
//restoreTabs invokes splitted
|
||||
Future<void> restoreTabsByList({
|
||||
required List<RecoverableTab> tabs,
|
||||
String? selectTabId,
|
||||
RestoreLocation restoreLocation = RestoreLocation.end,
|
||||
}) {
|
||||
return _api.restoreTabsByList(
|
||||
tabs: tabs, selectTabId: selectTabId, restoreLocation: restoreLocation);
|
||||
}
|
||||
|
||||
Future<void> restoreTabsByBrowserState({
|
||||
required RecoverableBrowserState state,
|
||||
RestoreLocation restoreLocation = RestoreLocation.end,
|
||||
}) {
|
||||
return _api.restoreTabsByBrowserState(
|
||||
state: state, restoreLocation: restoreLocation);
|
||||
}
|
||||
//The calls with engin storage for restore are not supported at the moment
|
||||
|
||||
//selectOrAddTab invokes splitted
|
||||
/// Selects an already existing tab with the matching [HistoryMetadataKey] or otherwise
|
||||
/// creates a new tab with the given [url].
|
||||
Future<String> selectOrAddTabByHistory({
|
||||
required Uri url,
|
||||
required HistoryMetadataKey historyMetadata,
|
||||
}) {
|
||||
return _api.selectOrAddTabByHistory(
|
||||
url: url.toString(), historyMetadata: historyMetadata);
|
||||
}
|
||||
|
||||
/// Selects an already existing tab displaying [url] or otherwise creates a new tab.
|
||||
Future<String> selectOrAddTabByUrl({
|
||||
required Uri url,
|
||||
bool private = false,
|
||||
Source source = Internal.newTab,
|
||||
LoadUrlFlags flags = LoadUrlFlags.NONE,
|
||||
bool ignoreFragment = false,
|
||||
}) {
|
||||
return _api.selectOrAddTabByUrl(
|
||||
url: url.toString(),
|
||||
private: private,
|
||||
source: source.toValue(),
|
||||
flags: flags.toValue(),
|
||||
ignoreFragment: ignoreFragment,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> duplicateTab({
|
||||
String? selectTabId,
|
||||
bool selectNewTab = true,
|
||||
}) {
|
||||
return _api.duplicateTab(
|
||||
selectTabId: selectTabId, selectNewTab: selectNewTab);
|
||||
}
|
||||
|
||||
Future<void> moveTabs({
|
||||
required List<String> tabIds,
|
||||
required String targetTabId,
|
||||
required bool placeAfter,
|
||||
}) {
|
||||
return _api.moveTabs(
|
||||
tabIds: tabIds, targetTabId: targetTabId, placeAfter: placeAfter);
|
||||
}
|
||||
|
||||
Future<String> migratePrivateTabUseCase({
|
||||
required String tabId,
|
||||
Uri? alternativeUrl,
|
||||
}) {
|
||||
return _api.migratePrivateTabUseCase(
|
||||
tabId: tabId, alternativeUrl: alternativeUrl?.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mozilla_components/src/domain/services/gecko_browser.dart';
|
||||
|
||||
class GeckoView extends StatelessWidget {
|
||||
const GeckoView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PlatformViewLink(
|
||||
viewType: 'eu.lensai/gecko',
|
||||
surfaceFactory: (
|
||||
context,
|
||||
controller,
|
||||
) {
|
||||
return AndroidViewSurface(
|
||||
controller: controller as AndroidViewController,
|
||||
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
|
||||
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||
);
|
||||
},
|
||||
onCreatePlatformView: (PlatformViewCreationParams params) {
|
||||
return PlatformViewsService.initExpensiveAndroidView(
|
||||
id: params.id,
|
||||
viewType: 'eu.lensai/gecko',
|
||||
layoutDirection: TextDirection.ltr,
|
||||
creationParams: {},
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
)
|
||||
..addOnPlatformViewCreatedListener((value) async {
|
||||
params.onPlatformViewCreated(value);
|
||||
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||
await GeckoBrowserService().showNativeFragment();
|
||||
});
|
||||
})
|
||||
..create();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'package:flutter_mozilla_components/src/data/models/source.dart';
|
||||
|
||||
/// Maps an int category (as it can be obtained from a package manager) to our internal representation.
|
||||
PackageCategory packageCategoryfromInt(int? id) {
|
||||
return PackageCategory.values.firstWhere(
|
||||
(category) => category.id == id,
|
||||
orElse: () => PackageCategory.unknown,
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,414 @@
|
||||
import 'package:pigeon/pigeon.dart';
|
||||
|
||||
/// Translation options that map to the Gecko Translations Options.
|
||||
///
|
||||
/// @property downloadModel If the necessary models should be downloaded on request. If false, then
|
||||
/// the translation will not complete and throw an exception if the models are not already available.
|
||||
class TranslationOptions {
|
||||
final bool downloadModel;
|
||||
|
||||
TranslationOptions({this.downloadModel = true});
|
||||
}
|
||||
|
||||
/// Value type that represents the state of reader mode/view.
|
||||
class ReaderState {
|
||||
/// Whether or not the current page can be transformed to
|
||||
/// be displayed in a reader view.
|
||||
final bool readerable;
|
||||
|
||||
/// Whether or not reader view is active.
|
||||
final bool active;
|
||||
|
||||
/// Whether or not a readerable check is required for the
|
||||
/// current page.
|
||||
final bool checkRequired;
|
||||
|
||||
/// Whether or not a new connection to the reader view
|
||||
/// content script is required.
|
||||
final bool connectRequired;
|
||||
|
||||
/// The base URL of the reader view extension page.
|
||||
final String? baseUrl;
|
||||
|
||||
/// The URL of the page currently displayed in reader view.
|
||||
final String? activeUrl;
|
||||
|
||||
/// The vertical scroll position of the page currently
|
||||
/// displayed in reader view.
|
||||
final int? scrollY;
|
||||
|
||||
const ReaderState({
|
||||
this.readerable = false,
|
||||
this.active = false,
|
||||
this.checkRequired = false,
|
||||
this.connectRequired = false,
|
||||
this.baseUrl,
|
||||
this.activeUrl,
|
||||
this.scrollY,
|
||||
});
|
||||
}
|
||||
|
||||
/// Details about the last playing media in this tab.
|
||||
class LastMediaAccessState {
|
||||
/// [ContentState.url] when media started playing.
|
||||
/// This is not the URL of the media but of the page when media started.
|
||||
/// Defaults to "" (an empty String) if media hasn't started playing.
|
||||
/// This value is only updated when media starts playing.
|
||||
/// Can be used as a backup to [mediaSessionActive] for knowing the user is still on the same website
|
||||
/// on which media was playing before media started playing in another tab.
|
||||
final String lastMediaUrl;
|
||||
|
||||
/// The last time media started playing in the current web document.
|
||||
/// Defaults to [0] if media hasn't started playing.
|
||||
/// This value is only updated when media starts playing.
|
||||
final int lastMediaAccess;
|
||||
|
||||
/// Whether or not the last accessed media is still active.
|
||||
/// Can be used as a backup to [lastMediaUrl] on websites which allow media to continue playing
|
||||
/// even when the users accesses another page (with another URL) in that same HTML document.
|
||||
final bool mediaSessionActive;
|
||||
|
||||
const LastMediaAccessState({
|
||||
this.lastMediaUrl = "",
|
||||
this.lastMediaAccess = 0,
|
||||
this.mediaSessionActive = false,
|
||||
});
|
||||
}
|
||||
|
||||
/// Represents a set of history metadata values that uniquely identify a record. Note that
|
||||
/// when recording observations, the same set of values may or may not cause a new record to be
|
||||
/// created, depending on the de-bouncing logic of the underlying storage i.e. recording history
|
||||
/// metadata observations with the exact same values may be combined into a single record.
|
||||
class HistoryMetadataKey {
|
||||
/// A url of the page.
|
||||
final String url;
|
||||
|
||||
/// An optional search term if this record was
|
||||
/// created as part of a search by the user.
|
||||
final String? searchTerm;
|
||||
|
||||
/// An optional url of the parent/referrer if
|
||||
/// this record was created in response to a user opening
|
||||
/// a page in a new tab.
|
||||
final String? referrerUrl;
|
||||
|
||||
const HistoryMetadataKey({
|
||||
required this.url,
|
||||
this.searchTerm,
|
||||
this.referrerUrl,
|
||||
});
|
||||
}
|
||||
|
||||
class PackageCategoryValue {
|
||||
final int value;
|
||||
|
||||
const PackageCategoryValue(this.value);
|
||||
}
|
||||
|
||||
/// Describes an external package.
|
||||
class ExternalPackage {
|
||||
/// An Android package id.
|
||||
final String packageId;
|
||||
|
||||
/// A [PackageCategory] as defined by the application.
|
||||
final PackageCategoryValue category;
|
||||
|
||||
const ExternalPackage(this.packageId, this.category);
|
||||
}
|
||||
|
||||
class LoadUrlFlagsValue {
|
||||
final int value;
|
||||
|
||||
const LoadUrlFlagsValue(this.value);
|
||||
}
|
||||
|
||||
class SourceValue {
|
||||
final int id;
|
||||
final ExternalPackage? caller;
|
||||
|
||||
const SourceValue(this.id, this.caller);
|
||||
}
|
||||
|
||||
/// A tab that is no longer open and in the list of tabs, but that can be restored (recovered) at
|
||||
/// any time if it's combined with an [EngineSessionState] to form a [RecoverableTab].
|
||||
///
|
||||
/// The values of this data class are usually filled with the values of a [TabSessionState] when
|
||||
/// getting closed.
|
||||
class TabState {
|
||||
/// Unique ID identifying this tab.
|
||||
final String id;
|
||||
|
||||
/// The last URL of this tab.
|
||||
final String url;
|
||||
|
||||
/// The unique ID of the parent tab if this tab was opened from another tab (e.g. via
|
||||
/// the context menu).
|
||||
final String? parentId;
|
||||
|
||||
/// The last title of this tab (or an empty String).
|
||||
final String title;
|
||||
|
||||
/// The last used search terms, or an empty string if no
|
||||
/// search was executed for this session.
|
||||
final String searchTerm;
|
||||
|
||||
/// The context ID ("container") this tab used (or null).
|
||||
final String? contextId;
|
||||
|
||||
/// The last [ReaderState] of the tab.
|
||||
final ReaderState readerState;
|
||||
|
||||
/// The last time this tab was selected.
|
||||
final int lastAccess;
|
||||
|
||||
/// Timestamp of the tab's creation.
|
||||
final int createdAt;
|
||||
|
||||
/// Details about the last time was playing in this tab.
|
||||
final LastMediaAccessState lastMediaAccessState;
|
||||
|
||||
/// If tab was private.
|
||||
final bool private;
|
||||
|
||||
/// The last [HistoryMetadataKey] of the tab.
|
||||
final HistoryMetadataKey? historyMetadata;
|
||||
|
||||
/// The last [Source] of the tab.
|
||||
final SourceValue source;
|
||||
|
||||
/// The index the tab should be restored at.
|
||||
final int index;
|
||||
|
||||
/// Whether the tab has form data.
|
||||
final bool hasFormData;
|
||||
|
||||
TabState({
|
||||
required this.id,
|
||||
required this.url,
|
||||
this.parentId,
|
||||
this.title = "",
|
||||
this.searchTerm = "",
|
||||
this.contextId,
|
||||
this.readerState = const ReaderState(),
|
||||
this.lastAccess = 0,
|
||||
this.createdAt = 0,
|
||||
this.lastMediaAccessState = const LastMediaAccessState(),
|
||||
this.private = false,
|
||||
this.historyMetadata,
|
||||
required this.source, //= Internal.none,
|
||||
this.index = -1,
|
||||
this.hasFormData = false,
|
||||
});
|
||||
}
|
||||
|
||||
/// A recoverable version of [TabState].
|
||||
class RecoverableTab {
|
||||
/// The [EngineSessionState] needed for restoring the previous state of this tab.
|
||||
final String? engineSessionStateJson;
|
||||
|
||||
/// A [TabState] instance containing basic tab state.
|
||||
final TabState state;
|
||||
|
||||
const RecoverableTab({
|
||||
this.engineSessionStateJson,
|
||||
required this.state,
|
||||
});
|
||||
}
|
||||
|
||||
/// A restored browser state, read from disk.
|
||||
class RecoverableBrowserState {
|
||||
/// The list of restored tabs.
|
||||
final List<RecoverableTab?> tabs;
|
||||
|
||||
/// The ID of the selected tab in [tabs]. Or `null` if no selection was restored.
|
||||
final String? selectedTabId;
|
||||
|
||||
const RecoverableBrowserState({
|
||||
required this.tabs,
|
||||
this.selectedTabId,
|
||||
});
|
||||
}
|
||||
|
||||
/// Indicates what location the tabs should be restored at
|
||||
enum RestoreLocation {
|
||||
/// Restore tabs at the beginning of the tab list
|
||||
beginning,
|
||||
|
||||
/// Restore tabs at the end of the tab list
|
||||
end,
|
||||
|
||||
/// Restore tabs at a specific index in the tab list
|
||||
atIndex,
|
||||
}
|
||||
|
||||
@ConfigurePigeon(PigeonOptions(
|
||||
dartOut: 'lib/src/pigeons/gecko.g.dart',
|
||||
dartOptions: DartOptions(),
|
||||
kotlinOut:
|
||||
'android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt',
|
||||
kotlinOptions: KotlinOptions(),
|
||||
dartPackageName: 'flutter_mozilla_components',
|
||||
))
|
||||
@HostApi()
|
||||
abstract class GeckoBrowserApi {
|
||||
void showNativeFragment();
|
||||
}
|
||||
|
||||
@HostApi()
|
||||
abstract class GeckoSessionApi {
|
||||
void loadUrl({
|
||||
required String? tabId, //If null = current tab
|
||||
required String url,
|
||||
required LoadUrlFlagsValue flags, //== LoadUrlFlagsBuilder.NONE,
|
||||
required Map<String, String>? additionalHeaders,
|
||||
});
|
||||
|
||||
void loadData({
|
||||
required String? tabId, //If null = current tab
|
||||
required String data,
|
||||
required String mimeType,
|
||||
required String encoding,
|
||||
});
|
||||
|
||||
void reload({
|
||||
required String? tabId, //If null = current tab
|
||||
required LoadUrlFlagsValue flags, //== LoadUrlFlagsBuilder.NONE,
|
||||
});
|
||||
|
||||
void stopLoading({
|
||||
required String? tabId, //If null = current tab
|
||||
});
|
||||
|
||||
void goBack({
|
||||
required String? tabId, //If null = current tab
|
||||
required bool userInteraction,
|
||||
});
|
||||
|
||||
void goForward({
|
||||
required String? tabId, //If null = current tab
|
||||
required bool userInteraction,
|
||||
});
|
||||
|
||||
void goToHistoryIndex({
|
||||
required int index,
|
||||
required String? tabId, //If null = current tab
|
||||
});
|
||||
|
||||
void requestDesktopSite({
|
||||
required String? tabId, //If null = current tab
|
||||
required bool enable,
|
||||
});
|
||||
|
||||
void exitFullscreen({
|
||||
required String? tabId, //If null = current tab
|
||||
});
|
||||
|
||||
void saveToPdf({
|
||||
required String? tabId, //If null = current tab
|
||||
});
|
||||
|
||||
void printContent({
|
||||
required String? tabId, //If null = current tab
|
||||
});
|
||||
|
||||
void translate({
|
||||
required String? tabId, //If null = current tab
|
||||
required String fromLanguage,
|
||||
required String toLanguage,
|
||||
required TranslationOptions? options,
|
||||
});
|
||||
|
||||
void translateRestore({
|
||||
required String? tabId, //If null = current tab
|
||||
});
|
||||
|
||||
void crashRecovery({
|
||||
required List<String>? tabIds,
|
||||
});
|
||||
|
||||
void purgeHistory();
|
||||
|
||||
void updateLastAccess({
|
||||
required String? tabId, //If null = current tab
|
||||
required int? lastAccess, //If null datetime.now
|
||||
});
|
||||
}
|
||||
|
||||
@HostApi()
|
||||
abstract class GeckoTabsApi {
|
||||
void selectTab({required String tabId});
|
||||
|
||||
void removeTab({required String tabId});
|
||||
|
||||
String addTab({
|
||||
required String url,
|
||||
required bool selectTab,
|
||||
required bool startLoading,
|
||||
required String? parentId,
|
||||
required LoadUrlFlagsValue flags,
|
||||
required String? contextId,
|
||||
//engineSession: EngineSession? = null,
|
||||
required SourceValue source, //Internal.NewTab
|
||||
//searchTerms: String = "",
|
||||
required bool private,
|
||||
required HistoryMetadataKey? historyMetadata,
|
||||
//isSearch: Boolean = false,
|
||||
//searchEngineName: String? = null,
|
||||
required Map<String, String>? additionalHeaders,
|
||||
});
|
||||
|
||||
void removeAllTabs({required bool recoverable});
|
||||
|
||||
void removeTabs({required List<String> ids});
|
||||
|
||||
void removeNormalTabs();
|
||||
|
||||
void removePrivateTabs();
|
||||
|
||||
void undo();
|
||||
|
||||
//restoreTabs invokes splitted
|
||||
void restoreTabsByList({
|
||||
required List<RecoverableTab> tabs,
|
||||
required String? selectTabId,
|
||||
required RestoreLocation restoreLocation,
|
||||
});
|
||||
void restoreTabsByBrowserState({
|
||||
required RecoverableBrowserState state,
|
||||
required RestoreLocation restoreLocation,
|
||||
});
|
||||
//The calls with engin storage for restore are not supported at the moment
|
||||
|
||||
//selectOrAddTab invokes splitted
|
||||
/// Selects an already existing tab with the matching [HistoryMetadataKey] or otherwise
|
||||
/// creates a new tab with the given [url].
|
||||
String selectOrAddTabByHistory({
|
||||
required String url,
|
||||
required HistoryMetadataKey historyMetadata,
|
||||
});
|
||||
|
||||
/// Selects an already existing tab displaying [url] or otherwise creates a new tab.
|
||||
String selectOrAddTabByUrl({
|
||||
required String url,
|
||||
required bool private,
|
||||
required SourceValue source, // = Internal.newTab,
|
||||
required LoadUrlFlagsValue flags,
|
||||
required bool ignoreFragment,
|
||||
});
|
||||
|
||||
String duplicateTab({
|
||||
required String? selectTabId,
|
||||
required bool selectNewTab,
|
||||
});
|
||||
|
||||
void moveTabs({
|
||||
required List<String> tabIds,
|
||||
required String targetTabId,
|
||||
required bool placeAfter,
|
||||
});
|
||||
|
||||
String migratePrivateTabUseCase({
|
||||
required String tabId,
|
||||
required String? alternativeUrl,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
name: flutter_mozilla_components
|
||||
description: "A new Flutter plugin project."
|
||||
version: 0.0.1
|
||||
homepage:
|
||||
|
||||
environment:
|
||||
sdk: ^3.6.0-198.0.dev
|
||||
flutter: '>=3.3.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
plugin_platform_interface: ^2.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^4.0.0
|
||||
pigeon: ^22.3.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
|
||||
# which should be registered in the plugin registry. This is required for
|
||||
# using method channels.
|
||||
# The Android 'package' specifies package in which the registered class is.
|
||||
# This is required for using method channels on Android.
|
||||
# The 'ffiPlugin' specifies that native code should be built and bundled.
|
||||
# This is required for using `dart:ffi`.
|
||||
# All these are used by the tooling to maintain consistency when
|
||||
# adding or updating assets for this project.
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: eu.lensai.flutter_mozilla_components
|
||||
pluginClass: FlutterMozillaComponentsPlugin
|
||||
|
||||
# To add assets to your plugin package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/to/asset-from-package
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
||||
# To add custom fonts to your plugin package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/to/font-from-package
|
||||
Reference in New Issue
Block a user