Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@
"versionDetection": "Version detection",
"standardVersionDetection": "Standard version detection",
"groupByCategory": "Group by category",
"showCategoryEmojis": "Show category emojis",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all translation files and check for the new key
fd -e json . assets/translations/ --exec grep -L "showCategoryEmojis" {} \;

Repository: omeritzics/Updatium

Length of output: 987


Add showCategoryEmojis translation key to all locale files.

The new showCategoryEmojis key has been added to en.json but is missing from 30+ other locale files: vi.json, zh.json, tr.json, pt.json, zh-Hant-TW.json, uk.json, ru.json, sv.json, pl.json, pt-BR.json, nl.json, ko.json, ja.json, ml.json, id.json, it.json, he.json, gl.json, fr.json, fa.json, et.json, hu.json, es.json, de.json, cs.json, da.json, en-EO.json, ca.json, ar.json, and bs.json. Add this key with an appropriate translation to each file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/translations/en.json` at line 250, The new translation key
"showCategoryEmojis" has been added to en.json but is missing from many other
locale files; update each listed locale (e.g., vi.json, zh.json, tr.json,
pt.json, zh-Hant-TW.json, uk.json, ru.json, sv.json, pl.json, pt-BR.json,
nl.json, ko.json, ja.json, ml.json, id.json, it.json, he.json, gl.json, fr.json,
fa.json, et.json, hu.json, es.json, de.json, cs.json, da.json, en-EO.json,
ca.json, ar.json, bs.json) by adding the "showCategoryEmojis" key with an
appropriate translated string matching the style/tone of that locale; ensure the
key name is identical ("showCategoryEmojis"), insert it in the same JSON object
structure as other keys, validate JSON syntax after edit, and keep translations
consistent with the English source in en.json.

"listView": "List view",
"gridView": "Grid view",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
Expand Down
162 changes: 83 additions & 79 deletions lib/pages/apps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -146,28 +147,24 @@ class AppsPageState extends State<AppsPage> {
DateTime? refreshingSince;
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = GlobalKey();

// Cache gradient stops by category count to avoid recomputation
final Map<int, List<double>> _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
// over any theme-based color overrides
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() {
Expand Down Expand Up @@ -646,37 +643,25 @@ class AppsPageState extends State<AppsPage> {
},
);

var transparent = Theme.of(
context,
).colorScheme.surface.withAlpha(0).toARGB32();
List<double> 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<String> 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 {
Expand Down Expand Up @@ -770,61 +755,53 @@ class AppsPageState extends State<AppsPage> {
)
: 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<double> categoryStops(List<String> categories) {
final n = categories.length;
final cached = _stopsCache[n];
if (cached != null) return cached;
List<double> result;
if (n > 1) {
result = [
...List<double>.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<String> 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 {
Expand Down Expand Up @@ -1024,6 +1001,23 @@ class AppsPageState extends State<AppsPage> {
),
),
),
// Emoji overlay
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),
),
),
),
],
),
),
Expand All @@ -1047,10 +1041,20 @@ class AppsPageState extends State<AppsPage> {
.toList();

capFirstChar(String str) => str[0].toUpperCase() + str.substring(1);

final settingsProvider = context.read<SettingsProvider>();
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,
Expand Down
13 changes: 13 additions & 0 deletions lib/pages/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,19 @@ class _SettingsPageState extends State<SettingsPage> {
],
),
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: [
Expand Down
32 changes: 32 additions & 0 deletions lib/providers/settings_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,29 @@ class SettingsProvider with ChangeNotifier {
notifyListeners();
}

Map<String, String> get categoryEmojis =>
Map<String, String>.from(jsonDecode(prefs?.getString('categoryEmojis') ?? '{}'));

void setCategoryEmojis(Map<String, String> emojis, {AppsProvider? appsProvider}) {
if (appsProvider != null) {
List<App> changedApps = appsProvider
.getAppValues()
.map((a) {
var n1 = a.app.categories.length;
a.app.categories.removeWhere((c) => !emojis.keys.contains(c));
return n1 > a.app.categories.length ? a.app : null;
})
.where((element) => element != null)
.map((e) => e as App)
.toList();
if (changedApps.isNotEmpty) {
appsProvider.saveApps(changedApps);
}
}
prefs?.setString('categoryEmojis', jsonEncode(emojis));
notifyListeners();
}

Comment on lines +274 to +279
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new method setCategoryEmojis is not used anywhere in the project. This appears to be dead code and should be removed to improve maintainability.

Map<String, int> get categories =>
Map<String, int>.from(jsonDecode(prefs?.getString('categories') ?? '{}'));

Expand Down Expand Up @@ -535,4 +558,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();
}
}
Loading
Loading