add option to enable / disable interaction with widgets

This commit is contained in:
Josia Pietsch 2025-04-24 14:34:27 +02:00
parent 13c88122b2
commit 72f77c8294
Signed by: jrpie
GPG key ID: E70B571D66986A2D
20 changed files with 214 additions and 124 deletions

View file

@ -22,6 +22,8 @@ import de.jrpie.android.launcher.apps.isPrivateSpaceLocked
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion
import de.jrpie.android.launcher.preferences.resetPreferences
import de.jrpie.android.launcher.widgets.LauncherWidgetProvider
import de.jrpie.android.launcher.widgets.Widget
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -32,6 +34,7 @@ const val APP_WIDGET_HOST_ID = 42;
class Application : android.app.Application() {
val apps = MutableLiveData<List<AbstractDetailedAppInfo>>()
val widgets = MutableLiveData<Set<Widget>>()
val privateSpaceLocked = MutableLiveData<Boolean>()
lateinit var appWidgetHost: AppWidgetHost
lateinit var appWidgetManager: AppWidgetManager
@ -98,6 +101,8 @@ class Application : android.app.Application() {
customAppNames = LauncherPreferences.apps().customNames()
} else if (pref == LauncherPreferences.apps().keys().pinnedShortcuts()) {
loadApps()
} else if (pref == LauncherPreferences.widgets().keys().widgets()) {
widgets.postValue(LauncherPreferences.widgets().widgets() ?: setOf())
}
}

View file

@ -15,13 +15,12 @@ import de.jrpie.android.launcher.BuildConfig
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.actions.lock.LauncherAccessibilityService
import de.jrpie.android.launcher.apps.AppFilter
import de.jrpie.android.launcher.apps.hidePrivateSpaceWhenLocked
import de.jrpie.android.launcher.apps.isPrivateSpaceSupported
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.widgets.manage.ManageWidgetsActivity
import de.jrpie.android.launcher.ui.widgets.manage.SelectWidgetActivity
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -62,10 +61,7 @@ enum class LauncherAction(
"choose_from_favorites",
R.string.list_other_list_favorites,
R.drawable.baseline_favorite_24,
{ context ->
context.startActivity(Intent(context.applicationContext, ManageWidgetsActivity::class.java))
},
//openAppsList(context, favorite = true) },
{ context -> openAppsList(context, favorite = true) },
true
),
CHOOSE_FROM_PRIVATE_SPACE(
@ -73,15 +69,12 @@ enum class LauncherAction(
R.string.list_other_list_private_space,
R.drawable.baseline_security_24,
{ context ->
context.startActivity(Intent(context.applicationContext, SelectWidgetActivity::class.java))
},
/*{ context ->
if ((context.applicationContext as Application).privateSpaceLocked.value != true
|| !hidePrivateSpaceWhenLocked(context)
) {
openAppsList(context, private = true)
}
}, */
},
available = { _ ->
isPrivateSpaceSupported()
}

View file

@ -27,7 +27,6 @@ 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 = SetWidgetSerializer.class)
}),
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
@Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class),
@ -84,5 +83,8 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
@PreferenceGroup(name = "actions", prefix = "settings_actions_", suffix = "_key", value = {
@Preference(name = "lock_method", type = LockMethod.class, defaultValue = "DEVICE_ADMIN"),
}),
@PreferenceGroup(name = "widgets", prefix = "settings_widgets_", suffix= "_key", value = {
@Preference(name = "widgets", type = Set.class, serializer = SetWidgetSerializer.class)
}),
})
public final class LauncherPreferences$Config {}

View file

