Merge branch 'master' into migrate-build-files-to-kotlin

This commit is contained in:
Luke Wass 2025-05-12 12:56:32 -05:00
commit 9e2c1531b2
19 changed files with 210 additions and 47 deletions

View file

@ -16,6 +16,7 @@ import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersio
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.ClockWidget
import de.jrpie.android.launcher.widgets.DebugInfoWidget
import de.jrpie.android.launcher.widgets.WidgetPanel import de.jrpie.android.launcher.widgets.WidgetPanel
import de.jrpie.android.launcher.widgets.WidgetPosition import de.jrpie.android.launcher.widgets.WidgetPosition
import de.jrpie.android.launcher.widgets.deleteAllWidgets import de.jrpie.android.launcher.widgets.deleteAllWidgets
@ -95,8 +96,23 @@ fun resetPreferences(context: Context) {
) )
) )
if (BuildConfig.DEBUG) {
LauncherPreferences.widgets().widgets(
LauncherPreferences.widgets().widgets().also {
it.add(
DebugInfoWidget(
(context.applicationContext as Application).appWidgetHost.allocateAppWidgetId(),
WidgetPosition(1, 1, 10, 4),
WidgetPanel.HOME.id
)
)
}
)
}
val hidden: MutableSet<AbstractAppInfo> = mutableSetOf() val hidden: MutableSet<AbstractAppInfo> = mutableSetOf()
if (!BuildConfig.DEBUG) {
val launcher = DetailedAppInfo.fromAppInfo( val launcher = DetailedAppInfo.fromAppInfo(
AppInfo( AppInfo(
BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID,
@ -106,6 +122,7 @@ fun resetPreferences(context: Context) {
) )
launcher?.getRawInfo()?.let { hidden.add(it) } launcher?.getRawInfo()?.let { hidden.add(it) }
Log.i(TAG, "Hiding ${launcher?.getRawInfo()}") Log.i(TAG, "Hiding ${launcher?.getRawInfo()}")
}
LauncherPreferences.apps().hidden(hidden) LauncherPreferences.apps().hidden(hidden)
Action.resetToDefaultActions(context) Action.resetToDefaultActions(context)

View file

@ -12,11 +12,10 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import android.window.OnBackInvokedDispatcher import android.window.OnBackInvokedDispatcher
import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.Gesture
import de.jrpie.android.launcher.actions.LauncherAction import de.jrpie.android.launcher.actions.LauncherAction
import de.jrpie.android.launcher.databinding.HomeBinding import de.jrpie.android.launcher.databinding.ActivityHomeBinding
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
@ -35,7 +34,7 @@ import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
*/ */
class HomeActivity : UIObject, Activity() { class HomeActivity : UIObject, Activity() {
private lateinit var binding: HomeBinding private lateinit var binding: ActivityHomeBinding
private var touchGestureDetector: TouchGestureDetector? = null private var touchGestureDetector: TouchGestureDetector? = null
private var sharedPreferencesListener = private var sharedPreferencesListener =
@ -60,7 +59,7 @@ class HomeActivity : UIObject, Activity() {
// Initialise layout // Initialise layout
binding = HomeBinding.inflate(layoutInflater) binding = ActivityHomeBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)

View file

@ -6,13 +6,13 @@ import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.Gesture
import de.jrpie.android.launcher.databinding.ClockBinding import de.jrpie.android.launcher.databinding.WidgetClockBinding
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
import java.util.Locale import java.util.Locale
class ClockView(context: Context, attrs: AttributeSet? = null, val appWidgetId: Int): ConstraintLayout(context, attrs) { class ClockView(context: Context, attrs: AttributeSet? = null, val appWidgetId: Int): ConstraintLayout(context, attrs) {
val binding: ClockBinding = ClockBinding.inflate(LayoutInflater.from(context), this, true) val binding: WidgetClockBinding = WidgetClockBinding.inflate(LayoutInflater.from(context), this, true)
init { init {
initClock() initClock()
setOnClicks() setOnClicks()

View file

@ -0,0 +1,17 @@
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 de.jrpie.android.launcher.databinding.WidgetDebugInfoBinding
import de.jrpie.android.launcher.getDeviceInfo
class DebugInfoView(context: Context, attrs: AttributeSet? = null, val appWidgetId: Int): ConstraintLayout(context, attrs) {
val binding: WidgetDebugInfoBinding = WidgetDebugInfoBinding.inflate(LayoutInflater.from(context), this, true)
init {
binding.debugInfoText.text = getDeviceInfo()
}
}

View file

@ -18,13 +18,16 @@ import de.jrpie.android.launcher.widgets.updateWidgetPanel
class ManageWidgetPanelsActivity : AppCompatActivity(), UIObject { class ManageWidgetPanelsActivity : AppCompatActivity(), UIObject {
@SuppressLint("NotifyDataSetChanged")
private val sharedPreferencesListener = private val sharedPreferencesListener =
SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey -> SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey ->
if (prefKey == LauncherPreferences.widgets().keys().customPanels()) { if (
prefKey == LauncherPreferences.widgets().keys().customPanels()
|| prefKey == LauncherPreferences.widgets().keys().widgets()
) {
viewAdapter.widgetPanels = viewAdapter.widgetPanels =
(LauncherPreferences.widgets().customPanels() ?: setOf()).toTypedArray() (LauncherPreferences.widgets().customPanels() ?: setOf()).toTypedArray()
@SuppressLint("NotifyDataSetChanged")
viewAdapter.notifyDataSetChanged() viewAdapter.notifyDataSetChanged()
} }
} }
@ -76,7 +79,6 @@ class ManageWidgetPanelsActivity : AppCompatActivity(), UIObject {
) )
) )
} }
true
} }
} }

