Skip to content
Closed
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
39 changes: 29 additions & 10 deletions lib/app_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'package:kazumi/bean/dialog/dialog_helper.dart';
import 'package:kazumi/bean/settings/theme_provider.dart';
import 'package:provider/provider.dart';
import 'package:kazumi/utils/constants.dart';
import 'package:flutter/services.dart';

class AppWidget extends StatefulWidget {
const AppWidget({super.key});
Expand All @@ -36,6 +37,15 @@ class _AppWidgetState extends State<AppWidget>
setPreventClose();
WidgetsBinding.instance.addObserver(this);
super.initState();

// Sync Windows title bar theme on startup and when theme changes
if (Platform.isWindows) {
final themeProvider = Provider.of<ThemeProvider>(context, listen: false);
_syncTitleBarTheme(themeProvider);
themeProvider.addListener(() {
if (mounted) _syncTitleBarTheme(themeProvider);
});
}
}

void setPreventClose() async {
Expand Down Expand Up @@ -167,15 +177,24 @@ class _AppWidgetState extends State<AppWidget>
@override
Future<void> didChangePlatformBrightness() async {
super.didChangePlatformBrightness();
final ThemeProvider themeProvider = Provider.of<ThemeProvider>(context, listen: false);
KazumiLogger().i("Platform brightness changed, themeMode: ${themeProvider.themeMode}");
if (Platform.isWindows) {
final themeProvider = Provider.of<ThemeProvider>(context, listen: false);
_syncTitleBarTheme(themeProvider);
}
}

// Only update title bar theme when following system
// If user has forced a specific theme, keep title bar consistent with app content
if (themeProvider.themeMode == ThemeMode.system && Platform.isWindows) {
final brightness = WidgetsBinding.instance.platformDispatcher.platformBrightness;
KazumiLogger().i("Updating title bar brightness: $brightness");
await windowManager.setBrightness(brightness);
/// Sync Windows title bar theme with app theme using native DwmSetWindowAttribute.
/// This bypasses the window_manager plugin's registry check to force dark/light
/// title bar regardless of system-wide theme setting.
Future<void> _syncTitleBarTheme(ThemeProvider themeProvider) async {
final bool isDark = themeProvider.isEffectiveDark();
try {
await const MethodChannel('com.predidit.kazumi/titlebar')
.invokeMethod('setImmersiveDarkMode', {'enable': isDark});
} catch (_) {
// Fallback to window_manager plugin if native channel fails
await windowManager.setBrightness(
isDark ? Brightness.dark : Brightness.light);
}
}

Expand Down Expand Up @@ -231,9 +250,9 @@ class _AppWidgetState extends State<AppWidget>
themeProvider.setThemeMode(ThemeMode.system, notify: false);
}

// Set Windows title bar theme based on app theme
// Sync Windows title bar theme after theme mode is set
if (Platform.isWindows) {
windowManager.setBrightness(themeProvider.isEffectiveDark() ? Brightness.dark : Brightness.light);
_syncTitleBarTheme(themeProvider);
}

themeProvider.setFontFamily(useSystemFont, notify: false);
Expand Down
6 changes: 0 additions & 6 deletions lib/pages/settings/theme_settings_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import 'package:kazumi/bean/settings/color_type.dart';
import 'package:kazumi/utils/utils.dart';
import 'package:card_settings_ui/card_settings_ui.dart';
import 'package:provider/provider.dart';
import 'package:window_manager/window_manager.dart';

class ThemeSettingsPage extends StatefulWidget {
const ThemeSettingsPage({super.key});
Expand Down Expand Up @@ -123,11 +122,6 @@ class _ThemeSettingsPageState extends State<ThemeSettingsPage> {
setState(() {
defaultThemeMode = theme;
});

// Update Windows title bar theme
if (Platform.isWindows) {
await windowManager.setBrightness(themeProvider.isEffectiveDark() ? Brightness.dark : Brightness.light);
}
}

void updateOledEnhance() {
Expand Down
34 changes: 34 additions & 0 deletions windows/runner/flutter_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
#include <flutter/standard_method_codec.h>
#include <flutter/plugin_registrar_windows.h>
#include <windows.h>
#include <dwmapi.h>

#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif

#include "flutter/generated_plugin_registrant.h"

Expand Down Expand Up @@ -54,6 +59,9 @@ bool FlutterWindow::OnCreate() {
// Register Shortcut MethodChannel
RegisterShortcutChannel();

// Register TitleBar Theme MethodChannel
RegisterTitleBarThemeChannel();

return true;
}

Expand Down Expand Up @@ -176,3 +184,29 @@ void FlutterWindow::RegisterShortcutChannel() {
}
});
}

// TitleBar Theme MethodChannel setup
void FlutterWindow::RegisterTitleBarThemeChannel() {
auto channel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
flutter_controller_->engine()->messenger(), "com.predidit.kazumi/titlebar",
&flutter::StandardMethodCodec::GetInstance());

channel->SetMethodCallHandler([this](const auto& call, auto result) {
if (call.method_name() == "setImmersiveDarkMode") {
const auto* arguments = std::get_if<flutter::EncodableMap>(call.arguments());
if (arguments) {
auto it = arguments->find(flutter::EncodableValue("enable"));
if (it != arguments->end()) {
BOOL dark_mode = std::get<bool>(it->second) ? TRUE : FALSE;
DwmSetWindowAttribute(GetHandle(), DWMWA_USE_IMMERSIVE_DARK_MODE,
&dark_mode, sizeof(dark_mode));
result->Success();
return;
}
}
result->Error("InvalidArguments", "Missing 'enable' argument");
} else {
result->NotImplemented();
}
});
}
3 changes: 3 additions & 0 deletions windows/runner/flutter_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ class FlutterWindow : public Win32Window {

// Register Shortcut MethodChannel
void RegisterShortcutChannel();

// Register TitleBar Theme MethodChannel
void RegisterTitleBarThemeChannel();
};

#endif // RUNNER_FLUTTER_WINDOW_H_
Loading