diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b6ffe5bd..ec226a5c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -28,6 +28,10 @@ android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" /> + diff --git a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/TaskWarriorWidgetProvider.kt b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/TaskWarriorWidgetProvider.kt index d57d4cb0..5eeb70ec 100644 --- a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/TaskWarriorWidgetProvider.kt +++ b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/TaskWarriorWidgetProvider.kt @@ -1,239 +1,237 @@ package com.ccextractor.taskwarriorflutter + import android.annotation.TargetApi +import android.app.PendingIntent import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider import android.content.Context +import android.content.Intent import android.net.Uri +import android.os.Build import android.widget.RemoteViews -import es.antonborri.home_widget.HomeWidgetBackgroundIntent +import android.widget.RemoteViewsService import es.antonborri.home_widget.HomeWidgetLaunchIntent -import es.antonborri.home_widget.HomeWidgetProvider import es.antonborri.home_widget.HomeWidgetPlugin +import org.json.JSONArray as OrgJSONArray import org.json.JSONException -import android.content.Intent -import android.widget.RemoteViewsService import org.json.JSONObject -import org.json.JSONArray as OrgJSONArray -import android.os.Bundle -import android.app.PendingIntent -import android.appwidget.AppWidgetProvider -import android.os.Build - @TargetApi(Build.VERSION_CODES.CUPCAKE) class TaskWarriorWidgetProvider : AppWidgetProvider() { - override fun onReceive(context: Context, intent: Intent) { - // Handle the custom action from your Widget buttons/list - if (intent.action == "TASK_ACTION") { - val uuid = intent.getStringExtra("uuid") ?: "" - val launchedFor = intent.getStringExtra("launchedFor") - - // 1. Construct the URI exactly as Flutter expects it - // Scheme: taskwarrior:// - // Host: cardclicked OR addclicked - val deepLinkUri = if (launchedFor == "ADD_TASK") { - Uri.parse("taskwarrior://addclicked") - } else { - // For list items, we attach the UUID - Uri.parse("taskwarrior://cardclicked?uuid=$uuid") - } - - // 2. Create the Intent to open MainActivity - val launchIntent = Intent(context, MainActivity::class.java).apply { - action = Intent.ACTION_VIEW - data = deepLinkUri - // These flags ensure the app opens correctly whether running or not - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP - } - - context.startActivity(launchIntent) - } - super.onReceive(context, intent) - } - fun getLayoutId(context: Context) : Int{ - val sharedPrefs = HomeWidgetPlugin.getData(context) - val theme = sharedPrefs.getString("themeMode", "") - val layoutId = if (theme.equals("dark")) { - R.layout.taskwarrior_layout_dark // Define a dark mode layout in your resources - } else { - R.layout.taskwarrior_layout - } - return layoutId - } -@TargetApi(Build.VERSION_CODES.DONUT) -override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { - appWidgetIds.forEach { widgetId -> - // 1. Get the latest data from HomeWidget/SharedPrefs + fun getLayoutId(context: Context): Int { val sharedPrefs = HomeWidgetPlugin.getData(context) - val tasks = sharedPrefs.getString("tasks", "") - - // 2. Create the Intent for the ListView service - // We add the widgetId to the data URI to make it unique, preventing caching issues - val intent = Intent(context, ListViewRemoteViewsService::class.java).apply { - putExtra("tasksJsonString", tasks) - data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME) + widgetId) - } - - // 3. Initialize RemoteViews with the THEMED layout (getLayoutId handles dark/light logic) - val views = RemoteViews(context.packageName, getLayoutId(context)).apply { - - // Set up the Logo click (Open App) - val pendingIntent: PendingIntent = HomeWidgetLaunchIntent.getActivity( - context, - MainActivity::class.java - ) - setOnClickPendingIntent(R.id.logo, pendingIntent) - - // Set up the Add Button click (Custom Action) - val intent_for_add = Intent(context, TaskWarriorWidgetProvider::class.java).apply { - action = "TASK_ACTION" - putExtra("launchedFor", "ADD_TASK") - // Unique data to ensure the broadcast is fresh - data = Uri.parse("taskwarrior://addtask/$widgetId") - } - - val pendingIntentAdd: PendingIntent = PendingIntent.getBroadcast( - context, - widgetId, - intent_for_add, - PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) - setOnClickPendingIntent(R.id.add_btn, pendingIntentAdd) - - // Attach the adapter to the ListView - setRemoteAdapter(R.id.list_view, intent) - } - - // 4. Set up the Click Template for List Items (Deep Linking) - val clickPendingIntent: PendingIntent = Intent( - context, - TaskWarriorWidgetProvider::class.java - ).run { - action = "TASK_ACTION" - // Important: Use widgetId as requestCode to keep it unique - PendingIntent.getBroadcast( - context, - widgetId, - this, - PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) + val theme = sharedPrefs.getString("themeMode", "") + val layoutId = + if (theme.equals("dark")) { + R.layout.taskwarrior_layout_dark // Define a dark mode layout in your resources + } else { + R.layout.taskwarrior_layout + } + return layoutId + } + @TargetApi(Build.VERSION_CODES.DONUT) + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray + ) { + appWidgetIds.forEach { widgetId -> + // 1. Get the latest data from HomeWidget/SharedPrefs + val sharedPrefs = HomeWidgetPlugin.getData(context) + val tasks = sharedPrefs.getString("tasks", "") + + // 2. Create the Intent for the ListView service + // We add the widgetId to the data URI to make it unique, preventing caching issues + val intent = + Intent(context, ListViewRemoteViewsService::class.java).apply { + putExtra("tasksJsonString", tasks) + data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME) + widgetId) + } + + // 3. Initialize RemoteViews with the THEMED layout (getLayoutId handles dark/light + // logic) + val views = + RemoteViews(context.packageName, getLayoutId(context)).apply { + + // Set up the Logo click (Open App) + val pendingIntent: PendingIntent = + HomeWidgetLaunchIntent.getActivity( + context, + MainActivity::class.java + ) + setOnClickPendingIntent(R.id.logo, pendingIntent) + + // Set up the Add Button click (Direct to MainActivity) + val intentForAdd = + Intent(context, MainActivity::class.java).apply { + action = Intent.ACTION_VIEW + data = Uri.parse("taskwarrior://addclicked") + flags = + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TOP or + Intent.FLAG_ACTIVITY_SINGLE_TOP + } + + val pendingIntentAdd: PendingIntent = + PendingIntent.getActivity( + context, + widgetId, + intentForAdd, + PendingIntent.FLAG_IMMUTABLE or + PendingIntent.FLAG_UPDATE_CURRENT + ) + setOnClickPendingIntent(R.id.add_btn, pendingIntentAdd) + + // Attach the adapter to the ListView + setRemoteAdapter(R.id.list_view, intent) + } + + // 4. Set up the Click Template for List Items (Deep Linking) + val clickIntentTemplate = + Intent(context, MainActivity::class.java).apply { + action = Intent.ACTION_VIEW + flags = + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TOP or + Intent.FLAG_ACTIVITY_SINGLE_TOP + } + val clickPendingIntentTemplate: PendingIntent = + PendingIntent.getActivity( + context, + widgetId, + clickIntentTemplate, + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + views.setPendingIntentTemplate(R.id.list_view, clickPendingIntentTemplate) + + // 5. THE THEME FIX: Notify the manager that the list data/layout needs a refresh + appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.list_view) + + // 6. Push the update to the widget + appWidgetManager.updateAppWidget(widgetId, views) } - views.setPendingIntentTemplate(R.id.list_view, clickPendingIntent) - - // 5. THE THEME FIX: Notify the manager that the list data/layout needs a refresh - appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.list_view) - - // 6. Push the update to the widget - appWidgetManager.updateAppWidget(widgetId, views) + super.onUpdate(context, appWidgetManager, appWidgetIds) } - super.onUpdate(context, appWidgetManager, appWidgetIds) -} } +} + class ListViewRemoteViewsFactory( - private val context: Context, - private val tasksJsonString: String? + private val context: Context, + private val tasksJsonString: String? ) : RemoteViewsService.RemoteViewsFactory { private val tasks = mutableListOf() - override fun onCreate() {} - - override fun onDataSetChanged() { - tasks.clear() // Add this! - val sharedPrefs = HomeWidgetPlugin.getData(context) - val latestTasksJson = sharedPrefs.getString("tasks", "") - - if (!latestTasksJson.isNullOrEmpty()) { - try { - val jsonArray = OrgJSONArray(latestTasksJson) - for (i in 0 until jsonArray.length()) { - tasks.add(Task.fromJson(jsonArray.getJSONObject(i))) - } - } catch (e: JSONException) { - e.printStackTrace() - } - } - } - - override fun onDestroy() {} + override fun onCreate() = Unit + + override fun onDataSetChanged() { + val newTasks = mutableListOf() + val sharedPrefs = HomeWidgetPlugin.getData(context) + val latestTasksJson = sharedPrefs.getString("tasks", "") + + if (!latestTasksJson.isNullOrEmpty()) { + try { + val jsonArray = OrgJSONArray(latestTasksJson) + for (i in 0 until jsonArray.length()) { + newTasks.add(Task.fromJson(jsonArray.getJSONObject(i))) + } + } catch (e: JSONException) { + e.printStackTrace() + } + } + // Atomic swap + tasks.clear() + tasks.addAll(newTasks) + } + + override fun onDestroy() = Unit override fun getCount(): Int = tasks.size - fun getListItemLayoutId(): Int{ - val sharedPrefs = HomeWidgetPlugin.getData(context) - val theme = sharedPrefs.getString("themeMode", "") - val layoutId = if (theme.equals("dark")) { - R.layout.listitem_layout_dark // Define a dark mode layout in your resources - } else { - R.layout.listitem_layout - } - return layoutId - } - fun getListItemLayoutIdForR1(): Int{ - val sharedPrefs = HomeWidgetPlugin.getData(context) - val theme = sharedPrefs.getString("themeMode", "") - val layoutId = if (theme.equals("dark")) { - R.layout.no_tasks_found_li_dark // Define a dark mode layout in your resources - } else { - R.layout.no_tasks_found_li - } - return layoutId - } - fun getDotIdByPriority(p: String) : Int{ - println("PRIORITY: "+p) - if(p.equals("L")) return R.drawable.low_priority_dot - if(p.equals("M")) return R.drawable.mid_priority_dot - if(p.equals("H")) return R.drawable.high_priority_dot - return R.drawable.no_priority_dot - } + fun getListItemLayoutId(): Int { + val sharedPrefs = HomeWidgetPlugin.getData(context) + val theme = sharedPrefs.getString("themeMode", "") + val layoutId = + if (theme.equals("dark")) { + R.layout.listitem_layout_dark // Define a dark mode layout in your resources + } else { + R.layout.listitem_layout + } + return layoutId + } + fun getListItemLayoutIdForR1(): Int { + val sharedPrefs = HomeWidgetPlugin.getData(context) + val theme = sharedPrefs.getString("themeMode", "") + val layoutId = + if (theme.equals("dark")) { + R.layout.no_tasks_found_li_dark // Define a dark mode layout in your resources + } else { + R.layout.no_tasks_found_li + } + return layoutId + } + fun getDotIdByPriority(p: String): Int { + if (p.equals("L")) return R.drawable.low_priority_dot + if (p.equals("M")) return R.drawable.mid_priority_dot + if (p.equals("H")) return R.drawable.high_priority_dot + return R.drawable.no_priority_dot + } override fun getViewAt(position: Int): RemoteViews { + // Safe guard against Android out-of-bounds scrolling crashes + if (position !in tasks.indices) { + return RemoteViews(context.packageName, getListItemLayoutIdForR1()).apply { + setTextViewText(R.id.tv, "Loading...") + } + } + val task = tasks[position] - if(task.uuid.equals("NO_TASK")) - return RemoteViews(context.packageName, getListItemLayoutIdForR1()).apply { - if(task.priority.equals("1")) - setTextViewText(R.id.tv, "No tasks added yet") - if(task.priority.equals("2")) - setTextViewText(R.id.tv, "Filters applied are hiding all tasks") - } - return RemoteViews(context.packageName, getListItemLayoutId()).apply { - setTextViewText(R.id.todo__title, task.title) - setImageViewResource(R.id.dot, getDotIdByPriority(task.priority)) - val a = Intent().apply { - - Bundle().also { extras -> - extras.putString("action", "show_task") - extras.putString("uuid", tasks[position].uuid) - putExtras(extras) - } - - } - setOnClickFillInIntent(R.id.list_item_container,a) - } - + if (task.uuid.equals("NO_TASK")) + return RemoteViews(context.packageName, getListItemLayoutIdForR1()).apply { + if (task.priority.equals("1")) setTextViewText(R.id.tv, "No tasks added yet") + if (task.priority.equals("2")) + setTextViewText(R.id.tv, "Filters applied are hiding all tasks") + } + return RemoteViews(context.packageName, getListItemLayoutId()).apply { + setTextViewText(R.id.todo__title, task.title) + setImageViewResource(R.id.dot, getDotIdByPriority(task.priority)) + val fillInIntent = + Intent().apply { + data = Uri.parse("taskwarrior://cardclicked?uuid=${task.uuid}") + } + setOnClickFillInIntent(R.id.list_item_container, fillInIntent) + } } override fun getLoadingView(): RemoteViews? = null - override fun getViewTypeCount(): Int = 2 + override fun getViewTypeCount(): Int = 2 override fun getItemId(position: Int): Long = position.toLong() override fun hasStableIds(): Boolean = true } + class ListViewRemoteViewsService : RemoteViewsService() { - override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { - val tasksJsonString = intent.getStringExtra("tasksJsonString") - return ListViewRemoteViewsFactory(applicationContext, tasksJsonString) - } + override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { + val tasksJsonString = intent.getStringExtra("tasksJsonString") + return ListViewRemoteViewsFactory(applicationContext, tasksJsonString) + } +} + +data class Task( + val title: String, + val urgencyLevel: String, + val uuid: String, + val priority: String +) { + companion object { + fun fromJson(json: JSONObject): Task { + val title = json.optString("description", "") + val urgencyLevel = json.optString("urgency", "") + val uuid = json.optString("uuid", "") + val priority = json.optString("priority", "") + return Task(title, urgencyLevel, uuid, priority) + } + } } -data class Task(val title: String, val urgencyLevel: String,val uuid:String, val priority: String) { - companion object { - fun fromJson(json: JSONObject): Task { - val title = json.optString("description", "") - val urgencyLevel = json.optString("urgency", "") - val uuid = json.optString("uuid","") - val priority = json.optString("priority", "") - return Task(title, urgencyLevel, uuid, priority) - } - } -} \ No newline at end of file diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart index 053f2bcc..3cf342f8 100644 --- a/lib/app/modules/home/controllers/home_controller.dart +++ b/lib/app/modules/home/controllers/home_controller.dart @@ -12,8 +12,8 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:taskwarrior/app/models/filters.dart'; import 'package:taskwarrior/app/models/json/task.dart'; -import 'package:taskwarrior/app/models/storage.dart'; import 'package:taskwarrior/app/models/storage/client.dart'; +import 'package:taskwarrior/app/models/storage.dart'; import 'package:taskwarrior/app/models/tag_meta_data.dart'; import 'package:taskwarrior/app/modules/home/controllers/widget.controller.dart'; import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart'; @@ -70,6 +70,7 @@ class HomeController extends GetxController { @override void onInit() { + debugPrint("🚀 BOOT: HomeController.onInit()"); super.onInit(); storage = Storage( Directory( @@ -78,6 +79,7 @@ class HomeController extends GetxController { ); serverCertExists = RxBool(storage.guiPemFiles.serverCertExists()); addListenerToScrollController(); + _profileSet(); loadDelayTask(); initLanguageAndDarkMode(); @@ -127,9 +129,17 @@ class HomeController extends GetxController { @override void onReady() { super.onReady(); - if (Get.isRegistered()) { - Get.find().consumePendingActions(this); - } + // Replaced 50ms delay with a secure PostFrameCallback + WidgetsBinding.instance.addPostFrameCallback((_) { + if (isClosed) return; + + final deepLinkService = Get.find(); + if (deepLinkService.queuedUri != null) { + debugPrint( + "🚀 TRACE: HomeController.onReady() consuming deferred queue!"); + deepLinkService.consumePendingActions(this); + } + }); } Future> getUniqueProjects() async { @@ -577,8 +587,7 @@ class HomeController extends GetxController { await synchronize(context, false); } if (context.mounted) { - final tColors = - Theme.of(context).extension()!; + final tColors = Theme.of(context).extension()!; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( diff --git a/lib/app/modules/splash/controllers/splash_controller.dart b/lib/app/modules/splash/controllers/splash_controller.dart index 126aaebb..a126ef0e 100644 --- a/lib/app/modules/splash/controllers/splash_controller.dart +++ b/lib/app/modules/splash/controllers/splash_controller.dart @@ -13,6 +13,7 @@ import 'package:taskwarrior/app/routes/app_pages.dart'; import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart'; import 'package:taskwarrior/app/utils/taskfunctions/profiles.dart'; import 'package:taskwarrior/app/v3/models/task.dart'; +import 'package:taskwarrior/app/services/deep_link_service.dart'; class SplashController extends GetxController { late Rx baseDirectory = Directory('').obs; @@ -22,15 +23,29 @@ class SplashController extends GetxController { Profiles get _profiles => Profiles(baseDirectory.value); @override - void onInit() async { + void onInit() { + debugPrint("🚀 BOOT: SplashController.onInit()"); super.onInit(); + } + + @override + void onReady() async { + super.onReady(); + + await initBaseDir(); + _checkProfiles(); + profilesMap.value = _profiles.profilesMap(); + currentProfile.value = _profiles.getCurrentProfile()!; + + final deepLinkService = Get.find(); + if (deepLinkService.queuedUri != null) { + debugPrint("🚀 TRACE: Bypassing Splash routing for queued URI"); + Get.offNamed(Routes.HOME); + return; + } + await checkForUpdate(); - initBaseDir().then((_) { - _checkProfiles(); - profilesMap.value = _profiles.profilesMap(); - currentProfile.value = _profiles.getCurrentProfile()!; - sendToNextPage(); - }); + sendToNextPage(); } Future initBaseDir() async { diff --git a/lib/app/services/deep_link_service.dart b/lib/app/services/deep_link_service.dart index f016c93c..c80df04c 100644 --- a/lib/app/services/deep_link_service.dart +++ b/lib/app/services/deep_link_service.dart @@ -1,3 +1,4 @@ +import 'dart:async'; // Add this import at the top import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:app_links/app_links.dart'; @@ -7,35 +8,54 @@ import 'package:taskwarrior/app/routes/app_pages.dart'; class DeepLinkService extends GetxService { late AppLinks _appLinks; - Uri? _queuedUri; + String? _queuedUri; // Made private + String? get queuedUri => _queuedUri; // Added getter + StreamSubscription? _linkSubscription; // Added stream subscription - @override - void onReady() { - super.onReady(); - _initDeepLinks(); - } - - void _initDeepLinks() { + Future init() async { _appLinks = AppLinks(); - _appLinks.uriLinkStream.listen((uri) { + + try { + final initialUri = await _appLinks.getInitialLink(); + if (initialUri != null) { + _queuedUri = initialUri.toString(); + debugPrint('🔗 INITIAL LINK QUEUED: $_queuedUri'); + } + } catch (e) { + debugPrint('Deep link init error: $e'); + } + + _linkSubscription = _appLinks.uriLinkStream.listen((uri) { debugPrint('🔗 LINK RECEIVED: $uri'); _handleWidgetUri(uri); + }, onError: (err) { + debugPrint('🔗 LINK STREAM ERROR: $err'); }); } + @override + void onClose() { + _linkSubscription?.cancel(); + super.onClose(); + } + void _handleWidgetUri(Uri uri) { if (Get.isRegistered()) { _executeAction(uri, Get.find()); } else { debugPrint("⏳ HomeController not ready. Queuing action."); - _queuedUri = uri; + _queuedUri = uri.toString(); } } void consumePendingActions(HomeController controller) { if (_queuedUri != null) { debugPrint("🚀 Executing queued action..."); - _executeAction(_queuedUri!, controller); + try { + _executeAction(Uri.parse(_queuedUri!), controller); + } catch (e) { + debugPrint("🔗 FAILED TO PARSE URI: $_queuedUri - Error: $e"); + } _queuedUri = null; } } @@ -54,15 +74,17 @@ class DeepLinkService extends GetxService { } } else if (uri.host == "addclicked") { if (Get.context != null) { - Get.dialog( - Material( - child: AddTaskBottomSheet( - homeController: controller, - forTaskC: isTaskChampion, - forReplica: isReplica, + WidgetsBinding.instance.addPostFrameCallback((_) { + Get.dialog( + Material( + child: AddTaskBottomSheet( + homeController: controller, + forTaskC: isTaskChampion, + forReplica: isReplica, + ), ), - ), - ); + ); + }); } } } diff --git a/lib/main.dart b/lib/main.dart index 09f0268c..16812cf3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,8 +3,6 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -// 1. Add this import -import 'package:app_links/app_links.dart'; import 'package:taskwarrior/app/services/deep_link_service.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; @@ -29,6 +27,9 @@ DynamicLibrary loadNativeLibrary() { } void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Move the logger override ABOVE the first boot print! debugPrint = (String? message, {int? wrapWidth}) { if (message != null) { debugPrintSynchronously(message, wrapWidth: wrapWidth); @@ -36,19 +37,32 @@ void main() async { } }; + debugPrint("🚀 BOOT: main() started"); + loadNativeLibrary(); await RustLib.init(); - WidgetsFlutterBinding.ensureInitialized(); await AppSettings.init(); - Get.put(DeepLinkService(), permanent: true); + // fix: Actually await the service initialization so the OS intent is caught BEFORE runApp. + await Get.putAsync(() async { + final service = DeepLinkService(); + await service.init(); + return service; + }, permanent: true); runApp( GetMaterialApp( darkTheme: darkTheme, theme: lightTheme, title: "Application", initialRoute: AppPages.INITIAL, + unknownRoute: AppPages.routes.firstWhere( + (page) => page.name == AppPages.INITIAL, + orElse: () { + debugPrint("⚠️ Unknown route requested, falling back to default"); + return AppPages.routes.first; + }, + ), getPages: AppPages.routes, themeMode: AppSettings.isDarkMode ? ThemeMode.dark : ThemeMode.light, ),