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