Files
FlClash/lib/manager/window_manager.dart
T

286 lines
7.1 KiB
Dart
Raw Normal View History

2025-03-12 17:15:31 +08:00
import 'dart:async';
2024-07-19 02:14:58 +08:00
2024-07-08 17:34:14 +08:00
import 'package:fl_clash/common/common.dart';
2025-12-16 11:23:09 +08:00
import 'package:fl_clash/controller.dart';
2024-12-09 01:40:39 +08:00
import 'package:fl_clash/enum/enum.dart';
2025-06-07 01:48:34 +08:00
import 'package:fl_clash/providers/providers.dart';
2024-04-30 23:38:49 +08:00
import 'package:flutter/material.dart';
2025-02-09 18:39:38 +08:00
import 'package:flutter_riverpod/flutter_riverpod.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';
2025-02-09 18:39:38 +08:00
class WindowManager extends ConsumerStatefulWidget {
2024-04-30 23:38:49 +08:00
final Widget child;
2025-07-31 17:09:18 +08:00
const WindowManager({super.key, required this.child});
2024-04-30 23:38:49 +08:00
@override
2025-02-09 18:39:38 +08:00
ConsumerState<WindowManager> createState() => _WindowContainerState();
2024-04-30 23:38:49 +08:00
}
2025-02-09 18:39:38 +08:00
class _WindowContainerState extends ConsumerState<WindowManager>
2024-10-27 16:59:23 +08:00
with WindowListener, WindowExtListener {
2025-02-09 18:39:38 +08:00
@override
Widget build(BuildContext context) {
return widget.child;
}
2024-08-23 18:33:40 +08:00
2025-02-09 18:39:38 +08:00
@override
void initState() {
super.initState();
2025-07-31 17:09:18 +08:00
ref.listenManual(appSettingProvider.select((state) => state.autoLaunch), (
prev,
next,
) {
if (prev != next) {
debouncer.call(FunctionTag.autoLaunch, () {
autoLaunch?.updateStatus(next);
});
}
});
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 {
2025-12-16 11:23:09 +08:00
await appController.handleBackOrExit();
2024-04-30 23:38:49 +08:00
super.onWindowClose();
}
2024-11-09 20:17:57 +08:00
@override
2025-01-13 19:08:17 +08:00
void onWindowFocus() {
super.onWindowFocus();
2025-06-07 01:48:34 +08:00
commonPrint.log('focus');
2025-01-13 19:08:17 +08:00
render?.resume();
}
@override
2024-11-09 20:17:57 +08:00
Future<void> onShouldTerminate() async {
2025-12-16 11:23:09 +08:00
await appController.handleExit();
2024-11-09 20:17:57 +08:00
super.onShouldTerminate();
}
2024-07-15 22:06:09 +08:00
@override
2025-12-16 11:23:09 +08:00
void onWindowMoved() {
2024-07-15 22:06:09 +08:00
super.onWindowMoved();
2025-12-16 11:23:09 +08:00
windowManager.getPosition().then((offset) {
ref.read(windowSettingProvider.notifier);
// .update((state) => state.copyWith(top: offset.dy, left: offset.dx));
});
2024-07-15 22:06:09 +08:00
}
@override
Future<void> onWindowResized() async {
super.onWindowResized();
final size = await windowManager.getSize();
2025-07-31 17:09:18 +08:00
ref
.read(windowSettingProvider.notifier)
2025-12-16 11:23:09 +08:00
.update(
2025-07-31 17:09:18 +08:00
(state) => state.copyWith(width: size.width, height: size.height),
2025-02-09 18:39:38 +08:00
);
2024-07-15 22:06:09 +08:00
}
2024-04-30 23:38:49 +08:00
@override
void onWindowMinimize() async {
2025-12-16 11:23:09 +08:00
appController.savePreferencesDebounce();
2025-06-07 01:48:34 +08:00
commonPrint.log('minimize');
2025-04-09 16:46:14 +08:00
render?.pause();
2024-04-30 23:38:49 +08:00
super.onWindowMinimize();
}
2024-10-08 17:28:18 +08:00
@override
2025-05-02 02:24:12 +08:00
void onWindowRestore() {
2025-06-07 01:48:34 +08:00
commonPrint.log('restore');
2025-05-02 02:24:12 +08:00
render?.resume();
super.onWindowRestore();
2024-10-08 17:28:18 +08:00
}
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;
2025-07-31 17:09:18 +08:00
const WindowHeaderContainer({super.key, required this.child});
2024-08-15 16:18:00 +08:00
@override
Widget build(BuildContext context) {
2025-02-09 18:39:38 +08:00
return Consumer(
builder: (_, ref, child) {
2025-06-07 01:48:34 +08:00
final isMobileView = ref.watch(isMobileViewProvider);
2025-02-09 18:39:38 +08:00
final version = ref.watch(versionProvider);
2025-06-07 01:48:34 +08:00
if ((version <= 10 || !isMobileView) && system.isMacOS) {
2024-09-08 21:21:21 +08:00
return child!;
}
return Stack(
2024-08-15 16:18:00 +08:00
children: [
2024-09-08 21:21:21 +08:00
Column(
children: [
2025-07-31 17:09:18 +08:00
SizedBox(height: kHeaderHeight),
Expanded(flex: 1, child: child!),
2024-09-08 21:21:21 +08:00
],
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();
}
2025-06-07 01:48:34 +08:00
Future<void> _initNotifier() async {
2024-07-19 02:14:58 +08:00
isMaximizedNotifier.value = await windowManager.isMaximized();
isPinNotifier.value = await windowManager.isAlwaysOnTop();
}
@override
void dispose() {
isMaximizedNotifier.dispose();
isPinNotifier.dispose();
2024-12-09 01:40:39 +08:00
super.dispose();
2024-07-19 02:14:58 +08:00
}
2025-06-07 01:48:34 +08:00
Future<void> _updateMaximized() async {
final isMaximized = await windowManager.isMaximized();
switch (isMaximized) {
2024-07-19 02:14:58 +08:00
case true:
await windowManager.unmaximize();
break;
2024-07-19 02:14:58 +08:00
case false:
await windowManager.maximize();
break;
2024-07-19 02:14:58 +08:00
}
isMaximizedNotifier.value = await windowManager.isMaximized();
2024-07-19 02:14:58 +08:00
}
2025-06-07 01:48:34 +08:00
Future<void> _updatePin() async {
final isAlwaysOnTop = await windowManager.isAlwaysOnTop();
await windowManager.setAlwaysOnTop(!isAlwaysOnTop);
isPinNotifier.value = await windowManager.isAlwaysOnTop();
2024-07-19 02:14:58 +08:00
}
2025-06-07 01:48:34 +08:00
Widget _buildActions() {
2024-07-19 02:14:58 +08:00
return Row(
children: [
IconButton(
onPressed: () async {
_updatePin();
},
icon: ValueListenableBuilder(
valueListenable: isPinNotifier,
2025-07-31 17:09:18 +08:00
builder: (_, value, _) {
2024-07-19 02:14:58 +08:00
return value
2025-07-31 17:09:18 +08:00
? const Icon(Icons.push_pin)
: const Icon(Icons.push_pin_outlined);
2024-07-19 02:14:58 +08:00
},
),
),
IconButton(
onPressed: () {
windowManager.minimize();
},
icon: const Icon(Icons.remove),
),
IconButton(
onPressed: () async {
_updateMaximized();
},
icon: ValueListenableBuilder(
valueListenable: isMaximizedNotifier,
2025-07-31 17:09:18 +08:00
builder: (_, value, _) {
2024-07-19 02:14:58 +08:00
return value
2025-07-31 17:09:18 +08:00
? const Icon(Icons.filter_none, size: 20)
: const Icon(Icons.crop_square);
2024-07-19 02:14:58 +08:00
},
),
),
IconButton(
onPressed: () {
2025-12-16 11:23:09 +08:00
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(
2025-03-12 17:15:31 +08:00
color: context.colorScheme.secondary.opacity15,
2024-07-19 02:14:58 +08:00
alignment: Alignment.centerLeft,
height: kHeaderHeight,
),
),
),
2025-06-07 01:48:34 +08:00
if (system.isMacOS)
2025-07-31 17:09:18 +08:00
const Text(appName)
2024-09-08 21:21:21 +08:00
else ...[
2025-07-31 17:09:18 +08:00
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(
2025-07-31 17:09:18 +08:00
decoration: ShapeDecoration(
color: context.colorScheme.surfaceContainerHighest,
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(14),
2025-06-07 01:48:34 +08:00
),
2024-07-19 02:14:58 +08:00
),
2025-07-31 17:09:18 +08:00
padding: EdgeInsets.all(8),
child: Transform.translate(
offset: Offset(0, -1),
child: Image.asset('assets/images/icon.png', width: 34, height: 34),
),
2024-07-19 02:14:58 +08:00
);
}
}