Compare commits

..

10 commits

28 changed files with 314 additions and 98 deletions

View file

@ -23,8 +23,8 @@ android {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 35 targetSdkVersion 35
compileSdk 35 compileSdk 35
versionCode 45 versionCode 46
versionName "0.2.0" versionName "0.2.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

View file

@ -27,6 +27,7 @@
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".ui.widgets.manage.ManageWidgetsActivity" android:name=".ui.widgets.manage.ManageWidgetsActivity"
android:configChanges="orientation|screenSize"
android:exported="false" android:exported="false"
android:theme="@style/launcherHomeTheme" /> android:theme="@style/launcherHomeTheme" />
<activity <activity

View file

@ -78,7 +78,8 @@ class TorchManager(context: Context) {
cameraManager.setTorchMode(camera, !torchEnabled) cameraManager.setTorchMode(camera, !torchEnabled)
} }
} catch (e: CameraAccessException) { } catch (e: Exception) {
// CameraAccessException, IllegalArgumentException
Toast.makeText( Toast.makeText(
context, context,
context.getString(R.string.alert_torch_access_exception), context.getString(R.string.alert_torch_access_exception),

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

@ -21,7 +21,5 @@ fun migratePreferencesFromVersion4(context: Context) {
) )
) )
) )
LauncherPreferences.internal().versionCode(100) LauncherPreferences.internal().versionCode(100)
} }

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)
@ -129,15 +128,7 @@ class HomeActivity : UIObject, Activity() {
} }
override fun getTheme(): Resources.Theme { override fun getTheme(): Resources.Theme {
val mTheme = modifyTheme(super.getTheme()) return 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 onResume() { override fun onResume() {

View file

@ -10,6 +10,7 @@ import android.view.WindowInsets
import android.view.WindowInsetsController import android.view.WindowInsetsController
import android.view.WindowManager import android.view.WindowManager
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.preferences.theme.Background
/** /**
* An interface implemented by every [Activity], Fragment etc. in Launcher. * An interface implemented by every [Activity], Fragment etc. in Launcher.
@ -65,8 +66,14 @@ interface UIObject {
theme, theme,
LauncherPreferences.theme().textShadow() LauncherPreferences.theme().textShadow()
) )
if (isHomeScreen()) {
Background.TRANSPARENT.applyToTheme(theme)
LauncherPreferences.clock().font().applyToTheme(theme)
} else {
LauncherPreferences.theme().background().applyToTheme(theme) LauncherPreferences.theme().background().applyToTheme(theme)
LauncherPreferences.theme().font().applyToTheme(theme) LauncherPreferences.theme().font().applyToTheme(theme)
}
return theme return theme
} }

View file

@ -132,7 +132,12 @@ class SettingsFragmentMeta : Fragment(), UIObject {
startActivity(Intent(this.context, LegalInfoActivity::class.java)) startActivity(Intent(this.context, LegalInfoActivity::class.java))
} }
// version
binding.settingsMetaTextVersion.text = BuildConfig.VERSION_NAME binding.settingsMetaTextVersion.text = BuildConfig.VERSION_NAME
binding.settingsMetaTextVersion.setOnClickListener {
val deviceInfo = getDeviceInfo()
copyToClipboard(requireContext(), deviceInfo)
}
} }
} }

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

@ -3,6 +3,7 @@ package de.jrpie.android.launcher.ui.widgets
import android.app.Activity import android.app.Activity
import android.content.res.Resources import android.content.res.Resources
import android.os.Bundle import android.os.Bundle
import androidx.core.view.ViewCompat
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.databinding.ActivityWidgetPanelBinding import de.jrpie.android.launcher.databinding.ActivityWidgetPanelBinding
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
@ -12,13 +13,20 @@ import de.jrpie.android.launcher.widgets.WidgetPanel
class WidgetPanelActivity : Activity(), UIObject { class WidgetPanelActivity : Activity(), UIObject {
lateinit var binding: ActivityWidgetPanelBinding lateinit var binding: ActivityWidgetPanelBinding
var widgetPanelId: Int = WidgetPanel.Companion.HOME.id private var widgetPanelId: Int = WidgetPanel.HOME.id
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super<Activity>.onCreate(savedInstanceState) super<Activity>.onCreate(savedInstanceState)
super<UIObject>.onCreate() super<UIObject>.onCreate()
widgetPanelId = intent.getIntExtra(EXTRA_PANEL_ID, WidgetPanel.Companion.HOME.id) widgetPanelId = intent.getIntExtra(EXTRA_PANEL_ID, WidgetPanel.HOME.id)
val binding = ActivityWidgetPanelBinding.inflate(layoutInflater) val binding = ActivityWidgetPanelBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
// The widget container should extend below the status and navigation bars,
// so let's set an empty WindowInsetsListener to prevent it from being moved.
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets ->
windowInsets
}
binding.widgetPanelWidgetContainer.widgetPanelId = widgetPanelId binding.widgetPanelWidgetContainer.widgetPanelId = widgetPanelId
binding.widgetPanelWidgetContainer.updateWidgets( binding.widgetPanelWidgetContainer.updateWidgets(
this, this,
@ -38,6 +46,13 @@ class WidgetPanelActivity : Activity(), UIObject {
return mTheme return mTheme
} }
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus && LauncherPreferences.display().hideNavigationBar()) {
hideNavigationBar()
}
}
override fun onStart() { override fun onStart() {
super<Activity>.onStart() super<Activity>.onStart()

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

@ -8,18 +8,21 @@ import android.content.res.Resources
import android.graphics.Rect import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.ViewGroup
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import com.google.android.material.floatingactionbutton.FloatingActionButton import androidx.core.view.updateLayoutParams
import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.R 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.ui.widgets.WidgetContainerView
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
@ -30,14 +33,16 @@ const val REQUEST_PICK_APPWIDGET = 2
const val EXTRA_PANEL_ID = "widgetPanelId" const val EXTRA_PANEL_ID = "widgetPanelId"
// We can't use AppCompatActivity, since some AppWidgets don't work there. // We can't use AppCompatActivity, since some AppWidgets don't work there.
class ManageWidgetsActivity : Activity(), UIObject { class ManageWidgetsActivity : UIObject, Activity() {
private var panelId: Int = WidgetPanel.HOME.id private var panelId: Int = WidgetPanel.HOME.id
private lateinit var binding: ActivityManageWidgetsBinding
private var sharedPreferencesListener = private var sharedPreferencesListener =
SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey -> SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey ->
if (prefKey == LauncherPreferences.widgets().keys().widgets()) { if (prefKey == LauncherPreferences.widgets().keys().widgets()) {
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this, binding.manageWidgetsContainer.updateWidgets(
this,
LauncherPreferences.widgets().widgets() LauncherPreferences.widgets().widgets()
) )
} }
@ -46,21 +51,33 @@ class ManageWidgetsActivity : Activity(), UIObject {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super<Activity>.onCreate(savedInstanceState) super<Activity>.onCreate(savedInstanceState)
super<UIObject>.onCreate() super<UIObject>.onCreate()
setContentView(R.layout.activity_manage_widgets) binding = ActivityManageWidgetsBinding.inflate(layoutInflater)
setContentView(binding.root)
panelId = intent.extras?.getInt(EXTRA_PANEL_ID, WidgetPanel.HOME.id) ?: WidgetPanel.HOME.id panelId = intent.extras?.getInt(EXTRA_PANEL_ID, WidgetPanel.HOME.id) ?: WidgetPanel.HOME.id
findViewById<FloatingActionButton>(R.id.manage_widgets_button_add).setOnClickListener { binding.manageWidgetsButtonAdd.setOnClickListener {
selectWidget() selectWidget()
} }
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> // The widget container should extend below the status and navigation bars,
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) // so let's set an empty WindowInsetsListener to prevent it from being moved.
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets ->
insets windowInsets
} }
findViewById<WidgetContainerView>(R.id.manage_widgets_container).let { // The button must not be placed under the navigation bar
ViewCompat.setOnApplyWindowInsetsListener(binding.manageWidgetsButtonAdd) { v, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updateLayoutParams<ViewGroup.MarginLayoutParams> {
leftMargin = insets.left
bottomMargin = insets.bottom
rightMargin = insets.right
}
WindowInsetsCompat.CONSUMED
}
binding.manageWidgetsContainer.let {
it.widgetPanelId = panelId it.widgetPanelId = panelId
it.updateWidgets(this, LauncherPreferences.widgets().widgets()) it.updateWidgets(this, LauncherPreferences.widgets().widgets())
} }
@ -77,20 +94,23 @@ class ManageWidgetsActivity : Activity(), UIObject {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this, binding.manageWidgetsContainer.updateWidgets(
this,
LauncherPreferences.widgets().widgets() LauncherPreferences.widgets().widgets()
) )
} }
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus && LauncherPreferences.display().hideNavigationBar()) {
hideNavigationBar()
}
}
override fun getTheme(): Resources.Theme { override fun getTheme(): Resources.Theme {
val mTheme = modifyTheme(super.getTheme()) return 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() { override fun onDestroy() {
@ -100,7 +120,7 @@ class ManageWidgetsActivity : Activity(), UIObject {
} }
fun selectWidget() { private fun selectWidget() {
val appWidgetHost = (application as Application).appWidgetHost val appWidgetHost = (application as Application).appWidgetHost
startActivityForResult( startActivityForResult(
Intent(this, SelectWidgetActivity::class.java).also { Intent(this, SelectWidgetActivity::class.java).also {
@ -117,7 +137,7 @@ class ManageWidgetsActivity : Activity(), UIObject {
} }
fun createWidget(data: Intent) { private fun createWidget(data: Intent) {
Log.i("Launcher", "creating widget") Log.i("Launcher", "creating widget")
val appWidgetManager = (application as Application).appWidgetManager val appWidgetManager = (application as Application).appWidgetManager
val appWidgetId = data.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: return val appWidgetId = data.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: return
@ -126,13 +146,12 @@ class ManageWidgetsActivity : Activity(), UIObject {
val display = windowManager.defaultDisplay val display = windowManager.defaultDisplay
val position = WidgetPosition.fromAbsoluteRect( val widgetInfo = appWidgetManager.getAppWidgetInfo(appWidgetId)
Rect(0,0,
min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minWidth), val position = WidgetPosition.findFreeSpace(
min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minHeight) WidgetPanel.byId(panelId),
), max(3, (GRID_SIZE * (widgetInfo.minWidth) / display.width.toFloat()).roundToInt()),
display.width, max(3, (GRID_SIZE * (widgetInfo.minHeight) / display.height.toFloat()).roundToInt())
display.height
) )
val widget = AppWidget(appWidgetId, position, panelId, provider) val widget = AppWidget(appWidgetId, position, panelId, provider)

View file

@ -2,11 +2,13 @@ package de.jrpie.android.launcher.ui.widgets.manage
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu import android.widget.PopupMenu
import androidx.core.graphics.toRectF import androidx.core.graphics.toRectF
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
@ -20,23 +22,32 @@ private const val HANDLE_EDGE_SIZE = (1.2 * HANDLE_SIZE).toInt()
/** /**
* An overlay to show configuration options for a widget in [WidgetManagerView] * An overlay to show configuration options for a widget in [WidgetManagerView]
*/ */
class WidgetOverlayView : View { class WidgetOverlayView : ViewGroup {
private val paint = Paint()
private val handlePaint = Paint()
private val selectedHandlePaint = Paint()
private val popupAnchor = View(context)
val paint = Paint()
val handlePaint = Paint()
val selectedHandlePaint = Paint()
var mode: WidgetManagerView.EditMode? = null var mode: WidgetManagerView.EditMode? = null
class Handle(val mode: WidgetManagerView.EditMode, val position: Rect) class Handle(val mode: WidgetManagerView.EditMode, val position: Rect)
init { init {
addView(popupAnchor)
setWillNotDraw(false)
handlePaint.style = Paint.Style.STROKE handlePaint.style = Paint.Style.STROKE
handlePaint.setARGB(255, 255, 255, 255) handlePaint.color = Color.WHITE
handlePaint.strokeWidth = 2f
handlePaint.setShadowLayer(10f,0f,0f, Color.BLACK)
selectedHandlePaint.style = Paint.Style.FILL_AND_STROKE selectedHandlePaint.style = Paint.Style.FILL_AND_STROKE
selectedHandlePaint.setARGB(100, 255, 255, 255) selectedHandlePaint.setARGB(100, 255, 255, 255)
handlePaint.setShadowLayer(10f,0f,0f, Color.BLACK)
paint.style = Paint.Style.STROKE paint.style = Paint.Style.STROKE
paint.setARGB(255, 255, 255, 255) paint.color = Color.WHITE
paint.setShadowLayer(10f,0f,0f, Color.BLACK)
} }
private var preview: Drawable? = null private var preview: Drawable? = null
@ -75,21 +86,23 @@ class WidgetOverlayView : View {
} }
} }
val bounds = getBounds() val bounds = getBounds()
canvas.drawRoundRect(bounds.toRectF(), 5f, 5f, paint) canvas.drawRoundRect(bounds.toRectF(), 5f, 5f, paint)
if (mode == null) { if (mode == null) {
return return
} }
//preview?.bounds = bounds //preview?.bounds = bounds
//preview?.draw(canvas) //preview?.draw(canvas)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
popupAnchor.layout(0,0,0,0)
} }
fun showPopupMenu() { fun showPopupMenu() {
val widget = Widget.byId(context, widgetId)?: return val widget = Widget.byId(context, widgetId)?: return
val menu = PopupMenu(context, this) val menu = PopupMenu(context, popupAnchor)
menu.menu.let { menu.menu.let {
it.add( it.add(
context.getString(R.string.widget_menu_remove) context.getString(R.string.widget_menu_remove)

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,5 +1,6 @@
<?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" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main" android:id="@+id/main"
@ -7,6 +8,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context=".ui.widgets.manage.ManageWidgetsActivity"> tools:context=".ui.widgets.manage.ManageWidgetsActivity">
<de.jrpie.android.launcher.ui.widgets.manage.WidgetManagerView <de.jrpie.android.launcher.ui.widgets.manage.WidgetManagerView
android:id="@+id/manage_widgets_container" android:id="@+id/manage_widgets_container"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -22,4 +24,5 @@
android:src="@drawable/baseline_add_24" android:src="@drawable/baseline_add_24"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

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!)