@ -76,7 +76,7 @@ fun resetPreferences(context: Context) {
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
deleteAllWidgets(context)
LauncherPreferences.internal().widgets(
LauncherPreferences.widgets().widgets(
setOf(
ClockWidget(-500, WidgetPosition(1,4,10,3))
)

View file

@ -44,16 +44,14 @@ class HomeActivity : UIObject, Activity() {
prefKey?.startsWith("display.") == true
) {
recreate()
}
if (prefKey?.startsWith("action.") == true) {
} else if (prefKey?.startsWith("action.") == true) {
updateSettingsFallbackButtonVisibility()
} else if (prefKey == LauncherPreferences.widgets().keys().widgets()) {
binding.homeWidgetContainer.updateWidgets(this@HomeActivity,
LauncherPreferences.widgets().widgets()
)
}
if (prefKey?.startsWith("internal.widgets") == true) {
binding.homeWidgetContainer.updateWidgets(this)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -77,7 +75,6 @@ class HomeActivity : UIObject, Activity() {
binding.buttonFallbackSettings.setOnClickListener {
LauncherAction.SETTINGS.invoke(this)
}
binding.homeWidgetContainer.updateWidgets(this)
}
override fun onConfigurationChanged(newConfig: Configuration) {
@ -98,7 +95,6 @@ class HomeActivity : UIObject, Activity() {
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
(application as Application).appWidgetHost.startListening()
binding.homeWidgetContainer.updateWidgets(this)
}
@ -170,6 +166,10 @@ class HomeActivity : UIObject, Activity() {
}
}
updateSettingsFallbackButtonVisibility()
binding.homeWidgetContainer.updateWidgets(this@HomeActivity,
LauncherPreferences.widgets().widgets()
)
}
override fun onDestroy() {
@ -206,12 +206,6 @@ class HomeActivity : UIObject, Activity() {
return true
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
// TODO: fix!
touchGestureDetector?.onTouchEvent(event)
return false
}
override fun onTouchEvent(event: MotionEvent): Boolean {
android.util.Log.e("Launcher", "on touch")
touchGestureDetector?.onTouchEvent(event)
@ -225,4 +219,4 @@ class HomeActivity : UIObject, Activity() {
override fun isHomeScreen(): Boolean {
return true
}
}
}

View file

@ -11,6 +11,7 @@ import de.jrpie.android.launcher.actions.openAppsList
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.preferences.theme.ColorTheme
import de.jrpie.android.launcher.setDefaultHomeScreen
import de.jrpie.android.launcher.ui.widgets.manage.ManageWidgetsActivity
/**
@ -81,6 +82,14 @@ class SettingsFragmentLauncher : PreferenceFragmentCompat() {
true
}
val manageWidgets = findPreference<androidx.preference.Preference>(
LauncherPreferences.widgets().keys().widgets()
)
manageWidgets?.setOnPreferenceClickListener {
startActivity(Intent(requireActivity(), ManageWidgetsActivity::class.java))
true
}
val hiddenApps = findPreference<androidx.preference.Preference>(
LauncherPreferences.apps().keys().hidden()
)

View file

@ -2,34 +2,60 @@ package de.jrpie.android.launcher.ui.widgets
import android.app.Activity
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.graphics.PointF
import android.graphics.RectF
import android.util.AttributeSet
import android.util.Log
import android.util.SizeF
import android.view.MotionEvent
import android.view.View
import android.view.View.MeasureSpec.makeMeasureSpec
import android.view.ViewGroup
import androidx.core.graphics.contains
import androidx.core.view.size
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.widgets.Widget
import de.jrpie.android.launcher.widgets.WidgetPosition
import kotlin.math.max
// TODO: implement layout logic instead of linear layout
/**
* This only works in an Activity, not AppCompatActivity
*/
open class WidgetContainerView(context: Context, attrs: AttributeSet? = null) : ViewGroup(context, attrs) {
open fun updateWidgets(activity: Activity) {
var widgetViewById = HashMap<Int, View>()
open fun updateWidgets(activity: Activity, widgets: Set<Widget>?) {
if (widgets == null) {
return
}
Log.i("WidgetContainer", "updating ${activity.localClassName}")
widgetViewById.clear()
(0..<size).forEach { removeViewAt(0) }
LauncherPreferences.internal().widgets()?.forEach { widget ->
widgets.forEach { widget ->
widget.createView(activity)?.let {
addView(it, WidgetContainerView.Companion.LayoutParams(widget.position))
widgetViewById.put(widget.id, it)
}
}
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
if (ev == null) {
return false
}
val position = PointF(ev.x, ev.y)
return widgetViewById.filter {
RectF(
it.value.x,
it.value.y,
it.value.x + it.value.width,
it.value.y + it.value.height
).contains(position) == true
}.any {
Widget.byId(context, it.key)?.allowInteraction == false
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var maxHeight = suggestedMinimumHeight
@ -70,15 +96,11 @@ open class WidgetContainerView(context: Context, attrs: AttributeSet? = null) :
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
for (i in 0..<size) {
val child = getChildAt(i)
//if (child.visibility != GONE) {
val lp = child.layoutParams as LayoutParams
val position = lp.position.getAbsoluteRect(r - l, b - t)
Log.e("onLayout", "$l, $t, $r, $b, absolute rect: $position")
child.layout(position.left, position.top, position.right, position.bottom)
val lp = child.layoutParams as LayoutParams
val position = lp.position.getAbsoluteRect(r - l, b - t)
child.layout(position.left, position.top, position.right, position.bottom)
child.layoutParams.width = position.width()
child.layoutParams.height = position.height()
//}
}
}

View file

@ -27,12 +27,16 @@ import kotlin.math.min
const val REQUEST_CREATE_APPWIDGET = 1
const val REQUEST_PICK_APPWIDGET = 2
// We can't use AppCompatActivity, since some AppWidgets don't work there.
class ManageWidgetsActivity : Activity(), UIObject {
private var sharedPreferencesListener =
SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey ->
if (prefKey?.startsWith("internal.widgets") == true) {
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this)
if (prefKey == LauncherPreferences.widgets().keys().widgets()) {
// We can't observe the livedata because this is not an AppCompatActivity
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this,
LauncherPreferences.widgets().widgets()
)
}
}
@ -50,7 +54,9 @@ class ManageWidgetsActivity : Activity(), UIObject {
insets
}
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this)
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this,
(application as Application).widgets.value
)
}
override fun onStart() {
@ -60,6 +66,14 @@ class ManageWidgetsActivity : Activity(), UIObject {
LauncherPreferences.getSharedPreferences()
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
}
override fun onResume() {
super.onResume()
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this,
LauncherPreferences.widgets().widgets()
)
}
override fun getTheme(): Resources.Theme {
val mTheme = modifyTheme(super.getTheme())
@ -111,30 +125,19 @@ class ManageWidgetsActivity : Activity(), UIObject {
)
val widget = AppWidget(appWidgetId, provider, position)
LauncherPreferences.internal().widgets(
(LauncherPreferences.internal().widgets() ?: HashSet()).also {
LauncherPreferences.widgets().widgets(
(LauncherPreferences.widgets().widgets() ?: HashSet()).also {
it.add(widget)
}
)
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this)
}
private fun configureWidget(data: Intent) {
val extras = data.extras
val appWidgetId = extras!!.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
val appWidgetHost = (application as Application).appWidgetHost
val appWidgetInfo: AppWidgetProviderInfo =
(application as Application).appWidgetManager.getAppWidgetInfo(appWidgetId) ?: return
if (appWidgetInfo.configure != null) {
appWidgetHost.startAppWidgetConfigureActivityForResult(
this,
appWidgetId,
0,
REQUEST_CREATE_APPWIDGET,
null
)
val widget = AppWidget(appWidgetId)
if (widget.isConfigurable(this)) {
widget.configure(this, REQUEST_CREATE_APPWIDGET)
} else {
createWidget(data)
}
@ -146,14 +149,12 @@ class ManageWidgetsActivity : Activity(), UIObject {
) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
Log.i("Manage Widgets", "result ok")
if (requestCode == REQUEST_PICK_APPWIDGET) {
configureWidget(data!!)
} else if (requestCode == REQUEST_CREATE_APPWIDGET) {
createWidget(data!!)
}
} else if (resultCode == RESULT_CANCELED && data != null) {
Log.i("Manage Widgets", "result canceled")
val appWidgetId =
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
if (appWidgetId != -1) {
@ -169,4 +170,4 @@ class ManageWidgetsActivity : Activity(), UIObject {
override fun isHomeScreen(): Boolean {
return true
}
}
}

View file

@ -6,28 +6,21 @@ import android.content.Intent
import android.content.res.Resources
import android.os.Build
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
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.databinding.ActivitySelectWidgetBinding
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.ui.UIObject
import de.jrpie.android.launcher.widgets.ClockWidget
import de.jrpie.android.launcher.widgets.LauncherAppWidgetProvider
import de.jrpie.android.launcher.widgets.LauncherClockWidgetProvider
import de.jrpie.android.launcher.widgets.LauncherWidgetProvider
import de.jrpie.android.launcher.widgets.Widget
import de.jrpie.android.launcher.widgets.WidgetPosition
import de.jrpie.android.launcher.widgets.bindAppWidgetOrRequestPermission
import de.jrpie.android.launcher.widgets.getAppWidgetHost
@ -110,7 +103,6 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject {
if (requestCode == REQUEST_WIDGET_PERMISSION && resultCode == RESULT_OK) {
data ?: return
Log.i("SelectWidget", "permission granted")
val provider = (data.getSerializableExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER) as? AppWidgetProviderInfo) ?: return
tryBindWidget(LauncherAppWidgetProvider(provider))
}
@ -139,7 +131,7 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject {
}
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
val label = widgets[i].label
val label = widgets[i].loadLabel(this@SelectWidgetActivity)
val description = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
widgets[i].loadDescription(this@SelectWidgetActivity)
} else {

View file

@ -1,7 +1,7 @@
package de.jrpie.android.launcher.ui.widgets.manage
import android.annotation.SuppressLint
import android.app.Activity
import android.appwidget.AppWidgetHostView
import android.content.Context
import android.graphics.Point
import android.graphics.Rect
@ -18,7 +18,6 @@ import androidx.core.graphics.contains
import androidx.core.graphics.minus
import androidx.core.graphics.toRect
import androidx.core.view.children
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.ui.widgets.WidgetContainerView
import de.jrpie.android.launcher.widgets.Widget
import de.jrpie.android.launcher.widgets.WidgetPosition
@ -72,11 +71,12 @@ class WidgetManagerView(context: Context, attrs: AttributeSet? = null) :
private val longPressHandler = Handler(Looper.getMainLooper())
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
onTouchEvent(ev)
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
return true
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event == null) {
return false
@ -91,7 +91,7 @@ class WidgetManagerView(context: Context, attrs: AttributeSet? = null) :
val position = (view.layoutParams as Companion.LayoutParams).position.getAbsoluteRect(width, height)
selectedWidgetOverlayView = view
selectedWidgetView = Widget.byId(view.widgetId)?.findView(children) ?: return true
selectedWidgetView = widgetViewById.get(view.widgetId) ?: return true
startWidgetPosition = position
val positionInView = start.minus(Point(position.left, position.top))
@ -136,7 +136,7 @@ class WidgetManagerView(context: Context, attrs: AttributeSet? = null) :
if (event.actionMasked == MotionEvent.ACTION_UP) {
longPressHandler.removeCallbacksAndMessages(null)
val id = selectedWidgetOverlayView?.widgetId ?: return true
val widget = Widget.byId(id) ?: return true
val widget = Widget.byId(context, id) ?: return true
widget.position = newPosition
endInteraction()
updateWidget(widget)
@ -155,10 +155,13 @@ class WidgetManagerView(context: Context, attrs: AttributeSet? = null) :
selectedWidgetOverlayView?.mode = null
}
override fun updateWidgets(activity: Activity) {
super.updateWidgets(activity)
override fun updateWidgets(activity: Activity, widgets: Set<Widget>?) {
super.updateWidgets(activity, widgets)
if (widgets == null) {
return
}
LauncherPreferences.internal().widgets()?.forEach { widget ->
widgets.forEach { widget ->
WidgetOverlayView(activity).let {
addView(it)
it.widgetId = widget.id

View file

@ -9,7 +9,9 @@ import android.util.AttributeSet
import android.view.View
import android.widget.PopupMenu
import androidx.core.graphics.toRectF
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.widgets.Widget
import de.jrpie.android.launcher.widgets.updateWidget
/**
* An overlay to show configuration options for a widget.
@ -29,11 +31,9 @@ class WidgetOverlayView : View {
handlePaint.style = Paint.Style.STROKE
handlePaint.setARGB(255, 255, 255, 255)
selectedHandlePaint.style = Paint.Style.FILL_AND_STROKE
selectedHandlePaint.setARGB(100, 255, 255, 255)
paint.style = Paint.Style.STROKE
paint.setARGB(255, 255, 255, 255)
}
@ -42,7 +42,7 @@ class WidgetOverlayView : View {
var widgetId: Int = -1
set(newId) {
field = newId
preview = Widget.byId(widgetId)?.getPreview(context)
preview = Widget.byId(context, widgetId)?.getPreview(context)
}
constructor(context: Context) : super(context) {
@ -74,7 +74,7 @@ class WidgetOverlayView : View {
}
}
val bounds = getBounds()
canvas.drawRect(bounds, paint)
canvas.drawRoundRect(bounds.toRectF(), 5f, 5f, paint)
if (mode == null) {
return
@ -87,16 +87,26 @@ class WidgetOverlayView : View {
}
fun showPopupMenu() {
val widget = Widget.byId(context, widgetId)?: return
val menu = PopupMenu(context, this)
menu.menu.let {
it.add("Remove").setOnMenuItemClickListener { _ ->
Widget.byId(widgetId)?.delete(context)
it.add(
context.getString(R.string.widget_menu_remove)
).setOnMenuItemClickListener { _ ->
Widget.byId(context, widgetId)?.delete(context)
return@setOnMenuItemClickListener true
}
it.add("Allow Interaction").setOnMenuItemClickListener { _ ->
return@setOnMenuItemClickListener true
it.add(
if (widget.allowInteraction) {
context.getString(R.string.widget_menu_disable_interaction)
} else {
context.getString(R.string.widget_menu_enable_interaction)
}
it.add("Add Padding")
).setOnMenuItemClickListener { _ ->
widget.allowInteraction = !widget.allowInteraction
updateWidget(widget)
return@setOnMenuItemClickListener true
}
}
menu.show()
}
@ -118,5 +128,4 @@ class WidgetOverlayView : View {
private fun getBounds(): Rect {
return Rect(0,0, width, height)
}
}

View file

@ -1,32 +1,26 @@
package de.jrpie.android.launcher.widgets;
import android.app.Activity
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetHostView
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.util.Log
import android.util.SizeF
import android.view.View
import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.ui.HomeActivity
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
@SerialName("widget:app")
class AppWidget(
override val id: Int,
override var position: WidgetPosition = WidgetPosition(0,0,1,1),
override var allowInteraction: Boolean = false,
// We keep track of packageName, className and user to make it possible to restore the widget
// on a new device when restoring settings (currently not implemented)
@ -39,7 +33,9 @@ class AppWidget(
constructor(id: Int, widgetProviderInfo: AppWidgetProviderInfo, position: WidgetPosition) :
this(
id, position,
id,
position,
false,
widgetProviderInfo.provider.packageName,
widgetProviderInfo.provider.className,
widgetProviderInfo.profile.hashCode()
@ -105,4 +101,20 @@ class AppWidget(
override fun getPreview(context: Context): Drawable? {
return context.getAppWidgetManager().getAppWidgetInfo(id)?.loadPreviewImage(context, DisplayMetrics.DENSITY_HIGH)
}
override fun isConfigurable(context: Context): Boolean {
return context.getAppWidgetManager().getAppWidgetInfo(id)?.configure != null
}
override fun configure(activity: Activity, requestCode: Int) {
if (!isConfigurable(activity)) {
return
}
activity.getAppWidgetHost().startAppWidgetConfigureActivityForResult(
activity,
id,
0,
requestCode,
null
)
}
}

View file

@ -11,7 +11,11 @@ import kotlinx.serialization.Serializable
@Serializable
@SerialName("widget:clock")
class ClockWidget(override val id: Int, override var position: WidgetPosition): Widget() {
class ClockWidget(
override val id: Int,
override var position: WidgetPosition,
override var allowInteraction: Boolean = true
) : Widget() {
override fun createView(activity: Activity): View? {
return ClockView(activity, null, id)
@ -28,4 +32,10 @@ class ClockWidget(override val id: Int, override var position: WidgetPosition):
override fun getIcon(context: Context): Drawable? {
return null
}
override fun isConfigurable(context: Context): Boolean {
return false
}
override fun configure(activity: Activity, requestCode: Int) { }
}

View file

@ -5,17 +5,21 @@ import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Build
import android.util.DisplayMetrics
import androidx.appcompat.content.res.AppCompatResources
import de.jrpie.android.launcher.R
sealed class LauncherWidgetProvider {
abstract val label: String?
abstract fun loadLabel(context: Context): CharSequence?
abstract fun loadPreviewImage(context: Context): Drawable?
abstract fun loadIcon(context: Context): Drawable?
abstract fun loadDescription(context: Context): CharSequence?
}
class LauncherAppWidgetProvider(val info: AppWidgetProviderInfo) : LauncherWidgetProvider() {
override val label: String? = info.label
override fun loadLabel(context: Context): CharSequence? {
return info.loadLabel(context.packageManager)
}
override fun loadPreviewImage(context: Context): Drawable? {
return info.loadPreviewImage(context, DisplayMetrics.DENSITY_DEFAULT)
}
@ -34,19 +38,21 @@ class LauncherAppWidgetProvider(val info: AppWidgetProviderInfo) : LauncherWidge
}
class LauncherClockWidgetProvider : LauncherWidgetProvider() {
override val label: String?
get() = "Clock"
override fun loadLabel(context: Context): CharSequence? {
return context.getString(R.string.widget_clock_label)
}
override fun loadDescription(context: Context): CharSequence? {
return context.getString(R.string.widget_clock_description)
}
override fun loadPreviewImage(context: Context): Drawable? {
return null
}
override fun loadIcon(context: Context): Drawable? {
return null
}
override fun loadDescription(context: Context): CharSequence? {
return null
return AppCompatResources.getDrawable(context, R.drawable.baseline_clock_24)
}
}

View file

@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.View
import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.preferences.LauncherPreferences
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@ -13,6 +14,7 @@ import kotlinx.serialization.json.Json
sealed class Widget {
abstract val id: Int
abstract var position: WidgetPosition
abstract var allowInteraction: Boolean
/**
* @param activity The activity where the view will be used. Must not be an AppCompatActivity.
@ -21,12 +23,14 @@ sealed class Widget {
abstract fun findView(views: Sequence<View>): View?
abstract fun getPreview(context: Context): Drawable?
abstract fun getIcon(context: Context): Drawable?
abstract fun isConfigurable(context: Context): Boolean
abstract fun configure(activity: Activity, requestCode: Int)
fun delete(context: Context) {
context.getAppWidgetHost().deleteAppWidgetId(id)
LauncherPreferences.internal().widgets(
LauncherPreferences.internal().widgets()?.also {
LauncherPreferences.widgets().widgets(
LauncherPreferences.widgets().widgets()?.also {
it.remove(this)
}
)
@ -47,9 +51,10 @@ sealed class Widget {
fun deserialize(serialized: String): Widget {
return Json.decodeFromString(serialized)
}
fun byId(id: Int): Widget? {
return (LauncherPreferences.internal().widgets() ?: setOf())
.firstOrNull { it.id == id }
fun byId(context: Context, id: Int): Widget? {
return (context.applicationContext as Application).widgets.value?.firstOrNull {
it.id == id
}
}
}
}
}

View file

@ -3,17 +3,14 @@ package de.jrpie.android.launcher.widgets
import android.app.Activity
import android.app.Service
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetHostView
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.Context
import android.content.Intent
import android.content.pm.LauncherApps
import android.os.Build
import android.os.Bundle
import android.os.UserManager
import android.util.Log
import android.util.SizeF
import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.preferences.LauncherPreferences
@ -77,9 +74,9 @@ fun getAppWidgetProviders( context: Context ): List<LauncherWidgetProvider> {
fun updateWidget(widget: Widget) {
var widgets = LauncherPreferences.internal().widgets() ?: setOf()
var widgets = LauncherPreferences.widgets().widgets() ?: setOf()
widgets = widgets.minus(widget).plus(widget)
LauncherPreferences.internal().widgets(widgets)
LauncherPreferences.widgets().widgets(widgets)
}
fun Context.getAppWidgetHost(): AppWidgetHost {

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?android:textColor"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" />
<path
android:fillColor="?android:textColor"
android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
</vector>

View file

@ -9,7 +9,7 @@
<string name="settings_internal_started_key" translatable="false">internal.started_before</string>
<string name="settings_internal_started_time_key" translatable="false">internal.first_startup</string>
<string name="settings_internal_version_code_key" translatable="false">internal.version_code</string>
<string name="settings_internal_widgets_key" translatable="false">internal.widgets</string>
<string name="settings_widgets_widgets_key" translatable="false">widgets.widgets</string>
<string name="settings_apps_favorites_key" translatable="false">apps.favorites</string>
<string name="settings_apps_hidden_key" translatable="false">apps.hidden</string>
<string name="settings_apps_pinned_shortcuts_key" translatable="false">apps.pinned_shortcuts</string>

View file

@ -98,6 +98,8 @@
<string name="settings_gesture_time">Time</string>
<string name="settings_gesture_description_time">Click on time</string>
<string name="settings_widgets_widgets">Manage widgets</string>
<string name="settings_apps_choose">Choose App</string>
@ -390,4 +392,13 @@
<string name="toast_activity_not_found_browser">Can\'t open URL: no browser found.</string>
<string name="select_widget_title">Choose Widget</string>
<string name="widget_menu_remove">Remove</string>
<string name="widget_menu_configure">Configure</string>
<string name="widget_menu_enable_interaction">Enable Interaction</string>
<string name="widget_menu_disable_interaction">Disable Interaction</string>
<string name="widget_clock_label">Clock</string>
<string name="widget_clock_description">The default clock of μLauncher</string>
</resources>

View file

@ -6,6 +6,10 @@
<Preference
android:key="@string/settings_general_choose_home_screen_key"
android:title="@string/settings_general_choose_home_screen"/>
<Preference
android:key="@string/settings_widgets_widgets_key"
android:title="@string/settings_widgets_widgets"/>
</PreferenceCategory>
<PreferenceCategory