Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions lib/constants/configurations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,9 @@ const kDeduplicateThresholdKey = 'deduplicate_threshold';
/// controls similar to the Windows implementation. For compatibility reasons,
/// this defaults to false (disabled) when the setting is missing.
const kLinuxCustomWindowControlsKey = 'linux_custom_window_controls';

/// This key is used to store the user's preference for the tray icon color mode.
/// It determines whether the tray icon should automatically adapt to the system
/// theme or use a fixed color mode. Options include "auto" (follows system theme),
/// "light" (always use light icon), and "dark" (always use dark icon).
const kTrayIconColorModeKey = 'tray_icon_color_mode';
10 changes: 10 additions & 0 deletions lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@
"@light": {
"description": "A color settings entry that enables light mode when selected."
},
"trayIconColorMode": "Tray Icon Color Mode",
"@trayIconColorMode": {
"description": "A settings entry that allows users to control the color of the system tray icon, choosing between automatic, light, and dark modes."
},
"trayIconColorModeSubtitle": "Control how the tray icon adapts to your system theme.",
"@trayIconColorModeSubtitle": {},
"automaticTrayIconMode": "Automatic",
"@automaticTrayIconMode": {
"description": "An option for tray icon color mode that automatically switches between light and dark based on the system theme."
},
"themeColor": "Theme Color",
"@themeColor": {
"description": "A settings entry that allows users to choose their preferred theme color."
Expand Down
10 changes: 10 additions & 0 deletions lib/l10n/intl_zh_Hans.arb
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@
"@light": {
"description": "一个色彩设置条目,选中后启用浅色模式。"
},
"trayIconColorMode": "托盘图标颜色模式",
"@trayIconColorMode": {
"description": "一个设置条目,允许用户控制系统托盘图标的颜色,可选择自动、亮色和暗色模式。"
},
"trayIconColorModeSubtitle": "控制托盘图标如何适应您的系统主题。",
"@trayIconColorModeSubtitle": {},
"automaticTrayIconMode": "自动",
"@automaticTrayIconMode": {
"description": "托盘图标颜色模式的一个选项,根据系统主题自动在亮色和暗色之间切换。"
},
"themeColor": "主题颜色",
"@themeColor": {
"description": "一个设置条目,允许用户选择自己喜欢的主题色。"
Expand Down
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ void main(List<String> arguments) async {
);

if (isDesktop) {
final icon = TrayManager.getTrayIcon();
final icon = await TrayManager.getTrayIcon();
await systemTray.initSystemTray(
title: Platform.isMacOS ? null : 'Rune',
iconPath: icon.path,
Expand Down
2 changes: 2 additions & 0 deletions lib/screens/settings_laboratory/settings_laboratory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'widgets/settings/branding_animation_settings.dart';
import 'widgets/settings/cover_wall_richness_settings.dart';
import 'widgets/settings/library_cover_wallpaper_settings.dart';
import 'widgets/settings/mild_spectrum_settings.dart';
import 'widgets/settings/tray_icon_color_mode_settings.dart';

class SettingsLaboratory extends StatelessWidget {
const SettingsLaboratory({super.key});
Expand Down Expand Up @@ -98,6 +99,7 @@ class _ResponsiveSettingsGrid extends StatelessWidget {
CafeModeSettings(),
ForceZuneSettings(),
MildSpectrumSettings(),
TrayIconColorModeSettings(),
],
),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'package:fluent_ui/fluent_ui.dart';

import '../../../../utils/tray_manager.dart';
import '../../../../utils/settings_manager.dart';
import '../../../../constants/configurations.dart';

import '../../utils/settings_combo_box_item.dart';

import '../settings_card.dart';
import '../settings_combo_box.dart';

class TrayIconColorModeSettings extends StatefulWidget {
const TrayIconColorModeSettings({super.key});

@override
TrayIconColorModeSettingsState createState() =>
TrayIconColorModeSettingsState();
}

class TrayIconColorModeSettingsState extends State<TrayIconColorModeSettings> {
String? trayIconColorMode = 'auto';
final SettingsManager _settingsManager = SettingsManager();

@override
void initState() {
super.initState();
_loadSettings();
}

Future<void> _loadSettings() async {
final storedMode =
await _settingsManager.getValue<String>(kTrayIconColorModeKey);
if (storedMode != null) {
setState(() => trayIconColorMode = storedMode);
}
}

@override
Widget build(BuildContext context) {
final items = [
SettingsComboBoxItem(
value: 'auto',
text: 'Automatic',
),
SettingsComboBoxItem(
value: 'light',
text: 'Light',
),
SettingsComboBoxItem(
value: 'dark',
text: 'Dark',
),
];

return SettingsCard(
title: "Tray Icon Color Mode",
description:
"Set the tray icon color mode preference. Automatic mode will adapt to your system theme.",
content: SettingsComboBox<String>(
value: trayIconColorMode,
items: items,
onChanged: (value) async {
if (value == null) return;
setState(() => trayIconColorMode = value);
await _settingsManager.setValue<String>(kTrayIconColorModeKey, value);

// Update the tray icon immediately
$tray.updateTrayIcon();
},
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:fluent_ui/fluent_ui.dart';

import '../../../utils/l10n.dart';
import '../../../utils/tray_manager.dart';
import '../../../widgets/settings/settings_box_combo_box.dart';
import '../../../constants/configurations.dart';
import '../../../constants/settings_manager.dart';

class TrayIconColorModeSetting extends StatefulWidget {
const TrayIconColorModeSetting({super.key});

@override
TrayIconColorModeSettingState createState() =>
TrayIconColorModeSettingState();
}

class TrayIconColorModeSettingState extends State<TrayIconColorModeSetting> {
String trayIconColorMode = "auto";

@override
void initState() {
super.initState();
_loadTrayIconColorMode();
}

Future<void> _loadTrayIconColorMode() async {
final storedMode =
await $settingsManager.getValue<String>(kTrayIconColorModeKey);
setState(() {
trayIconColorMode = storedMode ?? "auto";
});
}
Comment on lines +26 to +32
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): Consider handling asynchronous setState more robustly.

Check if the widget is mounted before calling setState in _loadTrayIconColorMode to prevent errors if the widget is disposed during the async operation.

Suggested change
Future<void> _loadTrayIconColorMode() async {
final storedMode =
await $settingsManager.getValue<String>(kTrayIconColorModeKey);
setState(() {
trayIconColorMode = storedMode ?? "auto";
});
}
Future<void> _loadTrayIconColorMode() async {
final storedMode =
await $settingsManager.getValue<String>(kTrayIconColorModeKey);
if (mounted) {
setState(() {
trayIconColorMode = storedMode ?? "auto";
});
}
}


Future<void> _updateTrayIconColorMode(String newMode) async {
setState(() {
trayIconColorMode = newMode;
});
await $settingsManager.setValue(kTrayIconColorModeKey, newMode);

// Update the tray icon immediately
$tray.updateTrayIcon();
Comment on lines +40 to +41
Copy link

Choose a reason for hiding this comment

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

suggestion: Consider awaiting updateTrayIcon for consistency.

Awaiting updateTrayIcon will ensure the tray icon is updated before proceeding and allow proper error handling.

Suggested change
// Update the tray icon immediately
$tray.updateTrayIcon();
// Update the tray icon immediately
await $tray.updateTrayIcon();

}

@override
Widget build(BuildContext context) {
final s = S.of(context);

return SettingsBoxComboBox(
title: s.trayIconColorMode,
subtitle: s.trayIconColorModeSubtitle,
value: trayIconColorMode,
items: [
SettingsBoxComboBoxItem(
value: "auto", title: s.automaticTrayIconMode),
SettingsBoxComboBoxItem(value: "light", title: s.light),
SettingsBoxComboBoxItem(value: "dark", title: s.dark),
],
onChanged: (newValue) {
if (newValue != null) {
_updateTrayIconColorMode(newValue);
}
},
);
}
}
35 changes: 28 additions & 7 deletions lib/utils/tray_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import 'package:bitsdojo_window/bitsdojo_window.dart';
import '../providers/status.dart';
import '../providers/router_path.dart';

import '../constants/configurations.dart';
import '../constants/settings_manager.dart';

import 'api/play_next.dart';
import 'api/play_pause.dart';
import 'api/play_play.dart';
Expand All @@ -26,16 +29,34 @@ class TrayIcon {
}

class TrayManager {
static TrayIcon getTrayIcon() {
static Future<TrayIcon> getTrayIcon() async {
if (Platform.isMacOS) {
return TrayIcon('assets/mac-tray.svg', false);
}

final brightness =
SchedulerBinding.instance.platformDispatcher.platformBrightness ==
Brightness.light
? Brightness.dark.name
: Brightness.light.name;
// Get user preference for tray icon color mode
final trayIconColorMode =
await $settingsManager.getValue<String>(kTrayIconColorModeKey) ??
"auto";
Comment on lines +38 to +40
Copy link

Choose a reason for hiding this comment

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

suggestion: Defaulting to "auto" is reasonable, but consider validating the value.

Validate trayIconColorMode against the allowed values before using it to prevent issues from invalid or corrupted settings.


String brightness;
if (trayIconColorMode == "auto") {
// Automatic mode: follow system theme
// Special case: GNOME always uses light icon
if (Platform.isLinux &&
Platform.environment['XDG_CURRENT_DESKTOP'] == 'GNOME') {
brightness = Brightness.light.name;
} else {
brightness =
SchedulerBinding.instance.platformDispatcher.platformBrightness ==
Brightness.light
? Brightness.dark.name
: Brightness.light.name;
}
} else {
// Manual mode: use user's selection directly
brightness = trayIconColorMode;
}

if (Platform.isWindows) {
return TrayIcon('assets/tray_icon_$brightness.ico', false);
Expand Down Expand Up @@ -113,7 +134,7 @@ class TrayManager {
}

Future<void> updateTrayIcon() async {
final icon = getTrayIcon();
final icon = await TrayManager.getTrayIcon();
await systemTray.setImage(icon.path, isTemplate: true, isInstalled: icon.isInstalled);
}

Expand Down
Loading