View file

@ -17,9 +17,12 @@ import de.jrpie.android.launcher.databinding.ActivityManageWidgetsBinding
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.AppWidget import de.jrpie.android.launcher.widgets.AppWidget
import de.jrpie.android.launcher.widgets.GRID_SIZE
import de.jrpie.android.launcher.widgets.WidgetPanel import de.jrpie.android.launcher.widgets.WidgetPanel
import de.jrpie.android.launcher.widgets.WidgetPosition import de.jrpie.android.launcher.widgets.WidgetPosition
import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt
// http://coderender.blogspot.com/2012/01/hosting-android-widgets-my.html // http://coderender.blogspot.com/2012/01/hosting-android-widgets-my.html
@ -143,14 +146,12 @@ class ManageWidgetsActivity : UIObject, Activity() {
val display = windowManager.defaultDisplay val display = windowManager.defaultDisplay
val position = WidgetPosition.fromAbsoluteRect( val widgetInfo = appWidgetManager.getAppWidgetInfo(appWidgetId)
Rect(
0, 0, val position = WidgetPosition.findFreeSpace(
min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minWidth), WidgetPanel.byId(panelId),
min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minHeight) max(3, (GRID_SIZE * (widgetInfo.minWidth) / display.width.toFloat()).roundToInt()),
), max(3, (GRID_SIZE * (widgetInfo.minHeight) / display.height.toFloat()).roundToInt())
display.width,
display.height
) )
val widget = AppWidget(appWidgetId, position, panelId, provider) val widget = AppWidget(appWidgetId, position, panelId, provider)

View file

