diff --git a/assets/translations/ar.json b/assets/translations/ar.json index 03c7f3d99..68cfd9e84 100644 --- a/assets/translations/ar.json +++ b/assets/translations/ar.json @@ -230,6 +230,7 @@ "versionDetection": "اكتشاف الإصدار", "standardVersionDetection": "اكتشاف الإصدار القياسي", "groupByCategory": "التجميع حسب التصنيف", + "showCategoryEmojis": "عرض رموز التصنيف", "listView": "عرض القائمة", "gridView": "عرض الشبكة", "autoApkFilterByArch": "محاولة تصفية ملفات APK حسب بنية المعالج إذا أمكن", diff --git a/assets/translations/bs.json b/assets/translations/bs.json index 73079629f..577407349 100644 --- a/assets/translations/bs.json +++ b/assets/translations/bs.json @@ -230,6 +230,7 @@ "versionDetection": "Otkrivanje verzije", "standardVersionDetection": "Detekcija standardne verzije", "groupByCategory": "Grupiši po kategoriji", + "showCategoryEmojis": "Prikaži emoji kategorije", "listView": "Prikaz liste", "gridView": "Prikaz mreže", "autoApkFilterByArch": "Pokušajte filtrirati APK-ove po arhitekturi procesora ako je moguće", diff --git a/assets/translations/ca.json b/assets/translations/ca.json index ff6fe9c0d..1c23b2114 100644 --- a/assets/translations/ca.json +++ b/assets/translations/ca.json @@ -230,6 +230,7 @@ "versionDetection": "Detecció de la versió", "standardVersionDetection": "Detecció de la versió estàndard", "groupByCategory": "Agrupa per categories", + "showCategoryEmojis": "Mostra els emojis de categoria", "listView": "Vista de llista", "gridView": "Vista de graella", "autoApkFilterByArch": "Intenta filtrar les APKs per l'aquitectura de la CPU, si és possible", diff --git a/assets/translations/cs.json b/assets/translations/cs.json index 82cc03aa3..c5f7760ad 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -230,6 +230,7 @@ "versionDetection": "Detekce verze", "standardVersionDetection": "Standardní detekce verze", "groupByCategory": "Seskupit podle kategorie", + "showCategoryEmojis": "Zobrazit emoji kategorií", "listView": "Zobrazení seznamu", "gridView": "Zobrazení mřížky", "autoApkFilterByArch": "Pokud je to možné, pokuste se filtrovat soubory APK podle architektury procesoru", diff --git a/assets/translations/da.json b/assets/translations/da.json index 18c8e22dd..6531dbfdc 100644 --- a/assets/translations/da.json +++ b/assets/translations/da.json @@ -230,6 +230,7 @@ "versionDetection": "Versionsregistrering", "standardVersionDetection": "Standard versionsregistrering", "groupByCategory": "Gruppér efter kategori", + "showCategoryEmojis": "Vis kategori-emojis", "listView": "Listevisning", "gridView": "Gittervisning", "autoApkFilterByArch": "Forsøg at filtrere APK'er efter CPU-arkitektur, hvis muligt", diff --git a/assets/translations/de.json b/assets/translations/de.json index 52356ab6b..c6f182cd1 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -247,6 +247,7 @@ "versionDetection": "Versionserkennung", "standardVersionDetection": "Standardversionserkennung", "groupByCategory": "Nach Kategorie gruppieren", + "showCategoryEmojis": "Kategorie-Emojis anzeigen", "listView": "Listenansicht", "gridView": "Rasteransicht", "autoApkFilterByArch": "Nach Möglichkeit versuchen, APKs nach CPU-Architektur zu filtern", diff --git a/assets/translations/en.json b/assets/translations/en.json index 871e5d8fa..1902f5486 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -247,6 +247,7 @@ "versionDetection": "Version detection", "standardVersionDetection": "Standard version detection", "groupByCategory": "Group by category", + "showCategoryEmojis": "Show category emojis", "listView": "List view", "gridView": "Grid view", "autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible", diff --git a/assets/translations/es.json b/assets/translations/es.json index c522b258b..d8698430a 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -247,6 +247,7 @@ "versionDetection": "Detección de versiones", "standardVersionDetection": "Por versión", "groupByCategory": "Agrupar por categoría", + "showCategoryEmojis": "Mostrar emojis de categoría", "listView": "Vista de lista", "gridView": "Vista de cuadrícula", "autoApkFilterByArch": "Filtrar APK por arquitectura del procesador (si es posible)", diff --git a/assets/translations/et.json b/assets/translations/et.json index e93fe1018..8e1efd825 100644 --- a/assets/translations/et.json +++ b/assets/translations/et.json @@ -230,6 +230,7 @@ "versionDetection": "Versiooni tuvastamine", "standardVersionDetection": "Standardne versiooni tuvastamine", "groupByCategory": "Grupeeri kategooria järgi", + "showCategoryEmojis": "Kategooria-emojide näitamine", "listView": "Loendivaade", "gridView": "Ruudustikuvaade", "autoApkFilterByArch": "Proovi filtreerida APK-sid CPU-arhitektuuri järgi, kui võimalik", diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 061bd0b5d..58c8ec2e2 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -230,6 +230,7 @@ "versionDetection": "تشخیص نسخه", "standardVersionDetection": "تشخیص نسخه استاندارد", "groupByCategory": "دسته‌بندی بر اساس گروه", + "showCategoryEmojis": "نمایش ایموجی دسته", "listView": "نمای لیست", "gridView": "نمای شبکه", "autoApkFilterByArch": "تلاش برای فیلتر کردن APK ها بر اساس معماری CPU در صورت امکان", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 31836d2d3..89c9bb3a1 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -247,6 +247,7 @@ "versionDetection": "Détection de la version", "standardVersionDetection": "Détection de la version standard", "groupByCategory": "Grouper par catégorie", + "showCategoryEmojis": "Afficher les émojis de catégorie", "listView": "Vue en liste", "gridView": "Vue en grille", "autoApkFilterByArch": "Essayer de filtrer les APKs par architecture CPU si possible", diff --git a/assets/translations/gl.json b/assets/translations/gl.json index 7ddbacc07..92dd6c416 100644 --- a/assets/translations/gl.json +++ b/assets/translations/gl.json @@ -230,6 +230,7 @@ "versionDetection": "Detección da versión", "standardVersionDetection": "Detcción estándar da versión", "groupByCategory": "Agrupar por categoría", + "showCategoryEmojis": "Mostrar emojis de categoría", "listView": "Vista de lista", "gridView": "Vista de grade", "autoApkFilterByArch": "Tentar filtrar APKs por arquitectura de CPU se é posible", diff --git a/assets/translations/he.json b/assets/translations/he.json index 14923bc17..4e8de8429 100644 --- a/assets/translations/he.json +++ b/assets/translations/he.json @@ -247,6 +247,7 @@ "versionDetection": "זיהוי גרסה", "standardVersionDetection": "זיהוי גרסה סטנדרטי", "groupByCategory": "קיבוץ לפי קטגוריה", + "showCategoryEmojis": "הצגת סמלי אמוג׳י לקטגוריות", "listView": "תצוגת רשימה", "gridView": "תצוגת רשת", "autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible", @@ -292,7 +293,7 @@ "exemptFromBackgroundUpdates": "הוצאה מהעדכונים ברקע (אם מופעלים)", "bgUpdatesOnWiFiOnly": "השבתת עדכוני רקע כאשר המכשיר אינו מחובר לרשת אלחוטית (Wi-Fi)", "bgUpdatesWhileChargingOnly": "השבתת עדכוני רקע כאשר המכשיר אינו בהטענה", - "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "autoSelectHighestVersionCode": "בחירה אוטומטית של קובץ ־APK עם קוד הגרסה הגבוה ביותר", "versionExtractionRegEx": "Version string extraction RegEx", "trimVersionString": "Trim version string with RegEx", "matchGroupToUseForX": "Match group to use for \"{}\"", diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 83d6d7c41..8c6ae6b67 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -230,6 +230,7 @@ "versionDetection": "Verziófelismerés", "standardVersionDetection": "Alapértelmezett verziófelismerés", "groupByCategory": "Csoportosítás kategória alapján", + "showCategoryEmojis": "Kategória emoji-k megjelenítése", "listView": "Lista nézet", "gridView": "Rács nézet", "autoApkFilterByArch": "APK-k CPU-architektúra szerinti szűrése, ha lehetséges", diff --git a/assets/translations/id.json b/assets/translations/id.json index a0892a981..c7e50b823 100644 --- a/assets/translations/id.json +++ b/assets/translations/id.json @@ -230,6 +230,7 @@ "versionDetection": "Pendeteksi versi", "standardVersionDetection": "Pendeteksi versi standar", "groupByCategory": "Kelompokkan berdasarkan kategori", + "showCategoryEmojis": "Tampilkan emoji kategori", "listView": "Tampilan daftar", "gridView": "Tampilan kisi", "autoApkFilterByArch": "Coba filter APK berdasarkan arsitektur CPU jika memungkinkan", diff --git a/assets/translations/it.json b/assets/translations/it.json index b9ba439a0..20fbd6584 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -230,6 +230,7 @@ "versionDetection": "Rilevamento di versione", "standardVersionDetection": "Rilevamento di versione standard", "groupByCategory": "Raggruppa per categoria", + "showCategoryEmojis": "Mostra emoji delle categorie", "listView": "Vista elenco", "gridView": "Vista griglia", "autoApkFilterByArch": "Tenta di filtrare gli APK in base all'architettura della CPU, se possibile", diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 99cf9020b..2486246d2 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -230,6 +230,7 @@ "versionDetection": "バージョン検出", "standardVersionDetection": "標準のバージョン検出", "groupByCategory": "カテゴリー別にグループ化", + "showCategoryEmojis": "カテゴリー絵文字を表示", "listView": "リスト表示", "gridView": "グリッド表示", "autoApkFilterByArch": "可能であれば、CPUアーキテクチャによるAPKのフィルタリングを試みる", diff --git a/assets/translations/ko.json b/assets/translations/ko.json index 4c71f9131..bc50ff8b7 100644 --- a/assets/translations/ko.json +++ b/assets/translations/ko.json @@ -230,6 +230,7 @@ "versionDetection": "버전 감지", "standardVersionDetection": "표준 버전 감지", "groupByCategory": "카테고리별 그룹화", + "showCategoryEmojis": "카테고리 이모지 표시", "listView": "목록 보기", "gridView": "그리드 보기", "autoApkFilterByArch": "가능한 경우 CPU 아키텍처별로 APK 필터링 시도", diff --git a/assets/translations/nl.json b/assets/translations/nl.json index 966d7f78f..37a7195bb 100644 --- a/assets/translations/nl.json +++ b/assets/translations/nl.json @@ -230,6 +230,7 @@ "versionDetection": "Versieherkenning", "standardVersionDetection": "Standaard versieherkenning", "groupByCategory": "Groeperen op categorie", + "showCategoryEmojis": "Categorie-emojis weergeven", "listView": "Lijstweergave", "gridView": "Rasterweergave", "autoApkFilterByArch": "Probeer APK's te filteren op CPU-architectuur, indien mogelijk", diff --git a/assets/translations/pl.json b/assets/translations/pl.json index e840bb8e5..9d6c17393 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -230,6 +230,7 @@ "versionDetection": "Wykrywanie wersji", "standardVersionDetection": "Standardowe wykrywanie wersji", "groupByCategory": "Grupuj według kategorii", + "showCategoryEmojis": "Pokaż emoji kategorii", "listView": "Widok listy", "gridView": "Widok siatki", "autoApkFilterByArch": "Spróbuj filtrować pliki APK według architektury procesora, jeśli to możliwe", diff --git a/assets/translations/pt-BR.json b/assets/translations/pt-BR.json index 99434c117..46a2637ec 100644 --- a/assets/translations/pt-BR.json +++ b/assets/translations/pt-BR.json @@ -230,6 +230,7 @@ "versionDetection": "Detecção de versão", "standardVersionDetection": "Detecção de versão padrão", "groupByCategory": "Agrupar por categoria", + "showCategoryEmojis": "Mostrar emojis de categoria", "listView": "Visualização em lista", "gridView": "Visualização em grade", "autoApkFilterByArch": "Tentar filtrar APKs pela arquitetura da CPU quando possível", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 5a973c5a0..800065879 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -230,6 +230,7 @@ "versionDetection": "Определение версии", "standardVersionDetection": "Стандартное", "groupByCategory": "Группировать по категориям", + "showCategoryEmojis": "Показывать эмодзи категорий", "listView": "Вид списка", "gridView": "Вид сетки", "autoApkFilterByArch": "Пытаться фильтровать APK по архитектуре процессора", diff --git a/assets/translations/sv.json b/assets/translations/sv.json index f8ece6c75..8e4659091 100644 --- a/assets/translations/sv.json +++ b/assets/translations/sv.json @@ -230,6 +230,7 @@ "versionDetection": "Versionsdetektering", "standardVersionDetection": "Standardversionsdetektering", "groupByCategory": "Gruppera via Kategori", + "showCategoryEmojis": "Viska kategori-emojis", "listView": "Listvy", "gridView": "Rutnätsvy", "autoApkFilterByArch": "Försök att filtrera APK-filer efter CPU-arkitektur om möjligt", diff --git a/assets/translations/tr.json b/assets/translations/tr.json index bbcc04577..e6e8672db 100644 --- a/assets/translations/tr.json +++ b/assets/translations/tr.json @@ -230,6 +230,7 @@ "versionDetection": "Sürüm tespiti", "standardVersionDetection": "Standart sürüm tespiti", "groupByCategory": "Kategoriye göre grupla", + "showCategoryEmojis": "Kategori emoji'lerini göster", "listView": "Liste görünümü", "gridView": "Izgara görünümü", "autoApkFilterByArch": "Mümkün olduğunda APK'ları CPU mimarisine göre filtrelemeye çalış", diff --git a/assets/translations/uk.json b/assets/translations/uk.json index b38257dc3..19fb8bbaa 100644 --- a/assets/translations/uk.json +++ b/assets/translations/uk.json @@ -230,6 +230,7 @@ "versionDetection": "Визначення версії", "standardVersionDetection": "Стандартне визначення версії", "groupByCategory": "Групувати за категоріями", + "showCategoryEmojis": "Показувати емодзі категорій", "listView": "Вигляд списку", "gridView": "Вигляд сітки", "autoApkFilterByArch": "Спробувати фільтрувати APK за архітектурою ЦП, якщо можливо", diff --git a/assets/translations/vi.json b/assets/translations/vi.json index 12e297c9b..943cf434b 100644 --- a/assets/translations/vi.json +++ b/assets/translations/vi.json @@ -230,6 +230,7 @@ "versionDetection": "Phát hiện phiên bản", "standardVersionDetection": "Phát hiện phiên bản tiêu chuẩn", "groupByCategory": "Nhóm theo danh mục", + "showCategoryEmojis": "Hiển thị emoji danh mục", "listView": "Chế độ xem danh sách", "gridView": "Chế độ xem lưới", "autoApkFilterByArch": "Cố gắng lọc APK theo kiến trúc CPU nếu có thể", diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 907d0e58a..a7d737b41 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -230,6 +230,7 @@ "versionDetection": "版本检测", "standardVersionDetection": "常规版本检测", "groupByCategory": "按类别分组", + "showCategoryEmojis": "显示类别表情符号", "listView": "列表视图", "gridView": "网格视图", "autoApkFilterByArch": "如果可能,尝试按 CPU 架构筛选 APK 文件", diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index 62f6c4a4d..7e9f188d5 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -635,7 +635,7 @@ class AddAppPageState extends State { Column( children: [ const SizedBox(height: 16), - CategoryEditorSelector( + EmojiCategoryEditorSelector( alignment: WrapAlignment.start, onSelected: (categories) { pickedCategories = categories; diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 2879ecbfa..9805c12ee 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -283,7 +283,7 @@ class _AppPageState extends State { ), height32, - CategoryEditorSelector( + EmojiCategoryEditorSelector( alignment: WrapAlignment.center, preselected: app?.app.categories != null ? app!.app.categories.toSet() diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 0a23460d1..9abbab3e2 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -15,6 +15,7 @@ import 'package:updatium/pages/settings.dart'; import 'package:updatium/providers/apps_provider.dart'; import 'package:updatium/providers/settings_provider.dart'; import 'package:updatium/providers/source_provider.dart'; +import 'package:updatium/utils/category_emojis.dart'; import 'package:provider/provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -146,9 +147,6 @@ class AppsPageState extends State { DateTime? refreshingSince; final GlobalKey _refreshIndicatorKey = GlobalKey(); - // Cache gradient stops by category count to avoid recomputation - final Map> _stopsCache = {}; - // Helper function to preserve transparency regardless of theme overrides Color preserveTransparency(Color baseColor, double alpha) { // Always apply the requested transparency, ensuring it takes priority @@ -156,18 +154,17 @@ class AppsPageState extends State { return baseColor.withOpacity(alpha); } - // Helper function to get category color with preserved transparency - Color getCategoryColor( + // Helper function to get category emoji + String getCategoryEmoji( String category, - int alpha, SettingsProvider settingsProvider, ) { - final categoryColorValue = settingsProvider.categories[category]; - if (categoryColorValue != null) { - return Color(categoryColorValue).withAlpha(alpha); + final categoryEmojiValue = settingsProvider.categoryEmojis[category]; + if (categoryEmojiValue != null) { + return categoryEmojiValue; } - // Fallback to truly transparent color - return Color.fromARGB(0, 0, 0, 0); + // Fallback to default emoji mapping + return CategoryEmojis.getEmojiForCategory(category); } bool clearSelected() { @@ -646,37 +643,25 @@ class AppsPageState extends State { }, ); - var transparent = Theme.of( - context, - ).colorScheme.surface.withAlpha(0).toARGB32(); - List stops = [ - ...listedApps[index].app.categories.asMap().entries.map( - (e) => - ((e.key / (listedApps[index].app.categories.length - 1)) - - 0.0001), - ), - 1, - ]; - if (stops.length == 2) { - stops[0] = 0.9999; - } + // Get category emojis for display + List categoryEmojis = listedApps[index].app.categories + .map((e) => getCategoryEmoji(e, settingsProvider)) + .toList(); + return Container( decoration: BoxDecoration( - gradient: LinearGradient( - stops: stops, - begin: const Alignment(-1, 0), - end: const Alignment(-0.97, 0), - colors: [ - ...listedApps[index].app.categories.map( - (e) => getCategoryColor(e, 255, settingsProvider), - ), - Colors.transparent, - ], + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Theme.of(context).colorScheme.outline.withOpacity(0.2), ), ), - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: () { + child: Stack( + children: [ + // Main content + InkWell( + borderRadius: BorderRadius.circular(16), + onTap: () { if (selectedAppIds.isNotEmpty) { toggleAppSelected(listedApps[index].app); } else { @@ -770,61 +755,53 @@ class AppsPageState extends State { ) : trailingRow, ), + ), + // Emoji overlay + if (categoryEmojis.isNotEmpty && settingsProvider.showCategoryEmojis) + Positioned( + top: 8, + left: 8, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.7), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + categoryEmojis.take(3).join(' '), + style: const TextStyle(fontSize: 16), + ), + ), + ), + ], ), ); } - List categoryStops(List categories) { - final n = categories.length; - final cached = _stopsCache[n]; - if (cached != null) return cached; - List result; - if (n > 1) { - result = [ - ...List.generate( - n, - (i) => ((i / (n - 1)) - 0.0001).clamp(0.0, 1.0), - ), - 1.0, - ]; - } else if (n == 1) { - result = const [0.9999, 1.0]; - } else { - result = const [1.0]; - } - _stopsCache[n] = result; - return result; - } - Widget getSingleAppGridTile(int index) { var hasUpdate = listedApps[index].app.installedVersion != null && listedApps[index].app.installedVersion != listedApps[index].app.latestVersion; - var transparent = Theme.of( - context, - ).colorScheme.surface.withAlpha(0).value; - final categories = listedApps[index].app.categories; - final stops = categoryStops(categories); + + // Get category emojis for display + List categoryEmojis = listedApps[index].app.categories + .map((e) => getCategoryEmoji(e, settingsProvider)) + .toList(); return Container( decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), - gradient: LinearGradient( - stops: stops, - begin: const Alignment(-1, -1), - end: const Alignment(1, 1), - colors: [ - ...listedApps[index].app.categories.map( - (e) => getCategoryColor(e, 40, settingsProvider), - ), - Colors.transparent, - ], + border: Border.all( + color: Theme.of(context).colorScheme.outline.withOpacity(0.2), ), ), - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () { + child: Stack( + children: [ + InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () { if (selectedAppIds.isNotEmpty) { toggleAppSelected(listedApps[index].app); } else { @@ -1027,6 +1004,23 @@ class AppsPageState extends State { ], ), ), + // Emoji overlay moved outside InkWell + if (categoryEmojis.isNotEmpty && settingsProvider.showCategoryEmojis) + Positioned( + top: 4, + left: 4, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.7), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + categoryEmojis.take(2).join(' '), + style: const TextStyle(fontSize: 12), + ), + ), + ), ); } @@ -1047,10 +1041,19 @@ class AppsPageState extends State { .toList(); capFirstChar(String str) => str[0].toUpperCase() + str.substring(1); + + final categoryName = listedCategories[index] ?? tr('noCategory'); + String displayText = capFirstChar(categoryName); + + if (settingsProvider.showCategoryEmojis && listedCategories[index] != null) { + final emoji = CategoryEmojis.getEmojiForCategory(listedCategories[index]!); + displayText = '$emoji $displayText'; + } + return ExpansionTile( initiallyExpanded: true, title: Text( - capFirstChar(listedCategories[index] ?? tr('noCategory')), + displayText, style: const TextStyle(fontWeight: FontWeight.bold), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -1246,7 +1249,7 @@ class AppsPageState extends State { initValid: true, singleNullReturnButton: tr('continue'), additionalWidgets: [ - CategoryEditorSelector( + EmojiCategoryEditorSelector( preselected: !showPrompt ? preselected ?? {} : {}, showLabelWhenNotEmpty: false, onSelected: (categories) { @@ -1560,7 +1563,7 @@ class AppsPageState extends State { ], additionalWidgets: [ height16, - CategoryEditorSelector( + EmojiCategoryEditorSelector( preselected: filter.categoryFilter, onSelected: (categories) { filter.categoryFilter = categories.toSet(); diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index e66248af2..5f6ba23db 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -1035,6 +1035,19 @@ class _SettingsPageState extends State { ], ), height16, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: Text(tr('showCategoryEmojis'))), + Switch( + value: settingsProvider.showCategoryEmojis, + onChanged: (value) { + settingsProvider.showCategoryEmojis = value; + }, + ), + ], + ), + height16, Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -1115,7 +1128,7 @@ class _SettingsPageState extends State { ), ), height16, - const CategoryEditorSelector( + const EmojiCategoryEditorSelector( showLabelWhenNotEmpty: false, ), ], @@ -1315,13 +1328,13 @@ class _LogsDialogState extends State { } } -class CategoryEditorSelector extends StatefulWidget { +class EmojiCategoryEditorSelector extends StatefulWidget { final void Function(List categories)? onSelected; final bool singleSelect; final Set preselected; final WrapAlignment alignment; final bool showLabelWhenNotEmpty; - const CategoryEditorSelector({ + const EmojiCategoryEditorSelector({ super.key, this.onSelected, this.singleSelect = false, @@ -1331,17 +1344,18 @@ class CategoryEditorSelector extends StatefulWidget { }); @override - State createState() => _CategoryEditorSelectorState(); + State createState() => _EmojiCategoryEditorSelectorState(); } -class _CategoryEditorSelectorState extends State { - Map> storedValues = {}; +class _EmojiCategoryEditorSelectorState extends State { + Map> storedValues = {}; + final TextEditingController _textController = TextEditingController(); @override Widget build(BuildContext context) { var settingsProvider = context.watch(); var appsProvider = context.watch(); - storedValues = settingsProvider.categories.map( + storedValues = settingsProvider.categoryEmojis.map( (key, value) => MapEntry( key, MapEntry( @@ -1350,39 +1364,94 @@ class _CategoryEditorSelectorState extends State { ), ), ); - return GeneratedForm( - items: [ - [ - GeneratedFormTagInput( - 'categories', - label: tr('categories'), - emptyMessage: tr('noCategories'), - defaultValue: storedValues, - alignment: widget.alignment, - deleteConfirmationMessage: MapEntry( - tr('deleteCategoriesQuestion'), - tr('categoryDeleteWarning'), + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + tr('categories'), + style: Theme.of(context).textTheme.titleMedium, + ), + height8, + Wrap( + alignment: widget.alignment, + children: storedValues.entries.map((entry) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + child: FilterChip( + label: Text('${entry.value.value} ${entry.key}'), + selected: entry.value.value, + onSelected: (selected) { + setState(() { + storedValues[entry.key] = MapEntry(entry.value.key, selected); + }); + }, + deleteIcon: const Icon(Icons.close, size: 16), + onDeleted: () { + setState(() { + storedValues.remove(entry.key); + }); + }, + ), + ); + }).toList(), + ), + height8, + if (storedValues.isEmpty) + Text( + tr('noCategories'), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - singleSelect: widget.singleSelect, - showLabelWhenNotEmpty: widget.showLabelWhenNotEmpty, ), - ], + height8, + Row( + children: [ + Expanded( + child: TextField( + controller: _textController, + decoration: InputDecoration( + labelText: tr('addCategory') + ' (emoji)', + hintText: '🎮 Gaming', + border: const OutlineInputBorder(), + ), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + final parts = value.trim().split(' '); + if (parts.length >= 2) { + final emoji = parts.first; + final categoryName = parts.sublist(1).join(' ').trim(); + if (categoryName.isNotEmpty) { + setState(() { + storedValues[categoryName] = MapEntry(emoji, true); + }); + _textController.clear(); + } + } + } + }, + ), + ), + const SizedBox(width: 8), + FilledButton.tonal( + onPressed: storedValues.isNotEmpty + ? () { + settingsProvider.setCategoryEmojis( + storedValues.map((key, value) => MapEntry(key, value.key)), + appsProvider: appsProvider, + ); + if (widget.onSelected != null) { + widget.onSelected!( + storedValues.keys.where((k) => storedValues[k]!.value).toList(), + ); + } + } + : null, + child: Text(tr('save')), + ), + ], + ), ], - onValueChanges: ((values, valid, isBuilding) { - if (!isBuilding) { - storedValues = - values['categories'] as Map>; - settingsProvider.setCategories( - storedValues.map((key, value) => MapEntry(key, value.key)), - appsProvider: appsProvider, - ); - if (widget.onSelected != null) { - widget.onSelected!( - storedValues.keys.where((k) => storedValues[k]!.value).toList(), - ); - } - } - }), ); } } diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 6af285f96..c40aadc6d 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -268,6 +268,15 @@ class SettingsProvider with ChangeNotifier { notifyListeners(); } + Map get categoryEmojis => + Map.from(jsonDecode(prefs?.getString('categoryEmojis') ?? '{}')); + + void setCategoryEmojis(Map emojis, {AppsProvider? appsProvider}) { + // Only save emoji mappings - don't modify app categories + prefs?.setString('categoryEmojis', jsonEncode(emojis)); + notifyListeners(); + } + Map get categories => Map.from(jsonDecode(prefs?.getString('categories') ?? '{}')); @@ -535,4 +544,13 @@ class SettingsProvider with ChangeNotifier { prefs?.setBool('safeMode', val); notifyListeners(); } + + bool get showCategoryEmojis { + return prefs?.getBool('showCategoryEmojis') ?? true; + } + + set showCategoryEmojis(bool val) { + prefs?.setBool('showCategoryEmojis', val); + notifyListeners(); + } } diff --git a/lib/utils/category_emojis.dart b/lib/utils/category_emojis.dart new file mode 100644 index 000000000..71439e33d --- /dev/null +++ b/lib/utils/category_emojis.dart @@ -0,0 +1,143 @@ +import 'dart:collection'; + +class CategoryEmojis { + static final HashMap _emojiMap = HashMap.from({ + // Social & Communication + 'social': '💬', + 'communication': '💬', + 'messaging': '💬', + 'chat': '💬', + 'email': '📧', + 'phone': '📞', + 'contacts': '👥', + + // Productivity & Office + 'productivity': '⚡', + 'office': '📊', + 'business': '💼', + 'finance': '💰', + 'banking': '🏦', + 'notes': '📝', + 'calendar': '📅', + 'tasks': '✅', + 'todo': '✅', + 'documents': '📄', + 'pdf': '📄', + + // Media & Entertainment + 'media': '🎬', + 'video': '🎬', + 'music': '🎵', + 'audio': '🎵', + 'player': '🎵', + 'photos': '📷', + 'gallery': '🖼️', + 'camera': '📸', + 'streaming': '📺', + 'tv': '📺', + 'movies': '🎭', + + // Games + 'games': '🎮', + 'gaming': '🎮', + 'arcade': '🕹️', + 'puzzle': '🧩', + 'strategy': '♟️', + 'adventure': '🗺️', + 'sports games': '⚽', + + // Utilities & Tools + 'utilities': '🔧', + 'tools': '🔧', + 'system': '⚙️', + 'file': '📁', + 'file manager': '📁', + 'browser': '🌐', + 'vpn': '🔒', + 'security': '🛡️', + 'antivirus': '🛡️', + 'cleaner': '🧹', + 'backup': '💾', + + // Development + 'development': '💻', + 'programming': '💻', + 'code': '💻', + 'developer': '👨‍💻', + 'terminal': '💻', + 'editor': '📝', + + // Education & Learning + 'education': '📚', + 'learning': '📚', + 'books': '📖', + 'study': '📖', + 'school': '🎓', + 'language': '🗣️', + 'dictionary': '📕', + + // Health & Fitness + 'health': '🏥', + 'fitness': '💪', + 'medical': '⚕️', + 'exercise': '🏃', + 'sports': '⚽', + 'yoga': '🧘', + + // Shopping & E-commerce + 'shopping': '🛒', + 'ecommerce': '🛍️', + 'store': '🏪', + 'food': '🍔', + 'delivery': '🚚', + + // Travel & Navigation + 'travel': '✈️', + 'navigation': '🗺️', + 'maps': '📍', + 'gps': '📍', + 'weather': '🌤️', + + // News & Information + 'news': '📰', + 'information': 'ℹ️', + 'magazine': '📖', + 'blog': '📝', + + // Graphics & Design + 'graphics': '🎨', + 'design': '🎨', + 'drawing': '✏️', + 'art': '🖼️', + 'photo editing': '🖼️', + + // Internet & Network + 'internet': '🌐', + 'network': '🌐', + 'wifi': '📶', + 'download': '⬇️', + 'upload': '⬆️', + + // Custom & Miscellaneous + 'custom': '⭐', + 'misc': '📦', + 'other': '📦', + 'default': '📱', + }); + + static String getEmojiForCategory(String category) { + // Try exact match first + String? emoji = _emojiMap[category.toLowerCase()]; + if (emoji != null) return emoji; + + // Try partial match + final sortedKeys = _emojiMap.keys.toList(); + sortedKeys.sort((a, b) => b.length.compareTo(a.length)); + for (final key in sortedKeys) { + if (category.toLowerCase().contains(key)) return _emojiMap[key]!; + } + + // Return default emoji + return _emojiMap['default']!; + } +}