mirror of
https://github.com/jrpie/Launcher.git
synced 2025-04-27 14:20:50 +02:00
turn clock into a widget
This commit is contained in:
parent
e1daa8d9be
commit
13c88122b2
22 changed files with 462 additions and 325 deletions
|
@ -8,7 +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.MapAbstractAppInfoStringPreferenceSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer;
|
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer;
|
import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.SetWidgetInfoSerializer;
|
import de.jrpie.android.launcher.preferences.serialization.SetWidgetSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.theme.Background;
|
import de.jrpie.android.launcher.preferences.theme.Background;
|
||||||
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
|
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
|
||||||
import de.jrpie.android.launcher.preferences.theme.Font;
|
import de.jrpie.android.launcher.preferences.theme.Font;
|
||||||
|
@ -27,7 +27,7 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
|
||||||
@Preference(name = "started_time", type = long.class),
|
@Preference(name = "started_time", type = long.class),
|
||||||
// see PREFERENCE_VERSION in de.jrpie.android.launcher.preferences.Preferences.kt
|
// see PREFERENCE_VERSION in de.jrpie.android.launcher.preferences.Preferences.kt
|
||||||
@Preference(name = "version_code", type = int.class, defaultValue = "-1"),
|
@Preference(name = "version_code", type = int.class, defaultValue = "-1"),
|
||||||
@Preference(name = "widgets", type = Set.class, serializer = SetWidgetInfoSerializer.class)
|
@Preference(name = "widgets", type = Set.class, serializer = SetWidgetSerializer.class)
|
||||||
}),
|
}),
|
||||||
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
|
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
|
||||||
@Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class),
|
@Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class),
|
||||||
|
|
|
@ -13,6 +13,8 @@ import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersio
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
|
||||||
import de.jrpie.android.launcher.ui.HomeActivity
|
import de.jrpie.android.launcher.ui.HomeActivity
|
||||||
|
import de.jrpie.android.launcher.widgets.ClockWidget
|
||||||
|
import de.jrpie.android.launcher.widgets.WidgetPosition
|
||||||
import de.jrpie.android.launcher.widgets.deleteAllWidgets
|
import de.jrpie.android.launcher.widgets.deleteAllWidgets
|
||||||
|
|
||||||
/* Current version of the structure of preferences.
|
/* Current version of the structure of preferences.
|
||||||
|
@ -74,6 +76,12 @@ fun resetPreferences(context: Context) {
|
||||||
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
|
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
|
||||||
deleteAllWidgets(context)
|
deleteAllWidgets(context)
|
||||||
|
|
||||||
|
LauncherPreferences.internal().widgets(
|
||||||
|
setOf(
|
||||||
|
ClockWidget(-500, WidgetPosition(1,4,10,3))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
val hidden: MutableSet<AbstractAppInfo> = mutableSetOf()
|
val hidden: MutableSet<AbstractAppInfo> = mutableSetOf()
|
||||||
val launcher = DetailedAppInfo.fromAppInfo(
|
val launcher = DetailedAppInfo.fromAppInfo(
|
||||||
|
|
|
@ -4,7 +4,7 @@ package de.jrpie.android.launcher.preferences.serialization
|
||||||
|
|
||||||
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||||
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
|
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
|
||||||
import de.jrpie.android.launcher.widgets.WidgetInfo
|
import de.jrpie.android.launcher.widgets.Widget
|
||||||
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
|
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
|
||||||
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
|
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@ -31,18 +31,18 @@ class SetAbstractAppInfoPreferenceSerializer :
|
||||||
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class SetWidgetInfoSerializer :
|
class SetWidgetSerializer :
|
||||||
PreferenceSerializer<java.util.Set<WidgetInfo>?, java.util.Set<java.lang.String>?> {
|
PreferenceSerializer<java.util.Set<Widget>?, java.util.Set<java.lang.String>?> {
|
||||||
@Throws(PreferenceSerializationException::class)
|
@Throws(PreferenceSerializationException::class)
|
||||||
override fun serialize(value: java.util.Set<WidgetInfo>?): java.util.Set<java.lang.String> {
|
override fun serialize(value: java.util.Set<Widget>?): java.util.Set<java.lang.String>? {
|
||||||
return value?.map(WidgetInfo::serialize)
|
return value?.map(Widget::serialize)
|
||||||
?.toHashSet() as java.util.Set<java.lang.String>
|
?.toHashSet() as? java.util.Set<java.lang.String>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(PreferenceSerializationException::class)
|
@Throws(PreferenceSerializationException::class)
|
||||||
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<WidgetInfo>? {
|
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<Widget>? {
|
||||||
return value?.map(java.lang.String::toString)?.map(WidgetInfo::deserialize)
|
return value?.map(java.lang.String::toString)?.map(Widget::deserialize)
|
||||||
?.toHashSet() as? java.util.Set<WidgetInfo>
|
?.toHashSet() as? java.util.Set<Widget>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.window.OnBackInvokedDispatcher
|
import android.window.OnBackInvokedDispatcher
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import de.jrpie.android.launcher.Application
|
import de.jrpie.android.launcher.Application
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.actions.Action
|
import de.jrpie.android.launcher.actions.Action
|
||||||
|
@ -21,7 +20,6 @@ import de.jrpie.android.launcher.databinding.HomeBinding
|
||||||
import de.jrpie.android.launcher.openTutorial
|
import de.jrpie.android.launcher.openTutorial
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [HomeActivity] is the actual application Launcher,
|
* [HomeActivity] is the actual application Launcher,
|
||||||
|
@ -134,44 +132,6 @@ class HomeActivity : UIObject, Activity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initClock() {
|
|
||||||
val locale = Locale.getDefault()
|
|
||||||
val dateVisible = LauncherPreferences.clock().dateVisible()
|
|
||||||
val timeVisible = LauncherPreferences.clock().timeVisible()
|
|
||||||
|
|
||||||
var dateFMT = "yyyy-MM-dd"
|
|
||||||
var timeFMT = "HH:mm"
|
|
||||||
if (LauncherPreferences.clock().showSeconds()) {
|
|
||||||
timeFMT += ":ss"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LauncherPreferences.clock().localized()) {
|
|
||||||
dateFMT = android.text.format.DateFormat.getBestDateTimePattern(locale, dateFMT)
|
|
||||||
timeFMT = android.text.format.DateFormat.getBestDateTimePattern(locale, timeFMT)
|
|
||||||
}
|
|
||||||
|
|
||||||
var upperFormat = dateFMT
|
|
||||||
var lowerFormat = timeFMT
|
|
||||||
var upperVisible = dateVisible
|
|
||||||
var lowerVisible = timeVisible
|
|
||||||
|
|
||||||
if (LauncherPreferences.clock().flipDateTime()) {
|
|
||||||
upperFormat = lowerFormat.also { lowerFormat = upperFormat }
|
|
||||||
upperVisible = lowerVisible.also { lowerVisible = upperVisible }
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.homeUpperView.isVisible = upperVisible
|
|
||||||
binding.homeLowerView.isVisible = lowerVisible
|
|
||||||
|
|
||||||
binding.homeUpperView.setTextColor(LauncherPreferences.clock().color())
|
|
||||||
binding.homeLowerView.setTextColor(LauncherPreferences.clock().color())
|
|
||||||
|
|
||||||
binding.homeLowerView.format24Hour = lowerFormat
|
|
||||||
binding.homeUpperView.format24Hour = upperFormat
|
|
||||||
binding.homeLowerView.format12Hour = lowerFormat
|
|
||||||
binding.homeUpperView.format12Hour = upperFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTheme(): Resources.Theme {
|
override fun getTheme(): Resources.Theme {
|
||||||
val mTheme = modifyTheme(super.getTheme())
|
val mTheme = modifyTheme(super.getTheme())
|
||||||
mTheme.applyStyle(R.style.backgroundWallpaper, true)
|
mTheme.applyStyle(R.style.backgroundWallpaper, true)
|
||||||
|
@ -209,8 +169,6 @@ class HomeActivity : UIObject, Activity() {
|
||||||
windowInsets
|
windowInsets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initClock()
|
|
||||||
updateSettingsFallbackButtonVisibility()
|
updateSettingsFallbackButtonVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,26 +218,6 @@ class HomeActivity : UIObject, Activity() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setOnClicks() {
|
|
||||||
|
|
||||||
binding.homeUpperView.setOnClickListener {
|
|
||||||
if (LauncherPreferences.clock().flipDateTime()) {
|
|
||||||
Gesture.TIME(this)
|
|
||||||
} else {
|
|
||||||
Gesture.DATE(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.homeLowerView.setOnClickListener {
|
|
||||||
if (LauncherPreferences.clock().flipDateTime()) {
|
|
||||||
Gesture.DATE(this)
|
|
||||||
} else {
|
|
||||||
Gesture.TIME(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun handleBack() {
|
private fun handleBack() {
|
||||||
Gesture.BACK(this)
|
Gesture.BACK(this)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package de.jrpie.android.launcher.ui.widgets
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
|
import de.jrpie.android.launcher.databinding.ClockBinding
|
||||||
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class ClockView(context: Context, attrs: AttributeSet? = null, val appWidgetId: Int): ConstraintLayout(context, attrs) {
|
||||||
|
|
||||||
|
val binding: ClockBinding = ClockBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
|
init {
|
||||||
|
initClock()
|
||||||
|
setOnClicks()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun initClock() {
|
||||||
|
val locale = Locale.getDefault()
|
||||||
|
val dateVisible = LauncherPreferences.clock().dateVisible()
|
||||||
|
val timeVisible = LauncherPreferences.clock().timeVisible()
|
||||||
|
|
||||||
|
var dateFMT = "yyyy-MM-dd"
|
||||||
|
var timeFMT = "HH:mm"
|
||||||
|
if (LauncherPreferences.clock().showSeconds()) {
|
||||||
|
timeFMT += ":ss"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LauncherPreferences.clock().localized()) {
|
||||||
|
dateFMT = android.text.format.DateFormat.getBestDateTimePattern(locale, dateFMT)
|
||||||
|
timeFMT = android.text.format.DateFormat.getBestDateTimePattern(locale, timeFMT)
|
||||||
|
}
|
||||||
|
|
||||||
|
var upperFormat = dateFMT
|
||||||
|
var lowerFormat = timeFMT
|
||||||
|
var upperVisible = dateVisible
|
||||||
|
var lowerVisible = timeVisible
|
||||||
|
|
||||||
|
if (LauncherPreferences.clock().flipDateTime()) {
|
||||||
|
upperFormat = lowerFormat.also { lowerFormat = upperFormat }
|
||||||
|
upperVisible = lowerVisible.also { lowerVisible = upperVisible }
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.clockUpperView.isVisible = upperVisible
|
||||||
|
binding.clockLowerView.isVisible = lowerVisible
|
||||||
|
|
||||||
|
binding.clockUpperView.setTextColor(LauncherPreferences.clock().color())
|
||||||
|
binding.clockLowerView.setTextColor(LauncherPreferences.clock().color())
|
||||||
|
|
||||||
|
binding.clockLowerView.format24Hour = lowerFormat
|
||||||
|
binding.clockUpperView.format24Hour = upperFormat
|
||||||
|
binding.clockLowerView.format12Hour = lowerFormat
|
||||||
|
binding.clockUpperView.format12Hour = upperFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnClicks() {
|
||||||
|
binding.clockUpperView.setOnClickListener {
|
||||||
|
if (LauncherPreferences.clock().flipDateTime()) {
|
||||||
|
Gesture.TIME(context)
|
||||||
|
} else {
|
||||||
|
Gesture.DATE(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.clockLowerView.setOnClickListener {
|
||||||
|
if (LauncherPreferences.clock().flipDateTime()) {
|
||||||
|
Gesture.DATE(context)
|
||||||
|
} else {
|
||||||
|
Gesture.TIME(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -12,7 +12,6 @@ import android.view.ViewGroup
|
||||||
import androidx.core.view.size
|
import androidx.core.view.size
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPosition
|
import de.jrpie.android.launcher.widgets.WidgetPosition
|
||||||
import de.jrpie.android.launcher.widgets.createAppWidgetView
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,22 +24,8 @@ open class WidgetContainerView(context: Context, attrs: AttributeSet? = null) :
|
||||||
open fun updateWidgets(activity: Activity) {
|
open fun updateWidgets(activity: Activity) {
|
||||||
Log.i("WidgetContainer", "updating ${activity.localClassName}")
|
Log.i("WidgetContainer", "updating ${activity.localClassName}")
|
||||||
(0..<size).forEach { removeViewAt(0) }
|
(0..<size).forEach { removeViewAt(0) }
|
||||||
val dp = activity.resources.displayMetrics.density
|
|
||||||
val screenWidth = activity.resources.displayMetrics.widthPixels
|
|
||||||
val screenHeight = activity.resources.displayMetrics.heightPixels
|
|
||||||
LauncherPreferences.internal().widgets()?.forEach { widget ->
|
LauncherPreferences.internal().widgets()?.forEach { widget ->
|
||||||
createAppWidgetView(activity, widget)?.let {
|
widget.createView(activity)?.let {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
val absolutePosition = widget.position.getAbsoluteRect(screenWidth, screenHeight)
|
|
||||||
it.updateAppWidgetSize(Bundle.EMPTY,
|
|
||||||
listOf(SizeF(
|
|
||||||
absolutePosition.width() / dp,
|
|
||||||
absolutePosition.height() / dp
|
|
||||||
)))
|
|
||||||
Log.i("WidgetContainer", "Adding widget ${widget.id} at ${widget.position} ($absolutePosition)")
|
|
||||||
} else {
|
|
||||||
Log.i("WidgetContainer", "Adding widget ${widget.id} at ${widget.position}")
|
|
||||||
}
|
|
||||||
addView(it, WidgetContainerView.Companion.LayoutParams(widget.position))
|
addView(it, WidgetContainerView.Companion.LayoutParams(widget.position))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,8 @@ import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
import de.jrpie.android.launcher.ui.UIObject
|
||||||
import de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
import de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
||||||
import de.jrpie.android.launcher.widgets.WidgetInfo
|
import de.jrpie.android.launcher.widgets.AppWidget
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPosition
|
import de.jrpie.android.launcher.widgets.WidgetPosition
|
||||||
import de.jrpie.android.launcher.widgets.deleteAppWidget
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,7 +110,7 @@ class ManageWidgetsActivity : Activity(), UIObject {
|
||||||
display.height
|
display.height
|
||||||
)
|
)
|
||||||
|
|
||||||
val widget = WidgetInfo(appWidgetId, provider, position)
|
val widget = AppWidget(appWidgetId, provider, position)
|
||||||
LauncherPreferences.internal().widgets(
|
LauncherPreferences.internal().widgets(
|
||||||
(LauncherPreferences.internal().widgets() ?: HashSet()).also {
|
(LauncherPreferences.internal().widgets() ?: HashSet()).also {
|
||||||
it.add(widget)
|
it.add(widget)
|
||||||
|
@ -158,7 +157,7 @@ class ManageWidgetsActivity : Activity(), UIObject {
|
||||||
val appWidgetId =
|
val appWidgetId =
|
||||||
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
||||||
if (appWidgetId != -1) {
|
if (appWidgetId != -1) {
|
||||||
deleteAppWidget(this, WidgetInfo(appWidgetId))
|
AppWidget(appWidgetId).delete(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,16 @@ import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.databinding.ActivitySelectWidgetBinding
|
import de.jrpie.android.launcher.databinding.ActivitySelectWidgetBinding
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.ui.UIObject
|
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.bindAppWidgetOrRequestPermission
|
||||||
|
import de.jrpie.android.launcher.widgets.getAppWidgetHost
|
||||||
import de.jrpie.android.launcher.widgets.getAppWidgetProviders
|
import de.jrpie.android.launcher.widgets.getAppWidgetProviders
|
||||||
|
import de.jrpie.android.launcher.widgets.updateWidget
|
||||||
|
|
||||||
|
|
||||||
private const val REQUEST_WIDGET_PERMISSION = 29
|
private const val REQUEST_WIDGET_PERMISSION = 29
|
||||||
|
@ -38,8 +46,16 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject {
|
||||||
lateinit var binding: ActivitySelectWidgetBinding
|
lateinit var binding: ActivitySelectWidgetBinding
|
||||||
var widgetId: Int = -1
|
var widgetId: Int = -1
|
||||||
|
|
||||||
private fun tryBindWidget(info: AppWidgetProviderInfo) {
|
private fun tryBindWidget(info: LauncherWidgetProvider) {
|
||||||
if(bindAppWidgetOrRequestPermission(this, info, widgetId, REQUEST_WIDGET_PERMISSION)) {
|
when (info) {
|
||||||
|
is LauncherAppWidgetProvider -> {
|
||||||
|
if (bindAppWidgetOrRequestPermission(
|
||||||
|
this,
|
||||||
|
info.info,
|
||||||
|
widgetId,
|
||||||
|
REQUEST_WIDGET_PERMISSION
|
||||||
|
)
|
||||||
|
) {
|
||||||
setResult(
|
setResult(
|
||||||
RESULT_OK,
|
RESULT_OK,
|
||||||
Intent().also {
|
Intent().also {
|
||||||
|
@ -49,6 +65,12 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is LauncherClockWidgetProvider -> {
|
||||||
|
updateWidget(ClockWidget(widgetId, WidgetPosition(0,4,12,3)))
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super<AppCompatActivity>.onStart()
|
super<AppCompatActivity>.onStart()
|
||||||
|
@ -64,6 +86,9 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject {
|
||||||
|
|
||||||
|
|
||||||
widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
||||||
|
if (widgetId == -1) {
|
||||||
|
widgetId = getAppWidgetHost().allocateAppWidgetId()
|
||||||
|
}
|
||||||
|
|
||||||
val viewManager = LinearLayoutManager(this)
|
val viewManager = LinearLayoutManager(this)
|
||||||
val viewAdapter = SelectWidgetRecyclerAdapter()
|
val viewAdapter = SelectWidgetRecyclerAdapter()
|
||||||
|
@ -87,7 +112,7 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject {
|
||||||
data ?: return
|
data ?: return
|
||||||
Log.i("SelectWidget", "permission granted")
|
Log.i("SelectWidget", "permission granted")
|
||||||
val provider = (data.getSerializableExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER) as? AppWidgetProviderInfo) ?: return
|
val provider = (data.getSerializableExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER) as? AppWidgetProviderInfo) ?: return
|
||||||
tryBindWidget(provider)
|
tryBindWidget(LauncherAppWidgetProvider(provider))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,9 +146,9 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
val preview =
|
val preview =
|
||||||
widgets[i].loadPreviewImage(this@SelectWidgetActivity, DisplayMetrics.DENSITY_DEFAULT )
|
widgets[i].loadPreviewImage(this@SelectWidgetActivity)
|
||||||
val icon =
|
val icon =
|
||||||
widgets[i].loadIcon(this@SelectWidgetActivity, DisplayMetrics.DENSITY_DEFAULT)
|
widgets[i].loadIcon(this@SelectWidgetActivity)
|
||||||
|
|
||||||
viewHolder.textView.text = label
|
viewHolder.textView.text = label
|
||||||
viewHolder.descriptionView.text = description
|
viewHolder.descriptionView.text = description
|
||||||
|
|
|
@ -20,8 +20,8 @@ import androidx.core.graphics.toRect
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
import de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
||||||
|
import de.jrpie.android.launcher.widgets.Widget
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPosition
|
import de.jrpie.android.launcher.widgets.WidgetPosition
|
||||||
import de.jrpie.android.launcher.widgets.getWidgetById
|
|
||||||
import de.jrpie.android.launcher.widgets.updateWidget
|
import de.jrpie.android.launcher.widgets.updateWidget
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
@ -91,9 +91,7 @@ class WidgetManagerView(context: Context, attrs: AttributeSet? = null) :
|
||||||
|
|
||||||
val position = (view.layoutParams as Companion.LayoutParams).position.getAbsoluteRect(width, height)
|
val position = (view.layoutParams as Companion.LayoutParams).position.getAbsoluteRect(width, height)
|
||||||
selectedWidgetOverlayView = view
|
selectedWidgetOverlayView = view
|
||||||
val widgetView = getWidgetViewById(view.widgetId)
|
selectedWidgetView = Widget.byId(view.widgetId)?.findView(children) ?: return true
|
||||||
selectedWidgetView = widgetView ?: return true
|
|
||||||
widgetView.visibility = GONE
|
|
||||||
startWidgetPosition = position
|
startWidgetPosition = position
|
||||||
|
|
||||||
val positionInView = start.minus(Point(position.left, position.top))
|
val positionInView = start.minus(Point(position.left, position.top))
|
||||||
|
@ -127,17 +125,18 @@ class WidgetManagerView(context: Context, attrs: AttributeSet? = null) :
|
||||||
)
|
)
|
||||||
if (newPosition != lastPosition) {
|
if (newPosition != lastPosition) {
|
||||||
lastPosition = absoluteNewPosition
|
lastPosition = absoluteNewPosition
|
||||||
|
(view.layoutParams as Companion.LayoutParams).position = newPosition
|
||||||
|
(selectedWidgetView?.layoutParams as? Companion.LayoutParams)?.position = newPosition
|
||||||
|
requestLayout()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS)
|
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(view.layoutParams as Companion.LayoutParams).position = newPosition
|
|
||||||
requestLayout()
|
|
||||||
|
|
||||||
if (event.actionMasked == MotionEvent.ACTION_UP) {
|
if (event.actionMasked == MotionEvent.ACTION_UP) {
|
||||||
longPressHandler.removeCallbacksAndMessages(null)
|
longPressHandler.removeCallbacksAndMessages(null)
|
||||||
val id = selectedWidgetOverlayView?.widgetId ?: return true
|
val id = selectedWidgetOverlayView?.widgetId ?: return true
|
||||||
val widget = getWidgetById(id) ?: return true
|
val widget = Widget.byId(id) ?: return true
|
||||||
widget.position = newPosition
|
widget.position = newPosition
|
||||||
endInteraction()
|
endInteraction()
|
||||||
updateWidget(widget)
|
updateWidget(widget)
|
||||||
|
@ -154,12 +153,6 @@ class WidgetManagerView(context: Context, attrs: AttributeSet? = null) :
|
||||||
private fun endInteraction() {
|
private fun endInteraction() {
|
||||||
startWidgetPosition = null
|
startWidgetPosition = null
|
||||||
selectedWidgetOverlayView?.mode = null
|
selectedWidgetOverlayView?.mode = null
|
||||||
selectedWidgetView?.visibility = VISIBLE
|
|
||||||
}
|
|
||||||
fun getWidgetViewById(id: Int): AppWidgetHostView? {
|
|
||||||
return children.mapNotNull { it as? AppWidgetHostView }.firstOrNull {
|
|
||||||
it.appWidgetId == id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateWidgets(activity: Activity) {
|
override fun updateWidgets(activity: Activity) {
|
||||||
|
|
|
@ -2,20 +2,14 @@ package de.jrpie.android.launcher.ui.widgets.manage
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.view.ContextMenu
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import androidx.core.graphics.toRectF
|
import androidx.core.graphics.toRectF
|
||||||
import de.jrpie.android.launcher.Application
|
import de.jrpie.android.launcher.widgets.Widget
|
||||||
import de.jrpie.android.launcher.widgets.WidgetInfo
|
|
||||||
import de.jrpie.android.launcher.widgets.WidgetPosition
|
|
||||||
import de.jrpie.android.launcher.widgets.deleteAppWidget
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An overlay to show configuration options for a widget.
|
* An overlay to show configuration options for a widget.
|
||||||
|
@ -33,27 +27,22 @@ class WidgetOverlayView : View {
|
||||||
class Handle(val mode: WidgetManagerView.EditMode, val position: Rect)
|
class Handle(val mode: WidgetManagerView.EditMode, val position: Rect)
|
||||||
init {
|
init {
|
||||||
handlePaint.style = Paint.Style.STROKE
|
handlePaint.style = Paint.Style.STROKE
|
||||||
handlePaint.setARGB(100, 255, 255, 255)
|
handlePaint.setARGB(255, 255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
selectedHandlePaint.style = Paint.Style.FILL_AND_STROKE
|
selectedHandlePaint.style = Paint.Style.FILL_AND_STROKE
|
||||||
selectedHandlePaint.setARGB(255, 255, 255, 255)
|
selectedHandlePaint.setARGB(100, 255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
paint.style = Paint.Style.STROKE
|
paint.style = Paint.Style.STROKE
|
||||||
paint.setARGB(50, 255, 255, 255)
|
paint.setARGB(255, 255, 255, 255)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var preview: Drawable? = null
|
private var preview: Drawable? = null
|
||||||
var widgetId: Int = -1
|
var widgetId: Int = -1
|
||||||
get() = field
|
|
||||||
set(newId) {
|
set(newId) {
|
||||||
field = newId
|
field = newId
|
||||||
val appWidgetManager= (context.applicationContext as Application).appWidgetManager
|
preview = Widget.byId(widgetId)?.getPreview(context)
|
||||||
|
|
||||||
preview =
|
|
||||||
appWidgetManager.getAppWidgetInfo(newId).loadPreviewImage(context, DisplayMetrics.DENSITY_HIGH) ?:
|
|
||||||
appWidgetManager.getAppWidgetInfo(newId).loadIcon(context, DisplayMetrics.DENSITY_HIGH)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context: Context) : super(context) {
|
constructor(context: Context) : super(context) {
|
||||||
|
@ -91,8 +80,8 @@ class WidgetOverlayView : View {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
preview?.bounds = bounds
|
//preview?.bounds = bounds
|
||||||
preview?.draw(canvas)
|
//preview?.draw(canvas)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -101,7 +90,7 @@ class WidgetOverlayView : View {
|
||||||
val menu = PopupMenu(context, this)
|
val menu = PopupMenu(context, this)
|
||||||
menu.menu.let {
|
menu.menu.let {
|
||||||
it.add("Remove").setOnMenuItemClickListener { _ ->
|
it.add("Remove").setOnMenuItemClickListener { _ ->
|
||||||
deleteAppWidget(context, WidgetInfo(widgetId))
|
Widget.byId(widgetId)?.delete(context)
|
||||||
return@setOnMenuItemClickListener true
|
return@setOnMenuItemClickListener true
|
||||||
}
|
}
|
||||||
it.add("Allow Interaction").setOnMenuItemClickListener { _ ->
|
it.add("Allow Interaction").setOnMenuItemClickListener { _ ->
|
||||||
|
|
108
app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt
Normal file
108
app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
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 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),
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
// In normal operation only id and position are used.
|
||||||
|
val packageName: String? = null,
|
||||||
|
val className: String? = null,
|
||||||
|
val user: Int? = null
|
||||||
|
): Widget() {
|
||||||
|
|
||||||
|
|
||||||
|
constructor(id: Int, widgetProviderInfo: AppWidgetProviderInfo, position: WidgetPosition) :
|
||||||
|
this(
|
||||||
|
id, position,
|
||||||
|
widgetProviderInfo.provider.packageName,
|
||||||
|
widgetProviderInfo.provider.className,
|
||||||
|
widgetProviderInfo.profile.hashCode()
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the [AppWidgetProviderInfo] by [id].
|
||||||
|
* If the widget is not installed, use [restoreAppWidgetProviderInfo] instead.
|
||||||
|
*/
|
||||||
|
fun getAppWidgetProviderInfo(context: Context): AppWidgetProviderInfo? {
|
||||||
|
if (id < 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (context.applicationContext as Application).appWidgetManager
|
||||||
|
.getAppWidgetInfo(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the AppWidgetProviderInfo from [user], [packageName] and [className].
|
||||||
|
* Only use this when the widget is not installed,
|
||||||
|
* in normal operation use [getAppWidgetProviderInfo] instead.
|
||||||
|
*/
|
||||||
|
/*fun restoreAppWidgetProviderInfo(context: Context): AppWidgetProviderInfo? {
|
||||||
|
return getAppWidgetProviders(context).firstOrNull {
|
||||||
|
it.profile.hashCode() == user
|
||||||
|
&& it.provider.packageName == packageName
|
||||||
|
&& it.provider.className == className
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "WidgetInfo(id=$id, position=$position, packageName=$packageName, className=$className, user=$user)"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createView(activity: Activity): AppWidgetHostView? {
|
||||||
|
val providerInfo = activity.getAppWidgetManager().getAppWidgetInfo(id) ?: return null
|
||||||
|
val view = activity.getAppWidgetHost()
|
||||||
|
.createView(activity, this.id, providerInfo)
|
||||||
|
|
||||||
|
val dp = activity.resources.displayMetrics.density
|
||||||
|
val screenWidth = activity.resources.displayMetrics.widthPixels
|
||||||
|
val screenHeight = activity.resources.displayMetrics.heightPixels
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val absolutePosition = position.getAbsoluteRect(screenWidth, screenHeight)
|
||||||
|
view.updateAppWidgetSize(Bundle.EMPTY,
|
||||||
|
listOf(SizeF(
|
||||||
|
absolutePosition.width() / dp,
|
||||||
|
absolutePosition.height() / dp
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
view.setPadding(0,0,0,0)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findView(views: Sequence<View>): AppWidgetHostView? {
|
||||||
|
return views.mapNotNull { it as? AppWidgetHostView }.firstOrNull { it.appWidgetId == id }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIcon(context: Context): Drawable? {
|
||||||
|
return context.getAppWidgetManager().getAppWidgetInfo(id)?.loadIcon(context, DisplayMetrics.DENSITY_HIGH)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPreview(context: Context): Drawable? {
|
||||||
|
return context.getAppWidgetManager().getAppWidgetInfo(id)?.loadPreviewImage(context, DisplayMetrics.DENSITY_HIGH)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package de.jrpie.android.launcher.widgets
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.view.View
|
||||||
|
import de.jrpie.android.launcher.ui.widgets.ClockView
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("widget:clock")
|
||||||
|
class ClockWidget(override val id: Int, override var position: WidgetPosition): Widget() {
|
||||||
|
|
||||||
|
override fun createView(activity: Activity): View? {
|
||||||
|
return ClockView(activity, null, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findView(views: Sequence<View>): ClockView? {
|
||||||
|
return views.mapNotNull { it as? ClockView }.firstOrNull { it.appWidgetId == id }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPreview(context: Context): Drawable? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIcon(context: Context): Drawable? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package de.jrpie.android.launcher.widgets
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetProviderInfo
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
|
||||||
|
sealed class LauncherWidgetProvider {
|
||||||
|
abstract val label: String?
|
||||||
|
|
||||||
|
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 loadPreviewImage(context: Context): Drawable? {
|
||||||
|
return info.loadPreviewImage(context, DisplayMetrics.DENSITY_DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadIcon(context: Context): Drawable? {
|
||||||
|
return info.loadIcon(context, DisplayMetrics.DENSITY_DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadDescription(context: Context): CharSequence? {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
info.loadDescription(context)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
class LauncherClockWidgetProvider : LauncherWidgetProvider() {
|
||||||
|
override val label: String?
|
||||||
|
get() = "Clock"
|
||||||
|
|
||||||
|
override fun loadPreviewImage(context: Context): Drawable? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadIcon(context: Context): Drawable? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadDescription(context: Context): CharSequence? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package de.jrpie.android.launcher.widgets
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.view.View
|
||||||
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
sealed class Widget {
|
||||||
|
abstract val id: Int
|
||||||
|
abstract var position: WidgetPosition
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param activity The activity where the view will be used. Must not be an AppCompatActivity.
|
||||||
|
*/
|
||||||
|
abstract fun createView(activity: Activity): View?
|
||||||
|
abstract fun findView(views: Sequence<View>): View?
|
||||||
|
abstract fun getPreview(context: Context): Drawable?
|
||||||
|
abstract fun getIcon(context: Context): Drawable?
|
||||||
|
|
||||||
|
fun delete(context: Context) {
|
||||||
|
context.getAppWidgetHost().deleteAppWidgetId(id)
|
||||||
|
|
||||||
|
LauncherPreferences.internal().widgets(
|
||||||
|
LauncherPreferences.internal().widgets()?.also {
|
||||||
|
it.remove(this)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return (other as? Widget)?.id == id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun serialize(): String {
|
||||||
|
return Json.encodeToString(serializer(), this)
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
fun deserialize(serialized: String): Widget {
|
||||||
|
return Json.decodeFromString(serialized)
|
||||||
|
}
|
||||||
|
fun byId(id: Int): Widget? {
|
||||||
|
return (LauncherPreferences.internal().widgets() ?: setOf())
|
||||||
|
.firstOrNull { it.id == id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,78 +0,0 @@
|
||||||
package de.jrpie.android.launcher.widgets;
|
|
||||||
|
|
||||||
import android.appwidget.AppWidgetProviderInfo
|
|
||||||
import android.content.Context
|
|
||||||
import de.jrpie.android.launcher.Application
|
|
||||||
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,
|
|
||||||
var position: WidgetPosition = WidgetPosition(0,0,1,1),
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
// In normal operation only id and position are used.
|
|
||||||
val packageName: String? = null,
|
|
||||||
val className: String? = null,
|
|
||||||
val user: Int? = null
|
|
||||||
) {
|
|
||||||
|
|
||||||
|
|
||||||
constructor(id: Int, widgetProviderInfo: AppWidgetProviderInfo, position: WidgetPosition) :
|
|
||||||
this(
|
|
||||||
id, position,
|
|
||||||
widgetProviderInfo.provider.packageName,
|
|
||||||
widgetProviderInfo.provider.className,
|
|
||||||
widgetProviderInfo.profile.hashCode()
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the [AppWidgetProviderInfo] by [id].
|
|
||||||
* If the widget is not installed, use [restoreAppWidgetProviderInfo] instead.
|
|
||||||
*/
|
|
||||||
fun getAppWidgetProviderInfo(context: Context): AppWidgetProviderInfo? {
|
|
||||||
return (context.applicationContext as Application).appWidgetManager
|
|
||||||
.getAppWidgetInfo(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore the AppWidgetProviderInfo from [user], [packageName] and [className].
|
|
||||||
* Only use this when the widget is not installed,
|
|
||||||
* in normal operation use [getAppWidgetProviderInfo] instead.
|
|
||||||
*/
|
|
||||||
fun restoreAppWidgetProviderInfo(context: Context): AppWidgetProviderInfo? {
|
|
||||||
return getAppWidgetProviders(context).firstOrNull {
|
|
||||||
it.profile.hashCode() == user
|
|
||||||
&& it.provider.packageName == packageName
|
|
||||||
&& it.provider.className == className
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "WidgetInfo(id=$id, position=$position, packageName=$packageName, className=$className, user=$user)"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,10 +19,19 @@ import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
|
||||||
fun deleteAllWidgets(context: Context) {
|
fun deleteAllWidgets(context: Context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
context.getAppWidgetHost().appWidgetIds.forEach { deleteAppWidget(context, WidgetInfo(it)) }
|
context.getAppWidgetHost().appWidgetIds.forEach { AppWidget(it).delete(context) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to bind [providerInfo] to the id [id].
|
||||||
|
* @param providerInfo The widget to be bound.
|
||||||
|
* @param id The id to bind the widget to. If -1 is provided, a new id is allocated.
|
||||||
|
* @param
|
||||||
|
* @param requestCode Used to start an activity to request permission to bind the widget.
|
||||||
|
*
|
||||||
|
* @return true iff the app widget was bound successfully.
|
||||||
|
*/
|
||||||
fun bindAppWidgetOrRequestPermission(activity: Activity, providerInfo: AppWidgetProviderInfo, id: Int, requestCode: Int? = null): Boolean {
|
fun bindAppWidgetOrRequestPermission(activity: Activity, providerInfo: AppWidgetProviderInfo, id: Int, requestCode: Int? = null): Boolean {
|
||||||
val appWidgetId = if(id == -1) {
|
val appWidgetId = if(id == -1) {
|
||||||
activity.getAppWidgetHost().allocateAppWidgetId()
|
activity.getAppWidgetHost().allocateAppWidgetId()
|
||||||
|
@ -34,50 +43,20 @@ fun bindAppWidgetOrRequestPermission(activity: Activity, providerInfo: AppWidget
|
||||||
providerInfo.provider
|
providerInfo.provider
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Log.e("Launcher", "not allowed to bind widget")
|
Log.i("Widgets", "requesting permission for widget")
|
||||||
requestAppWidgetPermission(activity, appWidgetId, providerInfo, requestCode)
|
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply {
|
||||||
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,appWidgetId)
|
||||||
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, providerInfo.provider)
|
||||||
|
}
|
||||||
|
activity.startActivityForResult(intent, requestCode ?: 0)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAppWidget(context: Context, widget: WidgetInfo) {
|
|
||||||
Log.i("Launcher", "Deleting widget ${widget.id}")
|
|
||||||
val appWidgetHost = (context.applicationContext as Application).appWidgetHost
|
|
||||||
|
|
||||||
appWidgetHost.deleteAppWidgetId(widget.id)
|
fun getAppWidgetProviders( context: Context ): List<LauncherWidgetProvider> {
|
||||||
|
val list = mutableListOf<LauncherWidgetProvider>(LauncherClockWidgetProvider())
|
||||||
LauncherPreferences.internal().widgets(
|
|
||||||
LauncherPreferences.internal().widgets()?.also {
|
|
||||||
it.remove(widget)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createAppWidgetView(activity: Activity, widget: WidgetInfo): AppWidgetHostView? {
|
|
||||||
val providerInfo = activity.getAppWidgetManager().getAppWidgetInfo(widget.id) ?: return null
|
|
||||||
|
|
||||||
val dp = activity.resources.displayMetrics.density
|
|
||||||
|
|
||||||
val view = activity.getAppWidgetHost()
|
|
||||||
.createView(activity, widget.id, providerInfo)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
view.updateAppWidgetSize(Bundle.EMPTY, listOf(SizeF(widget.position.width / dp, widget.position.height / dp)))
|
|
||||||
}
|
|
||||||
view.setPadding(0,0,0,0)
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
fun requestAppWidgetPermission(context: Activity, widgetId: Int, info: AppWidgetProviderInfo, requestCode: Int?) {
|
|
||||||
Log.i("Widgets", "requesting permission for widget")
|
|
||||||
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply {
|
|
||||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
|
|
||||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider)
|
|
||||||
}
|
|
||||||
context.startActivityForResult(intent, requestCode ?: 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAppWidgetProviders( context: Context ): List<AppWidgetProviderInfo> {
|
|
||||||
val appWidgetManager = context.getAppWidgetManager()
|
val appWidgetManager = context.getAppWidgetManager()
|
||||||
val profiles =
|
val profiles =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
@ -85,28 +64,27 @@ fun getAppWidgetProviders( context: Context ): List<AppWidgetProviderInfo> {
|
||||||
} else {
|
} else {
|
||||||
(context.getSystemService(Service.USER_SERVICE) as UserManager).userProfiles
|
(context.getSystemService(Service.USER_SERVICE) as UserManager).userProfiles
|
||||||
}
|
}
|
||||||
Log.i("Widgets", "profiles: ${profiles.size}, $profiles")
|
list.addAll(
|
||||||
|
profiles.map {
|
||||||
return profiles.map {
|
|
||||||
appWidgetManager.getInstalledProvidersForProfile(it)
|
appWidgetManager.getInstalledProvidersForProfile(it)
|
||||||
|
.map { LauncherAppWidgetProvider(it) }
|
||||||
}.flatten()
|
}.flatten()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getWidgetById(id: Int): WidgetInfo? {
|
|
||||||
return (LauncherPreferences.internal().widgets() ?: setOf()).firstOrNull {
|
|
||||||
it.id == id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateWidget(widget: WidgetInfo) {
|
fun updateWidget(widget: Widget) {
|
||||||
var widgets = LauncherPreferences.internal().widgets() ?: setOf()
|
var widgets = LauncherPreferences.internal().widgets() ?: setOf()
|
||||||
widgets = widgets.minus(widget).plus(widget)
|
widgets = widgets.minus(widget).plus(widget)
|
||||||
LauncherPreferences.internal().widgets(widgets)
|
LauncherPreferences.internal().widgets(widgets)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.getAppWidgetHost(): AppWidgetHost {
|
fun Context.getAppWidgetHost(): AppWidgetHost {
|
||||||
return (this.applicationContext as Application).appWidgetHost
|
return (this.applicationContext as Application).appWidgetHost
|
||||||
}
|
}
|
||||||
private fun Context.getAppWidgetManager(): AppWidgetManager {
|
fun Context.getAppWidgetManager(): AppWidgetManager {
|
||||||
return (this.applicationContext as Application).appWidgetManager
|
return (this.applicationContext as Application).appWidgetManager
|
||||||
}
|
}
|
||||||
|
|
35
app/src/main/res/layout/clock.xml
Normal file
35
app/src/main/res/layout/clock.xml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:longClickable="false"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
tools:context=".ui.widgets.ClockView">
|
||||||
|
|
||||||
|
<TextClock
|
||||||
|
android:id="@+id/clock_upper_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:textSize="30sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="2024-12-24" />
|
||||||
|
|
||||||
|
<TextClock
|
||||||
|
android:id="@+id/clock_lower_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:text="18:00:00"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/clock_upper_view"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -10,36 +10,11 @@
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
tools:context=".ui.HomeActivity">
|
tools:context=".ui.HomeActivity">
|
||||||
|
|
||||||
|
|
||||||
<de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
<de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
||||||
android:id="@+id/home_widget_container"
|
android:id="@+id/home_widget_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<TextClock
|
|
||||||
android:id="@+id/home_upper_view"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="start|center_vertical"
|
|
||||||
android:textSize="30sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintVertical_bias="0.45"
|
|
||||||
tools:text="2024-12-24" />
|
|
||||||
|
|
||||||
<TextClock
|
|
||||||
android:id="@+id/home_lower_view"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="start|center_vertical"
|
|
||||||
android:textSize="18sp"
|
|
||||||
tools:text="18:00:00"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/home_upper_view"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
|
||||||
|
|
||||||
<!-- only shown when µLauncher settings can't be reached by a gesture -->
|
<!-- only shown when µLauncher settings can't be reached by a gesture -->
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/button_fallback_settings"
|
android:id="@+id/button_fallback_settings"
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<de.jrpie.android.launcher.ui.widgets.WidgetOverlayView
|
|
||||||
style="@style/Widget.launcherBaseTheme.MyView"
|
|
||||||
android:layout_width="300dp"
|
|
||||||
android:layout_height="300dp"
|
|
||||||
android:paddingLeft="20dp"
|
|
||||||
android:paddingBottom="40dp"
|
|
||||||
app:exampleDimension="24sp"
|
|
||||||
app:exampleDrawable="@android:drawable/ic_menu_add"
|
|
||||||
app:exampleString="Hello, WidgetOverlayView" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<resources>
|
|
||||||
|
|
||||||
<style name="Widget.launcherBaseTheme.MyView" parent="">
|
|
||||||
<item name="android:background">@color/gray_600</item>
|
|
||||||
<item name="exampleColor">@color/light_blue_600</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<resources>
|
|
||||||
<declare-styleable name="WidgetOverlayView">
|
|
||||||
<attr name="exampleString" format="string" />
|
|
||||||
<attr name="exampleDimension" format="dimension" />
|
|
||||||
<attr name="exampleColor" format="color" />
|
|
||||||
<attr name="exampleDrawable" format="color|reference" />
|
|
||||||
</declare-styleable>
|
|
||||||
</resources>
|
|
|
@ -130,9 +130,4 @@
|
||||||
<item name="android:windowEnterAnimation">@android:anim/fade_in</item>
|
<item name="android:windowEnterAnimation">@android:anim/fade_in</item>
|
||||||
<item name="android:windowExitAnimation">@android:anim/fade_out</item>
|
<item name="android:windowExitAnimation">@android:anim/fade_out</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.launcherBaseTheme.MyView" parent="">
|
|
||||||
<item name="android:background">@color/gray_400</item>
|
|
||||||
<item name="exampleColor">@color/light_blue_400</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue