Files
venera/lib/main.dart
角砂糖 0ac857ef9a Temp solution for hyper os multi window display issue (#467)
Temp solution for hyper os multi window display
2025-08-10 16:02:00 +08:00

315 lines
9.2 KiB
Dart

import 'dart:async';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:venera/foundation/log.dart';
import 'package:venera/pages/auth_page.dart';
import 'package:venera/pages/main_page.dart';
import 'package:venera/utils/io.dart';
import 'package:window_manager/window_manager.dart';
import 'components/components.dart';
import 'components/window_frame.dart';
import 'foundation/app.dart';
import 'foundation/appdata.dart';
import 'init.dart';
void main(List<String> args) {
if (runWebViewTitleBarWidget(args)) return;
overrideIO(() {
runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
await init();
runApp(const MyApp());
if (App.isDesktop) {
await windowManager.ensureInitialized();
windowManager.waitUntilReadyToShow().then((_) async {
await windowManager.setTitleBarStyle(
TitleBarStyle.hidden,
windowButtonVisibility: App.isMacOS,
);
if (App.isLinux) {
await windowManager.setBackgroundColor(Colors.transparent);
}
await windowManager.setMinimumSize(const Size(500, 600));
var placement = await WindowPlacement.loadFromFile();
if (App.isLinux) {
await windowManager.show();
await placement.applyToWindow();
} else {
await placement.applyToWindow();
await windowManager.show();
}
WindowPlacement.loop();
});
}
}, (error, stack) {
Log.error("Unhandled Exception", error, stack);
});
});
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
App.registerForceRebuild(forceRebuild);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
WidgetsBinding.instance.addObserver(this);
checkUpdates();
super.initState();
}
bool isAuthPageActive = false;
OverlayEntry? hideContentOverlay;
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (!App.isMobile || !appdata.settings['authorizationRequired']) {
return;
}
if (state == AppLifecycleState.inactive && hideContentOverlay == null) {
hideContentOverlay = OverlayEntry(
builder: (context) {
return Positioned.fill(
child: Container(
width: double.infinity,
height: double.infinity,
color: App.rootContext.colorScheme.surface,
),
);
},
);
Overlay.of(App.rootContext).insert(hideContentOverlay!);
} else if (hideContentOverlay != null &&
state == AppLifecycleState.resumed) {
hideContentOverlay!.remove();
hideContentOverlay = null;
}
if (state == AppLifecycleState.hidden &&
!isAuthPageActive &&
!IO.isSelectingFiles) {
isAuthPageActive = true;
App.rootContext.to(
() => AuthPage(
onSuccessfulAuth: () {
App.rootContext.pop();
isAuthPageActive = false;
},
),
);
}
super.didChangeAppLifecycleState(state);
}
void forceRebuild() {
void rebuild(Element el) {
el.markNeedsBuild();
el.visitChildren(rebuild);
}
(context as Element).visitChildren(rebuild);
setState(() {});
}
Color translateColorSetting() {
return switch (appdata.settings['color']) {
'red' => Colors.red,
'pink' => Colors.pink,
'purple' => Colors.purple,
'green' => Colors.green,
'orange' => Colors.orange,
'blue' => Colors.blue,
'yellow' => Colors.yellow,
'cyan' => Colors.cyan,
_ => Colors.blue,
};
}
ThemeData getTheme(
Color primary,
Color? secondary,
Color? tertiary,
Brightness brightness,
) {
String? font;
List<String>? fallback;
if (App.isLinux || App.isWindows) {
font = 'Noto Sans CJK';
fallback = [
'Segoe UI',
'Noto Sans SC',
'Noto Sans TC',
'Noto Sans',
'Microsoft YaHei',
'PingFang SC',
'Arial',
'sans-serif'
];
}
return ThemeData(
colorScheme: SeedColorScheme.fromSeeds(
primaryKey: primary,
secondaryKey: secondary,
tertiaryKey: tertiary,
brightness: brightness,
tones: FlexTones.vividBackground(brightness),
),
fontFamily: font,
fontFamilyFallback: fallback,
);
}
@override
Widget build(BuildContext context) {
Widget home;
if (appdata.settings['authorizationRequired']) {
home = AuthPage(
onSuccessfulAuth: () {
App.rootContext.toReplacement(() => const MainPage());
},
);
} else {
home = const MainPage();
}
return DynamicColorBuilder(builder: (light, dark) {
Color? primary, secondary, tertiary;
if (appdata.settings['color'] != 'system' ||
light == null ||
dark == null) {
primary = translateColorSetting();
} else {
primary = light.primary;
secondary = light.secondary;
tertiary = light.tertiary;
}
return MaterialApp(
home: home,
debugShowCheckedModeBanner: false,
theme: getTheme(primary, secondary, tertiary, Brightness.light),
navigatorKey: App.rootNavigatorKey,
darkTheme: getTheme(primary, secondary, tertiary, Brightness.dark),
themeMode: switch (appdata.settings['theme_mode']) {
'light' => ThemeMode.light,
'dark' => ThemeMode.dark,
_ => ThemeMode.system
},
color: Colors.transparent,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
locale: () {
var lang = appdata.settings['language'];
if (lang == 'system') {
return null;
}
return switch (lang) {
'zh-CN' => const Locale('zh', 'CN'),
'zh-TW' => const Locale('zh', 'TW'),
'en-US' => const Locale('en'),
_ => null
};
}(),
supportedLocales: const [
Locale('zh', 'CN'),
Locale('zh', 'TW'),
Locale('en'),
],
builder: (context, widget) {
ErrorWidget.builder = (details) {
Log.error("Unhandled Exception",
"${details.exception}\n${details.stack}");
return Material(
child: Center(
child: Text(details.exception.toString()),
),
);
};
if (widget != null) {
/// 如果无法检测到状态栏高度设定指定高度
/// https://github.com/flutter/flutter/issues/161086
var isPaddingCheckError =
MediaQuery.of(context).viewPadding.top <= 0 ||
MediaQuery.of(context).viewPadding.top > 50;
if (isPaddingCheckError) {
widget = MediaQuery(
data: MediaQuery.of(context).copyWith(
viewPadding: const EdgeInsets.only(
top: 15,
bottom: 15,
),
padding: const EdgeInsets.only(
top: 15,
bottom: 15,
),
),
child: widget);
}
widget = OverlayWidget(widget);
if (App.isDesktop) {
widget = Shortcuts(
shortcuts: {
LogicalKeySet(LogicalKeyboardKey.escape): VoidCallbackIntent(
App.pop,
),
},
child: MouseBackDetector(
onTapDown: App.pop,
child: WindowFrame(widget),
),
);
}
return _SystemUiProvider(Material(
color: App.isLinux ? Colors.transparent : null,
child: widget,
));
}
throw ('widget is null');
},
);
});
}
}
class _SystemUiProvider extends StatelessWidget {
const _SystemUiProvider(this.child);
final Widget child;
@override
Widget build(BuildContext context) {
var brightness = Theme.of(context).brightness;
SystemUiOverlayStyle systemUiStyle;
if (brightness == Brightness.light) {
systemUiStyle = SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness: Brightness.dark,
);
} else {
systemUiStyle = SystemUiOverlayStyle.light.copyWith(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness: Brightness.light,
);
}
return AnnotatedRegion<SystemUiOverlayStyle>(
value: systemUiStyle,
child: child,
);
}
}