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 {
|
2025-04-18 17:50:46 +08:00
|
|
|
final isMaximized = await windowManager.isMaximized();
|
|
|
|
|
switch (isMaximized) {
|
2024-07-19 02:14:58 +08:00
|
|
|
case true:
|
2025-04-18 17:50:46 +08:00
|
|
|
await windowManager.unmaximize();
|
|
|
|
|
break;
|
2024-07-19 02:14:58 +08:00
|
|
|
case false:
|
2025-04-18 17:50:46 +08:00
|
|
|
await windowManager.maximize();
|
|
|
|
|
break;
|
2024-07-19 02:14:58 +08:00
|
|
|
}
|
2025-04-18 17:50:46 +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 {
|
2025-04-18 17:50:46 +08:00
|
|
|
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
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|