@ -26,10 +26,16 @@ class WidgetPanelsRecyclerAdapter(
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var labelView: TextView = itemView.findViewById(R.id.list_widget_panels_label) var labelView: TextView = itemView.findViewById(R.id.list_widget_panels_label)
var infoView: TextView = itemView.findViewById(R.id.list_widget_panels_info)
} }
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) { override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
viewHolder.labelView.text = widgetPanels[i].label viewHolder.labelView.text = widgetPanels[i].label
val numWidgets = widgetPanels[i].getWidgets().size
viewHolder.infoView.text = context.resources.getQuantityString(
R.plurals.widget_panel_number_of_widgets,
numWidgets, numWidgets
)
viewHolder.itemView.setOnClickListener { viewHolder.itemView.setOnClickListener {
onSelectWidgetPanel(widgetPanels[i]) onSelectWidgetPanel(widgetPanels[i])

View file

@ -0,0 +1,42 @@
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.DebugInfoView
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("widget:debuginfo")
class DebugInfoWidget(
override val id: Int,
override var position: WidgetPosition,
override val panelId: Int,
override var allowInteraction: Boolean = true
) : Widget() {
override fun createView(activity: Activity): View {
return DebugInfoView(activity, null, id)
}
override fun findView(views: Sequence<View>): DebugInfoView? {
return views.mapNotNull { it as? DebugInfoView }.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) { }
}

View file

@ -37,13 +37,14 @@ class LauncherAppWidgetProvider(val info: AppWidgetProviderInfo) : LauncherWidge
} }
} }
class LauncherClockWidgetProvider : LauncherWidgetProvider() {
override fun loadLabel(context: Context): CharSequence? { data object LauncherClockWidgetProvider : LauncherWidgetProvider() {
override fun loadLabel(context: Context): CharSequence {
return context.getString(R.string.widget_clock_label) return context.getString(R.string.widget_clock_label)
} }
override fun loadDescription(context: Context): CharSequence? { override fun loadDescription(context: Context): CharSequence {
return context.getString(R.string.widget_clock_description) return context.getString(R.string.widget_clock_description)
} }
@ -55,4 +56,3 @@ class LauncherClockWidgetProvider : LauncherWidgetProvider() {
return AppCompatResources.getDrawable(context, R.drawable.baseline_clock_24) return AppCompatResources.getDrawable(context, R.drawable.baseline_clock_24)
} }
} }

View file

@ -4,7 +4,6 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.view.View import android.view.View
import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json

View file

