diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 579d5c1..841c9bd 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,6 +8,7 @@
tools:ignore="QueryAllPackagesPermission" />
+
+
>()
+ val widgets = MutableLiveData>()
val privateSpaceLocked = MutableLiveData()
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())
}
}
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 0cc3dd4..6ba467e 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,8 +21,6 @@ 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
@@ -63,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, SelectWidgetActivity::class.java))
- },
- //openAppsList(context, favorite = true) },
+ { context -> 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 d8397a0..575346a 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,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.SetAbstractAppInfoPreferenceSerializer;
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.ColorTheme;
import de.jrpie.android.launcher.preferences.theme.Font;
@@ -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 = SetWidgetInfoSerializer.class)
}),
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
@Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class),
@@ -74,6 +73,7 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
@Preference(name = "search_auto_launch", type = boolean.class, defaultValue = "true"),
@Preference(name = "search_web", type = boolean.class, description = "false"),
@Preference(name = "search_auto_open_keyboard", type = boolean.class, defaultValue = "true"),
+ @Preference(name = "search_auto_close_keyboard", type = boolean.class, defaultValue = "false"),
}),
@PreferenceGroup(name = "enabled_gestures", prefix = "settings_enabled_gestures_", suffix = "_key", value = {
@Preference(name = "double_swipe", type = boolean.class, defaultValue = "true"),
@@ -83,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 {}
\ No newline at end of file
diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt
index 59ecc7a..332f4df 100644
--- a/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt
+++ b/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt
@@ -13,6 +13,9 @@ import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersio
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
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
/* Current version of the structure of preferences.
* Increase when breaking changes are introduced and write an appropriate case in
@@ -71,6 +74,13 @@ fun resetPreferences(context: Context) {
Log.i(TAG, "Resetting preferences")
LauncherPreferences.clear()
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
+ deleteAllWidgets(context)
+
+ LauncherPreferences.widgets().widgets(
+ setOf(
+ ClockWidget(-500, WidgetPosition(1,4,10,3))
+ )
+ )
val hidden: MutableSet = mutableSetOf()
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 1746e8a..a2749ae 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,7 +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 de.jrpie.android.launcher.widgets.Widget
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
import kotlinx.serialization.Serializable
@@ -31,18 +31,18 @@ class SetAbstractAppInfoPreferenceSerializer :
@Suppress("UNCHECKED_CAST")
-class SetWidgetInfoSerializer :
- PreferenceSerializer?, java.util.Set?> {
+class SetWidgetSerializer :
+ 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
+ override fun serialize(value: java.util.Set?): java.util.Set? {
+ return value?.map(Widget::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
+ override fun deserialize(value: java.util.Set?): java.util.Set? {
+ return value?.map(java.lang.String::toString)?.map(Widget::deserialize)
+ ?.toHashSet() as? java.util.Set
}
}
diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt b/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt
index 1ca4d2b..a863c67 100644
--- a/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt
+++ b/app/src/main/java/de/jrpie/android/launcher/ui/Helper.kt
@@ -1,5 +1,6 @@
package de.jrpie.android.launcher.ui
+import android.app.Activity
import android.content.Context
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
@@ -38,10 +39,17 @@ fun ImageView.transformGrayscale(grayscale: Boolean) {
}
-// Taken from https://stackoverflow.com/a/50743764/12787264
+// Taken from https://stackoverflow.com/a/50743764
fun View.openSoftKeyboard(context: Context) {
this.requestFocus()
- // open the soft keyboard
- val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
- imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
+ (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
+ .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
+
+// https://stackoverflow.com/a/17789187
+fun closeSoftKeyboard(activity: Activity) {
+ activity.currentFocus?.let { focus ->
+ (activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
+ .hideSoftInputFromWindow( focus.windowToken, 0 )
+ }
+}
\ No newline at end of file
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 44947bb..3f1e497 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
@@ -1,6 +1,7 @@
package de.jrpie.android.launcher.ui
import android.annotation.SuppressLint
+import android.app.Activity
import android.content.SharedPreferences
import android.content.res.Configuration
import android.content.res.Resources
@@ -9,11 +10,7 @@ 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
@@ -23,13 +20,6 @@ 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,
@@ -43,7 +33,7 @@ import kotlin.random.Random
* - Setting global variables (preferences etc.)
* - Opening the [TutorialActivity] on new installations
*/
-class HomeActivity : UIObject, AppCompatActivity() {
+class HomeActivity : UIObject, Activity() {
private lateinit var binding: HomeBinding
private var touchGestureDetector: TouchGestureDetector? = null
@@ -54,15 +44,18 @@ class HomeActivity : UIObject, AppCompatActivity() {
prefKey?.startsWith("display.") == true
) {
recreate()
+ } 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("action.") == true) {
- updateSettingsFallbackButtonVisibility()
- }
}
override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
+ super.onCreate(savedInstanceState)
super.onCreate()
@@ -82,17 +75,6 @@ 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) {
@@ -101,8 +83,7 @@ class HomeActivity : UIObject, AppCompatActivity() {
}
override fun onStart() {
- super.onStart()
-
+ super.onStart()
super.onStart()
// If the tutorial was not finished, start it
@@ -113,6 +94,15 @@ class HomeActivity : UIObject, AppCompatActivity() {
LauncherPreferences.getSharedPreferences()
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
+ (application as Application).appWidgetHost.startListening()
+
+ }
+
+
+
+ override fun onStop() {
+ (application as Application).appWidgetHost.stopListening()
+ super.onStop()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
@@ -138,44 +128,6 @@ class HomeActivity : UIObject, AppCompatActivity() {
}
}
- 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 {
val mTheme = modifyTheme(super.getTheme())
mTheme.applyStyle(R.style.backgroundWallpaper, true)
@@ -213,9 +165,11 @@ class HomeActivity : UIObject, AppCompatActivity() {
windowInsets
}
}
-
- initClock()
updateSettingsFallbackButtonVisibility()
+
+ binding.homeWidgetContainer.updateWidgets(this@HomeActivity,
+ LauncherPreferences.widgets().widgets()
+ )
}
override fun onDestroy() {
@@ -253,30 +207,11 @@ class HomeActivity : UIObject, AppCompatActivity() {
}
override fun onTouchEvent(event: MotionEvent): Boolean {
+ android.util.Log.e("Launcher", "on touch")
touchGestureDetector?.onTouchEvent(event)
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() {
Gesture.BACK(this)
}
diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt
index 71908ba..3dbdda8 100644
--- a/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt
+++ b/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt
@@ -49,7 +49,21 @@ class PinShortcutActivity : AppCompatActivity(), UIObject {
val request = launcherApps.getPinItemRequest(intent)
this.request = request
- if (request == null || request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) {
+ if (request == null) {
+ finish()
+ return
+ }
+
+ if (request.requestType == PinItemRequest.REQUEST_TYPE_APPWIDGET) {
+
+ // TODO
+ request.getAppWidgetProviderInfo(this)
+ // startActivity()
+ finish()
+ return
+ }
+
+ if (request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) {
finish()
return
}
diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt
index 1a55bbb..a8e59ba 100644
--- a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt
+++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt
@@ -11,13 +11,16 @@ import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.apps.AppFilter
import de.jrpie.android.launcher.databinding.ListAppsBinding
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.ui.UIObject
+import de.jrpie.android.launcher.ui.closeSoftKeyboard
import de.jrpie.android.launcher.ui.list.ListActivity
import de.jrpie.android.launcher.ui.openSoftKeyboard
+import kotlin.math.absoluteValue
/**
@@ -90,6 +93,20 @@ class ListFragmentApps : Fragment(), UIObject {
}
}
adapter = appsRecyclerAdapter
+ if (LauncherPreferences.functionality().searchAutoCloseKeyboard()) {
+ addOnScrollListener(object : RecyclerView.OnScrollListener() {
+ var totalDy: Int = 0
+ var threshold = (resources.displayMetrics.density * 100).toInt()
+ override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+ totalDy += dy
+
+ if (totalDy.absoluteValue > 100) {
+ totalDy = 0
+ closeSoftKeyboard(requireActivity())
+ }
+ }
+ })
+ }
}
binding.listAppsSearchview.setOnQueryTextListener(object :
diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt b/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt
index a8efb43..8907f04 100644
--- a/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt
+++ b/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt
@@ -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(
+ LauncherPreferences.widgets().keys().widgets()
+ )
+ manageWidgets?.setOnPreferenceClickListener {
+ startActivity(Intent(requireActivity(), ManageWidgetsActivity::class.java))
+ true
+ }
+
val hiddenApps = findPreference(
LauncherPreferences.apps().keys().hidden()
)
diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/ClockView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/ClockView.kt
new file mode 100644
index 0000000..33c4888
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/ClockView.kt
@@ -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)
+ }
+ }
+ }
+
+
+
+}
\ No newline at end of file
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
deleted file mode 100644
index 98dcbe7..0000000
--- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/SelectWidgetActivity.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-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
index 47a1480..04668ca 100644
--- 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
@@ -1,12 +1,137 @@
package de.jrpie.android.launcher.ui.widgets
+import android.app.Activity
import android.content.Context
+import android.graphics.PointF
+import android.graphics.RectF
import android.util.AttributeSet
-import android.widget.LinearLayout
+import android.util.Log
+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.widgets.Widget
+import de.jrpie.android.launcher.widgets.WidgetPosition
+import kotlin.math.max
-// TODO: implement layout logic instead of linear layout
-class WidgetContainerView(context: Context, attrs: AttributeSet?): LinearLayout(context, attrs) {
- init {
- orientation = VERTICAL
+
+/**
+ * This only works in an Activity, not AppCompatActivity
+ */
+open class WidgetContainerView(context: Context, attrs: AttributeSet? = null) : ViewGroup(context, attrs) {
+
+ var widgetViewById = HashMap()
+
+ open fun updateWidgets(activity: Activity, widgets: Set?) {
+ if (widgets == null) {
+ return
+ }
+ Log.i("WidgetContainer", "updating ${activity.localClassName}")
+ widgetViewById.clear()
+ (0..
+ 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
+ var maxWidth = suggestedMinimumWidth
+
+ val mWidth = MeasureSpec.getSize(widthMeasureSpec)
+ val mHeight = MeasureSpec.getSize(heightMeasureSpec)
+
+ (0..
+ if (prefKey == LauncherPreferences.widgets().keys().widgets()) {
+ // We can't observe the livedata because this is not an AppCompatActivity
+ findViewById(R.id.manage_widgets_container).updateWidgets(this,
+ LauncherPreferences.widgets().widgets()
+ )
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ super.onCreate()
+ setContentView(R.layout.activity_manage_widgets)
+ findViewById(R.id.manage_widgets_button_add).setOnClickListener {
+ selectWidget()
+ }
+
+ 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
+ }
+
+ findViewById(R.id.manage_widgets_container).updateWidgets(this,
+ (application as Application).widgets.value
+ )
+ }
+
+ override fun onStart() {
+ super.onStart()
+ super.onStart()
+
+ LauncherPreferences.getSharedPreferences()
+ .registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
+
+ }
+
+ override fun onResume() {
+ super.onResume()
+ findViewById(R.id.manage_widgets_container).updateWidgets(this,
+ LauncherPreferences.widgets().widgets()
+ )
+
+ }
+ override fun getTheme(): Resources.Theme {
+ val mTheme = modifyTheme(super.getTheme())
+ mTheme.applyStyle(R.style.backgroundWallpaper, true)
+ LauncherPreferences.clock().font().applyToTheme(mTheme)
+ LauncherPreferences.theme().colorTheme().applyToTheme(
+ mTheme,
+ LauncherPreferences.theme().textShadow()
+ )
+ return mTheme
+ }
+
+ override fun onDestroy() {
+ LauncherPreferences.getSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener)
+ super.onDestroy()
+ }
+
+
+ fun selectWidget() {
+ val appWidgetHost = (application as Application).appWidgetHost
+ startActivityForResult(
+ Intent(this, SelectWidgetActivity::class.java).also {
+ it.putExtra(
+ AppWidgetManager.EXTRA_APPWIDGET_ID,
+ appWidgetHost.allocateAppWidgetId()
+ )
+ }, REQUEST_PICK_APPWIDGET
+ )
+ }
+
+
+ fun createWidget(data: Intent) {
+ Log.i("Launcher", "creating widget")
+ val appWidgetManager = (application as Application).appWidgetManager
+ val appWidgetId = data.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: return
+
+ val provider = appWidgetManager.getAppWidgetInfo(appWidgetId)
+
+ val display = windowManager.defaultDisplay
+
+ val position = WidgetPosition.fromAbsoluteRect(
+ Rect(0,0,
+ min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minWidth),
+ min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minHeight)
+ ),
+ display.width,
+ display.height
+ )
+
+ val widget = AppWidget(appWidgetId, provider, position)
+ LauncherPreferences.widgets().widgets(
+ (LauncherPreferences.widgets().widgets() ?: HashSet()).also {
+ it.add(widget)
+ }
+ )
+ }
+
+ private fun configureWidget(data: Intent) {
+ val extras = data.extras
+ val appWidgetId = extras!!.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
+ val widget = AppWidget(appWidgetId)
+ if (widget.isConfigurable(this)) {
+ widget.configure(this, REQUEST_CREATE_APPWIDGET)
+ } else {
+ createWidget(data)
+ }
+ }
+
+ override fun onActivityResult(
+ requestCode: Int, resultCode: Int,
+ data: Intent?
+ ) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (resultCode == RESULT_OK) {
+ if (requestCode == REQUEST_PICK_APPWIDGET) {
+ configureWidget(data!!)
+ } else if (requestCode == REQUEST_CREATE_APPWIDGET) {
+ createWidget(data!!)
+ }
+ } else if (resultCode == RESULT_CANCELED && data != null) {
+ val appWidgetId =
+ data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
+ if (appWidgetId != -1) {
+ AppWidget(appWidgetId).delete(this)
+ }
+ }
+ }
+
+
+ /**
+ * For a better preview, [ManageWidgetsActivity] should behave exactly like [HomeActivity]
+ */
+ override fun isHomeScreen(): Boolean {
+ return true
+ }
+}
diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt
new file mode 100644
index 0000000..c414db6
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt
@@ -0,0 +1,168 @@
+package de.jrpie.android.launcher.ui.widgets.manage
+
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Intent
+import android.content.res.Resources
+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.appcompat.app.AppCompatActivity
+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.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.WidgetPosition
+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.updateWidget
+
+
+private const val REQUEST_WIDGET_PERMISSION = 29
+
+/**
+ * This activity lets the user pick an app widget to add.
+ * It provides an interface similar to [android.appwidget.AppWidgetManager.ACTION_APPWIDGET_PICK],
+ * but shows more information and also shows widgets from other user profiles.
+ */
+class SelectWidgetActivity : AppCompatActivity(), UIObject {
+ lateinit var binding: ActivitySelectWidgetBinding
+ var widgetId: Int = -1
+
+ private fun tryBindWidget(info: LauncherWidgetProvider) {
+ when (info) {
+ is LauncherAppWidgetProvider -> {
+ if (bindAppWidgetOrRequestPermission(
+ this,
+ info.info,
+ widgetId,
+ REQUEST_WIDGET_PERMISSION
+ )
+ ) {
+ setResult(
+ RESULT_OK,
+ Intent().also {
+ it.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
+ }
+ )
+ finish()
+ }
+ }
+ is LauncherClockWidgetProvider -> {
+ updateWidget(ClockWidget(widgetId, WidgetPosition(0,4,12,3)))
+ finish()
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ super.onStart()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ super.onCreate()
+
+ binding = ActivitySelectWidgetBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+
+ widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
+ if (widgetId == -1) {
+ widgetId = getAppWidgetHost().allocateAppWidgetId()
+ }
+
+ val viewManager = LinearLayoutManager(this)
+ val viewAdapter = SelectWidgetRecyclerAdapter()
+
+ binding.selectWidgetRecycler.apply {
+ setHasFixedSize(false)
+ layoutManager = viewManager
+ adapter = viewAdapter
+ }
+ }
+
+ override fun getTheme(): Resources.Theme {
+ return modifyTheme(super.getTheme())
+ }
+
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ if (requestCode == REQUEST_WIDGET_PERMISSION && resultCode == RESULT_OK) {
+ data ?: return
+ val provider = (data.getSerializableExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER) as? AppWidgetProviderInfo) ?: return
+ tryBindWidget(LauncherAppWidgetProvider(provider))
+ }
+ }
+
+ inner class SelectWidgetRecyclerAdapter() :
+ RecyclerView.Adapter() {
+
+ private val widgets = getAppWidgetProviders(this@SelectWidgetActivity).toTypedArray()
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
+ View.OnClickListener {
+ var textView: TextView = itemView.findViewById(R.id.list_widgets_row_name)
+ var descriptionView: TextView = itemView.findViewById(R.id.list_widgets_row_description)
+ 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) {
+ tryBindWidget(widgets[bindingAdapterPosition])
+ }
+
+ init {
+ itemView.setOnClickListener(this)
+ }
+ }
+
+ override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
+ val label = widgets[i].loadLabel(this@SelectWidgetActivity)
+ val description = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ widgets[i].loadDescription(this@SelectWidgetActivity)
+ } else {
+ ""
+ }
+ val preview =
+ widgets[i].loadPreviewImage(this@SelectWidgetActivity)
+ val icon =
+ widgets[i].loadIcon(this@SelectWidgetActivity)
+
+ viewHolder.textView.text = label
+ viewHolder.descriptionView.text = description
+ viewHolder.descriptionView.visibility =
+ if (description?.isEmpty() == false) { View.VISIBLE } else { View.GONE }
+ viewHolder.iconView.setImageDrawable(icon)
+
+ viewHolder.previewView.setImageDrawable(preview)
+ viewHolder.previewView.visibility =
+ if (preview != null) { View.VISIBLE } else { View.GONE }
+
+ viewHolder.previewView.requestLayout()
+ }
+
+ 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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt
new file mode 100644
index 0000000..2d41e13
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt
@@ -0,0 +1,172 @@
+package de.jrpie.android.launcher.ui.widgets.manage
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.graphics.RectF
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.util.AttributeSet
+import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+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.ui.widgets.WidgetContainerView
+import de.jrpie.android.launcher.widgets.Widget
+import de.jrpie.android.launcher.widgets.WidgetPosition
+import de.jrpie.android.launcher.widgets.updateWidget
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * A variant of the [WidgetContainerView] which allows to manage widgets.
+ */
+class WidgetManagerView(context: Context, attrs: AttributeSet? = null) :
+ WidgetContainerView(context, attrs) {
+
+ val TOUCH_SLOP: Int
+ val TOUCH_SLOP_SQUARE: Int
+ val LONG_PRESS_TIMEOUT: Long
+
+ init {
+ val configuration = ViewConfiguration.get(context)
+ TOUCH_SLOP = configuration.scaledTouchSlop
+ TOUCH_SLOP_SQUARE = TOUCH_SLOP * TOUCH_SLOP
+
+ LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong()
+ }
+
+
+
+ enum class EditMode(val resize: (dx: Int, dy: Int, rect: Rect) -> Rect) {
+ MOVE({ dx, dy, rect ->
+ Rect(rect.left + dx, rect.top + dy, rect.right + dx, rect.bottom + dy)
+ }),
+ TOP({ dx, dy, rect ->
+ Rect(rect.left, min(rect.top + dy, rect.bottom - 200), rect.right, rect.bottom)
+ }),
+ BOTTOM({ dx, dy, rect ->
+ Rect(rect.left, rect.top, rect.right, max(rect.top + 200, rect.bottom + dy))
+ }),
+ LEFT({ dx, dy, rect ->
+ Rect(min(rect.left + dx, rect.right - 200), rect.top, rect.right, rect.bottom)
+ }),
+ RIGHT({ dx, dy, rect ->
+ Rect(rect.left, rect.top, max(rect.left + 200, rect.right + dx), rect.bottom)
+ }),
+ }
+
+ var selectedWidgetOverlayView: WidgetOverlayView? = null
+ var selectedWidgetView: View? = null
+ var currentGestureStart: Point? = null
+ var startWidgetPosition: Rect? = null
+ var lastPosition = Rect()
+
+ private val longPressHandler = Handler(Looper.getMainLooper())
+
+
+ override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
+ return true
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
+ if (event == null) {
+ return false
+ }
+ synchronized(this) {
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ val start = Point(event.x.toInt(), event.y.toInt())
+ currentGestureStart = start
+ val view = children.mapNotNull { it as? WidgetOverlayView }.firstOrNull {
+ RectF(it.x, it.y, it.x + it.width, it.y + it.height).toRect().contains(start) == true
+ } ?: return false
+
+ val position = (view.layoutParams as Companion.LayoutParams).position.getAbsoluteRect(width, height)
+ selectedWidgetOverlayView = view
+ selectedWidgetView = widgetViewById.get(view.widgetId) ?: return true
+ startWidgetPosition = position
+
+ val positionInView = start.minus(Point(position.left, position.top))
+ view.mode = view.getHandles().firstOrNull { it.position.contains(positionInView) }?.mode ?: EditMode.MOVE
+
+ longPressHandler.postDelayed({
+ synchronized(this@WidgetManagerView) {
+ view.showPopupMenu()
+ view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+ endInteraction()
+ }
+ }, LONG_PRESS_TIMEOUT)
+ }
+ if (event.actionMasked == MotionEvent.ACTION_MOVE ||
+ event.actionMasked == MotionEvent.ACTION_UP
+ ) {
+ val distanceX = event.x - (currentGestureStart?.x ?: return true)
+ val distanceY = event.y - (currentGestureStart?.y ?: return true)
+ if (distanceX * distanceX + distanceY * distanceY > TOUCH_SLOP_SQUARE) {
+ longPressHandler.removeCallbacksAndMessages(null)
+ }
+ val view = selectedWidgetOverlayView ?: return true
+ val start = startWidgetPosition ?: return true
+ val absoluteNewPosition = view.mode?.resize(
+ distanceX.toInt(),
+ distanceY.toInt(),
+ start
+ ) ?: return true
+ val newPosition = WidgetPosition.fromAbsoluteRect(
+ absoluteNewPosition, width, height
+ )
+ if (newPosition != lastPosition) {
+ 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) {
+ view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS)
+ }
+ }
+
+ if (event.actionMasked == MotionEvent.ACTION_UP) {
+ longPressHandler.removeCallbacksAndMessages(null)
+ val id = selectedWidgetOverlayView?.widgetId ?: return true
+ val widget = Widget.byId(context, id) ?: return true
+ widget.position = newPosition
+ endInteraction()
+ updateWidget(widget)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ view.performHapticFeedback(HapticFeedbackConstants.GESTURE_END)
+ }
+ }
+ }
+ }
+
+
+ return true
+ }
+ private fun endInteraction() {
+ startWidgetPosition = null
+ selectedWidgetOverlayView?.mode = null
+ }
+
+ override fun updateWidgets(activity: Activity, widgets: Set?) {
+ super.updateWidgets(activity, widgets)
+ if (widgets == null) {
+ return
+ }
+
+ widgets.forEach { widget ->
+ WidgetOverlayView(activity).let {
+ addView(it)
+ it.widgetId = widget.id
+ (it.layoutParams as Companion.LayoutParams).position = widget.position
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt
new file mode 100644
index 0000000..0ce789f
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt
@@ -0,0 +1,131 @@
+package de.jrpie.android.launcher.ui.widgets.manage
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+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.
+ */
+
+private const val HANDLE_SIZE = 100
+private const val HANDLE_EDGE_SIZE = (1.2 * HANDLE_SIZE).toInt()
+class WidgetOverlayView : View {
+
+
+ val paint = Paint()
+ val handlePaint = Paint()
+ val selectedHandlePaint = Paint()
+ var mode: WidgetManagerView.EditMode? = null
+ class Handle(val mode: WidgetManagerView.EditMode, val position: Rect)
+ init {
+ 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)
+ }
+
+ private var preview: Drawable? = null
+ var widgetId: Int = -1
+ set(newId) {
+ field = newId
+ preview = Widget.byId(context, widgetId)?.getPreview(context)
+ }
+
+ constructor(context: Context) : super(context) {
+ init(null, 0)
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ init(attrs, 0)
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
+ context,
+ attrs,
+ defStyle
+ ) {
+ init(attrs, defStyle)
+ }
+
+ private fun init(attrs: AttributeSet?, defStyle: Int) { }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+
+ getHandles().forEach {
+ if (it.mode == mode) {
+ canvas.drawRoundRect(it.position.toRectF(), 5f, 5f, selectedHandlePaint)
+ } else {
+ canvas.drawRoundRect(it.position.toRectF(), 5f, 5f, handlePaint)
+ }
+ }
+ val bounds = getBounds()
+ canvas.drawRoundRect(bounds.toRectF(), 5f, 5f, paint)
+
+ if (mode == null) {
+ return
+ }
+
+ //preview?.bounds = bounds
+ //preview?.draw(canvas)
+
+
+ }
+
+ fun showPopupMenu() {
+ val widget = Widget.byId(context, widgetId)?: return
+ val menu = PopupMenu(context, this)
+ menu.menu.let {
+ it.add(
+ context.getString(R.string.widget_menu_remove)
+ ).setOnMenuItemClickListener { _ ->
+ Widget.byId(context, widgetId)?.delete(context)
+ 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)
+ }
+ ).setOnMenuItemClickListener { _ ->
+ widget.allowInteraction = !widget.allowInteraction
+ updateWidget(widget)
+ return@setOnMenuItemClickListener true
+ }
+ }
+ menu.show()
+ }
+
+ fun getHandles(): List {
+ return listOf(
+ Handle(WidgetManagerView.EditMode.TOP,
+ Rect(HANDLE_EDGE_SIZE, 0, width - HANDLE_EDGE_SIZE, HANDLE_SIZE)),
+ Handle(WidgetManagerView.EditMode.BOTTOM,
+ Rect(HANDLE_EDGE_SIZE, height - HANDLE_SIZE, width - HANDLE_EDGE_SIZE, height)),
+ Handle(WidgetManagerView.EditMode.LEFT,
+ Rect(0, HANDLE_EDGE_SIZE, HANDLE_SIZE, height - HANDLE_EDGE_SIZE)),
+ Handle(WidgetManagerView.EditMode.RIGHT,
+ Rect(width - HANDLE_SIZE, HANDLE_EDGE_SIZE, width, height - HANDLE_EDGE_SIZE))
+ )
+
+ }
+
+ private fun getBounds(): Rect {
+ return Rect(0,0, width, height)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt
new file mode 100644
index 0000000..3e9a2eb
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt
@@ -0,0 +1,120 @@
+package de.jrpie.android.launcher.widgets;
+
+import android.app.Activity
+import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.os.Bundle
+import android.util.DisplayMetrics
+import android.util.SizeF
+import android.view.View
+import de.jrpie.android.launcher.Application
+import de.jrpie.android.launcher.ui.HomeActivity
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@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)
+ // 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,
+ false,
+ 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): 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)
+ }
+
+ 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
+ )
+ }
+}
diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt
new file mode 100644
index 0000000..d819538
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt
@@ -0,0 +1,41 @@
+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,
+ override var allowInteraction: Boolean = true
+) : Widget() {
+
+ override fun createView(activity: Activity): View? {
+ return ClockView(activity, null, id)
+ }
+
+ override fun findView(views: Sequence): 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
+ }
+
+ override fun isConfigurable(context: Context): Boolean {
+ return false
+ }
+
+ override fun configure(activity: Activity, requestCode: Int) { }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt
new file mode 100644
index 0000000..018b29b
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt
@@ -0,0 +1,58 @@
+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
+import androidx.appcompat.content.res.AppCompatResources
+import de.jrpie.android.launcher.R
+
+sealed class LauncherWidgetProvider {
+ 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 fun loadLabel(context: Context): CharSequence? {
+ return info.loadLabel(context.packageManager)
+ }
+ 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 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 AppCompatResources.getDrawable(context, R.drawable.baseline_clock_24)
+ }
+}
+
diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt
new file mode 100644
index 0000000..d3610dd
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt
@@ -0,0 +1,60 @@
+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.Application
+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
+ abstract var allowInteraction: Boolean
+
+ /**
+ * @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?
+ 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.widgets().widgets(
+ LauncherPreferences.widgets().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(context: Context, id: Int): Widget? {
+ return (context.applicationContext as Application).widgets.value?.firstOrNull {
+ it.id == id
+ }
+ }
+ }
+}
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
deleted file mode 100644
index c3d9046..0000000
--- a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetInfo.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-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/WidgetPanel.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt
new file mode 100644
index 0000000..c5fbb7b
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt
@@ -0,0 +1,15 @@
+package de.jrpie.android.launcher.widgets
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+@SerialName("panel")
+data class WidgetPanel(val id: Int, val label: String) {
+ companion object {
+ val DEFAULT = WidgetPanel(0, "home")
+ }
+}
+
+
+
diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt
new file mode 100644
index 0000000..b575665
--- /dev/null
+++ b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt
@@ -0,0 +1,58 @@
+package de.jrpie.android.launcher.widgets
+
+import android.graphics.Rect
+import kotlinx.serialization.Serializable
+import kotlin.math.ceil
+import kotlin.math.roundToInt
+import kotlin.math.max
+
+const val GRID_SIZE: Short = 12
+
+@Serializable
+data class WidgetPosition(var x: Short, var y: Short, var width: Short, var height: Short) {
+
+ fun getAbsoluteRect(screenWidth: Int, screenHeight: Int): Rect {
+ val gridWidth = screenWidth / GRID_SIZE.toFloat()
+ val gridHeight= screenHeight / GRID_SIZE.toFloat()
+
+ return Rect(
+ (x * gridWidth).toInt(),
+ (y * gridHeight).toInt(),
+ ((x + width) * gridWidth).toInt(),
+ ((y + height) * gridHeight).toInt()
+ )
+ }
+
+ companion object {
+ fun fromAbsoluteRect(absolute: Rect, screenWidth: Int, screenHeight: Int): WidgetPosition {
+ val gridWidth = screenWidth / GRID_SIZE.toFloat()
+ val gridHeight= screenHeight / GRID_SIZE.toFloat()
+
+ val x = (absolute.left / gridWidth).roundToInt().toShort().coerceIn(0, (GRID_SIZE-1).toShort())
+ val y = (absolute.top / gridHeight).roundToInt().toShort().coerceIn(0, (GRID_SIZE-1).toShort())
+
+
+ val w = max(2, ((absolute.right - absolute.left) / gridWidth).roundToInt()).toShort()
+ val h = max(2, ((absolute.bottom - absolute.top) / gridHeight).roundToInt()).toShort()
+
+ return WidgetPosition(x,y,w,h)
+
+ }
+
+ fun center(minWidth: Int, minHeight: Int, screenWidth: Int, screenHeight: Int): WidgetPosition {
+ val gridWidth = screenWidth / GRID_SIZE.toFloat()
+ val gridHeight= screenHeight / GRID_SIZE.toFloat()
+
+ val cellsWidth = ceil(minWidth / gridWidth).toInt().toShort()
+ val cellsHeight = ceil(minHeight / gridHeight).toInt().toShort()
+
+ return WidgetPosition(
+ ((GRID_SIZE - cellsWidth) / 2).toShort(),
+ ((GRID_SIZE - cellsHeight) / 2).toShort(),
+ cellsWidth,
+ cellsHeight
+ )
+
+ }
+ }
+}
\ No newline at end of file
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
index ab4f40d..cd4ef29 100644
--- a/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt
+++ b/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt
@@ -2,122 +2,86 @@ package de.jrpie.android.launcher.widgets
import android.app.Activity
import android.app.Service
-import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetHost
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 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
+fun deleteAllWidgets(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- appWidgetHost.appWidgetIds.forEach { deleteAppWidget(activity, WidgetInfo(it, 0,0)) }
+ context.getAppWidgetHost().appWidgetIds.forEach { AppWidget(it).delete(context) }
}
}
-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()
+/**
+ * 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 {
+ val appWidgetId = if(id == -1) {
+ activity.getAppWidgetHost().allocateAppWidgetId()
+ } else { id }
Log.i("Launcher", "Binding new widget ${appWidgetId}")
- if (!appWidgetManager.bindAppWidgetIdIfAllowed(
+ if (!activity.getAppWidgetManager().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)
+ Log.i("Widgets", "requesting permission for widget")
+ 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 true
+}
+
+
+fun getAppWidgetProviders( context: Context ): List {
+ val list = mutableListOf(LauncherClockWidgetProvider())
+ val appWidgetManager = context.getAppWidgetManager()
+ val profiles =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ (context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps).profiles
+ } else {
+ (context.getSystemService(Service.USER_SERVICE) as UserManager).userProfiles
+ }
+ list.addAll(
+ profiles.map {
+ appWidgetManager.getInstalledProvidersForProfile(it)
+ .map { LauncherAppWidgetProvider(it) }
+ }.flatten()
)
- return widget
+ return list
}
-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 updateWidget(widget: Widget) {
+ var widgets = LauncherPreferences.widgets().widgets() ?: setOf()
+ widgets = widgets.minus(widget).plus(widget)
+ LauncherPreferences.widgets().widgets(widgets)
}
-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 Context.getAppWidgetHost(): AppWidgetHost {
+ return (this.applicationContext as Application).appWidgetHost
}
-
-fun getAppWidgetProviders(context: Context): List {
- return appWidgetProviders(context, (context.applicationContext as Application).appWidgetManager)
+fun Context.getAppWidgetManager(): AppWidgetManager {
+ return (this.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/drawable/baseline_add_24.xml b/app/src/main/res/drawable/baseline_add_24.xml
new file mode 100644
index 0000000..13267ce
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_add_24.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/baseline_clock_24.xml b/app/src/main/res/drawable/baseline_clock_24.xml
new file mode 100644
index 0000000..7968998
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_clock_24.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_manage_widgets.xml b/app/src/main/res/layout/activity_manage_widgets.xml
new file mode 100644
index 0000000..c77f0e3
--- /dev/null
+++ b/app/src/main/res/layout/activity_manage_widgets.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_select_widget.xml b/app/src/main/res/layout/activity_select_widget.xml
index 463a317..82db94d 100644
--- a/app/src/main/res/layout/activity_select_widget.xml
+++ b/app/src/main/res/layout/activity_select_widget.xml
@@ -2,10 +2,11 @@
+ android:fitsSystemWindows="true"
+ tools:context=".ui.widgets.manage.SelectWidgetActivity">
+ app:layout_constraintTop_toBottomOf="@+id/select_widget_appbar" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/clock.xml b/app/src/main/res/layout/clock.xml
new file mode 100644
index 0000000..d81fc5f
--- /dev/null
+++ b/app/src/main/res/layout/clock.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
\ 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 444bd4e..717151f 100644
--- a/app/src/main/res/layout/home.xml
+++ b/app/src/main/res/layout/home.xml
@@ -9,39 +9,11 @@
android:longClickable="false"
android:fitsSystemWindows="true"
tools:context=".ui.HomeActivity">
-
+
-
-
-
-
-
+ android:layout_height="match_parent" />
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_widgets_row.xml b/app/src/main/res/layout/list_widgets_row.xml
index 120ea5b..878aaad 100644
--- a/app/src/main/res/layout/list_widgets_row.xml
+++ b/app/src/main/res/layout/list_widgets_row.xml
@@ -8,23 +8,13 @@
android:layout_height="wrap_content"
android:layout_margin="15sp">
-
+
@@ -33,14 +23,38 @@
android:id="@+id/list_widgets_row_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginStart="20sp"
+ android:layout_marginStart="10sp"
+ android:layout_marginEnd="10sp"
android:gravity="start"
android:text=""
android:textSize="20sp"
tools:text="some widget"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@id/list_widgets_row_preview"
+ app:layout_constraintStart_toEndOf="@id/list_widgets_row_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 1e4d12b..2712036 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -11,5 +11,9 @@
#fff
#9999ff
#000
+ #FF29B6F6
+ #FF039BE5
+ #FFBDBDBD
+ #FF757575
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index d9652ed..30e4cda 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -9,7 +9,7 @@
internal.started_before
internal.first_startup
internal.version_code
- internal.widgets
+ widgets.widgets
apps.favorites
apps.hidden
apps.pinned_shortcuts
@@ -148,6 +148,7 @@
functionality.search_auto_launch
functionality.search_web
functionality.search_auto_keyboard
+ functionality.search_auto_close_keyboard
settings_action_lock_method
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 301ca5e..a8813f8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -98,6 +98,8 @@
Time
Click on time
+ Manage widgets
+
Choose App
@@ -167,6 +169,7 @@
Search the web
Press return while searching the app list to launch a web search.
Start keyboard for search
+ Close keyboard when scrolling
Sensitivity
@@ -389,4 +392,13 @@
Can\'t open URL: no browser found.
Choose Widget
+ Remove
+ Configure
+ Enable Interaction
+ Disable Interaction
+
+
+ Clock
+ The default clock of μLauncher
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 20ccb67..c5b7252 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -66,12 +66,12 @@
- 0
- 2
+
-
-
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 6ef5d07..b4bc5f0 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -6,6 +6,10 @@
+
+
+