prepare for multiple apps

This commit is contained in:
Fabian Freund
2026-04-06 12:23:11 +02:00
parent bd1600e8dc
commit 5afc323f04
904 changed files with 29 additions and 29 deletions
+121
View File
@@ -0,0 +1,121 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "org.jetbrains.kotlin.plugin.compose" version "2.3.10"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
flutterVersionCode = "1"
}
def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
flutterVersionName = "1.0"
}
android {
namespace = "eu.weblibre.gecko"
compileSdk = 36
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "eu.weblibre.gecko"
// https://api.flutter.dev/flutter/widgets/AndroidView-class.html
minSdkVersion = 26
targetSdk = 36
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
dependencies {
// implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.glance:glance-appwidget:1.1.1'
implementation 'com.google.android.material:material:1.13.0'
}
buildFeatures {
compose true
}
signingConfigs {
release {
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
storeFile System.getenv("KEY_PATH") ? file(System.getenv("KEY_PATH")) : null
storePassword System.getenv("KEY_PASSWORD")
}
}
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.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
matchingFallbacks = ['release'] // Use on the "release" build type in dependencies (AARs)
}
}
bundle {
// Profiler issues require us to temporarily package native code compressed to
// match the previous APK packaging.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1865634
packagingOptions {
jniLibs {
it.useLegacyPackaging = true
}
}
}
packagingOptions {
jniLibs {
useLegacyPackaging true
}
}
//Exclude dependenciesInfo for f-droid
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
}
flutter {
source = "../.."
}
//Override version code for f-droid
ext.abiCodes = ["x86_64": 1, "armeabi-v7a": 2, "arm64-v8a": 3]
import com.android.build.OutputFile
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
if (abiVersionCode != null) {
output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode
}
}
}
+72
View File
@@ -0,0 +1,72 @@
-dontobfuscate
####################################################################################################
# Android and GeckoView built-ins
####################################################################################################
-dontwarn android.**
-dontwarn androidx.**
-dontwarn com.google.**
-dontwarn org.mozilla.geckoview.**
# Raptor now writes a *-config.yaml file to specify Gecko runtime settings (e.g. the profile dir). This
# file gets deserialized into a DebugConfig object, which is why we need to keep this class
# and its members.
-keep class org.mozilla.gecko.util.DebugConfig { *; }
####################################################################################################
# kotlinx.coroutines: use the fast service loader to init MainDispatcherLoader by including a rule
# to rewrite this property to return true:
# https://github.com/Kotlin/kotlinx.coroutines/blob/8c98180f177bbe4b26f1ed9685a9280fea648b9c/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt#L19
#
# R8 is expected to optimize the default implementation to avoid a performance issue but a bug in R8
# as bundled with AGP v7.0.0 causes this optimization to fail so we use the fast service loader instead. See:
# https://github.com/mozilla-mobile/focus-android/issues/5102#issuecomment-897854121
#
# The fast service loader appears to be as performant as the R8 optimization so it's not worth the
# churn to later remove this workaround. If needed, the upstream fix is being handled in
# https://issuetracker.google.com/issues/196302685
####################################################################################################
-assumenosideeffects class kotlinx.coroutines.internal.MainDispatcherLoader {
boolean FAST_SERVICE_LOADER_ENABLED return true;
}
####################################################################################################
# REMOVE all Log messages except warnings and errors
####################################################################################################
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int d(...);
}
####################################################################################################
# Mozilla Application Services
####################################################################################################
-keep class mozilla.appservices.** { *; }
####################################################################################################
# ViewModels
####################################################################################################
-keep class org.mozilla.fenix.**ViewModel { *; }
# Keep Android Lifecycle methods
# https://bugzilla.mozilla.org/show_bug.cgi?id=1596302
-keep class androidx.lifecycle.** { *; }
-dontwarn java.beans.BeanInfo
-dontwarn java.beans.FeatureDescriptor
-dontwarn java.beans.IntrospectionException
-dontwarn java.beans.Introspector
-dontwarn java.beans.PropertyDescriptor
####################################################################################################
# Checker Framework
####################################################################################################
-dontwarn org.checkerframework.checker.nullness.qual.EnsuresNonNull
-dontwarn org.checkerframework.checker.nullness.qual.EnsuresNonNullIf
-dontwarn org.checkerframework.checker.nullness.qual.RequiresNonNull
@@ -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,289 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<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" />
<!-- Fenix only uses this in debug manifest -->
<uses-permission android:name="android.permission.ACCESS_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" />
<!-- Needed to interact with all apps installed on a device -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<!-- Needed to support Credential Manager with Android 14 and later. -->
<uses-permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ORIGIN" />
<uses-permission android:name="android.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS" />
<application
android:label="WebLibre"
android:name=".MyApplication"
android:icon="@mipmap/launcher_icon"
android:usesCleartextTraffic="true"
android:enableOnBackInvokedCallback="true">
<activity
android:name="eu.weblibre.flutter_mozilla_components.activities.NotificationActivity"
android:theme="@style/Theme.AppCompat.Translucent"
android:exported="false"
android:launchMode="singleTop" />
<activity
android:name=".MainActivity"
android:label="WebLibre"
android:exported="true"
android:launchMode="singleTask"
android:taskAffinity="eu.weblibre.gecko.task"
android:theme="@style/LaunchTheme"
android:supportsPictureInPicture="true"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- We do want to ahndle "open with" by ourselfs. Prevents automatically pushing routes. -->
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
<!-- 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"/>
<category android:name="android.intent.category.APP_BROWSER"/>
</intent-filter>
<intent-filter>
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="text/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PROCESS_TEXT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.WEB_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- Intent router: receives all VIEW intents and routes Custom Tab / PWA to
ExternalAppBrowserActivity, regular VIEW intents to MainActivity -->
<activity
android:name="eu.weblibre.flutter_mozilla_components.activities.IntentReceiverActivity"
android:theme="@style/Theme.AppCompat.Translucent"
android:exported="true"
android:excludeFromRecents="true"
android:relinquishTaskIdentity="true"
android:taskAffinity="">
<!-- Required exactly like this to appear in both default app choosers and when explicitly browsing -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="mozilla.components.pwa.category.SHORTCUT" />
<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>
<!-- like above but specifying additional schemes. Not qualified for app chooser. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:mimeType="text/html" />
<data android:mimeType="text/plain" />
<data android:mimeType="application/xhtml+xml" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="application/pdf" />
</intent-filter>
<!-- PWA launch from home screen shortcut -->
<intent-filter>
<action android:name="mozilla.components.feature.pwa.VIEW_PWA" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" />
</intent-filter>
</activity>
<!-- Native activity for Custom Tabs and PWA browsing (non-Flutter) -->
<activity
android:name="eu.weblibre.flutter_mozilla_components.activities.ExternalAppBrowserActivity"
android:exported="false"
android:taskAffinity=""
android:documentLaunchMode="always"
android:autoRemoveFromRecents="false"
android:theme="@style/ExternalAppBrowserTheme"
android:supportsPictureInPicture="true"
android:resizeableActivity="true"
android:persistableMode="persistNever"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name="eu.weblibre.flutter_mozilla_components.activities.AuthCustomTabActivity"
android:exported="false"
android:taskAffinity=""
android:autoRemoveFromRecents="false"
android:theme="@style/ExternalAppBrowserTheme"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|layoutDirection|smallestScreenSize|screenLayout"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name="eu.weblibre.flutter_mozilla_components.activities.AuthIntentReceiverActivity"
android:exported="false" />
<activity
android:theme="@style/AddonsActivityTheme"
android:name="eu.weblibre.flutter_mozilla_components.addons.AddonsActivity"
android:label="@string/mozac_feature_addons_addons"
android:exported="false"
android:parentActivityName=".MainActivity" />
<activity
android:theme="@style/AppTheme"
android:name="eu.weblibre.flutter_mozilla_components.addons.AddonDetailsActivity"
android:exported="false"
android:label="@string/mozac_feature_addons_addons" />
<activity
android:name="eu.weblibre.flutter_mozilla_components.addons.InstalledAddonDetailsActivity"
android:label="@string/mozac_feature_addons_addons"
android:parentActivityName="eu.weblibre.flutter_mozilla_components.addons.AddonsActivity"
android:exported="false"
android:theme="@style/AppTheme" />
<activity
android:name="eu.weblibre.flutter_mozilla_components.addons.PermissionsDetailsActivity"
android:label="@string/mozac_feature_addons_addons"
android:exported="false"
android:theme="@style/AppTheme" />
<activity
android:name="eu.weblibre.flutter_mozilla_components.addons.AddonInternalSettingsActivity"
android:label="@string/mozac_feature_addons_addons"
android:exported="false"
android:theme="@style/AppTheme" />
<activity
android:name="eu.weblibre.flutter_mozilla_components.addons.WebExtensionActionPopupActivity"
android:label="@string/mozac_feature_addons_addons"
android:theme="@style/AppTheme"/>
<receiver
android:name=".SearchBarWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/search_bar_widget_info" />
</receiver>
<service
android:name="eu.weblibre.flutter_mozilla_components.services.CustomTabsService"
android:exported="true">
<intent-filter>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent-filter>
</service>
<service
android:name="eu.weblibre.flutter_mozilla_components.services.DownloadService"
android:foregroundServiceType="dataSync"
android:exported="false" />
<service android:name="eu.weblibre.flutter_mozilla_components.services.MediaSessionService"
android:foregroundServiceType="mediaPlayback"
android:exported="false" />
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="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>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="*" />
</intent>
</queries>
<!-- 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. -->
<!-- This will show a lot of app actions in context menu when selecting text
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries> -->
</manifest>
@@ -0,0 +1,155 @@
/*
* Copyright (c) 2024-2025 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.weblibre.gecko
import android.content.Context
import android.os.Bundle
import android.util.Log
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.embedding.engine.FlutterJNI
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterFragmentActivity() {
companion object {
private const val TAG = "MainActivity"
}
private val TRIM_MEMORY_CHANNEL = "eu.weblibre.flutter_mozilla_components/trim_memory"
private val ACTIVITY_CHANNEL = "eu.weblibre.gecko/activity"
private val ENGINE_ID = "engine_id"
private var trimMemoryChannel: MethodChannel? = null
private fun engineTag(engine: FlutterEngine?): String {
return engine?.let { "0x${System.identityHashCode(it).toString(16)}" } ?: "null"
}
override fun onCreate(savedInstanceState: Bundle?) {
Log.d(TAG, "onCreate: savedInstanceState=${savedInstanceState != null}, " +
"cachedEngine=${engineTag(FlutterEngineCache.getInstance().get(ENGINE_ID))}")
super.onCreate(null)
}
/**
* Check whether the FlutterEngine's native JNI layer is still attached.
* Note: binaryMessenger.send() does NOT throw when JNI is detached — it just
* logs a warning. We must use reflection to access FlutterJNI.isAttachedToJni().
*/
private fun isEngineNativeAlive(engine: FlutterEngine): Boolean {
return try {
val field = FlutterEngine::class.java.getDeclaredField("flutterJNI")
field.isAccessible = true
val jni = field.get(engine) as FlutterJNI
jni.isAttached
} catch (e: Exception) {
Log.w(TAG, "Could not check JNI attachment state: ${e.message}")
false
}
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
trimMemoryChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, TRIM_MEMORY_CHANNEL)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, ACTIVITY_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"moveTaskToBack" -> {
moveTaskToBack(true)
result.success(null)
}
else -> result.notImplemented()
}
}
}
override fun onPause() {
Log.d(TAG, "onPause")
super.onPause()
}
override fun onDestroy() {
Log.d(TAG, "onDestroy: isFinishing=$isFinishing, " +
"cachedEngine=${engineTag(FlutterEngineCache.getInstance().get(ENGINE_ID))}")
super.onDestroy()
if (!isFinishing) {
val cache = FlutterEngineCache.getInstance()
val engine = cache.get(ENGINE_ID)
if (engine != null) {
Log.d(TAG, "onDestroy: system-initiated destroy, clearing stale engine")
cache.remove(ENGINE_ID)
try {
engine.destroy()
} catch (e: Exception) {
Log.w(TAG, "Error destroying engine in onDestroy", e)
}
}
}
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
trimMemoryChannel?.invokeMethod("onTrimMemory", level)
}
override fun provideFlutterEngine(context: Context): FlutterEngine {
val cache = FlutterEngineCache.getInstance()
val cachedEngine = cache.get(ENGINE_ID)
if (cachedEngine != null) {
val isHealthy = try {
cachedEngine.dartExecutor.isExecutingDart && isEngineNativeAlive(cachedEngine)
} catch (e: Exception) {
Log.w(TAG, "Cached engine health check failed", e)
false
}
if (isHealthy) {
Log.d(TAG, "provideFlutterEngine: reusing cached engine ${engineTag(cachedEngine)}")
return cachedEngine
}
Log.w(TAG, "provideFlutterEngine: cached engine ${engineTag(cachedEngine)} is stale, creating fresh")
cache.remove(ENGINE_ID)
try {
cachedEngine.destroy()
} catch (e: Exception) {
Log.w(TAG, "Error destroying stale engine", e)
}
}
val flutterEngine = FlutterEngine(context.applicationContext)
flutterEngine.navigationChannel.setInitialRoute("/")
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
cache.put(ENGINE_ID, flutterEngine)
Log.d(TAG, "provideFlutterEngine: created new engine ${engineTag(flutterEngine)}")
return flutterEngine
}
override fun shouldDestroyEngineWithHost(): Boolean {
return false // Keep engine alive when activity is destroyed
}
}
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024-2025 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.weblibre.gecko
import android.app.Application
import android.content.SharedPreferences
import eu.weblibre.flutter_mozilla_components.ActiveProfile
import eu.weblibre.flutter_mozilla_components.MegazordSetup
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
MegazordSetup.setupEarlyMainProcess()
// Resolve active profile EARLY so cold-start WorkManager workers
// get profile-prefixed SharedPreferences
ActiveProfile.resolveFromDisk(this)
}
override fun getSharedPreferences(name: String, mode: Int): SharedPreferences {
val pfx = ActiveProfile.prefix
if (pfx != null && name in ActiveProfile.FXA_SHARED_PREFERENCE_NAMES) {
return super.getSharedPreferences("${pfx}_$name", mode)
}
return super.getSharedPreferences(name, mode)
}
}
@@ -0,0 +1,79 @@
package eu.weblibre.gecko
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.action.clickable
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import androidx.glance.appwidget.provideContent
import androidx.glance.background
import androidx.glance.layout.Alignment
import androidx.glance.layout.Row
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.padding
import androidx.glance.layout.wrapContentHeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import android.net.Uri
import es.antonborri.home_widget.actionStartActivity
import androidx.core.net.toUri
class SearchBarGlanceWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
SearchBarContent(context)
}
}
}
@Composable
private fun SearchBarContent(context: Context) {
Row(
modifier = GlanceModifier
.fillMaxWidth()
.wrapContentHeight()
.background(ImageProvider(R.drawable.search_text_field))
.padding(12.dp)
.clickable(onClick = actionStartActivity<MainActivity>(context,
"widget://search".toUri())),
verticalAlignment = Alignment.CenterVertically
) {
// Start icon
Image(
provider = ImageProvider(R.drawable.icon_with_size),
contentDescription = "Search icon",
modifier = GlanceModifier.padding(end = 8.dp)
)
// Search text
Text(
text = "Search with WebLibre...",
style = TextStyle(
color = ColorProvider(Color(0xFF848388)),
fontSize = 16.sp
),
modifier = GlanceModifier.defaultWeight()
)
// End icon (microphone)
Image(
provider = ImageProvider(R.drawable.mdi_icon_microphone_tint),
contentDescription = "Microphone icon",
modifier = GlanceModifier.padding(start = 8.dp)
)
}
}
class SearchBarWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = SearchBarGlanceWidget()
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF"/> <!-- Background color -->
<corners android:radius="18dp"/> <!-- Corner radius -->
</shape>
@@ -0,0 +1,10 @@
<!-- res/drawable/icon_with_size.xml -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:width="24dp"
android:height="24dp">
<bitmap
android:src="@mipmap/launcher_icon"
android:width="24dp"
android:height="24dp"/>
</item>
</layer-list>
@@ -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>
@@ -0,0 +1 @@
<!-- drawable/brain.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M21.33,12.91C21.42,14.46 20.71,15.95 19.44,16.86L20.21,18.35C20.44,18.8 20.47,19.33 20.27,19.8C20.08,20.27 19.69,20.64 19.21,20.8L18.42,21.05C18.25,21.11 18.06,21.14 17.88,21.14C17.37,21.14 16.89,20.91 16.56,20.5L14.44,18C13.55,17.85 12.71,17.47 12,16.9C11.5,17.05 11,17.13 10.5,17.13C9.62,17.13 8.74,16.86 8,16.34C7.47,16.5 6.93,16.57 6.38,16.56C5.59,16.57 4.81,16.41 4.08,16.11C2.65,15.47 1.7,14.07 1.65,12.5C1.57,11.78 1.69,11.05 2,10.39C1.71,9.64 1.68,8.82 1.93,8.06C2.3,7.11 3,6.32 3.87,5.82C4.45,4.13 6.08,3 7.87,3.12C9.47,1.62 11.92,1.46 13.7,2.75C14.12,2.64 14.56,2.58 15,2.58C16.36,2.55 17.65,3.15 18.5,4.22C20.54,4.75 22,6.57 22.08,8.69C22.13,9.8 21.83,10.89 21.22,11.82C21.29,12.18 21.33,12.54 21.33,12.91M16.33,11.5C16.9,11.57 17.35,12 17.35,12.57A1,1 0 0,1 16.35,13.57H15.72C15.4,14.47 14.84,15.26 14.1,15.86C14.35,15.95 14.61,16 14.87,16.07C20,16 19.4,12.87 19.4,12.82C19.34,11.39 18.14,10.27 16.71,10.33A1,1 0 0,1 15.71,9.33A1,1 0 0,1 16.71,8.33C17.94,8.36 19.12,8.82 20.04,9.63C20.09,9.34 20.12,9.04 20.12,8.74C20.06,7.5 19.5,6.42 17.25,6.21C16,3.25 12.85,4.89 12.85,5.81V5.81C12.82,6.04 13.06,6.53 13.1,6.56A1,1 0 0,1 14.1,7.56C14.1,8.11 13.65,8.56 13.1,8.56V8.56C12.57,8.54 12.07,8.34 11.67,8C11.19,8.31 10.64,8.5 10.07,8.56V8.56C9.5,8.61 9.03,8.21 9,7.66C8.92,7.1 9.33,6.61 9.88,6.56C10.04,6.54 10.82,6.42 10.82,5.79V5.79C10.82,5.13 11.07,4.5 11.5,4C10.58,3.75 9.59,4.08 8.59,5.29C6.75,5 6,5.25 5.45,7.2C4.5,7.67 4,8 3.78,9C4.86,8.78 5.97,8.87 7,9.25C7.5,9.44 7.78,10 7.59,10.54C7.4,11.06 6.82,11.32 6.3,11.13C5.57,10.81 4.75,10.79 4,11.07C3.68,11.34 3.68,11.9 3.68,12.34C3.68,13.08 4.05,13.77 4.68,14.17C5.21,14.44 5.8,14.58 6.39,14.57C6.24,14.31 6.11,14.04 6,13.76C5.81,13.22 6.1,12.63 6.64,12.44C7.18,12.25 7.77,12.54 7.96,13.08C8.36,14.22 9.38,15 10.58,15.13C11.95,15.06 13.17,14.25 13.77,13C14,11.62 15.11,11.5 16.33,11.5M18.33,18.97L17.71,17.67L17,17.83L18,19.08L18.33,18.97M13.68,10.36C13.7,9.83 13.3,9.38 12.77,9.33C12.06,9.29 11.37,9.53 10.84,10C10.27,10.58 9.97,11.38 10,12.19A1,1 0 0,0 11,13.19C11.57,13.19 12,12.74 12,12.19C12,11.92 12.07,11.65 12.23,11.43C12.35,11.33 12.5,11.28 12.66,11.28C13.21,11.31 13.68,10.9 13.68,10.36Z" /></vector>
@@ -0,0 +1 @@
<!-- drawable/microphone.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#848388" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M12,2A3,3 0 0,1 15,5V11A3,3 0 0,1 12,14A3,3 0 0,1 9,11V5A3,3 0 0,1 12,2M19,11C19,14.53 16.39,17.44 13,17.93V21H11V17.93C7.61,17.44 5,14.53 5,11H7A5,5 0 0,0 12,16A5,5 0 0,0 17,11H19Z" /></vector>
@@ -0,0 +1 @@
<!-- drawable/search_web.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M15.5,14L20.5,19L19,20.5L14,15.5V14.71L13.73,14.43C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.43,13.73L14.71,14H15.5M9.5,4.5L8.95,4.53C8.71,5.05 8.34,5.93 8.07,7H10.93C10.66,5.93 10.29,5.05 10.05,4.53C9.87,4.5 9.69,4.5 9.5,4.5M13.83,7C13.24,5.97 12.29,5.17 11.15,4.78C11.39,5.31 11.7,6.08 11.93,7H13.83M5.17,7H7.07C7.3,6.08 7.61,5.31 7.85,4.78C6.71,5.17 5.76,5.97 5.17,7M4.5,9.5C4.5,10 4.58,10.53 4.73,11H6.87L6.75,9.5L6.87,8H4.73C4.58,8.47 4.5,9 4.5,9.5M14.27,11C14.42,10.53 14.5,10 14.5,9.5C14.5,9 14.42,8.47 14.27,8H12.13C12.21,8.5 12.25,9 12.25,9.5C12.25,10 12.21,10.5 12.13,11H14.27M7.87,8L7.75,9.5L7.87,11H11.13C11.21,10.5 11.25,10 11.25,9.5C11.25,9 11.21,8.5 11.13,8H7.87M9.5,14.5C9.68,14.5 9.86,14.5 10.03,14.47C10.28,13.95 10.66,13.07 10.93,12H8.07C8.34,13.07 8.72,13.95 8.97,14.47L9.5,14.5M13.83,12H11.93C11.7,12.92 11.39,13.69 11.15,14.22C12.29,13.83 13.24,13.03 13.83,12M5.17,12C5.76,13.03 6.71,13.83 7.85,14.22C7.61,13.69 7.3,12.92 7.07,12H5.17Z" /></vector>
@@ -0,0 +1 @@
<!-- drawable/text.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M21,6V8H3V6H21M3,18H12V16H3V18M3,13H21V11H3V13Z" /></vector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#333237"/> <!-- Background color -->
<corners android:radius="32dp"/> <!-- Rounded corners -->
<stroke android:width="1dp" android:color="#BDBDBD"/> <!-- Border -->
</shape>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="25%" />
</foreground>
<monochrome>
<inset
android:drawable="@drawable/ic_launcher_monochrome"
android:inset="25%" />
</monochrome>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

