import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:device_apps/device_apps.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; import 'day.dart'; import 'night.dart'; void main() { runApp(MyApp()); } class AppSettings { bool showTags; bool showIcons; bool colorIcons; // true = color icons; false = monochrome icons bool darkTheme; bool sortAlphabetically; AppSettings({ this.showTags = true, this.showIcons = true, this.colorIcons = true, this.darkTheme = false, this.sortAlphabetically = false, }); } class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { AppSettings settings = AppSettings(); @override void initState() { super.initState(); _loadSettings(); } Future _loadSettings() async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { settings.showTags = prefs.getBool('showTags') ?? true; settings.showIcons = prefs.getBool('showIcons') ?? true; settings.colorIcons = prefs.getBool('colorIcons') ?? true; settings.darkTheme = prefs.getBool('darkTheme') ?? false; settings.sortAlphabetically = prefs.getBool('sortAlphabetically') ?? false; }); } Future _updateSetting(String key, bool value) async { SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setBool(key, value); } void _updateSettings(AppSettings newSettings) { setState(() { settings = newSettings; }); _updateSetting('showTags', settings.showTags); _updateSetting('showIcons', settings.showIcons); _updateSetting('colorIcons', settings.colorIcons); _updateSetting('darkTheme', settings.darkTheme); _updateSetting('sortAlphabetically', settings.sortAlphabetically); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Plasmoid Launcher', theme: ThemeData( brightness: Brightness.light, scaffoldBackgroundColor: Color(0xffF6F4F1), appBarTheme: AppBarTheme(backgroundColor: Color(0xffF6F4F1)), textTheme: GoogleFonts.ibmPlexMonoTextTheme(dayText), ), darkTheme: ThemeData( brightness: Brightness.dark, scaffoldBackgroundColor: Color(0xFF121212), appBarTheme: AppBarTheme(backgroundColor: Color(0xFF121212)), textTheme: GoogleFonts.ibmPlexMonoTextTheme(nightText), ), themeMode: settings.darkTheme ? ThemeMode.dark : ThemeMode.light, home: AppLauncherScreen( settings: settings, onSettingsChanged: _updateSettings, ), ); } } class AppInfo { final Uint8List? icon; final String name; final String packageName; final List tags; AppInfo({ this.icon, required this.name, required this.packageName, required this.tags, }); } class AppLauncherScreen extends StatefulWidget { final AppSettings settings; final Function(AppSettings) onSettingsChanged; AppLauncherScreen({required this.settings, required this.onSettingsChanged}); @override _AppLauncherScreenState createState() => _AppLauncherScreenState(); } class _AppLauncherScreenState extends State { final TextEditingController searchController = TextEditingController(); List apps = []; List filteredApps = []; bool isLoading = true; @override void initState() { super.initState(); _loadInstalledApps(); searchController.addListener(_filterApps); } Future _loadInstalledApps() async { List installedApps = await DeviceApps.getInstalledApplications( includeAppIcons: true, onlyAppsWithLaunchIntent: true, ); List loadedApps = installedApps.map((app) { return AppInfo( icon: app is ApplicationWithIcon ? app.icon : null, name: app.appName, packageName: app.packageName, tags: [], ); }).toList(); setState(() { apps = loadedApps; filteredApps = apps; isLoading = false; }); } void _filterApps() { String query = searchController.text.toLowerCase(); setState(() { filteredApps = apps.where((app) { bool matchesName = app.name.toLowerCase().contains(query); bool matchesTag = app.tags.any((tag) => tag.toLowerCase().contains(query)); return matchesName || matchesTag; }).toList(); if (widget.settings.sortAlphabetically) { filteredApps.sort((a, b) => a.name.compareTo(b.name)); } if (filteredApps.length == 1 && query.isNotEmpty) { _launchApp(filteredApps.first); } }); } Future _launchApp(AppInfo app) async { bool launched = await DeviceApps.openApp(app.packageName); if (!launched) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Could not launch ${app.name}')), ); } } Future _handleDismiss(DismissDirection direction, AppInfo app) async { if (direction == DismissDirection.endToStart) { _openAppInfo(app); } else if (direction == DismissDirection.startToEnd) { _editAppTags(app); } return false; } Future _openAppInfo(AppInfo app) async { bool launched = await DeviceApps.openAppSettings(app.packageName); if (!launched) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Could not launch ${app.name} Settings')), ); } } void _editAppTags(AppInfo app) { TextEditingController tagController = TextEditingController(text: app.tags.join(" ")); showDialog( context: context, builder: (_) => AlertDialog( title: Text('Edit Tags for ${app.name}'), content: TextField( controller: tagController, decoration: InputDecoration( hintText: 'Enter tags (e.g., #general, @context, +project)', ), ), actions: [ TextButton( onPressed: () { setState(() { app.tags ..clear() ..addAll(tagController.text.split(" ").where((t) => t.isNotEmpty)); }); Navigator.of(context).pop(); _filterApps(); }, child: Text('Save'), ), TextButton( onPressed: () => Navigator.of(context).pop(), child: Text('Cancel'), ), ], ), ); } Widget _buildAppIcon(AppInfo app) { Widget iconWidget; if (app.icon != null) { iconWidget = Image.memory( app.icon!, width: 40, height: 40, fit: BoxFit.contain, ); } else { iconWidget = Icon(Icons.apps, size: 40); } if (!widget.settings.colorIcons) { iconWidget = ColorFiltered( colorFilter: ColorFilter.matrix([ 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0, 0, 0, 1, 0, ]), child: iconWidget, ); } return iconWidget; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: TextField( controller: searchController, decoration: InputDecoration( hintText: 'Search apps...', border: InputBorder.none, ), style: TextStyle(color: Colors.white), ), actions: [ Padding( padding: EdgeInsets.symmetric(horizontal: 15), child: PopupMenuButton( enabled: searchController.text.isEmpty, icon: SvgPicture.asset( "assets/plasmoid.svg", width: 30, color: Colors.orange, ), onSelected: (String value) { switch (value) { case 'Toggle Tags': widget.onSettingsChanged(AppSettings( showTags: !widget.settings.showTags, showIcons: widget.settings.showIcons, colorIcons: widget.settings.colorIcons, darkTheme: widget.settings.darkTheme, sortAlphabetically: widget.settings.sortAlphabetically, )); break; case 'Toggle Icons': widget.onSettingsChanged(AppSettings( showTags: widget.settings.showTags, showIcons: !widget.settings.showIcons, colorIcons: widget.settings.colorIcons, darkTheme: widget.settings.darkTheme, sortAlphabetically: widget.settings.sortAlphabetically, )); break; case 'Toggle Color Icons': widget.onSettingsChanged(AppSettings( showTags: widget.settings.showTags, showIcons: widget.settings.showIcons, colorIcons: !widget.settings.colorIcons, darkTheme: widget.settings.darkTheme, sortAlphabetically: widget.settings.sortAlphabetically, )); break; case 'Toggle Theme': widget.onSettingsChanged(AppSettings( showTags: widget.settings.showTags, showIcons: widget.settings.showIcons, colorIcons: widget.settings.colorIcons, darkTheme: !widget.settings.darkTheme, sortAlphabetically: widget.settings.sortAlphabetically, )); break; case 'Toggle Sorting': widget.onSettingsChanged(AppSettings( showTags: widget.settings.showTags, showIcons: widget.settings.showIcons, colorIcons: widget.settings.colorIcons, darkTheme: widget.settings.darkTheme, sortAlphabetically: !widget.settings.sortAlphabetically, )); _filterApps(); break; } }, itemBuilder: (BuildContext context) => [ PopupMenuItem( value: 'Toggle Tags', child: ListTile( leading: Icon(widget.settings.showTags ? Icons.short_text : Icons.tag), title: Text(widget.settings.showTags ? 'Hide Tags' : 'Show Tags'), ), ), PopupMenuItem( value: 'Toggle Icons', child: ListTile( leading: Icon(widget.settings.showIcons ? Icons.apps : Icons.apps_outlined), title: Text(widget.settings.showIcons ? 'Hide Icons' : 'Show Icons'), ), ), PopupMenuItem( value: 'Toggle Color Icons', child: ListTile( leading: Icon(Icons.palette), title: Text(widget.settings.colorIcons ? 'Use Monochrome Icons' : 'Use Color Icons'), ), ), PopupMenuItem( value: 'Toggle Theme', child: ListTile( leading: Icon(widget.settings.darkTheme ? Icons.light_mode : Icons.dark_mode), title: Text(widget.settings.darkTheme ? 'Switch to Light Mode' : 'Switch to Dark Mode'), ), ), PopupMenuItem( value: 'Toggle Sorting', child: ListTile( leading: Icon(widget.settings.sortAlphabetically ? Icons.access_time : Icons.sort_by_alpha), title: Text(widget.settings.sortAlphabetically ? 'List by Last Used' : 'List Alphabetically'), ), ), ], ), ), ], ), body: isLoading ? Center(child: CircularProgressIndicator()) : ListView.builder( itemCount: filteredApps.length, itemBuilder: (context, index) { final app = filteredApps[index]; return Dismissible( key: Key(app.packageName), confirmDismiss: (direction) => _handleDismiss(direction, app), background: Container( color: Colors.orange, alignment: Alignment.centerLeft, padding: EdgeInsets.only(left: 20.0), child: Icon(Icons.edit, color: Colors.white), ), secondaryBackground: Container( color: Colors.green, alignment: Alignment.centerRight, padding: EdgeInsets.only(right: 20.0), child: Icon(Icons.info, color: Colors.white), ), child: ListTile( leading: widget.settings.showIcons ? _buildAppIcon(app) : null, title: Text(app.name), subtitle: widget.settings.showTags ? Text(app.tags.join(" ")) : null, onTap: () => _launchApp(app), ), ); }, ), ); } }