@ -32,6 +32,12 @@ class WidgetPanel(val id: Int, var label: String) {
.filter { it.panelId == this.id }.forEach { it.delete(context) } .filter { it.panelId == this.id }.forEach { it.delete(context) }
} }
fun getWidgets(): List<Widget> {
return LauncherPreferences.widgets().widgets().filter {
it.panelId == this.id
}
}
companion object { companion object {
val HOME = WidgetPanel(0, "home") val HOME = WidgetPanel(0, "home")

View file

@ -11,6 +11,17 @@ const val GRID_SIZE: Short = 12
@Serializable @Serializable
data class WidgetPosition(var x: Short, var y: Short, var width: Short, var height: Short) { data class WidgetPosition(var x: Short, var y: Short, var width: Short, var height: Short) {
constructor(rect: Rect) : this(
rect.left.toShort(),
rect.top.toShort(),
(rect.right - rect.left).toShort(),
(rect.bottom - rect.top).toShort()
)
fun toRect(): Rect {
return Rect(x.toInt(), y.toInt(), x + width, y + height)
}
fun getAbsoluteRect(screenWidth: Int, screenHeight: Int): Rect { fun getAbsoluteRect(screenWidth: Int, screenHeight: Int): Rect {
val gridWidth = screenWidth / GRID_SIZE.toFloat() val gridWidth = screenWidth / GRID_SIZE.toFloat()
val gridHeight = screenHeight / GRID_SIZE.toFloat() val gridHeight = screenHeight / GRID_SIZE.toFloat()
@ -23,13 +34,16 @@ data class WidgetPosition(var x: Short, var y: Short, var width: Short, var heig
) )
} }
companion object { companion object {
fun fromAbsoluteRect(absolute: Rect, screenWidth: Int, screenHeight: Int): WidgetPosition { fun fromAbsoluteRect(absolute: Rect, screenWidth: Int, screenHeight: Int): WidgetPosition {
val gridWidth = screenWidth / GRID_SIZE.toFloat() val gridWidth = screenWidth / GRID_SIZE.toFloat()
val gridHeight = screenHeight / GRID_SIZE.toFloat() val gridHeight = screenHeight / GRID_SIZE.toFloat()
val x = (absolute.left / gridWidth).roundToInt().toShort().coerceIn(0, (GRID_SIZE-1).toShort()) val x = (absolute.left / gridWidth).roundToInt().toShort()
val y = (absolute.top / gridHeight).roundToInt().toShort().coerceIn(0, (GRID_SIZE-1).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 w = max(2, ((absolute.right - absolute.left) / gridWidth).roundToInt()).toShort()
@ -39,7 +53,12 @@ data class WidgetPosition(var x: Short, var y: Short, var width: Short, var heig
} }
fun center(minWidth: Int, minHeight: Int, screenWidth: Int, screenHeight: Int): WidgetPosition { fun center(
minWidth: Int,
minHeight: Int,
screenWidth: Int,
screenHeight: Int
): WidgetPosition {
val gridWidth = screenWidth / GRID_SIZE.toFloat() val gridWidth = screenWidth / GRID_SIZE.toFloat()
val gridHeight = screenHeight / GRID_SIZE.toFloat() val gridHeight = screenHeight / GRID_SIZE.toFloat()
@ -52,7 +71,32 @@ data class WidgetPosition(var x: Short, var y: Short, var width: Short, var heig
cellsWidth, cellsWidth,
cellsHeight cellsHeight
) )
}
fun findFreeSpace(
widgetPanel: WidgetPanel?,
minWidth: Int,
minHeight: Int
): WidgetPosition {
val rect = Rect(0, 0, minWidth, minHeight)
if (widgetPanel == null) {
return WidgetPosition(rect)
}
val widgets = widgetPanel.getWidgets().map { it.position.toRect() }
for (x in 0..<GRID_SIZE - minWidth) {
rect.left = x
rect.right = x + minWidth
for (y in 0..<GRID_SIZE - minHeight) {
rect.top = y
rect.bottom = y + minHeight
if (!widgets.any { Rect(it).intersect(rect) }) {
return WidgetPosition(rect)
}
}
}
return WidgetPosition(0, 0, minWidth.toShort(), minHeight.toShort())
} }
} }
} }

View file

@ -53,7 +53,7 @@ fun bindAppWidgetOrRequestPermission(activity: Activity, providerInfo: AppWidget
fun getAppWidgetProviders( context: Context ): List<LauncherWidgetProvider> { fun getAppWidgetProviders( context: Context ): List<LauncherWidgetProvider> {
val list = mutableListOf<LauncherWidgetProvider>(LauncherClockWidgetProvider()) val list = mutableListOf<LauncherWidgetProvider>(LauncherClockWidgetProvider)
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) {
@ -68,11 +68,9 @@ fun getAppWidgetProviders( context: Context ): List<LauncherWidgetProvider> {
}.flatten() }.flatten()
) )
return list return list
} }
fun updateWidget(widget: Widget) { fun updateWidget(widget: Widget) {
LauncherPreferences.widgets().widgets( LauncherPreferences.widgets().widgets(
(LauncherPreferences.widgets().widgets() ?: setOf()) (LauncherPreferences.widgets().widgets() ?: setOf())

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/list_widget_panels_row_container" android:id="@+id/list_widget_panels_row_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -14,10 +15,23 @@
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:gravity="start" android:gravity="start"
android:text="" android:text=""
android:textSize="20sp" tools:text="Panel #1"
app:layout_constraintBottom_toBottomOf="parent" android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/list_widget_panels_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="60sp"
android:layout_marginEnd="5dp"
android:gravity="start"
android:text=""
tools:text="Contains 5 widgets"
android:textSize="11sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/list_widget_panels_label" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
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.DebugInfoView">
<TextView
android:id="@+id/debugInfoText"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -406,8 +406,8 @@
<string name="widget_panel_default_name">Widget Panel #%1$d</string> <string name="widget_panel_default_name">Widget Panel #%1$d</string>
<plurals name="widget_panel_number_of_widgets"> <plurals name="widget_panel_number_of_widgets">
<item quantity="one">Contains %d widget.</item> <item quantity="one">Contains %1$d widget.</item>
<item quantity="other">Contains %d widgets.</item> <item quantity="other">Contains %1$d widgets.</item>
</plurals> </plurals>

View file

@ -0,0 +1,2 @@
* Fixed several bugs related to widgets
* Copy device info when clicking the version number (thank you, wassupluke!)