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
12 changes: 8 additions & 4 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'application/providers/credentials_provider.dart';
import 'application/providers/locale_provider.dart';
import 'application/providers/navigation_provider.dart';
import 'application/providers/playback_provider.dart';
import 'domain/entities/playback_state.dart';
import 'core/services/system_tray_service.dart';
import 'presentation/screens/home_screen.dart';
import 'presentation/screens/login_screen.dart';
Expand Down Expand Up @@ -55,6 +56,7 @@ class _AppShellState extends ConsumerState<_AppShell> with WindowListener {
final _systemTray = SystemTrayService();
final _navigatorKey = GlobalKey<NavigatorState>();
bool _isExiting = false;
ProviderSubscription? _playbackSubscription;

@override
void initState() {
Expand All @@ -80,6 +82,7 @@ class _AppShellState extends ConsumerState<_AppShell> with WindowListener {

@override
void dispose() {
_playbackSubscription?.close();
windowManager.removeListener(this);
_systemTray.dispose();
super.dispose();
Expand Down Expand Up @@ -124,11 +127,12 @@ class _AppShellState extends ConsumerState<_AppShell> with WindowListener {
);

// Listen to playback state changes to update tray
ref.listenManual(playbackProvider, (previous, next) {
_playbackSubscription = ref.listenManual(playbackProvider, (previous, next) {
final state = next as PlaybackState;
_systemTray.updatePlaybackState(
isPlaying: next.isPlaying,
trackName: next.currentTrack?.name,
artistName: next.currentTrack?.artistNames,
isPlaying: state.isPlaying,
trackName: state.currentTrack?.name,
artistName: state.currentTrack?.artistNames,
);
});
}
Expand Down
28 changes: 20 additions & 8 deletions lib/core/services/system_tray_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class SystemTrayService {
SystemTrayService._internal();

final SystemTray _systemTray = SystemTray();
final AppWindow _appWindow = AppWindow();

bool _isInitialized = false;
TrayCallback? _onShowWindow;
Expand Down Expand Up @@ -275,18 +276,29 @@ class SystemTrayService {
/// Show the main window
/// Uses opacity trick to prevent transparent window flash on restore
Future<void> showWindow() async {
// Set opacity to 0 before showing to prevent transparent flash
await windowManager.setOpacity(0);
await windowManager.show();
await windowManager.focus();
// Small delay to let Flutter render, then restore opacity
await Future<void>.delayed(const Duration(milliseconds: 50));
await windowManager.setOpacity(1);
if (Platform.isMacOS) {
// On macOS, use system_tray's AppWindow to avoid window_manager nil crash
await _appWindow.show();
} else {
// On Windows/Linux, use window_manager with opacity trick
await windowManager.setOpacity(0);
await windowManager.show();
await windowManager.focus();
// Small delay to let Flutter render, then restore opacity
await Future<void>.delayed(const Duration(milliseconds: 50));
await windowManager.setOpacity(1);
}
}

/// Hide the window to tray
Future<void> hideToTray() async {
await windowManager.hide();
try {
await windowManager.hide();
} catch (e) {
// Fallback: use AppWindow.hide() which minimizes on macOS
AppLogger.warning('windowManager.hide() failed, using fallback', e);
await _appWindow.hide();
}
}

/// Dispose of tray resources
Expand Down
3 changes: 2 additions & 1 deletion macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class AppDelegate: FlutterAppDelegate {
}

override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
// Return false to allow the app to keep running when minimized to tray
return false
}

override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
Expand Down
Loading