Files
MWClash/lib/manager/window_manager.dart

286 lines
7.1 KiB
Dart
Raw Normal View History

import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/providers/providers.dart';
2024-04-30 23:38:49 +08:00
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:window_ext/window_ext.dart';
2024-04-30 23:38:49 +08:00
import 'package:window_manager/window_manager.dart';
class WindowManager extends ConsumerStatefulWidget {
2024-04-30 23:38:49 +08:00
final Widget child;
const WindowManager({super.key, required this.child});
2024-04-30 23:38:49 +08:00
@override
ConsumerState<WindowManager> createState() => _WindowContainerState();
2024-04-30 23:38:49 +08:00
}
class _WindowContainerState extends ConsumerState<WindowManager>
with WindowListener, WindowExtListener {
@override
Widget build(BuildContext context) {
return widget.child;
}
@override
void initState() {
super.initState();
ref.listenManual(appSettingProvider.select((state) => state.autoLaunch), (
prev,
next,
) {
if (prev != next) {
debouncer.call(FunctionTag.autoLaunch, () {
autoLaunch?.updateStatus(next);
});
}
});
windowExtManager.addListener(this);
2024-04-30 23:38:49 +08:00
windowManager.addListener(this);
}
@override
void onWindowClose() async {
await appController.handleBackOrExit();
2024-04-30 23:38:49 +08:00
super.onWindowClose();
}
@override
void onWindowFocus() {
super.onWindowFocus();
commonPrint.log('focus');
render?.resume();
}
@override
Future<void> onShouldTerminate() async {
await appController.handleExit();
super.onShouldTerminate();
}
@override
void onWindowMoved() {
super.onWindowMoved();
windowManager.getPosition().then((offset) {
ref.read(windowSettingProvider.notifier);
// .update((state) => state.copyWith(top: offset.dy, left: offset.dx));
});
}
@override
Future<void> onWindowResized() async {
super.onWindowResized();
final size = await windowManager.getSize();
ref
.read(windowSettingProvider.notifier)
.update(
(state) => state.copyWith(width: size.width, height: size.height),
);
}
2024-04-30 23:38:49 +08:00
@override
void onWindowMinimize() async {
appController.savePreferencesDebounce();
commonPrint.log('minimize');
render?.pause();
2024-04-30 23:38:49 +08:00
super.onWindowMinimize();
}
@override
void onWindowRestore() {
commonPrint.log('restore');
render?.resume();
super.onWindowRestore();
}
2024-04-30 23:38:49 +08:00
@override
Future<void> dispose() async {
windowManager.removeListener(this);
windowExtManager.removeListener(this);
2024-04-30 23:38:49 +08:00
super.dispose();
}
}
class WindowHeaderContainer extends StatelessWidget {
final Widget child;
const WindowHeaderContainer({super.key, required this.child});
@override
Widget build(BuildContext context) {
return Consumer(
builder: (_, ref, child) {
final isMobileView = ref.watch(isMobileViewProvider);
final version = ref.watch(versionProvider);
if ((version <= 10 || !isMobileView) && system.isMacOS) {
return child!;
}
return Stack(
children: [
Column(
children: [
SizedBox(height: kHeaderHeight),
Expanded(flex: 1, child: child!),
],
),
const WindowHeader(),
],
);
},
child: child,
);
}
}
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();
}
Future<void> _initNotifier() async {
isMaximizedNotifier.value = await windowManager.isMaximized();
isPinNotifier.value = await windowManager.isAlwaysOnTop();
}
@override
void dispose() {
isMaximizedNotifier.dispose();
isPinNotifier.dispose();
super.dispose();
}
Future<void> _updateMaximized() async {
final isMaximized = await windowManager.isMaximized();
switch (isMaximized) {
case true:
await windowManager.unmaximize();
break;
case false:
await windowManager.maximize();
break;
}
isMaximizedNotifier.value = await windowManager.isMaximized();
}
Future<void> _updatePin() async {
final isAlwaysOnTop = await windowManager.isAlwaysOnTop();
await windowManager.setAlwaysOnTop(!isAlwaysOnTop);
isPinNotifier.value = await windowManager.isAlwaysOnTop();
}
Widget _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: () {
appController.handleBackOrExit();
},
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(
color: context.colorScheme.secondary.opacity15,
alignment: Alignment.centerLeft,
height: kHeaderHeight,
),
),
),
if (system.isMacOS)
const Text(appName)
else ...[
Positioned(right: 0, child: _buildActions()),
],
],
),
);
}
}
class AppIcon extends StatelessWidget {
const AppIcon({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: ShapeDecoration(
color: context.colorScheme.surfaceContainerHighest,
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(14),
),
),
padding: EdgeInsets.all(8),
child: Transform.translate(
offset: Offset(0, -1),
child: Image.asset('assets/images/icon.png', width: 34, height: 34),
),
);
}
}