Support proxies search Add some scenes auto close connections Update core Optimize more details
330 lines
7.8 KiB
Dart
330 lines
7.8 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:fl_clash/common/common.dart';
|
|
import 'package:fl_clash/enum/enum.dart';
|
|
import 'package:fl_clash/providers/app.dart';
|
|
import 'package:fl_clash/providers/config.dart';
|
|
import 'package:fl_clash/state.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:window_ext/window_ext.dart';
|
|
import 'package:window_manager/window_manager.dart';
|
|
|
|
class WindowManager extends ConsumerStatefulWidget {
|
|
final Widget child;
|
|
|
|
const WindowManager({
|
|
super.key,
|
|
required this.child,
|
|
});
|
|
|
|
@override
|
|
ConsumerState<WindowManager> createState() => _WindowContainerState();
|
|
}
|
|
|
|
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);
|
|
windowManager.addListener(this);
|
|
}
|
|
|
|
@override
|
|
void onWindowClose() async {
|
|
await globalState.appController.handleBackOrExit();
|
|
super.onWindowClose();
|
|
}
|
|
|
|
@override
|
|
void onWindowFocus() {
|
|
super.onWindowFocus();
|
|
commonPrint.log("focus");
|
|
render?.resume();
|
|
}
|
|
|
|
@override
|
|
Future<void> onShouldTerminate() async {
|
|
await globalState.appController.handleExit();
|
|
super.onShouldTerminate();
|
|
}
|
|
|
|
@override
|
|
Future<void> onWindowMoved() async {
|
|
super.onWindowMoved();
|
|
final offset = await windowManager.getPosition();
|
|
ref.read(windowSettingProvider.notifier).updateState(
|
|
(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).updateState(
|
|
(state) => state.copyWith(
|
|
width: size.width,
|
|
height: size.height,
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void onWindowMinimize() async {
|
|
globalState.appController.savePreferencesDebounce();
|
|
commonPrint.log("minimize");
|
|
render?.pause();
|
|
super.onWindowMinimize();
|
|
}
|
|
|
|
@override
|
|
Future<void> onTaskbarCreated() async {
|
|
// globalState.appController.updateTray(true);
|
|
super.onTaskbarCreated();
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() async {
|
|
windowManager.removeListener(this);
|
|
windowExtManager.removeListener(this);
|
|
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 version = ref.watch(versionProvider);
|
|
if (version <= 10 && Platform.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();
|
|
}
|
|
|
|
_initNotifier() async {
|
|
isMaximizedNotifier.value = await windowManager.isMaximized();
|
|
isPinNotifier.value = await windowManager.isAlwaysOnTop();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
isMaximizedNotifier.dispose();
|
|
isPinNotifier.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
_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();
|
|
}
|
|
|
|
_updatePin() async {
|
|
final isAlwaysOnTop = await windowManager.isAlwaysOnTop();
|
|
await windowManager.setAlwaysOnTop(!isAlwaysOnTop);
|
|
isPinNotifier.value = await windowManager.isAlwaysOnTop();
|
|
}
|
|
|
|
_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: () {
|
|
globalState.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 (Platform.isMacOS)
|
|
const Text(
|
|
appName,
|
|
)
|
|
else ...[
|
|
const Positioned(
|
|
left: 0,
|
|
child: AppIcon(),
|
|
),
|
|
Positioned(
|
|
right: 0,
|
|
child: _buildActions(),
|
|
),
|
|
]
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
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(
|
|
width: 24,
|
|
height: 24,
|
|
child: CircleAvatar(
|
|
foregroundImage: AssetImage("assets/images/icon.png"),
|
|
backgroundColor: Colors.transparent,
|
|
),
|
|
),
|
|
SizedBox(
|
|
width: 8,
|
|
),
|
|
Text(
|
|
appName,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|