reactivate widgets

This commit is contained in:
Fabian Freund
2025-09-18 14:09:30 +02:00
parent 7b9ff5b3da
commit 24c3cdbb94
12 changed files with 89 additions and 312 deletions
+6 -1
View File
@@ -1,6 +1,7 @@
plugins { plugins {
id "com.android.application" id "com.android.application"
id "kotlin-android" id "kotlin-android"
id "org.jetbrains.kotlin.plugin.compose" version "2.1.20"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin" id "dev.flutter.flutter-gradle-plugin"
} }
@@ -49,10 +50,14 @@ android {
dependencies { dependencies {
// implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// implementation 'androidx.glance:glance-appwidget:1.0.0' implementation 'androidx.glance:glance-appwidget:1.1.1'
implementation 'com.google.android.material:material:1.12.0' implementation 'com.google.android.material:material:1.12.0'
} }
buildFeatures {
compose true
}
signingConfigs { signingConfigs {
release { release {
keyAlias System.getenv("KEY_ALIAS") keyAlias System.getenv("KEY_ALIAS")
+7 -22
View File
@@ -88,7 +88,7 @@
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="eu.weblibre.gecko.action.LAUNCH" /> <action android:name="es.antonborri.home_widget.action.LAUNCH" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
@@ -176,32 +176,17 @@
android:label="@string/mozac_feature_addons_addons" android:label="@string/mozac_feature_addons_addons"
android:theme="@style/Theme.AppCompat.Light"/> android:theme="@style/Theme.AppCompat.Light"/>
<receiver android:name="FullWidgetProvider" android:exported="true"> <receiver
android:name=".SearchBarWidgetReceiver"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
<meta-data android:name="android.appwidget.provider" <meta-data
android:resource="@xml/full_widget_info"/> android:name="android.appwidget.provider"
android:resource="@xml/search_bar_widget_info" />
</receiver> </receiver>
<receiver android:name="CompactWidgetProvider" 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/compact_widget_info"/>
</receiver>
<receiver android:name="SearchBarWidgetProvider" 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 <service
android:name="eu.weblibre.flutter_mozilla_components.services.CustomTabsService" android:name="eu.weblibre.flutter_mozilla_components.services.CustomTabsService"
@@ -1,59 +0,0 @@
/*
* 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.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.graphics.BitmapFactory
import android.net.Uri
import android.view.View
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetBackgroundIntent
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider
class CompactWidgetProvider : HomeWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
appWidgetIds.forEach { widgetId ->
val views = RemoteViews(context.packageName, R.layout.compact_widget).apply {
val searchIntent = HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
Uri.parse("widget://search"))
setOnClickPendingIntent(R.id.search_button, searchIntent)
val assistantIntent = HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
Uri.parse("widget://assistant"))
setOnClickPendingIntent(R.id.assistant_button, assistantIntent)
val summarizerIntent = HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
Uri.parse("widget://summarizer"))
setOnClickPendingIntent(R.id.summarizer_button, summarizerIntent)
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}
@@ -1,59 +0,0 @@
/*
* 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.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.graphics.BitmapFactory
import android.net.Uri
import android.view.View
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetBackgroundIntent
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider
class FullWidgetProvider : HomeWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
appWidgetIds.forEach { widgetId ->
val views = RemoteViews(context.packageName, R.layout.full_widget).apply {
val searchIntent = HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
Uri.parse("widget://search"))
setOnClickPendingIntent(R.id.search_button, searchIntent)
val assistantIntent = HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
Uri.parse("widget://assistant"))
setOnClickPendingIntent(R.id.assistant_button, assistantIntent)
val summarizerIntent = HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
Uri.parse("widget://summarizer"))
setOnClickPendingIntent(R.id.summarizer_button, summarizerIntent)
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}
@@ -1,47 +1,79 @@
/*
* 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 package eu.weblibre.gecko
import android.appwidget.AppWidgetManager
import android.content.Context import android.content.Context
import android.content.SharedPreferences import androidx.compose.runtime.Composable
import android.graphics.BitmapFactory import androidx.compose.ui.graphics.Color
import android.net.Uri import androidx.compose.ui.unit.dp
import android.view.View import androidx.compose.ui.unit.sp
import android.widget.RemoteViews import androidx.glance.GlanceId
import es.antonborri.home_widget.HomeWidgetBackgroundIntent 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 es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider import android.net.Uri
import es.antonborri.home_widget.actionStartActivity
import androidx.core.net.toUri
class SearchBarWidgetProvider : HomeWidgetProvider() { class SearchBarGlanceWidget : GlanceAppWidget() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
appWidgetIds.forEach { widgetId ->
val views = RemoteViews(context.packageName, R.layout.search_bar_widget).apply {
val searchIntent = HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
Uri.parse("widget://search"))
setOnClickPendingIntent(R.id.search_button, searchIntent)
}
appWidgetManager.updateAppWidget(widgetId, views) 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()
}
@@ -1,45 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.WebLibreWidget.AppWidget.Container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:theme="@style/Theme.WebLibreWidget.AppWidgetContainer"
android:gravity="center_vertical">
<Button
android:id="@+id/search_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/search_text_field"
android:drawableStart="@drawable/icon_with_size"
android:drawableEnd="@drawable/mdi_icon_microphone_tint"
android:drawablePadding="8dp"
android:gravity="start|center_vertical"
android:padding="12dp"
android:text="Search with Kagi..."
android:textColor="#848388" />
<Button
android:id="@+id/summarizer_button"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginStart="8dp"
android:backgroundTint="#333237"
android:background="@drawable/button"
android:drawableTint="@color/white"
android:drawableStart="@drawable/mdi_icon_text"
android:padding="16dp" />
<Button
android:id="@+id/assistant_button"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginStart="8dp"
android:backgroundTint="#333237"
android:background="@drawable/button"
android:drawableTint="@color/white"
android:drawableStart="@drawable/mdi_icon_brain"
android:padding="16dp" />
</LinearLayout>
@@ -1,55 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.WebLibreWidget.AppWidget.Container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:theme="@style/Theme.WebLibreWidget.AppWidgetContainer">
<Button
android:id="@+id/search_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/search_text_field"
android:text="Search with Kagi..."
android:textColor="#848388"
android:drawablePadding="8dp"
android:padding="12dp"
android:drawableStart="@drawable/icon_with_size"
android:drawableEnd="@drawable/mdi_icon_microphone_tint"
android:gravity="start|center_vertical" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:paddingTop="12dp">
<Button
android:layout_marginStart="8dp"
android:id="@+id/summarizer_button"
android:padding="16dp"
android:backgroundTint="#333237"
android:background="@drawable/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTint="@color/white"
android:drawableStart="@drawable/mdi_icon_text"
android:drawablePadding="8dp"
android:text="Summarizer" />
<Button
android:layout_marginEnd="8dp"
android:id="@+id/assistant_button"
android:padding="16dp"
android:backgroundTint="#333237"
android:background="@drawable/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTint="@color/white"
android:drawableStart="@drawable/mdi_icon_brain"
android:drawablePadding="8dp"
android:text="Assistant" />
</LinearLayout>
</LinearLayout>
@@ -1,13 +0,0 @@
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/search_text_field"
android:text="Search with Kagi..."
android:textColor="#848388"
android:drawablePadding="8dp"
android:padding="12dp"
android:drawableStart="@drawable/icon_with_size"
android:drawableEnd="@drawable/mdi_icon_microphone_tint"
android:gravity="start|center_vertical" />
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/compact_widget"
android:resizeMode="none"
android:previewLayout="@layout/compact_widget"
android:targetCellWidth="5"
android:targetCellHeight="1"
android:widgetCategory="home_screen" />
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/full_widget"
android:resizeMode="none"
android:previewLayout="@layout/full_widget"
android:targetCellWidth="5"
android:targetCellHeight="2"
android:widgetCategory="home_screen" />
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/search_bar_widget" android:initialLayout="@layout/glance_default_loading_layout"
android:resizeMode="none" android:resizeMode="none"
android:previewLayout="@layout/search_bar_widget"
android:targetCellWidth="5" android:targetCellWidth="5"
android:targetCellHeight="1" android:targetCellHeight="1"
android:widgetCategory="home_screen" /> android:widgetCategory="home_screen" />
@@ -22,6 +22,7 @@ import 'dart:async';
import 'package:nullability/nullability.dart'; import 'package:nullability/nullability.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/data/models/received_intent_parameter.dart'; import 'package:weblibre/data/models/received_intent_parameter.dart';
import 'package:weblibre/features/app_widget/domain/services/home_widget.dart'; import 'package:weblibre/features/app_widget/domain/services/home_widget.dart';
import 'package:weblibre/features/geckoview/domain/providers.dart'; import 'package:weblibre/features/geckoview/domain/providers.dart';
@@ -39,6 +40,8 @@ final _contentParserTransformer =
if (parsed != null) { if (parsed != null) {
sink.add(parsed); sink.add(parsed);
} else if (parameter.tool == 'search') {
sink.add(SharedText(SearchRoute.emptySearchText));
} }
}, },
); );