2024-07-19 02:14:58 +08:00
|
|
|
import 'dart:io';
|
|
|
|
|
|
2024-07-08 17:34:14 +08:00
|
|
|
import 'package:fl_clash/common/common.dart';
|
|
|
|
|
import 'package:fl_clash/models/models.dart';
|
2024-05-11 17:02:34 +08:00
|
|
|
import 'package:fl_clash/state.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:flutter/material.dart';
|
2024-07-08 17:34:14 +08:00
|
|
|
import 'package:provider/provider.dart';
|
2024-10-08 17:28:18 +08:00
|
|
|
import 'package:window_ext/window_ext.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:window_manager/window_manager.dart';
|
|
|
|
|
|
2024-09-08 21:21:21 +08:00
|
|
|
class WindowManager extends StatefulWidget {
|
2024-04-30 23:38:49 +08:00
|
|
|
final Widget child;
|
|
|
|
|
|
2024-09-08 21:21:21 +08:00
|
|
|
const WindowManager({
|
2024-04-30 23:38:49 +08:00
|
|
|
super.key,
|
|
|
|
|
required this.child,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@override
|
2024-09-08 21:21:21 +08:00
|
|
|
State<WindowManager> createState() => _WindowContainerState();
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2024-10-08 17:28:18 +08:00
|
|
|
class _WindowContainerState extends State<WindowManager> with WindowListener, WindowExtListener {
|
2024-08-23 18:33:40 +08:00
|
|
|
Function? updateLaunchDebounce;
|
|
|
|
|
|
2024-07-08 17:34:14 +08:00
|
|
|
_autoLaunchContainer(Widget child) {
|
2024-09-26 14:29:04 +08:00
|
|
|
return Selector<Config, AutoLaunchState>(
|
|
|
|
|
selector: (_, config) => AutoLaunchState(
|
|
|
|
|
isAutoLaunch: config.appSetting.autoLaunch,
|
|
|
|
|
isAdminAutoLaunch: config.appSetting.adminAutoLaunch,
|
|
|
|
|
),
|
2024-08-15 16:18:00 +08:00
|
|
|
builder: (_, state, child) {
|
2024-08-23 18:33:40 +08:00
|
|
|
updateLaunchDebounce ??= debounce((AutoLaunchState state) {
|
|
|
|
|
autoLaunch?.updateStatus(state);
|
|
|
|
|
});
|
|
|
|
|
updateLaunchDebounce!([state]);
|
2024-07-08 17:34:14 +08:00
|
|
|
return child!;
|
|
|
|
|
},
|
|
|
|
|
child: child,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2024-08-15 16:18:00 +08:00
|
|
|
return _autoLaunchContainer(widget.child);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
2024-10-08 17:28:18 +08:00
|
|
|
windowExtManager.addListener(this);
|
2024-04-30 23:38:49 +08:00
|
|
|
windowManager.addListener(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void onWindowClose() async {
|
2024-05-11 17:02:34 +08:00
|
|
|
await globalState.appController.handleBackOrExit();
|
2024-04-30 23:38:49 +08:00
|
|
|
super.onWindowClose();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-15 22:06:09 +08:00
|
|
|
@override
|
|
|
|
|
Future<void> onWindowMoved() async {
|
|
|
|
|
super.onWindowMoved();
|
|
|
|
|
final offset = await windowManager.getPosition();
|
|
|
|
|
final config = globalState.appController.config;
|
|
|
|
|
config.windowProps = config.windowProps.copyWith(
|
|
|
|
|
top: offset.dy,
|
|
|
|
|
left: offset.dx,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<void> onWindowResized() async {
|
|
|
|
|
super.onWindowResized();
|
|
|
|
|
final size = await windowManager.getSize();
|
|
|
|
|
final config = globalState.appController.config;
|
|
|
|
|
config.windowProps = config.windowProps.copyWith(
|
|
|
|
|
width: size.width,
|
|
|
|
|
height: size.height,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
@override
|
|
|
|
|
void onWindowMinimize() async {
|
2024-05-11 17:02:34 +08:00
|
|
|
await globalState.appController.savePreferences();
|
2024-04-30 23:38:49 +08:00
|
|
|
super.onWindowMinimize();
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-08 17:28:18 +08:00
|
|
|
@override
|
|
|
|
|
void onTaskbarCreated() {
|
2024-10-14 10:03:23 +08:00
|
|
|
globalState.appController.updateTray(true);
|
2024-10-08 17:28:18 +08:00
|
|
|
super.onTaskbarCreated();
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
@override
|
|
|
|
|
Future<void> dispose() async {
|
|
|
|
|
windowManager.removeListener(this);
|
2024-10-08 17:28:18 +08:00
|
|
|
windowExtManager.removeListener(this);
|
2024-04-30 23:38:49 +08:00
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-19 02:14:58 +08:00
|
|
|
|
2024-08-15 16:18:00 +08:00
|
|
|
class WindowHeaderContainer extends StatelessWidget {
|
|
|
|
|
final Widget child;
|
|
|
|
|
|
|
|
|
|
const WindowHeaderContainer({
|
|
|
|
|
super.key,
|
|
|
|
|
required this.child,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2024-09-08 21:21:21 +08:00
|
|
|
return Selector<AppState, int>(
|
|
|
|
|
selector: (_, appState) => appState.version,
|
|
|
|
|
builder: (_, version, child) {
|
|
|
|
|
if (version <= 10 && Platform.isMacOS) {
|
|
|
|
|
return child!;
|
|
|
|
|
}
|
|
|
|
|
return Stack(
|
2024-08-15 16:18:00 +08:00
|
|
|
children: [
|
2024-09-08 21:21:21 +08:00
|
|
|
Column(
|
|
|
|
|
children: [
|
|
|
|
|
SizedBox(
|
|
|
|
|
height: kHeaderHeight,
|
|
|
|
|
),
|
|
|
|
|
Expanded(
|
|
|
|
|
flex: 1,
|
|
|
|
|
child: child!,
|
|
|
|
|
),
|
|
|
|
|
],
|
2024-08-15 16:18:00 +08:00
|
|
|
),
|
2024-09-08 21:21:21 +08:00
|
|
|
const WindowHeader(),
|
2024-08-15 16:18:00 +08:00
|
|
|
],
|
2024-09-08 21:21:21 +08:00
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
child: child,
|
2024-08-15 16:18:00 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-19 02:14:58 +08:00
|
|
|
class WindowHeader extends StatefulWidget {
|
|
|
|
|
const WindowHeader({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<WindowHeader> createState() => _WindowHeaderState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _WindowHeaderState extends State<WindowHeader> {
|
|
|
|
|
final isMaximizedNotifier = ValueNotifier<bool>(false);
|
|
|
|
|
final isPinNotifier = ValueNotifier<bool>(false);
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
_initNotifier();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_initNotifier() async {
|
|
|
|
|
isMaximizedNotifier.value = await windowManager.isMaximized();
|
|
|
|
|
isPinNotifier.value = await windowManager.isAlwaysOnTop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
super.dispose();
|
|
|
|
|
isMaximizedNotifier.dispose();
|
|
|
|
|
isPinNotifier.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_updateMaximized() {
|
|
|
|
|
isMaximizedNotifier.value = !isMaximizedNotifier.value;
|
|
|
|
|
switch (isMaximizedNotifier.value) {
|
|
|
|
|
case true:
|
|
|
|
|
windowManager.maximize();
|
|
|
|
|
case false:
|
|
|
|
|
windowManager.unmaximize();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_updatePin() {
|
|
|
|
|
isPinNotifier.value = !isPinNotifier.value;
|
|
|
|
|
windowManager.setAlwaysOnTop(isPinNotifier.value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_buildActions() {
|
|
|
|
|
return Row(
|
|
|
|
|
children: [
|
|
|
|
|
IconButton(
|
|
|
|
|
onPressed: () async {
|
|
|
|
|
_updatePin();
|
|
|
|
|
},
|
|
|
|
|
icon: ValueListenableBuilder(
|
|
|
|
|
valueListenable: isPinNotifier,
|
|
|
|
|
builder: (_, value, ___) {
|
|
|
|
|
return value
|
|
|
|
|
? const Icon(
|
|
|
|
|
Icons.push_pin,
|
|
|
|
|
)
|
|
|
|
|
: const Icon(
|
|
|
|
|
Icons.push_pin_outlined,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
IconButton(
|
|
|
|
|
onPressed: () {
|
|
|
|
|
windowManager.minimize();
|
|
|
|
|
},
|
|
|
|
|
icon: const Icon(Icons.remove),
|
|
|
|
|
),
|
|
|
|
|
IconButton(
|
|
|
|
|
onPressed: () async {
|
|
|
|
|
_updateMaximized();
|
|
|
|
|
},
|
|
|
|
|
icon: ValueListenableBuilder(
|
|
|
|
|
valueListenable: isMaximizedNotifier,
|
|
|
|
|
builder: (_, value, ___) {
|
|
|
|
|
return value
|
|
|
|
|
? const Icon(
|
|
|
|
|
Icons.filter_none,
|
|
|
|
|
size: 20,
|
|
|
|
|
)
|
|
|
|
|
: const Icon(
|
|
|
|
|
Icons.crop_square,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
IconButton(
|
|
|
|
|
onPressed: () {
|
2024-08-15 16:18:00 +08:00
|
|
|
globalState.appController.handleBackOrExit();
|
2024-07-19 02:14:58 +08:00
|
|
|
},
|
|
|
|
|
icon: const Icon(Icons.close),
|
|
|
|
|
),
|
|
|
|
|
// const SizedBox(
|
|
|
|
|
// width: 8,
|
|
|
|
|
// ),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
return Material(
|
|
|
|
|
child: Stack(
|
|
|
|
|
alignment: AlignmentDirectional.center,
|
|
|
|
|
children: [
|
|
|
|
|
Positioned(
|
|
|
|
|
child: GestureDetector(
|
|
|
|
|
onPanStart: (_) {
|
|
|
|
|
windowManager.startDragging();
|
|
|
|
|
},
|
|
|
|
|
onDoubleTap: () {
|
|
|
|
|
_updateMaximized();
|
|
|
|
|
},
|
|
|
|
|
child: Container(
|
2024-08-15 16:18:00 +08:00
|
|
|
color: context.colorScheme.secondary.toSoft(),
|
2024-07-19 02:14:58 +08:00
|
|
|
alignment: Alignment.centerLeft,
|
|
|
|
|
height: kHeaderHeight,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
2024-09-08 21:21:21 +08:00
|
|
|
if (Platform.isMacOS)
|
|
|
|
|
const Text(
|
|
|
|
|
appName,
|
|
|
|
|
)
|
|
|
|
|
else ...[
|
|
|
|
|
const Positioned(
|
|
|
|
|
left: 0,
|
|
|
|
|
child: AppIcon(),
|
|
|
|
|
),
|
|
|
|
|
Positioned(
|
|
|
|
|
right: 0,
|
|
|
|
|
child: _buildActions(),
|
|
|
|
|
),
|
|
|
|
|
]
|
2024-07-19 02:14:58 +08:00
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class AppIcon extends StatelessWidget {
|
|
|
|
|
const AppIcon({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
return Container(
|
|
|
|
|
margin: const EdgeInsets.only(left: 8),
|
|
|
|
|
child: const Row(
|
|
|
|
|
children: [
|
|
|
|
|
SizedBox(
|
2024-07-21 21:51:56 +08:00
|
|
|
width: 24,
|
|
|
|
|
height: 24,
|
2024-07-19 02:14:58 +08:00
|
|
|
child: CircleAvatar(
|
2024-07-21 21:51:56 +08:00
|
|
|
foregroundImage: AssetImage("assets/images/icon.png"),
|
2024-07-19 02:14:58 +08:00
|
|
|
backgroundColor: Colors.transparent,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
SizedBox(
|
|
|
|
|
width: 8,
|
|
|
|
|
),
|
|
|
|
|
Text(
|
|
|
|
|
appName,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|