diff --git a/lib/widgets/fluffy_chat_app.dart b/lib/widgets/fluffy_chat_app.dart index 060ce98ec..4f1c62e7a 100644 --- a/lib/widgets/fluffy_chat_app.dart +++ b/lib/widgets/fluffy_chat_app.dart @@ -51,6 +51,12 @@ class FluffyChatApp extends StatefulWidget { class _FluffyChatAppState extends State { final _androidSystemFontPlugin = AndroidSystemFont(); + static const _platform = MethodChannel('xyz.extera.next/window'); + + // Keeps track of the last Brightness applied to the native window frame + // (title bar) so we don't spam the platform channel with redundant calls + // on every rebuild. + Brightness? _lastWindowBrightness; @override void initState() { @@ -88,6 +94,25 @@ class _FluffyChatAppState extends State { }); } + void _syncWindowBrightness(BuildContext context, ThemeMode themeMode) { + if (!Platform.isWindows) return; + + final resolvedBrightness = switch (themeMode) { + ThemeMode.dark => Brightness.dark, + ThemeMode.light => Brightness.light, + ThemeMode.system => MediaQuery.platformBrightnessOf(context), + }; + + if (resolvedBrightness == _lastWindowBrightness) return; + _lastWindowBrightness = resolvedBrightness; + + _platform.invokeMethod('setTitleBarDarkMode', { + 'isDark': resolvedBrightness == Brightness.dark, + }).catchError((e) { + debugPrint('Failed to set title bar color: $e'); + }); + } + @override Widget build(BuildContext context) { return ThemeBuilder( @@ -99,45 +124,51 @@ class _FluffyChatAppState extends State { schemeVariant, pureBlack, twemoji, - ) => MaterialApp.router( - title: AppConfig.applicationName, - themeMode: themeMode, - theme: FluffyThemes.buildTheme( - context, - Brightness.light, - primaryColor, - schemeVariant, - pureBlack, - twemoji, - ), - darkTheme: FluffyThemes.buildTheme( - context, - Brightness.dark, - primaryColor, - schemeVariant, - pureBlack, - twemoji, - ), - scrollBehavior: CustomScrollBehavior(), - localizationsDelegates: L10n.localizationsDelegates, - supportedLocales: L10n.supportedLocales, - routerConfig: FluffyChatApp.router, - builder: (context, child) => AppLockWidget( - pincode: widget.pincode, - clients: widget.clients, + ) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _syncWindowBrightness(context, themeMode); + }); + + return MaterialApp.router( + title: AppConfig.applicationName, + themeMode: themeMode, + theme: FluffyThemes.buildTheme( + context, + Brightness.light, + primaryColor, + schemeVariant, + pureBlack, + twemoji, + ), + darkTheme: FluffyThemes.buildTheme( + context, + Brightness.dark, + primaryColor, + schemeVariant, + pureBlack, + twemoji, + ), + scrollBehavior: CustomScrollBehavior(), + localizationsDelegates: L10n.localizationsDelegates, + supportedLocales: L10n.supportedLocales, + routerConfig: FluffyChatApp.router, + builder: (context, child) => AppLockWidget( + pincode: widget.pincode, + clients: widget.clients, // Need a navigator above the Matrix widget for // displaying dialogs - child: DownloadManager( - child: BackgroundAudioPlayer( - child: Matrix( - clients: widget.clients, - store: widget.store, - child: widget.testWidget ?? child, + child: DownloadManager( + child: BackgroundAudioPlayer( + child: Matrix( + clients: widget.clients, + store: widget.store, + child: widget.testWidget ?? child, + ), ), ), ), - ), - ), + ); + }, ); } -} +} \ No newline at end of file diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index b43b9095e..832aec2a7 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -26,9 +26,42 @@ bool FlutterWindow::OnCreate() { } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + SetupChannelHandlers(); return true; } +void FlutterWindow::SetupChannelHandlers() { + auto channel = + std::make_unique>( + flutter_controller_->engine()->messenger(), "xyz.extera.next/window", + &flutter::StandardMethodCodec::GetInstance()); + + channel->SetMethodCallHandler( + [this](const flutter::MethodCall& call, + std::unique_ptr> + result) { + if (call.method_name() == "setTitleBarDarkMode") { + const auto* arguments = std::get_if(call.arguments()); + if (arguments) { + auto isDark_variant = arguments->find(flutter::EncodableValue("isDark")); + if (isDark_variant != arguments->end()) { + if (const auto* isDark = std::get_if(&isDark_variant->second)) { + SetTitleBarColor(*isDark); + result->Success(); + return; + } + } + } + result->Error("INVALID_ARGUMENT", "isDark parameter is required"); + } else { + result->NotImplemented(); + } + }); + + method_channel_ = std::move(channel); +} + void FlutterWindow::OnDestroy() { if (flutter_controller_) { flutter_controller_ = nullptr; diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h index 6da0652f0..945958f86 100644 --- a/windows/runner/flutter_window.h +++ b/windows/runner/flutter_window.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include @@ -28,6 +30,10 @@ class FlutterWindow : public Win32Window { // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; + std::unique_ptr> + method_channel_; + + void SetupChannelHandlers(); }; #endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp index c10f08dc7..9d4e1afd9 100644 --- a/windows/runner/win32_window.cpp +++ b/windows/runner/win32_window.cpp @@ -1,9 +1,12 @@ #include "win32_window.h" #include +#include #include "resource.h" +#pragma comment(lib, "dwmapi.lib") + namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; @@ -235,6 +238,15 @@ void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } +void Win32Window::SetTitleBarColor(bool isDark) { + if (!window_handle_) return; + + // Use DwmSetWindowAttribute to set the title bar color + // DWMWA_USE_IMMERSIVE_DARK_MODE = 20 for Windows 11 + BOOL value = isDark ? TRUE : FALSE; + DwmSetWindowAttribute(window_handle_, 20, &value, sizeof(value)); +} + bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h index 17ba43112..1025e9aaa 100644 --- a/windows/runner/win32_window.h +++ b/windows/runner/win32_window.h @@ -54,6 +54,10 @@ class Win32Window { // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); + // Sets the title bar color based on brightness (dark/light mode). + // isDark: true for dark mode (dark title bar), false for light mode (light title bar) + void SetTitleBarColor(bool isDark); + protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that