feat(desktop): 增加桌面端系统托盘和窗口管理功能
- 主函数中添加窗口管理初始化,支持Windows、Linux和MacOS平台 - HomeScreen中集成托盘服务和窗口服务,实现托盘菜单交互 - 实现窗口关闭时弹出对话框,支持最小化到托盘或退出程序 - 添加托盘服务TrayService,封装系统托盘图标和菜单的初始化、事件处理 - 添加窗口服务WindowService,实现窗口的显示、隐藏、关闭及事件监听 - 在界面添加最小化到托盘的按钮,仅桌面端显示 - pubspec.yaml添加assets目录,包含托盘图标资源文件
This commit is contained in:
BIN
home_monitor/assets/app_icon.ico
Normal file
BIN
home_monitor/assets/app_icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -1,9 +1,19 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'providers/providers.dart';
|
import 'providers/providers.dart';
|
||||||
import 'screens/screens.dart';
|
import 'screens/screens.dart';
|
||||||
|
import 'services/services.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// 初始化窗口管理(仅桌面端)
|
||||||
|
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
||||||
|
await WindowService().init();
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
|
||||||
runApp(const HomeMonitorApp());
|
runApp(const HomeMonitorApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../providers/providers.dart';
|
import '../providers/providers.dart';
|
||||||
|
import '../services/services.dart';
|
||||||
import '../widgets/widgets.dart';
|
import '../widgets/widgets.dart';
|
||||||
|
|
||||||
/// 主页面
|
/// 主页面
|
||||||
@@ -12,6 +15,9 @@ class HomeScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _HomeScreenState extends State<HomeScreen> {
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
|
final TrayService _trayService = TrayService();
|
||||||
|
final WindowService _windowService = WindowService();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -21,9 +27,71 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
provider.initDefaultDevices();
|
provider.initDefaultDevices();
|
||||||
provider.refreshAll();
|
provider.refreshAll();
|
||||||
provider.startAutoRefresh(intervalSeconds: 30);
|
provider.startAutoRefresh(intervalSeconds: 30);
|
||||||
|
|
||||||
|
// 初始化系统托盘
|
||||||
|
_initSystemTray(provider);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 初始化系统托盘
|
||||||
|
Future<void> _initSystemTray(MonitorProvider provider) async {
|
||||||
|
if (!_trayService.isSupported) return;
|
||||||
|
|
||||||
|
await _trayService.init(
|
||||||
|
onShow: () => _windowService.showWindow(),
|
||||||
|
onHide: () => _windowService.hideWindow(),
|
||||||
|
onExit: () => _exitApp(),
|
||||||
|
onRefresh: () => provider.refreshAll(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置窗口关闭事件(最小化到托盘)
|
||||||
|
_windowService.onCloseRequested = () => _handleCloseRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 处理窗口关闭请求
|
||||||
|
void _handleCloseRequest() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('关闭窗口'),
|
||||||
|
content: const Text('请选择操作:'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_windowService.hideWindow();
|
||||||
|
},
|
||||||
|
child: const Text('最小化到托盘'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_exitApp();
|
||||||
|
},
|
||||||
|
child: const Text('退出程序'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('取消'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 退出应用
|
||||||
|
Future<void> _exitApp() async {
|
||||||
|
await _trayService.destroy();
|
||||||
|
await _windowService.closeWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_trayService.destroy();
|
||||||
|
_windowService.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -60,6 +128,13 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
},
|
},
|
||||||
tooltip: '设置',
|
tooltip: '设置',
|
||||||
),
|
),
|
||||||
|
// 最小化到托盘按钮(仅桌面端)
|
||||||
|
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS))
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.minimize),
|
||||||
|
onPressed: () => _windowService.hideWindow(),
|
||||||
|
tooltip: '最小化到托盘',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
// 服务层导出文件
|
// 服务层导出文件
|
||||||
export 'network_service.dart';
|
export 'network_service.dart';
|
||||||
export 'speed_test_service.dart';
|
export 'speed_test_service.dart';
|
||||||
|
export 'tray_service.dart';
|
||||||
|
|||||||
218
home_monitor/lib/services/tray_service.dart
Normal file
218
home_monitor/lib/services/tray_service.dart
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:system_tray/system_tray.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
/// 系统托盘服务
|
||||||
|
class TrayService {
|
||||||
|
static final TrayService _instance = TrayService._internal();
|
||||||
|
factory TrayService() => _instance;
|
||||||
|
TrayService._internal();
|
||||||
|
|
||||||
|
final SystemTray _systemTray = SystemTray();
|
||||||
|
bool _isInitialized = false;
|
||||||
|
|
||||||
|
// 回调函数
|
||||||
|
VoidCallback? onShow;
|
||||||
|
VoidCallback? onHide;
|
||||||
|
VoidCallback? onExit;
|
||||||
|
VoidCallback? onRefresh;
|
||||||
|
|
||||||
|
/// 是否支持系统托盘(仅桌面平台)
|
||||||
|
bool get isSupported => !kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS);
|
||||||
|
|
||||||
|
/// 初始化系统托盘
|
||||||
|
Future<void> init({
|
||||||
|
VoidCallback? onShow,
|
||||||
|
VoidCallback? onHide,
|
||||||
|
VoidCallback? onExit,
|
||||||
|
VoidCallback? onRefresh,
|
||||||
|
}) async {
|
||||||
|
if (!isSupported || _isInitialized) return;
|
||||||
|
|
||||||
|
this.onShow = onShow;
|
||||||
|
this.onHide = onHide;
|
||||||
|
this.onExit = onExit;
|
||||||
|
this.onRefresh = onRefresh;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 初始化托盘图标
|
||||||
|
String iconPath = Platform.isWindows
|
||||||
|
? 'assets/app_icon.ico'
|
||||||
|
: 'assets/app_icon.png';
|
||||||
|
|
||||||
|
await _systemTray.initSystemTray(
|
||||||
|
title: '家庭网络监控',
|
||||||
|
iconPath: iconPath,
|
||||||
|
toolTip: '家庭网络监控 - 运行中',
|
||||||
|
);
|
||||||
|
|
||||||
|
// 创建右键菜单
|
||||||
|
final menu = Menu();
|
||||||
|
await menu.buildFrom([
|
||||||
|
MenuItemLabel(
|
||||||
|
label: '显示窗口',
|
||||||
|
onClicked: (menuItem) => _handleShow(),
|
||||||
|
),
|
||||||
|
MenuItemLabel(
|
||||||
|
label: '隐藏窗口',
|
||||||
|
onClicked: (menuItem) => _handleHide(),
|
||||||
|
),
|
||||||
|
MenuSeparator(),
|
||||||
|
MenuItemLabel(
|
||||||
|
label: '刷新状态',
|
||||||
|
onClicked: (menuItem) => _handleRefresh(),
|
||||||
|
),
|
||||||
|
MenuSeparator(),
|
||||||
|
MenuItemLabel(
|
||||||
|
label: '退出程序',
|
||||||
|
onClicked: (menuItem) => _handleExit(),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await _systemTray.setContextMenu(menu);
|
||||||
|
|
||||||
|
// 注册托盘事件
|
||||||
|
_systemTray.registerSystemTrayEventHandler((eventName) {
|
||||||
|
if (eventName == kSystemTrayEventClick) {
|
||||||
|
// 单击托盘图标显示窗口
|
||||||
|
_handleShow();
|
||||||
|
} else if (eventName == kSystemTrayEventRightClick) {
|
||||||
|
// 右键显示菜单
|
||||||
|
_systemTray.popUpContextMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('系统托盘初始化失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新托盘提示文字
|
||||||
|
Future<void> updateToolTip(String message) async {
|
||||||
|
if (!_isInitialized) return;
|
||||||
|
await _systemTray.setToolTip('家庭网络监控 - $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新托盘标题
|
||||||
|
Future<void> updateTitle(String title) async {
|
||||||
|
if (!_isInitialized) return;
|
||||||
|
await _systemTray.setTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleShow() {
|
||||||
|
onShow?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleHide() {
|
||||||
|
onHide?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleExit() {
|
||||||
|
onExit?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleRefresh() {
|
||||||
|
onRefresh?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 销毁托盘
|
||||||
|
Future<void> destroy() async {
|
||||||
|
if (!_isInitialized) return;
|
||||||
|
await _systemTray.destroy();
|
||||||
|
_isInitialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 窗口管理服务
|
||||||
|
class WindowService with WindowListener {
|
||||||
|
static final WindowService _instance = WindowService._internal();
|
||||||
|
factory WindowService() => _instance;
|
||||||
|
WindowService._internal();
|
||||||
|
|
||||||
|
bool _isInitialized = false;
|
||||||
|
VoidCallback? onCloseRequested;
|
||||||
|
|
||||||
|
/// 是否支持窗口管理
|
||||||
|
bool get isSupported => !kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS);
|
||||||
|
|
||||||
|
/// 初始化窗口管理
|
||||||
|
Future<void> init({VoidCallback? onCloseRequested}) async {
|
||||||
|
if (!isSupported || _isInitialized) return;
|
||||||
|
|
||||||
|
this.onCloseRequested = onCloseRequested;
|
||||||
|
|
||||||
|
await windowManager.ensureInitialized();
|
||||||
|
|
||||||
|
// 配置窗口选项
|
||||||
|
const windowOptions = WindowOptions(
|
||||||
|
size: Size(420, 700),
|
||||||
|
minimumSize: Size(350, 500),
|
||||||
|
center: true,
|
||||||
|
backgroundColor: Color(0x00000000),
|
||||||
|
skipTaskbar: false,
|
||||||
|
titleBarStyle: TitleBarStyle.normal,
|
||||||
|
title: '家庭网络监控',
|
||||||
|
);
|
||||||
|
|
||||||
|
await windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||||
|
await windowManager.show();
|
||||||
|
await windowManager.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加窗口监听器
|
||||||
|
windowManager.addListener(this);
|
||||||
|
|
||||||
|
// 阻止默认关闭行为
|
||||||
|
await windowManager.setPreventClose(true);
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 显示窗口
|
||||||
|
Future<void> showWindow() async {
|
||||||
|
if (!isSupported) return;
|
||||||
|
await windowManager.show();
|
||||||
|
await windowManager.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 隐藏窗口(最小化到托盘)
|
||||||
|
Future<void> hideWindow() async {
|
||||||
|
if (!isSupported) return;
|
||||||
|
await windowManager.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 最小化窗口
|
||||||
|
Future<void> minimizeWindow() async {
|
||||||
|
if (!isSupported) return;
|
||||||
|
await windowManager.minimize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 关闭窗口
|
||||||
|
Future<void> closeWindow() async {
|
||||||
|
if (!isSupported) return;
|
||||||
|
await windowManager.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置窗口始终置顶
|
||||||
|
Future<void> setAlwaysOnTop(bool isAlwaysOnTop) async {
|
||||||
|
if (!isSupported) return;
|
||||||
|
await windowManager.setAlwaysOnTop(isAlwaysOnTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 窗口关闭事件
|
||||||
|
@override
|
||||||
|
void onWindowClose() {
|
||||||
|
onCloseRequested?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 销毁
|
||||||
|
void dispose() {
|
||||||
|
if (_isInitialized) {
|
||||||
|
windowManager.removeListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -80,10 +80,9 @@ flutter:
|
|||||||
# the material Icons class.
|
# the material Icons class.
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# 资源文件
|
||||||
# assets:
|
assets:
|
||||||
# - images/a_dot_burr.jpeg
|
- assets/
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/to/resolution-aware-images
|
# https://flutter.dev/to/resolution-aware-images
|
||||||
|
|||||||
Reference in New Issue
Block a user