diff --git a/lib/main.dart b/lib/main.dart index 51d7952..93705a4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -55,7 +55,9 @@ class AppInfo { final String name; final String packageName; final List tags; - DateTime? lastUsed; // New field to track last used time + DateTime? lastUsed; + bool isPinned; + bool isDemoted; AppInfo({ this.icon, @@ -63,6 +65,8 @@ class AppInfo { required this.packageName, required this.tags, this.lastUsed, + this.isPinned = false, + this.isDemoted = false, }); } @@ -114,6 +118,9 @@ class _AppLauncherScreenState extends State with WidgetsBindi List loadedApps = installedApps.map((app) { final lastUsedMillis = SharedPreferencesService().getInt("lastUsed_${app.packageName}"); final storedTags = SharedPreferencesService().getString("tags_${app.packageName}"); + final isPinned = SharedPreferencesService().getBool("isPinned_${app.packageName}", false); + final isDemoted = SharedPreferencesService().getBool("isDemoted_${app.packageName}", false); + List tags = []; if (storedTags != null && storedTags.trim().isNotEmpty) { @@ -131,6 +138,8 @@ class _AppLauncherScreenState extends State with WidgetsBindi packageName: app.packageName, tags: tags, lastUsed: lastUsedMillis != null ? DateTime.fromMillisecondsSinceEpoch(lastUsedMillis) : null, + isPinned: isPinned, + isDemoted: isDemoted, ); }).toList(); @@ -151,15 +160,21 @@ class _AppLauncherScreenState extends State with WidgetsBindi return matchesName || matchesTag; }).toList(); - if (context.read().sortLastUsed) { - filteredApps.sort((a, b) { + // Sort pinned apps first, then by last used or alphabetically, then demoted apps + filteredApps.sort((a, b) { + if (a.isPinned && !b.isPinned) return -1; + if (!a.isPinned && b.isPinned) return 1; + if (a.isDemoted && !b.isDemoted) return 1; + if (!a.isDemoted && b.isDemoted) return -1; + + if (context.read().sortLastUsed) { DateTime aTime = a.lastUsed ?? DateTime.fromMillisecondsSinceEpoch(0); DateTime bTime = b.lastUsed ?? DateTime.fromMillisecondsSinceEpoch(0); return bTime.compareTo(aTime); - }); - } else { - filteredApps.sort((a, b) => a.name.compareTo(b.name)); - } + } else { + return a.name.compareTo(b.name); + } + }); }); } @@ -197,6 +212,57 @@ class _AppLauncherScreenState extends State with WidgetsBindi } } + void _showContextMenu(BuildContext context, AppInfo app) { + showModalBottomSheet( + context: context, + builder: (context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: Icon(app.isPinned ? Icons.push_pin : Icons.push_pin_outlined), + title: Text(app.isPinned ? 'Unpin' : 'Pin'), + onTap: () async { + Navigator.pop(context); // Close the menu + await _togglePin(app); + }, + ), + ListTile( + leading: Icon(app.isDemoted ? Icons.arrow_upward : Icons.arrow_downward), + title: Text(app.isDemoted ? 'Reset' : 'Demote'), + onTap: () async { + Navigator.pop(context); // Close the menu + await _toggleDemote(app); + }, + ), + ], + ); + }, + ); + } + + Future _togglePin(AppInfo app) async { + setState(() { + app.isPinned = !app.isPinned; + if (app.isPinned) { + app.isDemoted = false; // Cannot be pinned and demoted at the same time + } + }); + await SharedPreferencesService().setBool("isPinned_${app.packageName}", app.isPinned); + _filterApps(); + } + + Future _toggleDemote(AppInfo app) async { + setState(() { + app.isDemoted = !app.isDemoted; + if (app.isDemoted) { + app.isPinned = false; // Cannot be pinned and demoted at the same time + } + }); + await SharedPreferencesService().setBool("isDemoted_${app.packageName}", app.isDemoted); + _filterApps(); + } + void _editAppTags(AppInfo app) { TextEditingController tagController = TextEditingController(text: app.tags.join(" ")); showDialog( @@ -303,6 +369,15 @@ class _AppLauncherScreenState extends State with WidgetsBindi ); } + Widget _buildTrailingIcon(AppInfo app) { + if (app.isPinned) { + return Icon(Icons.push_pin, color: Colors.grey); // Pin icon for pinned apps + } else if (app.isDemoted) { + return Icon(Icons.disabled_by_default_outlined, color: Colors.grey); // Demote icon for demoted apps + } + return SizedBox.shrink(); // No icon for normal apps + } + @override Widget build(BuildContext context) { return Scaffold( @@ -452,11 +527,13 @@ class _AppLauncherScreenState extends State with WidgetsBindi overflow: TextOverflow.ellipsis, ) : null, + trailing: _buildTrailingIcon(app), // Add trailing icon onTap: () => _launchApp(app), + onLongPress: () => _showContextMenu(context, app), ), ); }, - ), + ) ); } }