Skip to content

Commit 4fee3a2

Browse files
Feat: Add Widget Page feature
This commit introduces a new Widget Page feature, allowing users to add, resize, and manage app widgets. **Core Functionality:** - A new fragment `widgetFragment` and its corresponding layout `fragment_widget_container` have been added. - `ResizableWidgetWrapper` class has been created to handle widget resizing, dragging, and context menu operations (Resize, Delete, Open, View in Store). - Widgets are placed on a grid defined by `GRID_COLUMNS` and `CELL_MARGIN`. - Widget state (ID, position, size, cell span) is persisted in a `widgets.json` file using Moshi. **MainActivity Changes:** - Added `widgetPermissionLauncher` to handle widget bind and configuration intents. - Introduced `launchWidgetPermission`, `safeCreateWidget`, and `flushPendingWidgets` to manage the widget creation lifecycle, especially when `WidgetFragment` might not be immediately available. **WidgetFragment Logic:** - Initializes `AppWidgetManager` and `AppWidgetHost`. - Handles widget addition via a custom picker that groups widgets by app. - Manages the lifecycle of widgets: allocation, binding, configuration (if needed), and creation of `ResizableWidgetWrapper`. - Provides functionality to reset all widgets. - Saves and restores widget configurations to/from `widgets.json`. - Includes logic to find empty slots in the grid for new widgets. - Overrides `onBackPressed` to handle exiting resize mode for widgets. **UI and Resources:** - New string resources for widget-related actions and titles have been added to `strings.xml`. - `fragment_widget_container.xml` defines the `FrameLayout` (`widget_grid`) that hosts the widgets. - Resize handles and a grid overlay are displayed when a widget is in resize mode.
1 parent 421881b commit 4fee3a2

File tree

5 files changed

+1289
-0
lines changed

5 files changed

+1289
-0
lines changed

app/src/main/java/com/github/droidworksstudio/mlauncher/MainActivity.kt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package com.github.droidworksstudio.mlauncher
22

33
import android.app.Activity
44
import android.app.role.RoleManager
5+
import android.appwidget.AppWidgetManager
6+
import android.appwidget.AppWidgetProviderInfo
57
import android.content.Context
68
import android.content.Intent
79
import android.content.pm.ActivityInfo
@@ -21,6 +23,7 @@ import androidx.core.view.WindowCompat
2123
import androidx.lifecycle.ViewModelProvider
2224
import androidx.navigation.NavController
2325
import androidx.navigation.findNavController
26+
import com.github.droidworksstudio.common.AppLogger
2427
import com.github.droidworksstudio.common.CrashHandler
2528
import com.github.droidworksstudio.common.showLongToast
2629
import com.github.droidworksstudio.mlauncher.data.Constants
@@ -33,6 +36,7 @@ import com.github.droidworksstudio.mlauncher.helper.emptyString
3336
import com.github.droidworksstudio.mlauncher.helper.ismlauncherDefault
3437
import com.github.droidworksstudio.mlauncher.helper.utils.AppReloader
3538
import com.github.droidworksstudio.mlauncher.helper.utils.SystemBarObserver
39+
import com.github.droidworksstudio.mlauncher.ui.WidgetFragment
3640
import com.github.droidworksstudio.mlauncher.ui.onboarding.OnboardingActivity
3741
import com.squareup.moshi.Moshi
3842
import com.squareup.moshi.Types
@@ -49,6 +53,10 @@ import java.util.concurrent.Executors
4953

5054
class MainActivity : AppCompatActivity() {
5155

56+
companion object {
57+
private const val TAG = "BaseWidgets"
58+
}
59+
5260
private lateinit var prefs: Prefs
5361
private lateinit var migration: Migration
5462
private lateinit var navController: NavController
@@ -116,9 +124,24 @@ class MainActivity : AppCompatActivity() {
116124
}
117125
}
118126

127+
private lateinit var widgetPermissionLauncher: ActivityResultLauncher<Intent>
128+
private var widgetResultCallback: ((Int, Int, Intent?) -> Unit)? = null
129+
private val pendingWidgets = mutableListOf<Pair<AppWidgetProviderInfo, Int>>()
130+
119131
override fun onCreate(savedInstanceState: Bundle?) {
120132
super.onCreate(savedInstanceState)
121133

134+
widgetPermissionLauncher =
135+
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
136+
AppLogger.v(TAG, "🎬 ActivityResultLauncher invoked. ResultCode=${result.resultCode}, data=${result.data != null}")
137+
138+
widgetResultCallback?.invoke(
139+
result.resultCode,
140+
result.data?.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) ?: -1,
141+
result.data
142+
)
143+
}
144+
122145
// Enables edge-to-edge mode
123146
WindowCompat.setDecorFitsSystemWindows(window, false)
124147

@@ -319,6 +342,40 @@ class MainActivity : AppCompatActivity() {
319342
lifecycle.addObserver(systemBarObserver)
320343
}
321344

345+
/** Called by a Fragment to launch a widget intent */
346+
/** Widget permission helper */
347+
fun launchWidgetPermission(intent: Intent, callback: (resultCode: Int, appWidgetId: Int, data: Intent?) -> Unit) {
348+
widgetResultCallback = callback
349+
widgetPermissionLauncher.launch(intent)
350+
}
351+
352+
/** Safely create widget or store pending */
353+
fun safeCreateWidget(widgetInfo: AppWidgetProviderInfo, appWidgetId: Int) {
354+
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
355+
val fragment = navHostFragment?.childFragmentManager?.fragments
356+
?.filterIsInstance<WidgetFragment>()
357+
?.firstOrNull()
358+
if (fragment != null && fragment.isAdded) {
359+
fragment.createWidgetWrapperSafe(widgetInfo, appWidgetId)
360+
AppLogger.i(TAG, "✅ WidgetFragment ready, creating widget wrapper for widgetId=$appWidgetId")
361+
} else {
362+
pendingWidgets.add(widgetInfo to appWidgetId)
363+
AppLogger.w(TAG, "⚠️ WidgetFragment not attached, storing pending widget id=$appWidgetId")
364+
}
365+
}
366+
367+
fun flushPendingWidgets() {
368+
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
369+
val fragment = navHostFragment?.childFragmentManager?.fragments
370+
?.filterIsInstance<WidgetFragment>()
371+
?.firstOrNull()
372+
pendingWidgets.forEach { (info, id) ->
373+
AppLogger.i(TAG, "➡ Flushing pending widgetId=$fragment")
374+
fragment?.createWidgetWrapperSafe(info, id)
375+
}
376+
}
377+
378+
322379
private fun handleFontSelected(uri: Uri?) {
323380
if (uri == null) return
324381

0 commit comments

Comments
 (0)