diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart index eafe976..6e41745 100644 --- a/lib/services/notification_service.dart +++ b/lib/services/notification_service.dart @@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart'; import '../helpers/reaction_helper.dart'; import '../l10n/app_localizations.dart'; import '../utils/platform_info.dart'; +import 'windows_taskbar_service.dart'; class NotificationService { static final NotificationService _instance = NotificationService._internal(); @@ -166,6 +167,7 @@ class NotificationService { int? badgeCount, }) async { if (!await _ensureInitialized()) return; + await WindowsTaskbarService.flash(); final androidDetails = AndroidNotificationDetails( 'messages', @@ -266,6 +268,7 @@ class NotificationService { int? badgeCount, }) async { if (!await _ensureInitialized()) return; + await WindowsTaskbarService.flash(); final androidDetails = AndroidNotificationDetails( 'channel_messages', diff --git a/lib/services/windows_taskbar_service.dart b/lib/services/windows_taskbar_service.dart new file mode 100644 index 0000000..648fd15 --- /dev/null +++ b/lib/services/windows_taskbar_service.dart @@ -0,0 +1,29 @@ +import 'dart:io' show Platform; + +import 'package:flutter/foundation.dart' show debugPrint; +import 'package:flutter/services.dart'; + +/// Flashes the Windows taskbar button to draw attention — e.g. a new message +/// arriving while the app window is not in the foreground. +/// +/// The native side (`windows/runner/flutter_window.cpp`) only flashes when the +/// window is not already the foreground window, so callers don't need to track +/// focus themselves. No-op on every non-Windows platform. +class WindowsTaskbarService { + WindowsTaskbarService._(); + + static const MethodChannel _channel = MethodChannel( + 'meshcore_open/windows_taskbar', + ); + + /// Flash the taskbar button if the window isn't focused. Safe on any platform + /// (no-op off Windows) and harmless if the native handler isn't registered. + static Future flash() async { + if (!Platform.isWindows) return; + try { + await _channel.invokeMethod('flash'); + } catch (e) { + debugPrint('WindowsTaskbarService.flash failed: $e'); + } + } +} diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index 955ee30..9eda059 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -1,5 +1,9 @@ #include "flutter_window.h" +#include + +#include + #include #include "flutter/generated_plugin_registrant.h" @@ -25,6 +29,35 @@ bool FlutterWindow::OnCreate() { return false; } RegisterPlugins(flutter_controller_->engine()); + + // Taskbar flash channel - Dart flashes the taskbar button on a new message + // when the window isn't focused (see lib/services/windows_taskbar_service.dart). + taskbar_channel_ = + std::make_unique>( + flutter_controller_->engine()->messenger(), + "meshcore_open/windows_taskbar", + &flutter::StandardMethodCodec::GetInstance()); + taskbar_channel_->SetMethodCallHandler( + [this](const flutter::MethodCall& call, + std::unique_ptr> + result) { + if (call.method_name() == "flash") { + HWND hwnd = GetHandle(); + if (hwnd != nullptr && GetForegroundWindow() != hwnd) { + FLASHWINFO info = {}; + info.cbSize = static_cast(sizeof(info)); + info.hwnd = hwnd; + info.dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG; + info.uCount = 0; + info.dwTimeout = 0; + FlashWindowEx(&info); + } + result->Success(); + } else { + result->NotImplemented(); + } + }); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); flutter_controller_->engine()->SetNextFrameCallback([&]() { diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h index 6da0652..64ba7b4 100644 --- a/windows/runner/flutter_window.h +++ b/windows/runner/flutter_window.h @@ -3,6 +3,7 @@ #include #include +#include #include @@ -28,6 +29,10 @@ class FlutterWindow : public Win32Window { // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; + + // Channel for native taskbar control (e.g. flash on new message). + std::unique_ptr> + taskbar_channel_; }; #endif // RUNNER_FLUTTER_WINDOW_H_