diff --git a/app/build.gradle b/app/build.gradle
index 1a0a6fb..eaf97f7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -106,6 +106,7 @@ dependencies {
implementation 'com.google.android.material:material:1.12.0'
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
implementation "eu.jonahbauer:android-preference-annotations:1.1.2"
+ implementation 'androidx.activity:activity:1.10.1'
annotationProcessor "eu.jonahbauer:android-preference-annotations:1.1.2"
annotationProcessor "com.android.databinding:compiler:$android_plugin_version"
testImplementation 'junit:junit:4.13.2'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a5f8831..579d5c1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,6 +19,9 @@
android:supportsRtl="true"
android:theme="@style/launcherBaseTheme"
tools:ignore="UnusedAttribute">
+
-
+
\ No newline at end of file
diff --git a/app/src/main/java/de/jrpie/android/launcher/Application.kt b/app/src/main/java/de/jrpie/android/launcher/Application.kt
index e6cce23..3c2e3bc 100644
--- a/app/src/main/java/de/jrpie/android/launcher/Application.kt
+++ b/app/src/main/java/de/jrpie/android/launcher/Application.kt
@@ -12,6 +12,8 @@ import android.os.Build.VERSION_CODES
import android.os.UserHandle
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetManager
import androidx.preference.PreferenceManager
import de.jrpie.android.launcher.actions.TorchManager
import de.jrpie.android.launcher.apps.AbstractAppInfo
@@ -24,9 +26,15 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+
+const val APP_WIDGET_HOST_ID = 42;
+
+
class Application : android.app.Application() {
val apps = MutableLiveData>()
val privateSpaceLocked = MutableLiveData()
+ lateinit var appWidgetHost: AppWidgetHost
+ lateinit var appWidgetManager: AppWidgetManager
private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
@@ -103,10 +111,15 @@ class Application : android.app.Application() {
torchManager = TorchManager(this)
}
+ appWidgetHost = AppWidgetHost(this.applicationContext, APP_WIDGET_HOST_ID)
+ appWidgetManager = AppWidgetManager.getInstance(this.applicationContext)
+
+ appWidgetHost.startListening()
+
+
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
LauncherPreferences.init(preferences, this.resources)
-
// Try to restore old preferences
migratePreferencesToNewVersion(this)
@@ -157,4 +170,10 @@ class Application : android.app.Application() {
apps.postValue(getApps(packageManager, applicationContext))
}
}
+
+ override fun onTerminate() {
+ appWidgetHost.stopListening()
+ super.onTerminate()
+
+ }
}
diff --git a/app/src/main/java/de/jrpie/android/launcher/Functions.kt b/app/src/main/java/de/jrpie/android/launcher/Functions.kt
index afc2c31..9679ae5 100644
--- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt
+++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt
@@ -6,6 +6,9 @@ import android.app.role.RoleManager
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.appwidget.AppWidgetProviderInfo
import android.content.Context
import android.content.Intent
import android.content.pm.LauncherApps
@@ -223,4 +226,4 @@ fun copyToClipboard(context: Context, text: String) {
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipData = ClipData.newPlainText("Debug Info", text)
clipboardManager.setPrimaryClip(clipData)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt b/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt
index 6ba467e..0cc3dd4 100644
--- a/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt
+++ b/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt
@@ -21,6 +21,8 @@ import de.jrpie.android.launcher.apps.togglePrivateSpaceLock
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.ui.list.ListActivity
import de.jrpie.android.launcher.ui.settings.SettingsActivity
+import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
+import de.jrpie.android.launcher.ui.widgets.SelectWidgetActivity
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -61,7 +63,10 @@ enum class LauncherAction(
"choose_from_favorites",
R.string.list_other_list_favorites,
R.drawable.baseline_favorite_24,
- { context -> openAppsList(context, favorite = true) },
+ { context ->
+ context.startActivity(Intent(context.applicationContext, SelectWidgetActivity::class.java))
+ },
+ //openAppsList(context, favorite = true) },
true
),
CHOOSE_FROM_PRIVATE_SPACE(
diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java b/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java
index 85979fe..d8397a0 100644
--- a/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java
+++ b/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java
@@ -8,6 +8,7 @@ import de.jrpie.android.launcher.actions.lock.LockMethod;
import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer;
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer;
import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer;
+import de.jrpie.android.launcher.preferences.serialization.SetWidgetInfoSerializer;
import de.jrpie.android.launcher.preferences.theme.Background;
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
import de.jrpie.android.launcher.preferences.theme.Font;
@@ -26,6 +27,7 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
@Preference(name = "started_time", type = long.class),
// see PREFERENCE_VERSION in de.jrpie.android.launcher.preferences.Preferences.kt
@Preference(name = "version_code", type = int.class, defaultValue = "-1"),
+ @Preference(name = "widgets", type = Set.class, serializer = SetWidgetInfoSerializer.class)
}),
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
@Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class),
diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt
index 3e19daf..1746e8a 100644
--- a/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt
+++ b/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt
@@ -4,6 +4,7 @@ package de.jrpie.android.launcher.preferences.serialization
import de.jrpie.android.launcher.apps.AbstractAppInfo
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
+import de.jrpie.android.launcher.widgets.WidgetInfo
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
import kotlinx.serialization.Serializable
@@ -28,6 +29,24 @@ class SetAbstractAppInfoPreferenceSerializer :
}
}
+
+@Suppress("UNCHECKED_CAST")
+class SetWidgetInfoSerializer :
+ PreferenceSerializer?, java.util.Set?> {
+ @Throws(PreferenceSerializationException::class)
+ override fun serialize(value: java.util.Set?): java.util.Set {
+ return value?.map(WidgetInfo::serialize)
+ ?.toHashSet() as java.util.Set
+ }
+
+ @Throws(PreferenceSerializationException::class)
+ override fun deserialize(value: java.util.Set?): java.util.Set? {
+ return value?.map(java.lang.String::toString)?.map(WidgetInfo::deserialize)
+ ?.toHashSet() as? java.util.Set
+ }
+}
+
+
@Suppress("UNCHECKED_CAST")
class SetPinnedShortcutInfoPreferenceSerializer :
PreferenceSerializer?, java.util.Set?> {
diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt
index 2ab5d9f..44947bb 100644
--- a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt
+++ b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt
@@ -9,9 +9,12 @@ import android.os.Bundle
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
+import android.view.ViewGroup
import android.window.OnBackInvokedDispatcher
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
+import androidx.core.view.marginTop
+import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.actions.Gesture
@@ -20,7 +23,13 @@ import de.jrpie.android.launcher.databinding.HomeBinding
import de.jrpie.android.launcher.openTutorial
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
+import de.jrpie.android.launcher.widgets.bindAppWidget
+import de.jrpie.android.launcher.widgets.createAppWidgetView
+import de.jrpie.android.launcher.widgets.deleteAllWidgets
+import de.jrpie.android.launcher.widgets.getAppWidgetProviders
import java.util.Locale
+import kotlin.math.absoluteValue
+import kotlin.random.Random
/**
* [HomeActivity] is the actual application Launcher,
@@ -73,6 +82,17 @@ class HomeActivity : UIObject, AppCompatActivity() {
binding.buttonFallbackSettings.setOnClickListener {
LauncherAction.SETTINGS.invoke(this)
}
+
+ // deleteAllWidgets(this)
+
+ LauncherPreferences.internal().widgets().forEach { widget ->
+ createAppWidgetView(this, widget)?.let {
+ binding.homeWidgetContainer.addView(it)
+ }
+ }
+
+ // TODO: appWidgetHost.deleteAppWidgetId(appWidgetId)
+
}
override fun onConfigurationChanged(newConfig: Configuration) {
diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/SelectWidgetActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/SelectWidgetActivity.kt
new file mode 100644
index 0000000..98dcbe7
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/SelectWidgetActivity.kt
@@ -0,0 +1,105 @@
+package de.jrpie.android.launcher.ui.widgets
+
+import android.app.Activity
+import android.os.Build
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import de.jrpie.android.launcher.R
+import de.jrpie.android.launcher.actions.Action
+import de.jrpie.android.launcher.actions.Gesture
+import de.jrpie.android.launcher.actions.LauncherAction
+import de.jrpie.android.launcher.databinding.ActivitySelectWidgetBinding
+import de.jrpie.android.launcher.databinding.HomeBinding
+import de.jrpie.android.launcher.ui.list.ListActivity
+import de.jrpie.android.launcher.ui.list.other.OtherRecyclerAdapter
+import de.jrpie.android.launcher.widgets.bindAppWidget
+import de.jrpie.android.launcher.widgets.getAppWidgetProviders
+
+class SelectWidgetActivity : AppCompatActivity() {
+ lateinit var binding: ActivitySelectWidgetBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ enableEdgeToEdge()
+ // Initialise layout
+ binding = ActivitySelectWidgetBinding.inflate(layoutInflater)
+
+ setContentView(binding.root)
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+ val viewManager = LinearLayoutManager(this)
+ val viewAdapter = SelectWidgetRecyclerAdapter(this)
+
+ binding.selectWidgetRecycler.apply {
+ // improve performance (since content changes don't change the layout size)
+ setHasFixedSize(true)
+ layoutManager = viewManager
+ adapter = viewAdapter
+ }
+ }
+}
+
+class SelectWidgetRecyclerAdapter(val activity: Activity) :
+ RecyclerView.Adapter() {
+
+ private val widgets = getAppWidgetProviders(activity).toTypedArray()
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
+ View.OnClickListener {
+ var textView: TextView = itemView.findViewById(R.id.list_widgets_row_name)
+ var iconView: ImageView = itemView.findViewById(R.id.list_widgets_row_icon)
+ var previewView: ImageView = itemView.findViewById(R.id.list_widgets_row_preview)
+
+
+ override fun onClick(v: View) {
+ val pos = bindingAdapterPosition
+ val content = widgets[pos]
+
+ bindAppWidget(activity, content)
+ activity.finish()
+ }
+
+ init {
+ itemView.setOnClickListener(this)
+ }
+ }
+
+ override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
+ val label = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ "${widgets[i].activityInfo.loadLabel(activity.packageManager)} ${widgets[i].loadDescription(activity)}"
+ } else {
+ widgets[i].label
+ }
+ val preview = widgets[i].loadPreviewImage(activity, 100)
+ val icon = widgets[i].loadIcon(activity, 100)
+
+ viewHolder.textView.text = label
+ viewHolder.iconView.setImageDrawable(icon)
+ viewHolder.previewView.setImageDrawable(preview)
+ }
+
+ override fun getItemCount(): Int {
+ return widgets.size
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ val view: View = inflater.inflate(R.layout.list_widgets_row, parent, false)
+ return ViewHolder(view)
+ }
+}
diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt
new file mode 100644
index 0000000..47a1480
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt
@@ -0,0 +1,12 @@
+package de.jrpie.android.launcher.ui.widgets
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+
+// TODO: implement layout logic instead of linear layout
+class WidgetContainerView(context: Context, attrs: AttributeSet?): LinearLayout(context, attrs) {
+ init {
+ orientation = VERTICAL
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetInfo.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetInfo.kt
new file mode 100644
index 0000000..c3d9046
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetInfo.kt
@@ -0,0 +1,28 @@
+package de.jrpie.android.launcher.widgets;
+
+import de.jrpie.android.launcher.apps.AbstractAppInfo
+import kotlinx.serialization.SerialName;
+import kotlinx.serialization.Serializable;
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+
+@Serializable
+@SerialName("widget")
+class WidgetInfo(val id: Int, val width: Int, val height: Int) {
+ fun serialize(): String {
+ return Json.encodeToString(this)
+ }
+
+ override fun hashCode(): Int {
+ return id
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return (other as? WidgetInfo)?.id == id
+ }
+ companion object {
+ fun deserialize(serialized: String): WidgetInfo {
+ return Json.decodeFromString(serialized)
+ }
+ }
+}
diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt
new file mode 100644
index 0000000..ab4f40d
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt
@@ -0,0 +1,123 @@
+package de.jrpie.android.launcher.widgets
+
+import android.app.Activity
+import android.app.Service
+import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.os.UserManager
+import android.util.Log
+import de.jrpie.android.launcher.Application
+import de.jrpie.android.launcher.preferences.LauncherPreferences
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+fun deleteAllWidgets(activity: Activity) {
+ val appWidgetHost = (activity.application as Application).appWidgetHost
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ appWidgetHost.appWidgetIds.forEach { deleteAppWidget(activity, WidgetInfo(it, 0,0)) }
+ }
+}
+
+fun bindAppWidget(activity: Activity, providerInfo: AppWidgetProviderInfo): WidgetInfo? {
+ val appWidgetHost = (activity.application as Application).appWidgetHost
+ val appWidgetManager = (activity.application as Application).appWidgetManager
+ val appWidgetId = appWidgetHost.allocateAppWidgetId()
+
+ Log.i("Launcher", "Binding new widget ${appWidgetId}")
+ if (!appWidgetManager.bindAppWidgetIdIfAllowed(
+ appWidgetId,
+ providerInfo.provider
+ )
+ ) {
+ requestAppWidgetPermission(activity, appWidgetId, providerInfo)
+ return null
+ }
+ try {
+ Log.e("widgets", "configure widget")
+ appWidgetHost.startAppWidgetConfigureActivityForResult(activity, appWidgetId, 0, 1, null)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
+ val widget = WidgetInfo(appWidgetId, 500, 500)
+ LauncherPreferences.internal().widgets(
+ (LauncherPreferences.internal().widgets() ?: HashSet()).also {
+ it.add(widget)
+ }
+ )
+
+
+ return widget
+}
+
+fun deleteAppWidget(activity: Activity, widget: WidgetInfo) {
+ Log.i("Launcher", "Deleting widget ${widget.id}")
+ val appWidgetHost = (activity.application as Application).appWidgetHost
+
+ appWidgetHost.deleteAppWidgetId(widget.id)
+
+ LauncherPreferences.internal().widgets(
+ LauncherPreferences.internal().widgets()?.also {
+ it.remove(widget)
+ }
+ )
+}
+
+fun createAppWidgetView(activity: Activity, widget: WidgetInfo): AppWidgetHostView? {
+ val appWidgetHost = (activity.application as Application).appWidgetHost
+ val appWidgetManager = (activity.application as Application).appWidgetManager
+ val providerInfo = appWidgetManager.getAppWidgetInfo(widget.id) ?: return null
+ val view = appWidgetHost.createView(activity, widget.id, providerInfo)
+ .apply {
+ setAppWidget(appWidgetId, appWidgetInfo)
+ }
+
+
+ val newOptions = Bundle().apply {
+ putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widget.width)
+ putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, widget.width)
+ putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widget.height)
+ putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, widget.height)
+ }
+ appWidgetManager.updateAppWidgetOptions(
+ widget.id,
+ newOptions
+ )
+ //view.minimumWidth = widget.width
+ //view.minimumHeight = widget.height
+
+ return view
+}
+
+fun getAppWidgetProviders(context: Context): List {
+ return appWidgetProviders(context, (context.applicationContext as Application).appWidgetManager)
+}
+
+fun requestAppWidgetPermission(context: Activity, widgetId: Int, info: AppWidgetProviderInfo) {
+ val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply {
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider)
+ }
+ context.startActivityForResult(intent, 0)//REQUEST_CODE_BIND_WIDGET)
+}
+
+fun appWidgetProviders(
+ context: Context,
+ appWidgetManager: AppWidgetManager
+): List {
+ val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager
+ return userManager.userProfiles.map {
+ appWidgetManager.getInstalledProvidersForProfile(it)
+ }.flatten()
+}
+fun Activity.bindRandomWidget() {
+ val selectedWidget =
+ getAppWidgetProviders(this).let { it.get(Random.nextInt().absoluteValue % it.size) }
+ bindAppWidget(this, selectedWidget) ?: return
+}
+
diff --git a/app/src/main/res/layout/activity_select_widget.xml b/app/src/main/res/layout/activity_select_widget.xml
new file mode 100644
index 0000000..463a317
--- /dev/null
+++ b/app/src/main/res/layout/activity_select_widget.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/home.xml b/app/src/main/res/layout/home.xml
index ecefdea..444bd4e 100644
--- a/app/src/main/res/layout/home.xml
+++ b/app/src/main/res/layout/home.xml
@@ -9,6 +9,15 @@
android:longClickable="false"
android:fitsSystemWindows="true"
tools:context=".ui.HomeActivity">
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index 89ec086..d9652ed 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -9,6 +9,7 @@
internal.started_before
internal.first_startup
internal.version_code
+ internal.widgets
apps.favorites
apps.hidden
apps.pinned_shortcuts
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 21f25f5..301ca5e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -387,5 +387,6 @@
Open Source Licenses
No app found to handle search.
Can\'t open URL: no browser found.
+ Choose Widget