@@ -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="Theme.Material3.DynamicColors.DayNight.NoActionBar">
<!-- 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.Material3.DynamicColors.DayNight.NoActionBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<resources>
<declare-styleable name="AppWidgetAttrs">
<attr name="appWidgetPadding" format="dimension" />
<attr name="appWidgetInnerRadius" format="dimension" />
<attr name="appWidgetRadius" format="dimension" />
</declare-styleable>
</resources>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="light_blue_50">#FFE1F5FE</color>
<color name="light_blue_200">#FF81D4FA</color>
<color name="light_blue_600">#FF039BE5</color>
<color name="light_blue_900">#FF01579B</color>
<color name="ic_launcher_background">#FF000000</color>
</resources>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Refer to App Widget Documentation for margin information
http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout
-->
<dimen name="widget_margin">0dp</dimen>
</resources>
@@ -0,0 +1,41 @@
<?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="Theme.Material3.DynamicColors.DayNight.NoActionBar">
<!-- 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.Material3.DynamicColors.DayNight.NoActionBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
<style name="Widget.WebLibreWidget.AppWidget.Container" parent="android:Widget">
<item name="android:id">@android:id/background</item>
<item name="android:background">?android:attr/colorBackground</item>
</style>
<style name="Widget.WebLibreWidget.AppWidget.InnerView" parent="android:Widget">
<item name="android:background">?android:attr/colorBackground</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="AppTheme" parent="Theme.Material3.DynamicColors.DayNight">
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
</style>
<style name="ExternalAppBrowserTheme" parent="Theme.Material3.DynamicColors.DayNight.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="AddonsActivityTheme" parent="AppTheme">
<item name="mozac_primary_text_color">@color/photonDarkGrey90</item>
</style>
</resources>
@@ -0,0 +1,31 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.WebLibreWidget" parent="@style/Theme.Material3.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
<style name="Theme.WebLibreWidget.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault">
<!-- Radius of the outer bound of widgets to make the rounded corners -->
<item name="appWidgetRadius">16dp</item>
<!--
Radius of the inner view's bound of widgets to make the rounded corners.
It needs to be 8dp or less than the value of appWidgetRadius
-->
<item name="appWidgetInnerRadius">8dp</item>
</style>
<style name="Theme.WebLibreWidget.AppWidgetContainer" parent="Theme.WebLibreWidget.AppWidgetContainerParent">
<!-- Apply padding to avoid the content of the widget colliding with the rounded corners -->
<item name="appWidgetPadding">16dp</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/glance_default_loading_layout"
android:resizeMode="none"
android:targetCellWidth="5"
android:targetCellHeight="1"
android:widgetCategory="home_screen" />
@@ -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>