This commit is contained in:
randogoth 2025-02-24 12:57:02 +02:00
commit dc7af87493
132 changed files with 5659 additions and 0 deletions

408
lib/main.dart Normal file
View file

@ -0,0 +1,408 @@
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<MyApp> {
AppSettings settings = AppSettings();
@override
void initState() {
super.initState();
_loadSettings();
}
Future<void> _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<void> _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<String> 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<AppLauncherScreen> {
final TextEditingController searchController = TextEditingController();
List<AppInfo> apps = [];
List<AppInfo> filteredApps = [];
bool isLoading = true;
@override
void initState() {
super.initState();
_loadInstalledApps();
searchController.addListener(_filterApps);
}
Future<void> _loadInstalledApps() async {
List<Application> installedApps = await DeviceApps.getInstalledApplications(
includeAppIcons: true,
onlyAppsWithLaunchIntent: true,
);
List<AppInfo> 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<void> _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<bool> _handleDismiss(DismissDirection direction, AppInfo app) async {
if (direction == DismissDirection.endToStart) {
_openAppInfo(app);
} else if (direction == DismissDirection.startToEnd) {
_editAppTags(app);
}
return false;
}
Future<void> _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(<double>[
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<String>(
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<String>(
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<String>(
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<String>(
value: 'Toggle Color Icons',
child: ListTile(
leading: Icon(Icons.palette),
title: Text(widget.settings.colorIcons ? 'Use Monochrome Icons' : 'Use Color Icons'),
),
),
PopupMenuItem<String>(
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<String>(
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),
),
);
},
),
);
}
}