Skip to content

Commit d635fb8

Browse files
feat: Add placeholder for empty widget screen and improve widget management
This commit introduces a placeholder text view that is displayed when no widgets are added to the widget screen. It also includes several improvements to widget management: - The active dialog is now dismissed when a widget is being dragged. - The empty placeholder is added to the widget grid after resetting all widgets. - The empty placeholder visibility is updated when widgets are added or removed. - Logging has been added for better debugging and monitoring of widget states and actions. A new string resource `widgets_not_added` has been added for the placeholder text. The `WidgetFragment` now initializes and manages the `emptyPlaceholder` TextView. The `ResizableWidgetWrapper` now dismisses any active dialog during a drag operation.
1 parent 4979604 commit d635fb8

File tree

3 files changed

+55
-22
lines changed

3 files changed

+55
-22
lines changed

app/src/main/java/com/github/droidworksstudio/mlauncher/ui/WidgetFragment.kt

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ data class AppWidgetGroup(
5050
class WidgetFragment : BaseFragment() {
5151

5252
private lateinit var widgetGrid: FrameLayout
53+
private lateinit var emptyPlaceholder: TextView
5354
lateinit var appWidgetManager: AppWidgetManager
5455
lateinit var appWidgetHost: AppWidgetHost
5556
private val widgetWrappers = mutableListOf<ResizableWidgetWrapper>()
@@ -81,26 +82,44 @@ class WidgetFragment : BaseFragment() {
8182
super.onViewCreated(view, savedInstanceState)
8283

8384
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
84-
// Check if any widget is in resize mode
8585
val resizeWidget = widgetWrappers.firstOrNull { it.isResizeMode }
86-
8786
if (resizeWidget != null) {
88-
// Disable resize mode instead of going back
87+
AppLogger.i(TAG, "🔄 Exiting resize mode for widgetId=${resizeWidget.hostView.appWidgetId}")
8988
resizeWidget.isResizeMode = false
9089
resizeWidget.setHandlesVisible(false)
9190
resizeWidget.reloadParentFragment()
9291
} else {
93-
// No widget in resize mode, proceed with normal back
9492
isEnabled = false
9593
requireActivity().onBackPressed()
9694
}
9795
}
9896

9997
widgetGrid = view.findViewById(R.id.widget_grid)
98+
AppLogger.i(TAG, "🟢 Widget grid initialized")
99+
// Create placeholder programmatically
100+
AppLogger.i(TAG, "🟢 Creating empty placeholder for widgets")
101+
emptyPlaceholder = TextView(requireContext()).apply {
102+
text = context.getString(R.string.widgets_not_added)
103+
textSize = 16f
104+
setTextColor(resources.getColor(android.R.color.darker_gray, null))
105+
gravity = Gravity.CENTER
106+
visibility = View.GONE
107+
}
108+
AppLogger.i(TAG, "🟢 Empty placeholder TextView created")
109+
110+
widgetGrid.addView(
111+
emptyPlaceholder, FrameLayout.LayoutParams(
112+
FrameLayout.LayoutParams.WRAP_CONTENT,
113+
FrameLayout.LayoutParams.WRAP_CONTENT,
114+
Gravity.CENTER
115+
)
116+
)
117+
AppLogger.i(TAG, "🟢 Empty placeholder added to widgetGrid")
100118

101119
appWidgetManager = AppWidgetManager.getInstance(requireContext())
102120
appWidgetHost = AppWidgetHost(requireContext(), APP_WIDGET_HOST_ID)
103121
appWidgetHost.startListening()
122+
AppLogger.i(TAG, "🟢 AppWidgetHost started listening")
104123

105124
widgetGrid.setOnLongClickListener {
106125
showGridMenu()
@@ -110,21 +129,21 @@ class WidgetFragment : BaseFragment() {
110129
restoreWidgets()
111130

112131
AppLogger.i(TAG, "🟢 WidgetFragment onViewCreated, view is ready")
113-
114-
// Notify MainActivity to flush pending widgets
115132
(activity as? MainActivity)?.flushPendingWidgets()
116133
}
117134

118135
override fun onResume() {
119136
super.onResume()
120137
restoreWidgets()
138+
AppLogger.i(TAG, "🔄 WidgetFragment onResume, widgets restored")
121139
}
122140

123141
/** Grid menu for adding/resetting widgets */
124142
private fun showGridMenu() {
125143
activeGridDialog?.dismiss()
126144
val bottomSheetDialog = LockedBottomSheetDialog(requireContext())
127145
activeGridDialog = bottomSheetDialog
146+
AppLogger.d(TAG, "🎛️ Showing widget grid menu")
128147

129148
val container = LinearLayout(requireContext()).apply {
130149
orientation = LinearLayout.VERTICAL
@@ -149,16 +168,19 @@ class WidgetFragment : BaseFragment() {
149168
}
150169

151170
private fun resetAllWidgets() {
171+
AppLogger.w(TAG, "🧹 Resetting all widgets")
152172
widgetWrappers.forEach { wrapper ->
173+
AppLogger.d(TAG, "❌ Deleting widgetId=${wrapper.hostView.appWidgetId}")
153174
appWidgetHost.deleteAppWidgetId(wrapper.hostView.appWidgetId)
154175
}
155176
widgetWrappers.clear()
156177
widgetGrid.removeAllViews()
178+
widgetGrid.addView(emptyPlaceholder)
157179
saveWidgets()
158-
AppLogger.i(TAG, "🧹 Reset all widgets, cleared grid")
180+
updateEmptyPlaceholder(widgetWrappers)
181+
AppLogger.i(TAG, "🧹 All widgets cleared and placeholder shown")
159182
}
160183

161-
/** Shows the system widget picker */
162184
private fun showCustomWidgetPicker() {
163185
val widgets = appWidgetManager.installedProviders
164186
val pm = requireContext().packageManager
@@ -174,6 +196,8 @@ class WidgetFragment : BaseFragment() {
174196
AppWidgetGroup(appName, appIcon, widgetList.toMutableList())
175197
}.sortedBy { it.appName.lowercase() }
176198

199+
AppLogger.d(TAG, "🧩 Showing custom widget picker with ${grouped.size} apps")
200+
177201
val container = LinearLayout(requireContext()).apply { orientation = LinearLayout.VERTICAL; setPadding(16, 16, 16, 16) }
178202
val scrollView = ScrollView(requireContext()).apply { addView(container) }
179203

@@ -212,6 +236,7 @@ class WidgetFragment : BaseFragment() {
212236
textSize = 14f
213237
setPadding(32, 12, 12, 12)
214238
setOnClickListener {
239+
AppLogger.i(TAG, "➕ Selected widget ${widgetInfo.label} to add")
215240
addWidget(widgetInfo)
216241
bottomSheetDialog.dismiss()
217242
}
@@ -234,7 +259,6 @@ class WidgetFragment : BaseFragment() {
234259
}
235260
}
236261

237-
/** Add a widget and handle permissions/configure activities */
238262
private fun addWidget(widgetInfo: AppWidgetProviderInfo) {
239263
lastWidgetInfo = widgetInfo
240264
val widgetId = appWidgetHost.allocateAppWidgetId()
@@ -266,8 +290,7 @@ class WidgetFragment : BaseFragment() {
266290

267291
if (!bound) return
268292

269-
// 🔽 Map widget’s min size to grid span
270-
val parentFrame = requireActivity().findViewById<ViewGroup>(R.id.widget_grid) // adjust ID
293+
val parentFrame = requireActivity().findViewById<ViewGroup>(R.id.widget_grid)
271294
val gridWidth = parentFrame.width
272295
val cellSize = (gridWidth - (CELL_MARGIN * (GRID_COLUMNS - 1))) / GRID_COLUMNS
273296

@@ -276,25 +299,20 @@ class WidgetFragment : BaseFragment() {
276299

277300
AppLogger.d(TAG, "📐 Widget ${widgetInfo.provider} requires ${spanX}x$spanY cells")
278301

279-
// 🔍 Get current occupancy
280302
val occupied = getOccupiedGrid()
281303
val maxRows = occupied.size.coerceAtLeast(1)
282304

283-
// 🔍 Find first free block that fits
284305
var foundRow = -1
285306
var foundCol = -1
286307

287-
outer@ for (row in 0 until maxRows + 10) { // allow new rows
308+
outer@ for (row in 0 until maxRows + 10) {
288309
for (col in 0 until GRID_COLUMNS) {
289310
var fits = true
290311
for (dy in 0 until spanY) {
291312
for (dx in 0 until spanX) {
292313
val r = row + dy
293314
val c = col + dx
294-
if (c >= GRID_COLUMNS) {
295-
fits = false; break
296-
}
297-
if (r < occupied.size && occupied[r][c]) {
315+
if (c >= GRID_COLUMNS || (r < occupied.size && occupied[r][c])) {
298316
fits = false; break
299317
}
300318
}
@@ -314,11 +332,7 @@ class WidgetFragment : BaseFragment() {
314332
}
315333

316334
AppLogger.i(TAG, "📦 Placing widget ${widgetInfo.provider} at row=$foundRow col=$foundCol span=${spanX}x$spanY")
317-
318-
// Save placement (so it’s included in JSON next time)
319335
saveWidgetPlacement(widgetId, foundRow, foundCol, spanX, spanY, parentFrame)
320-
321-
// Create widget instance
322336
maybeConfigureOrCreate(widgetInfo, widgetId)
323337
}
324338

@@ -486,6 +500,7 @@ class WidgetFragment : BaseFragment() {
486500

487501
addWrapperToGrid(wrapper)
488502
AppLogger.i(TAG, "✅ Wrapper created for widgetId=$appWidgetId")
503+
updateEmptyPlaceholder(widgetWrappers)
489504
saveWidgets()
490505
}
491506

@@ -698,6 +713,22 @@ class WidgetFragment : BaseFragment() {
698713
AppLogger.i(TAG, "📐 Grid Snapshot:\n$snapshot")
699714
}
700715

716+
private fun updateEmptyPlaceholder(wrappers: List<ResizableWidgetWrapper>) {
717+
val wasVisible = emptyPlaceholder.isVisible
718+
val shouldBeVisible = wrappers.isEmpty()
719+
720+
emptyPlaceholder.isVisible = shouldBeVisible
721+
722+
if (shouldBeVisible && !wasVisible) {
723+
AppLogger.i(TAG, "🟨 No widgets present, showing placeholder")
724+
} else if (!shouldBeVisible && wasVisible) {
725+
AppLogger.i(TAG, "🟩 Widgets present, hiding placeholder")
726+
} else {
727+
AppLogger.v(TAG, "🔄 Placeholder visibility unchanged: ${emptyPlaceholder.isVisible}")
728+
}
729+
}
730+
731+
701732
override fun onAttach(context: Context) {
702733
super.onAttach(context)
703734
AppLogger.i(TAG, "🔗 WidgetFragment onAttach called, context=$context")

app/src/main/java/com/github/droidworksstudio/mlauncher/ui/widgets/ResizableWidgetWrapper.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ class ResizableWidgetWrapper(
282282
lastY = event.rawY
283283

284284
updateGhostPosition()
285+
activeDialog?.dismiss()
285286
}
286287

287288
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@
408408
<string name="widgets_select_widget">Select a Widget</string>
409409
<string name="widgets_add_widget">Add a Widget</string>
410410
<string name="widgets_reset_widget">Reset all Widgets</string>
411+
<string name="widgets_not_added">No widgets added yet. Long press to add one.</string>
411412

412413
<!-- Private Spaces Strings -->
413414
<string name="alert_requires_android_v">This functionality requires Android 15 or later.</string>

0 commit comments

Comments
 (0)