From 25886691ff02b65be4c5fcd54ffcb866e69ccf8c Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Sun, 10 Nov 2024 09:38:07 +0800 Subject: [PATCH 01/54] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E4=BF=AE=E6=94=B9=E7=8A=B6=E6=80=81=E6=97=A0=E6=95=88?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/page/home_base_page.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/page/home_base_page.dart b/lib/page/home_base_page.dart index e3862c8..82f8644 100644 --- a/lib/page/home_base_page.dart +++ b/lib/page/home_base_page.dart @@ -174,7 +174,8 @@ abstract class BaseHomePageState extends State { for (var host in selectHosts) { host.isUse = value; } - // syncFilterHosts(); + hostsFile.updateHostUseState(selectHosts); + syncFilterHosts(); }); } From 40445f724d37658c130eae01778d4b9670ea690b Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Wed, 19 Feb 2025 21:46:50 +0800 Subject: [PATCH 02/54] =?UTF-8?q?feat(=E8=A7=86=E5=9B=BE&=E6=95=B0?= =?UTF-8?q?=E6=8D=AE):=20=E9=87=8D=E6=9E=84=E4=B8=BB=E6=9C=BA=E8=A1=A8?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E4=BB=A5=E6=94=AF=E6=8C=81=20Syncfusion=20Da?= =?UTF-8?q?taGrid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 引入 Syncfusion DataGrid 替换原有的表格实现,增强表格的功能与可扩展性,包括列排序与自定义单元格样式支持。同时增加了 "全选" 功能,并调整了部分交互逻辑以配合新的表格组件。修改了相关依赖及配置以支持新功能。 --- lib/page/home_base_page.dart | 4 + lib/widget/app_bar/home_app_bar.dart | 24 +- lib/widget/host_base_view.dart | 4 + lib/widget/host_list.dart | 2 + lib/widget/host_table.dart | 417 +++++++++++++++++++-------- pubspec.yaml | 1 + 6 files changed, 317 insertions(+), 135 deletions(-) diff --git a/lib/page/home_base_page.dart b/lib/page/home_base_page.dart index 82f8644..e72840c 100644 --- a/lib/page/home_base_page.dart +++ b/lib/page/home_base_page.dart @@ -375,6 +375,8 @@ abstract class BaseHomePageState extends State { child: HostTable( hosts: filterHosts, selectHosts: selectHosts, + isCheckedAll: hostsFile.hosts.length == selectHosts.length, + onCheckedAllChanged: onCheckedAllChanged, onChecked: onChecked, onLink: onLink, onEdit: onEdit, @@ -393,6 +395,8 @@ abstract class BaseHomePageState extends State { child: HostList( hosts: filterHosts, selectHosts: selectHosts, + isCheckedAll: hostsFile.hosts.length == selectHosts.length, + onCheckedAllChanged: onCheckedAllChanged, onChecked: onChecked, onLink: onLink, onEdit: onEdit, diff --git a/lib/widget/app_bar/home_app_bar.dart b/lib/widget/app_bar/home_app_bar.dart index 6636dcb..427e08d 100644 --- a/lib/widget/app_bar/home_app_bar.dart +++ b/lib/widget/app_bar/home_app_bar.dart @@ -175,21 +175,12 @@ class HomeAppBar extends StatelessWidget { ], ), ), - if (editMode == EditMode.Table) + if (editMode == EditMode.Table && + MediaQuery.of(context).size.width < 1000) Table( - columnWidths: MediaQuery.of(context).size.width < 600 - ? const { - 0: FixedColumnWidth(50), - 2: FlexColumnWidth(1), - 3: FlexColumnWidth(1), - 5: FlexColumnWidth(1), - } - : const { - 0: FixedColumnWidth(50), - 2: FixedColumnWidth(100), - 3: FlexColumnWidth(2), - 5: FixedColumnWidth(150), - }, + columnWidths: const { + 0: FixedColumnWidth(50), + }, defaultVerticalAlignment: TableCellVerticalAlignment.middle, children: [tableHeader(context)], ) @@ -231,11 +222,6 @@ class HomeAppBar extends StatelessWidget { tableHeaderItem("isUse", AppLocalizations.of(context)!.status), tableHeaderItem("hosts", AppLocalizations.of(context)!.domain), tableHeaderItem("description", AppLocalizations.of(context)!.remark), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text(AppLocalizations.of(context)!.action, - style: const TextStyle(fontWeight: FontWeight.bold)), - ), ], ); } diff --git a/lib/widget/host_base_view.dart b/lib/widget/host_base_view.dart index bfbd2e9..d7d3815 100644 --- a/lib/widget/host_base_view.dart +++ b/lib/widget/host_base_view.dart @@ -4,6 +4,8 @@ import 'package:hosts/model/host_file.dart'; abstract class HostBaseView extends StatelessWidget { final List hosts; final List selectHosts; + final bool isCheckedAll; + final ValueChanged onCheckedAllChanged; final Function(int, HostsModel) onEdit; final Function(int, HostsModel) onLink; final Function(int, HostsModel) onChecked; @@ -15,6 +17,8 @@ abstract class HostBaseView extends StatelessWidget { super.key, required this.hosts, required this.selectHosts, + required this.isCheckedAll, + required this.onCheckedAllChanged, required this.onChecked, required this.onEdit, required this.onLink, diff --git a/lib/widget/host_list.dart b/lib/widget/host_list.dart index 64068aa..0ccde0c 100644 --- a/lib/widget/host_list.dart +++ b/lib/widget/host_list.dart @@ -18,6 +18,8 @@ class HostList extends HostBaseView { required super.onDelete, required super.onToggleUse, required super.onLaunchUrl, + required super.isCheckedAll, + required super.onCheckedAllChanged, }); @override diff --git a/lib/widget/host_table.dart b/lib/widget/host_table.dart index 45399ef..1cdeceb 100644 --- a/lib/widget/host_table.dart +++ b/lib/widget/host_table.dart @@ -6,12 +6,15 @@ import 'package:hosts/model/host_file.dart'; import 'package:hosts/widget/dialog/copy_dialog.dart'; import 'package:hosts/widget/dialog/test_dialog.dart'; import 'package:hosts/widget/host_base_view.dart'; +import 'package:syncfusion_flutter_datagrid/datagrid.dart'; class HostTable extends HostBaseView { const HostTable({ super.key, required super.hosts, required super.selectHosts, + required super.isCheckedAll, + required super.onCheckedAllChanged, required super.onEdit, required super.onLink, required super.onChecked, @@ -20,117 +23,291 @@ class HostTable extends HostBaseView { required super.onLaunchUrl, }); - List tableBody(BuildContext context) { - return hosts.asMap().entries.map((entry) { - final int index = entry.key; - final it = entry.value; - - bool isLink = false; - if (it.config.isNotEmpty) { - isLink = it.config["same"] != null && it.config["contrary"] != null; - } - return TableRow(children: [ - Checkbox( - value: selectHosts.contains(it), - onChanged: (bool? newValue) => onChecked(index, it), + @override + Widget build(BuildContext context) { + return SfDataGrid( + allowSorting: true, + columnWidthMode: ColumnWidthMode.fill, + gridLinesVisibility: GridLinesVisibility.none, + headerGridLinesVisibility: GridLinesVisibility.none, + source: HostDataSource( + hosts: hosts, + selectHosts: selectHosts, + onEdit: onEdit, + onLink: onLink, + onChecked: onChecked, + onDelete: onDelete, + onToggleUse: onToggleUse, + onLaunchUrl: onLaunchUrl, + context: context, + ), + columns: [ + GridColumn( + columnName: 'checkbox', + allowSorting: false, + label: Checkbox( + value: hosts.isNotEmpty && isCheckedAll, + onChanged: onCheckedAllChanged, + ), + width: 50, ), - Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: () => onLaunchUrl(it.host), - child: Text.rich(TextSpan( - children: [ - if (isLink) - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4), - child: Icon( - Icons.link, - color: Theme.of(context).colorScheme.primary, - size: 18, - ), - )), - TextSpan( - text: it.host, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold, - ), - ) - ], - )), + GridColumn( + columnName: 'host', + allowSorting: true, + label: Container( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(AppLocalizations.of(context)!.ip_address, + style: const TextStyle(fontWeight: FontWeight.bold)), + ), ), ), - Align( - alignment: Alignment.centerLeft, - child: Switch( - value: it.isUse, - onChanged: (value) { - it.isUse = value; - - final List updateUseHosts = [it]; - void updateHostStates(List hostNames, bool isUse) { - for (var tempHost - in hosts.where((item) => hostNames.contains(item.host))) { - tempHost.isUse = isUse; - updateUseHosts.add(tempHost); - } - } - - // 相同 - if (it.config["same"] != null) { - updateHostStates( - (it.config["same"] as List).cast(), value); - } - // 相反 - if (it.config["contrary"] != null) { - updateHostStates( - (it.config["contrary"] as List).cast(), - !value); - } - - onToggleUse(updateUseHosts); - }, + GridColumn( + columnName: 'use', + allowSorting: true, + label: Container( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(AppLocalizations.of(context)!.status, + style: const TextStyle(fontWeight: FontWeight.bold)), + ), ), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text.rich(TextSpan( - children: _buildTextSpans(it.hosts, context), - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold))), + GridColumn( + columnName: 'hosts', + allowSorting: true, + label: Container( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(AppLocalizations.of(context)!.domain, + style: const TextStyle(fontWeight: FontWeight.bold)), + ), + ), + ), + GridColumn( + columnName: 'description', + allowSorting: true, + label: Container( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(AppLocalizations.of(context)!.remark, + style: const TextStyle(fontWeight: FontWeight.bold)), + ), + ), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: SelectableText(it.description), + GridColumn( + columnName: 'actions', + allowSorting: false, + label: Container( + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(AppLocalizations.of(context)!.action, + style: const TextStyle(fontWeight: FontWeight.bold)), + ), + ), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - IconButton( - onPressed: () => onEdit(index, it), - icon: const Icon(Icons.edit), + ], + ); + } +} + +class HostDataSource extends DataGridSource { + HostDataSource({ + required this.hosts, + required this.selectHosts, + required this.onEdit, + required this.onLink, + required this.onChecked, + required this.onDelete, + required this.onToggleUse, + required this.onLaunchUrl, + required this.context, + }); + + final List hosts; + final List selectHosts; + final Function(int, HostsModel) onEdit; + final Function(int, HostsModel) onLink; + final Function(int, HostsModel) onChecked; + final Function(List) onDelete; + final Function(List) onToggleUse; + final Function(String) onLaunchUrl; + final BuildContext context; + + @override + List get rows => hosts.map((host) { + bool isLink = false; + if (host.config.isNotEmpty) { + isLink = + host.config["same"] != null && host.config["contrary"] != null; + } + + return DataGridRow(cells: [ + DataGridCell( + columnName: 'checkbox', + value: Checkbox( + value: selectHosts.contains(host), + onChanged: (bool? newValue) => + onChecked(hosts.indexOf(host), host), + ), + ), + DataGridCell( + columnName: 'host', + value: MyDataGridCell( + value: host.host, + child: GestureDetector( + onTap: () => onLaunchUrl(host.host), + child: Container( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + children: [ + if (isLink) + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 4), + child: Icon( + Icons.link, + color: Theme.of(context).colorScheme.primary, + size: 18, + ), + ), + ), + TextSpan( + text: host.host, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + )), + ), + DataGridCell( + columnName: 'use', + value: MyDataGridCell( + value: host.isUse, + child: Switch( + value: host.isUse, + onChanged: (value) { + host.isUse = value; + final List updateUseHosts = [host]; + void updateHostStates(List hostNames, bool isUse) { + for (var tempHost in hosts + .where((item) => hostNames.contains(item.host))) { + tempHost.isUse = isUse; + updateUseHosts.add(tempHost); + } + } + + if (host.config["same"] != null) { + updateHostStates( + (host.config["same"] as List).cast(), + value); + } + if (host.config["contrary"] != null) { + updateHostStates( + (host.config["contrary"] as List) + .cast(), + !value); + } + onToggleUse(updateUseHosts); + }, ), - const SizedBox(width: 8), - IconButton( - onPressed: () => onDelete([it]), - icon: const Icon(Icons.delete_outline), + ), + ), + DataGridCell( + columnName: 'hosts', + value: MyDataGridCell( + value: host.hosts, + child: Container( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + children: _buildTextSpans(host.hosts, context), + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ), ), - const SizedBox(width: 8), - buildMoreButton(context, index, it), - ], + ), + ), + DataGridCell( + columnName: 'description', + value: MyDataGridCell( + value: host.description, + child: Container( + alignment: Alignment.centerLeft, + child: SelectableText(host.description)), + ), + ), + DataGridCell( + columnName: 'actions', + value: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + onPressed: () => onEdit(hosts.indexOf(host), host), + icon: const Icon(Icons.edit), + ), + const SizedBox(width: 8), + IconButton( + onPressed: () => onDelete([host]), + icon: const Icon(Icons.delete_outline), + ), + const SizedBox(width: 8), + buildMoreButton(context, hosts.indexOf(host), host), + ], + ), ), - ) - ]); - }).toList(); + ]); + }).toList(); + + @override + Future performSorting(List rows) async { + // sortedColumns 是父类提供的属性,里面包含排序列及排序方向等信息。 + if (sortedColumns.isNotEmpty) { + final sortColumn = sortedColumns.first; + // SortColumnDetails 通常包含 columnName 和 sortDirection + final String columnName = sortColumn.name; + final bool isAscending = + sortColumn.sortDirection == DataGridSortDirection.ascending; + if (!["checkbox", "actions"].contains(columnName)) { + rows.sort((a, b) { + final valueA = a + .getCells() + .firstWhere((cell) => cell.columnName == columnName) + .value + .toString(); + final valueB = b + .getCells() + .firstWhere((cell) => cell.columnName == columnName) + .value + .toString(); + print(valueA); + print(valueB); + + return isAscending + ? valueA.compareTo(valueB) + : valueB.compareTo(valueA); + }); + } + } + // notifyListeners(); } List _buildTextSpans(List hosts, BuildContext context) { List textSpans = []; - for (int i = 0; i < hosts.length; i++) { textSpans.add(TextSpan( text: hosts[i], @@ -139,16 +316,16 @@ class HostTable extends HostBaseView { onLaunchUrl(hosts[i]); }, )); - if (i < hosts.length - 1) { textSpans.add(TextSpan( - text: ' - ', - style: TextStyle( - color: Theme.of(context).colorScheme.inverseSurface, - fontWeight: FontWeight.w900))); + text: ' - ', + style: TextStyle( + color: Theme.of(context).colorScheme.inverseSurface, + fontWeight: FontWeight.w900, + ), + )); } } - return textSpans; } @@ -185,7 +362,6 @@ class HostTable extends HostBaseView { "value": 3 }, ]; - return list .where((item) => !(item["value"] == 2 && kIsWeb)) .map((item) { @@ -205,18 +381,27 @@ class HostTable extends HostBaseView { } @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: Table( - columnWidths: const { - 0: FixedColumnWidth(50), - 2: FixedColumnWidth(100), - 3: FlexColumnWidth(2), - 5: FixedColumnWidth(180), - }, - defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: tableBody(context), - ), + DataGridRowAdapter? buildRow(DataGridRow row) { + return DataGridRowAdapter( + cells: + row.getCells().map((dataCell) => dataCell.value as Widget).toList(), ); } } + +class MyDataGridCell extends StatelessWidget { + final Widget child; + final dynamic value; + + const MyDataGridCell({super.key, required this.child, required this.value}); + + @override + Widget build(BuildContext context) { + return child; + } + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return value.toString(); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 14dfba4..52c4d6f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: sdk: flutter intl: any file_picker: ^8.1.2 + syncfusion_flutter_datagrid: ^28.2.6 dev_dependencies: flutter_test: From 2669f0cc8cbf2b2e21b18a86a1c09fd8f9d34b75 Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Wed, 21 May 2025 23:21:35 +0800 Subject: [PATCH 03/54] =?UTF-8?q?feat(=E6=95=B0=E6=8D=AE):=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81MacOS=E4=BF=AE=E6=94=B9Hosts=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macos/Runner/AppDelegate.swift | 4 + macos/Runner/DebugProfile.entitlements | 10 ++- macos/Runner/Info.plist | 8 ++ macos/Runner/MainFlutterWindow.swift | 102 +++++++++++++++++++++++-- macos/Runner/Release.entitlements | 8 ++ 5 files changed, 123 insertions(+), 9 deletions(-) diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index 8e02df2..b3c1761 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index dddb8a3..dc3d49d 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -3,10 +3,18 @@ com.apple.security.app-sandbox - + com.apple.security.cs.allow-jit com.apple.security.network.server + com.apple.security.files.user-selected.read-write + + com.apple.security.files.all + + com.apple.security.temporary-exception.files.absolute-path.read-write + + /etc/hosts + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index 4789daa..04c5c6e 100644 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -20,6 +20,8 @@ $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) + LSApplicationCategoryType + public.app-category.utilities LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright @@ -28,5 +30,11 @@ MainMenu NSPrincipalClass NSApplication + NSAppleEventsUsageDescription + 此应用需要管理员权限以修改系统hosts文件 + NSAppleMusicUsageDescription + 此应用需要访问系统资源 + NSSystemAdministrationUsageDescription + 此应用需要管理员权限修改系统hosts文件 diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 3cc05eb..da5d8ba 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -1,15 +1,101 @@ import Cocoa import FlutterMacOS +import Security class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) + private var hostsChannel: FlutterMethodChannel? - RegisterGeneratedPlugins(registry: flutterViewController) + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) - super.awakeFromNib() - } + // 设置方法通道 + hostsChannel = FlutterMethodChannel( + name: "top.webb_l.hosts/system", + binaryMessenger: flutterViewController.engine.binaryMessenger) + + hostsChannel?.setMethodCallHandler { [weak self] (call, result) in + print("收到方法调用: \(call.method)") + switch call.method { + case "modifyHostsFile": + if let args = call.arguments as? [String: Any], + let hostsContent = args["content"] as? String { + self?.modifyHostsFile(content: hostsContent, completion: { success, errorMessage in + if success { + result(true) + } else { + result(FlutterError(code: "HOSTS_MODIFY_ERROR", + message: errorMessage, + details: nil)) + } + }) + } else { + result(FlutterError(code: "INVALID_ARGUMENTS", + message: "缺少内容参数", + details: nil)) + } + default: + result(FlutterMethodNotImplemented) + } + } + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } + + func modifyHostsFile(content: String, completion: @escaping (Bool, String?) -> Void) { + print("尝试修改 Hosts 文件...") + + guard !content.isEmpty else { + print("内容不能为空") + completion(false, "Hosts 文件内容不能为空") + return + } + + // 1. 将新内容写入临时文件 + let tempFilePath = NSTemporaryDirectory().appending("top.webb_l.hosts.temp") + do { + try content.write(toFile: tempFilePath, atomically: true, encoding: .utf8) + } catch { + print("写入临时文件失败: \(error)") + completion(false, "写入临时文件失败: \(error.localizedDescription)") + return + } + + // 2. 使用osascript尝试以管理员身份运行命令 + let scriptProcess = Process() + scriptProcess.launchPath = "/usr/bin/osascript" + scriptProcess.arguments = [ + "-e", "do shell script \"/bin/cp \(tempFilePath) /etc/hosts\" with administrator privileges" + ] + + let pipe = Pipe() + scriptProcess.standardError = pipe + scriptProcess.standardOutput = pipe + + do { + try scriptProcess.run() + scriptProcess.waitUntilExit() + + if scriptProcess.terminationStatus == 0 { + print("成功修改hosts文件") + try? FileManager.default.removeItem(atPath: tempFilePath) + completion(true, nil) + } else { + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let errorOutput = String(data: data, encoding: .utf8) ?? "未知错误" + print("执行失败: \(errorOutput)") + completion(false, "操作失败: \(errorOutput)") + } + } catch { + print("启动进程失败: \(error)") + completion(false, "无法启动进程: \(error.localizedDescription)") + } + + // 清理临时文件 + try? FileManager.default.removeItem(atPath: tempFilePath) + } } diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 852fa1a..1a0843a 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -3,6 +3,14 @@ com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + com.apple.security.files.all + + com.apple.security.temporary-exception.files.absolute-path.read-write + + /etc/hosts + From 7202abcd1465c9f3c365df71618eb24f5ba11cf8 Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Sun, 8 Jun 2025 23:48:04 +0800 Subject: [PATCH 04/54] =?UTF-8?q?feat(=E6=9C=AC=E5=9C=B0=E5=8C=96):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9B=BD=E9=99=85=E5=8C=96=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=B9=B6=E6=96=B0=E5=A2=9E=E4=B8=AD=E8=8B=B1=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=8C=96=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加国际化机制,支持语言切换。新增中英文语言资源文件,覆盖常见UI文本及提示,优化用户体验。 --- l10n.yaml | 5 +- lib/l10n/app_localizations.dart | 535 +++++++++++++++++++ lib/l10n/app_localizations_en.dart | 224 ++++++++ lib/l10n/app_localizations_zh.dart | 218 ++++++++ lib/page/history_page.dart | 2 +- lib/page/home_base_page.dart | 2 +- lib/page/home_page.dart | 2 +- lib/page/host_page.dart | 2 +- lib/page/simple_home_page.dart | 2 +- lib/widget/app_bar/home_app_bar.dart | 4 +- lib/widget/dialog/copy_dialog.dart | 2 +- lib/widget/dialog/copy_multiple_dialog.dart | 2 +- lib/widget/dialog/dialog.dart | 2 +- lib/widget/dialog/link_dialog.dart | 2 +- lib/widget/dialog/test_dialog.dart | 2 +- lib/widget/error/error_empty.dart | 2 +- lib/widget/home_drawer.dart | 4 +- lib/widget/host_list.dart | 2 +- lib/widget/host_table.dart | 2 +- lib/widget/snakbar.dart | 3 +- lib/widget/text_field/search_text_field.dart | 2 +- 21 files changed, 1000 insertions(+), 21 deletions(-) create mode 100644 lib/l10n/app_localizations.dart create mode 100644 lib/l10n/app_localizations_en.dart create mode 100644 lib/l10n/app_localizations_zh.dart diff --git a/l10n.yaml b/l10n.yaml index 4e6692e..9fcf641 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,3 +1,4 @@ arb-dir: lib/l10n -template-arb-file: app_en.arb -output-localization-file: app_localizations.dart \ No newline at end of file +template-arb-file: app_zh.arb +output-localization-file: app_localizations.dart +output-class: AppLocalizations diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart new file mode 100644 index 0000000..bfc45ac --- /dev/null +++ b/lib/l10n/app_localizations.dart @@ -0,0 +1,535 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; +import 'app_localizations_zh.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en'), + Locale('zh') + ]; + + /// No description provided for @app_name. + /// + /// In zh, this message translates to: + /// **'Hosts 编辑器'** + String get app_name; + + /// No description provided for @ok. + /// + /// In zh, this message translates to: + /// **'确认'** + String get ok; + + /// No description provided for @cancel. + /// + /// In zh, this message translates to: + /// **'取消'** + String get cancel; + + /// No description provided for @add. + /// + /// In zh, this message translates to: + /// **'新增'** + String get add; + + /// No description provided for @create. + /// + /// In zh, this message translates to: + /// **'创建'** + String get create; + + /// No description provided for @edit. + /// + /// In zh, this message translates to: + /// **'编辑'** + String get edit; + + /// No description provided for @remove. + /// + /// In zh, this message translates to: + /// **'删除'** + String get remove; + + /// No description provided for @abort. + /// + /// In zh, this message translates to: + /// **'舍弃'** + String get abort; + + /// No description provided for @remark. + /// + /// In zh, this message translates to: + /// **'备注'** + String get remark; + + /// No description provided for @info. + /// + /// In zh, this message translates to: + /// **'信息'** + String get info; + + /// No description provided for @input_remark. + /// + /// In zh, this message translates to: + /// **'请输入备注'** + String get input_remark; + + /// No description provided for @remove_single_tip. + /// + /// In zh, this message translates to: + /// **'您确认需要删除《{name}》吗?'** + String remove_single_tip(Object name); + + /// No description provided for @remove_multiple_tip. + /// + /// In zh, this message translates to: + /// **'确认删除选中的{count}条记录吗?'** + String remove_multiple_tip(Object count); + + /// No description provided for @save. + /// + /// In zh, this message translates to: + /// **'保存'** + String get save; + + /// No description provided for @save_create_history. + /// + /// In zh, this message translates to: + /// **'保存并生成历史'** + String get save_create_history; + + /// No description provided for @default_hosts_text. + /// + /// In zh, this message translates to: + /// **'默认'** + String get default_hosts_text; + + /// No description provided for @input_search. + /// + /// In zh, this message translates to: + /// **'搜索...'** + String get input_search; + + /// No description provided for @use. + /// + /// In zh, this message translates to: + /// **'使用'** + String get use; + + /// No description provided for @prev. + /// + /// In zh, this message translates to: + /// **'上一个'** + String get prev; + + /// No description provided for @next. + /// + /// In zh, this message translates to: + /// **'下一个'** + String get next; + + /// No description provided for @ip_address. + /// + /// In zh, this message translates to: + /// **'IP地址'** + String get ip_address; + + /// No description provided for @input_ip_address. + /// + /// In zh, this message translates to: + /// **'请输入IP地址'** + String get input_ip_address; + + /// No description provided for @input_ip_address_hint. + /// + /// In zh, this message translates to: + /// **'支持IPV4和IPV6'** + String get input_ip_address_hint; + + /// No description provided for @input_ipv4_ipv6. + /// + /// In zh, this message translates to: + /// **'请输入IPV4或IPV6地址'** + String get input_ipv4_ipv6; + + /// No description provided for @create_host_template. + /// + /// In zh, this message translates to: + /// **'模板1 - 未启用:\n# 127.0.0.1 flutter.dev\n\n模板2 - 没备注:\n127.0.0.1 flutter.dev\n\n模板3 - 有备注:\n# Flutter\n127.0.0.1 flutter.dev\n\n...'** + String get create_host_template; + + /// No description provided for @history. + /// + /// In zh, this message translates to: + /// **'历史'** + String get history; + + /// No description provided for @domain. + /// + /// In zh, this message translates to: + /// **'域名'** + String get domain; + + /// No description provided for @input_domain. + /// + /// In zh, this message translates to: + /// **'请输入域名'** + String get input_domain; + + /// No description provided for @error_domain_tip. + /// + /// In zh, this message translates to: + /// **'请不要输入空格(“ ”)和换行(“\n”)。'** + String get error_domain_tip; + + /// No description provided for @error_exist_domain_tip. + /// + /// In zh, this message translates to: + /// **'该域名已存在'** + String get error_exist_domain_tip; + + /// No description provided for @history_remove_tip. + /// + /// In zh, this message translates to: + /// **'历史记录将在5秒后被移除。点击右侧按钮以取消。'** + String get history_remove_tip; + + /// No description provided for @error_null_data. + /// + /// In zh, this message translates to: + /// **'找不到数据'** + String get error_null_data; + + /// No description provided for @error_use_fail. + /// + /// In zh, this message translates to: + /// **'使用失败'** + String get error_use_fail; + + /// No description provided for @error_not_save. + /// + /// In zh, this message translates to: + /// **'当前文件包含未保存的更改'** + String get error_not_save; + + /// No description provided for @error_save_fail. + /// + /// In zh, this message translates to: + /// **'保存失败'** + String get error_save_fail; + + /// No description provided for @table. + /// + /// In zh, this message translates to: + /// **'表格'** + String get table; + + /// No description provided for @text. + /// + /// In zh, this message translates to: + /// **'文本'** + String get text; + + /// No description provided for @copy. + /// + /// In zh, this message translates to: + /// **'复制'** + String get copy; + + /// No description provided for @status. + /// + /// In zh, this message translates to: + /// **'状态'** + String get status; + + /// No description provided for @action. + /// + /// In zh, this message translates to: + /// **'操作'** + String get action; + + /// No description provided for @copy_selected. + /// + /// In zh, this message translates to: + /// **'复制选中'** + String get copy_selected; + + /// No description provided for @delete_selected. + /// + /// In zh, this message translates to: + /// **'删除选中'** + String get delete_selected; + + /// No description provided for @reduction. + /// + /// In zh, this message translates to: + /// **'还原'** + String get reduction; + + /// No description provided for @advanced_settings. + /// + /// In zh, this message translates to: + /// **'高级设置'** + String get advanced_settings; + + /// No description provided for @copy_to_tip. + /// + /// In zh, this message translates to: + /// **'已复制到剪贴板'** + String get copy_to_tip; + + /// No description provided for @warning. + /// + /// In zh, this message translates to: + /// **'警告'** + String get warning; + + /// No description provided for @warning_different. + /// + /// In zh, this message translates to: + /// **'系统 Hosts 文件与当前文件不一致!\n如果您不做覆盖处理,修改后保存当前文件会导致系统文件的数据被覆盖。'** + String get warning_different; + + /// No description provided for @warning_different_covering_system. + /// + /// In zh, this message translates to: + /// **'当前覆盖系统'** + String get warning_different_covering_system; + + /// No description provided for @warning_different_covering_current. + /// + /// In zh, this message translates to: + /// **'系统覆盖当前'** + String get warning_different_covering_current; + + /// No description provided for @error_not_update_save_tip. + /// + /// In zh, this message translates to: + /// **'内容已更新!请确保保存您的更改,以免丢失重要信息。'** + String get error_not_update_save_tip; + + /// No description provided for @error_not_update_save_permission_tip. + /// + /// In zh, this message translates to: + /// **'该文件已被使用保存时需要管理员权限。'** + String get error_not_update_save_permission_tip; + + /// No description provided for @test. + /// + /// In zh, this message translates to: + /// **'测试'** + String get test; + + /// No description provided for @error_test_ip_notfound. + /// + /// In zh, this message translates to: + /// **'未找到 IP 地址'** + String get error_test_ip_notfound; + + /// No description provided for @error_test_ip_different. + /// + /// In zh, this message translates to: + /// **'找到 IP 地址和设置 IP 地址并不一致'** + String get error_test_ip_different; + + /// No description provided for @link. + /// + /// In zh, this message translates to: + /// **'关联'** + String get link; + + /// No description provided for @delete. + /// + /// In zh, this message translates to: + /// **'删除'** + String get delete; + + /// No description provided for @open_file. + /// + /// In zh, this message translates to: + /// **'打开文件'** + String get open_file; + + /// No description provided for @error_open_file. + /// + /// In zh, this message translates to: + /// **'文件读取失败'** + String get error_open_file; + + /// No description provided for @error_open_file_size. + /// + /// In zh, this message translates to: + /// **'读取文件不能大于10MB'** + String get error_open_file_size; + + /// No description provided for @about. + /// + /// In zh, this message translates to: + /// **'关于'** + String get about; + + /// No description provided for @about_description. + /// + /// In zh, this message translates to: + /// **'Hosts Editor 是一个使用 Flutter 开发的应用程序,旨在简化 Linux、MacOS、Windows 系统上 hosts 文件的编辑和管理。\n该工具提供了一个用户友好的界面,使用户能够轻松地添加、修改和删除 hosts 文件中的条目。'** + String get about_description; + + /// No description provided for @link_contrary. + /// + /// In zh, this message translates to: + /// **'相反'** + String get link_contrary; + + /// No description provided for @link_same. + /// + /// In zh, this message translates to: + /// **'相同'** + String get link_same; + + /// No description provided for @link_and_description. + /// + /// In zh, this message translates to: + /// **'当 '** + String get link_and_description; + + /// No description provided for @link_status_update_description. + /// + /// In zh, this message translates to: + /// **' 状态变化时,下列数据切换为'** + String get link_status_update_description; + + /// No description provided for @link_status_description. + /// + /// In zh, this message translates to: + /// **'状态:'** + String get link_status_description; + + /// No description provided for @form. + /// + /// In zh, this message translates to: + /// **'表单'** + String get form; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['en', 'zh'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': + return AppLocalizationsEn(); + case 'zh': + return AppLocalizationsZh(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.'); +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart new file mode 100644 index 0000000..f305b4b --- /dev/null +++ b/lib/l10n/app_localizations_en.dart @@ -0,0 +1,224 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get app_name => 'Hosts Editor'; + + @override + String get ok => 'Confirm'; + + @override + String get cancel => 'Cancel'; + + @override + String get add => 'Add'; + + @override + String get create => 'Create'; + + @override + String get edit => 'Edit'; + + @override + String get remove => 'Delete'; + + @override + String get abort => 'Discard'; + + @override + String get remark => 'Remark'; + + @override + String get info => 'Information'; + + @override + String get input_remark => 'Please enter a remark'; + + @override + String remove_single_tip(Object name) { + return 'Are you sure you want to delete \'$name\'?'; + } + + @override + String remove_multiple_tip(Object count) { + return 'Are you sure you want to delete the selected $count records?'; + } + + @override + String get save => 'Save'; + + @override + String get save_create_history => 'Save and generate history'; + + @override + String get default_hosts_text => 'Default'; + + @override + String get input_search => 'Search...'; + + @override + String get use => 'Use'; + + @override + String get prev => 'Previous'; + + @override + String get next => 'Next'; + + @override + String get ip_address => 'IP Address'; + + @override + String get input_ip_address => 'Please enter IP address'; + + @override + String get input_ip_address_hint => 'Supports IPV4 and IPV6'; + + @override + String get input_ipv4_ipv6 => 'Please enter IPV4 or IPV6 address'; + + @override + String get create_host_template => + 'Template 1 - Not enabled:\n# 127.0.0.1 flutter.dev\n\nTemplate 2 - No remark:\n127.0.0.1 flutter.dev\n\nTemplate 3 - With remark:\n# Flutter\n127.0.0.1 flutter.dev\n\n...'; + + @override + String get history => 'History'; + + @override + String get domain => 'Domain'; + + @override + String get input_domain => 'Please enter domain'; + + @override + String get error_domain_tip => + 'Please do not enter spaces (\' \') or new lines (\'\n\').'; + + @override + String get error_exist_domain_tip => 'This domain already exists'; + + @override + String get history_remove_tip => + 'The history will be removed in 5 seconds. Click the button on the right to cancel.'; + + @override + String get error_null_data => 'No data found'; + + @override + String get error_use_fail => 'Use failed'; + + @override + String get error_not_save => 'The current file contains unsaved changes'; + + @override + String get error_save_fail => 'Save failed'; + + @override + String get table => 'Table'; + + @override + String get text => 'Text'; + + @override + String get copy => 'Copy'; + + @override + String get status => 'Status'; + + @override + String get action => 'Action'; + + @override + String get copy_selected => 'Copy selected'; + + @override + String get delete_selected => 'Delete selected'; + + @override + String get reduction => 'Restore'; + + @override + String get advanced_settings => 'Advanced Settings'; + + @override + String get copy_to_tip => 'Copied to clipboard'; + + @override + String get warning => 'Warning'; + + @override + String get warning_different => + 'The system Hosts file is inconsistent with the current file!\nIf you do not handle the overwrite, saving the current file after modification will cause the system file data to be overwritten.'; + + @override + String get warning_different_covering_system => 'Currently covering system'; + + @override + String get warning_different_covering_current => 'System covering current'; + + @override + String get error_not_update_save_tip => + 'Content has been updated! Please ensure to save your changes to avoid losing important information.'; + + @override + String get error_not_update_save_permission_tip => + 'This file is in use and requires administrator permissions to save.'; + + @override + String get test => 'Test'; + + @override + String get error_test_ip_notfound => 'IP address not found'; + + @override + String get error_test_ip_different => + 'Found IP address does not match the set IP address'; + + @override + String get link => 'Link'; + + @override + String get delete => 'Delete'; + + @override + String get open_file => 'Open file'; + + @override + String get error_open_file => 'Failed to read the file'; + + @override + String get error_open_file_size => 'The file size cannot exceed 10MB'; + + @override + String get about => 'About'; + + @override + String get about_description => + 'Hosts Editor is an application developed using Flutter, designed to simplify the editing and management of the hosts file on Linux, MacOS, and Windows systems.\nThis tool provides a user-friendly interface that allows users to easily add, modify, and delete entries in the hosts file.'; + + @override + String get link_contrary => 'Contrary'; + + @override + String get link_same => 'Same'; + + @override + String get link_and_description => 'When '; + + @override + String get link_status_update_description => + ' the status changes, the following data switches to'; + + @override + String get link_status_description => 'Status:'; + + @override + String get form => 'Form'; +} diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart new file mode 100644 index 0000000..7437689 --- /dev/null +++ b/lib/l10n/app_localizations_zh.dart @@ -0,0 +1,218 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Chinese (`zh`). +class AppLocalizationsZh extends AppLocalizations { + AppLocalizationsZh([String locale = 'zh']) : super(locale); + + @override + String get app_name => 'Hosts 编辑器'; + + @override + String get ok => '确认'; + + @override + String get cancel => '取消'; + + @override + String get add => '新增'; + + @override + String get create => '创建'; + + @override + String get edit => '编辑'; + + @override + String get remove => '删除'; + + @override + String get abort => '舍弃'; + + @override + String get remark => '备注'; + + @override + String get info => '信息'; + + @override + String get input_remark => '请输入备注'; + + @override + String remove_single_tip(Object name) { + return '您确认需要删除《$name》吗?'; + } + + @override + String remove_multiple_tip(Object count) { + return '确认删除选中的$count条记录吗?'; + } + + @override + String get save => '保存'; + + @override + String get save_create_history => '保存并生成历史'; + + @override + String get default_hosts_text => '默认'; + + @override + String get input_search => '搜索...'; + + @override + String get use => '使用'; + + @override + String get prev => '上一个'; + + @override + String get next => '下一个'; + + @override + String get ip_address => 'IP地址'; + + @override + String get input_ip_address => '请输入IP地址'; + + @override + String get input_ip_address_hint => '支持IPV4和IPV6'; + + @override + String get input_ipv4_ipv6 => '请输入IPV4或IPV6地址'; + + @override + String get create_host_template => + '模板1 - 未启用:\n# 127.0.0.1 flutter.dev\n\n模板2 - 没备注:\n127.0.0.1 flutter.dev\n\n模板3 - 有备注:\n# Flutter\n127.0.0.1 flutter.dev\n\n...'; + + @override + String get history => '历史'; + + @override + String get domain => '域名'; + + @override + String get input_domain => '请输入域名'; + + @override + String get error_domain_tip => '请不要输入空格(“ ”)和换行(“\n”)。'; + + @override + String get error_exist_domain_tip => '该域名已存在'; + + @override + String get history_remove_tip => '历史记录将在5秒后被移除。点击右侧按钮以取消。'; + + @override + String get error_null_data => '找不到数据'; + + @override + String get error_use_fail => '使用失败'; + + @override + String get error_not_save => '当前文件包含未保存的更改'; + + @override + String get error_save_fail => '保存失败'; + + @override + String get table => '表格'; + + @override + String get text => '文本'; + + @override + String get copy => '复制'; + + @override + String get status => '状态'; + + @override + String get action => '操作'; + + @override + String get copy_selected => '复制选中'; + + @override + String get delete_selected => '删除选中'; + + @override + String get reduction => '还原'; + + @override + String get advanced_settings => '高级设置'; + + @override + String get copy_to_tip => '已复制到剪贴板'; + + @override + String get warning => '警告'; + + @override + String get warning_different => + '系统 Hosts 文件与当前文件不一致!\n如果您不做覆盖处理,修改后保存当前文件会导致系统文件的数据被覆盖。'; + + @override + String get warning_different_covering_system => '当前覆盖系统'; + + @override + String get warning_different_covering_current => '系统覆盖当前'; + + @override + String get error_not_update_save_tip => '内容已更新!请确保保存您的更改,以免丢失重要信息。'; + + @override + String get error_not_update_save_permission_tip => '该文件已被使用保存时需要管理员权限。'; + + @override + String get test => '测试'; + + @override + String get error_test_ip_notfound => '未找到 IP 地址'; + + @override + String get error_test_ip_different => '找到 IP 地址和设置 IP 地址并不一致'; + + @override + String get link => '关联'; + + @override + String get delete => '删除'; + + @override + String get open_file => '打开文件'; + + @override + String get error_open_file => '文件读取失败'; + + @override + String get error_open_file_size => '读取文件不能大于10MB'; + + @override + String get about => '关于'; + + @override + String get about_description => + 'Hosts Editor 是一个使用 Flutter 开发的应用程序,旨在简化 Linux、MacOS、Windows 系统上 hosts 文件的编辑和管理。\n该工具提供了一个用户友好的界面,使用户能够轻松地添加、修改和删除 hosts 文件中的条目。'; + + @override + String get link_contrary => '相反'; + + @override + String get link_same => '相同'; + + @override + String get link_and_description => '当 '; + + @override + String get link_status_update_description => ' 状态变化时,下列数据切换为'; + + @override + String get link_status_description => '状态:'; + + @override + String get form => '表单'; +} diff --git a/lib/page/history_page.dart b/lib/page/history_page.dart index 90c369a..3507656 100644 --- a/lib/page/history_page.dart +++ b/lib/page/history_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/simple_host_file.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/widget/countdown_timer.dart'; diff --git a/lib/page/home_base_page.dart b/lib/page/home_base_page.dart index e72840c..4284b33 100644 --- a/lib/page/home_base_page.dart +++ b/lib/page/home_base_page.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/enums.dart'; import 'package:hosts/model/host_file.dart'; import 'package:hosts/model/simple_host_file.dart'; diff --git a/lib/page/home_page.dart b/lib/page/home_page.dart index 3869805..669e344 100644 --- a/lib/page/home_page.dart +++ b/lib/page/home_page.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/enums.dart'; import 'package:hosts/model/host_file.dart'; import 'package:hosts/model/simple_host_file.dart'; diff --git a/lib/page/host_page.dart b/lib/page/host_page.dart index 66b4256..7e0caf2 100644 --- a/lib/page/host_page.dart +++ b/lib/page/host_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/enums.dart'; import 'package:hosts/model/host_file.dart'; import 'package:hosts/util/regexp_util.dart'; diff --git a/lib/page/simple_home_page.dart b/lib/page/simple_home_page.dart index ec7b24e..aa67cb6 100644 --- a/lib/page/simple_home_page.dart +++ b/lib/page/simple_home_page.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/enums.dart'; import 'package:hosts/page/home_base_page.dart'; import 'package:hosts/widget/app_bar/home_app_bar.dart'; diff --git a/lib/widget/app_bar/home_app_bar.dart b/lib/widget/app_bar/home_app_bar.dart index 427e08d..7bbab9a 100644 --- a/lib/widget/app_bar/home_app_bar.dart +++ b/lib/widget/app_bar/home_app_bar.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/enums.dart'; import 'package:hosts/model/global_settings.dart'; import 'package:hosts/model/host_file.dart'; @@ -32,7 +32,7 @@ class HomeAppBar extends StatelessWidget { final SimpleHostFileHistory? selectHistory; final List history; final ValueChanged onSwitchHosts; - final ValueChanged onHistoryChanged; + final ValueChanged onHistoryChanged; const HomeAppBar({ super.key, diff --git a/lib/widget/dialog/copy_dialog.dart b/lib/widget/dialog/copy_dialog.dart index f6baca8..5b4ae27 100644 --- a/lib/widget/dialog/copy_dialog.dart +++ b/lib/widget/dialog/copy_dialog.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; Future copyDialog( diff --git a/lib/widget/dialog/copy_multiple_dialog.dart b/lib/widget/dialog/copy_multiple_dialog.dart index 49dccb9..dc0f35f 100644 --- a/lib/widget/dialog/copy_multiple_dialog.dart +++ b/lib/widget/dialog/copy_multiple_dialog.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; class CopyMultipleDialog extends StatelessWidget { diff --git a/lib/widget/dialog/dialog.dart b/lib/widget/dialog/dialog.dart index 2ea454a..5eedca7 100644 --- a/lib/widget/dialog/dialog.dart +++ b/lib/widget/dialog/dialog.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; Future hostConfigDialog(BuildContext context, [String defaultText = ""]) { diff --git a/lib/widget/dialog/link_dialog.dart b/lib/widget/dialog/link_dialog.dart index f1ce2d0..31ca974 100644 --- a/lib/widget/dialog/link_dialog.dart +++ b/lib/widget/dialog/link_dialog.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; Future>?> linkDialog( diff --git a/lib/widget/dialog/test_dialog.dart b/lib/widget/dialog/test_dialog.dart index 903d0db..755194a 100644 --- a/lib/widget/dialog/test_dialog.dart +++ b/lib/widget/dialog/test_dialog.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; Future testDialog(BuildContext context, HostsModel host) { diff --git a/lib/widget/error/error_empty.dart b/lib/widget/error/error_empty.dart index b26ab9f..36eb3f4 100644 --- a/lib/widget/error/error_empty.dart +++ b/lib/widget/error/error_empty.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; class ErrorEmpty extends StatelessWidget { const ErrorEmpty({super.key}); diff --git a/lib/widget/home_drawer.dart b/lib/widget/home_drawer.dart index e3c51db..314ad76 100644 --- a/lib/widget/home_drawer.dart +++ b/lib/widget/home_drawer.dart @@ -1,7 +1,7 @@ import "dart:io"; import "package:flutter/material.dart"; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import "package:hosts/model/simple_host_file.dart"; import "package:hosts/util/file_manager.dart"; import "package:hosts/util/settings_manager.dart"; @@ -268,4 +268,4 @@ class _HomeDrawerState extends State { }, ); } -} +} \ No newline at end of file diff --git a/lib/widget/host_list.dart b/lib/widget/host_list.dart index 0ccde0c..1511ad2 100644 --- a/lib/widget/host_list.dart +++ b/lib/widget/host_list.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; import 'package:hosts/widget/dialog/copy_dialog.dart'; import 'package:hosts/widget/dialog/test_dialog.dart'; diff --git a/lib/widget/host_table.dart b/lib/widget/host_table.dart index 1cdeceb..4e2cea5 100644 --- a/lib/widget/host_table.dart +++ b/lib/widget/host_table.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; import 'package:hosts/widget/dialog/copy_dialog.dart'; import 'package:hosts/widget/dialog/test_dialog.dart'; diff --git a/lib/widget/snakbar.dart b/lib/widget/snakbar.dart index f112871..6f2f07b 100644 --- a/lib/widget/snakbar.dart +++ b/lib/widget/snakbar.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; void deleteMultiple( BuildContext context, List array, VoidCallback onRemove) { if (array.isEmpty) return; + ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(array.length == 1 ? AppLocalizations.of(context)!.remove_single_tip(array.first) diff --git a/lib/widget/text_field/search_text_field.dart b/lib/widget/text_field/search_text_field.dart index 47e8aea..594d270 100644 --- a/lib/widget/text_field/search_text_field.dart +++ b/lib/widget/text_field/search_text_field.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hosts/l10n/app_localizations.dart'; class SearchTextField extends StatefulWidget { From 28f37c2d56c97da51d07f0c1b58d586d332a2061 Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Sun, 8 Jun 2025 23:53:12 +0800 Subject: [PATCH 05/54] =?UTF-8?q?feat(=E7=BB=93=E6=9E=84&=E5=8A=9F?= =?UTF-8?q?=E8=83=BD):=20=E5=88=9D=E5=A7=8B=E5=8C=96=E6=A0=B8=E5=BF=83?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=B9=B6=E6=96=B0=E5=A2=9E=E4=B8=BB=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增主页面相关模块和功能,包括主机观察者、编辑模式支持、主页面侧边栏及顶部导航栏组件。添加适配不同平台的逻辑,并实现主机信息的表格与文本编辑。 --- lib/app.dart | 70 ++++ lib/enums.dart | 9 + lib/home/cubit/home_cubit.dart | 261 ++++++++++++++ lib/home/cubit/home_state.dart | 96 +++++ lib/home/cubit/host_cubit.dart | 612 ++++++++++++++++++++++++++++++++ lib/home/cubit/host_state.dart | 209 +++++++++++ lib/home/view/home_app_bar.dart | 312 ++++++++++++++++ lib/home/view/home_drawer.dart | 215 +++++++++++ lib/home/view/home_page.dart | 25 ++ lib/home/view/home_view.dart | 115 ++++++ lib/home/view/host_list.dart | 383 ++++++++++++++++++++ lib/home/view/host_table.dart | 445 +++++++++++++++++++++++ lib/home/view/host_text.dart | 145 ++++++++ lib/home/view/host_view.dart | 59 +++ lib/host_observer.dart | 16 + 15 files changed, 2972 insertions(+) create mode 100644 lib/app.dart create mode 100644 lib/home/cubit/home_cubit.dart create mode 100644 lib/home/cubit/home_state.dart create mode 100644 lib/home/cubit/host_cubit.dart create mode 100644 lib/home/cubit/host_state.dart create mode 100644 lib/home/view/home_app_bar.dart create mode 100644 lib/home/view/home_drawer.dart create mode 100644 lib/home/view/home_page.dart create mode 100644 lib/home/view/home_view.dart create mode 100644 lib/home/view/host_list.dart create mode 100644 lib/home/view/host_table.dart create mode 100644 lib/home/view/host_text.dart create mode 100644 lib/home/view/host_view.dart create mode 100644 lib/host_observer.dart diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..bbe60a1 --- /dev/null +++ b/lib/app.dart @@ -0,0 +1,70 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter/material.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/home/view/home_page.dart'; +import 'package:hosts/model/global_settings.dart'; +import 'package:hosts/model/simple_host_file.dart'; +// import 'package:hosts/page/home_page.dart'; +import 'package:hosts/page/simple_home_page.dart'; +import 'package:hosts/theme.dart'; +import 'package:hosts/util/file_manager.dart'; +import 'package:hosts/util/settings_manager.dart'; + +class HostsApp extends MaterialApp { + final String filePath; + + HostsApp(this.filePath, {super.key}) + : super( + onGenerateTitle: (context) => AppLocalizations.of(context)!.app_name, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + theme: ThemeData( + brightness: Brightness.light, + colorScheme: MaterialTheme.lightScheme(), + useMaterial3: true, + ), + darkTheme: ThemeData( + brightness: Brightness.dark, + colorScheme: MaterialTheme.darkScheme(), + useMaterial3: true, + ), + themeMode: ThemeMode.system, + home: _platformSpecificWidget(filePath), + ); +} + +Widget _platformSpecificWidget(String filePath) { + GlobalSettings().isSimple = kIsWeb || filePath.isNotEmpty; + if (GlobalSettings().isSimple) { + return SimpleHomePage(filePath: filePath); + } else { + return FutureBuilder( + future: _initializeApp(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else { + return const HomePage(); + } + }, + ); + } +} + +Future _initializeApp() async { + SettingsManager settingsManager = SettingsManager(); + FileManager fileManager = FileManager(); + bool firstOpenApp = await settingsManager.getBool(settingKeyFirstOpenApp); + if (!firstOpenApp) { + const String fileName = "system"; + await fileManager.createHosts(fileName); + await settingsManager.setList(settingKeyHostConfigs, + [SimpleHostFile(fileName: fileName, remark: "")]); + await settingsManager.setString(settingKeyUseHostFile, fileName); + File(FileManager.systemHostFilePath) + .copy(await fileManager.getHostsFilePath(fileName)); + settingsManager.setBool(settingKeyFirstOpenApp, true); + } +} diff --git a/lib/enums.dart b/lib/enums.dart index 00feeb2..6d3182d 100644 --- a/lib/enums.dart +++ b/lib/enums.dart @@ -1,3 +1,12 @@ enum EditMode { Text, Table } enum AdvancedSettingsEnum { Open, Close } + +/// 排序方向枚举 +enum SortDirection { + /// 升序 + ascending, + + /// 降序 + descending, +} \ No newline at end of file diff --git a/lib/home/cubit/home_cubit.dart b/lib/home/cubit/home_cubit.dart new file mode 100644 index 0000000..c7c4d03 --- /dev/null +++ b/lib/home/cubit/home_cubit.dart @@ -0,0 +1,261 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:hosts/enums.dart'; +import 'package:hosts/l10n/app_localizations.dart' as gen; +import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/util/file_manager.dart'; +import 'package:hosts/util/settings_manager.dart'; +import 'package:hosts/util/string_util.dart'; + +part 'home_state.dart'; + +/// 首页业务逻辑处理类 +/// 管理首页的所有状态变更 +class HomeCubit extends Cubit { + /// 构造函数 + /// 初始化状态为HomeInitial + HomeCubit() : super(const HomeInitial(HomeStateData())); + + /// 设置管理器 + final SettingsManager _settingsManager = SettingsManager(); + + /// 文件管理器 + final FileManager _fileManager = FileManager(); + + /// 加载host文件列表 + /// [context] 用于本地化 + /// [isInit] 是否为初始化加载 + Future loadHostFiles(BuildContext context, + [bool isInit = false]) async { + List tempHostFiles = []; + List tempSelectHostFiles = []; + List hostConfigs = + await _settingsManager.getList(settingKeyHostConfigs); + + if (isInit) { + // TODO 支持多个文件选择 + tempSelectHostFiles = [ + (await _settingsManager.getString(settingKeyUseHostFile) ?? "") + ]; + } + + for (Map config in hostConfigs) { + SimpleHostFile hostFile = SimpleHostFile.fromJson(config); + tempHostFiles.add(hostFile); + + if (hostFile.fileName == "system") { + hostFile.remark = gen.AppLocalizations.of(context)!.default_hosts_text; + } + } + + final fileId = + tempSelectHostFiles.isNotEmpty ? tempSelectHostFiles.first : "system"; + + emit( + HomeInitial( + HomeStateData( + hostFiles: tempHostFiles, + useHostFiles: tempSelectHostFiles, + editMode: EditMode.Table, + ), + ), + ); + + selectHost(fileId); + } + + /// 添加新的host文件 + /// [remark] 文件备注信息 + Future addHostFile(String remark) async { + if (remark.isEmpty) return; + + // 获取当前 hostFiles 列表 + List currentHostFiles = List.from(state.data.hostFiles); + List hostConfigs = + await _settingsManager.getList(settingKeyHostConfigs); + + // 生成随机文件名 + final String fileName = generateRandomString(18); + + // 创建新的 hostFile + SimpleHostFile newHostFile = + SimpleHostFile(fileName: fileName, remark: remark); + + // 添加到 hostFiles 列表 + currentHostFiles.add(newHostFile); + hostConfigs.add(newHostFile.toJson()); + + // 创建实际的文件 + await _fileManager.createHosts(fileName); + + // 保存到 + await _settingsManager.setList(settingKeyHostConfigs, hostConfigs); + + // 更新状态 + emit( + HomeInitial( + state.data.copyWith( + hostFiles: currentHostFiles, + ), + ), + ); + } + + /// 更新host文件备注 + /// [fileName] 要更新的文件名 + /// [newRemark] 新的备注信息 + Future updateHostFileRemark(String fileName, String newRemark) async { + if (fileName.isEmpty || newRemark.isEmpty) return; + + List updatedHostFiles = []; + List hostConfigs = + await _settingsManager.getList(settingKeyHostConfigs); + + bool updated = false; + + // 更新 hostFiles + for (SimpleHostFile hostFile in state.data.hostFiles) { + if (hostFile.fileName == fileName) { + updatedHostFiles + .add(SimpleHostFile(fileName: fileName, remark: newRemark)); + updated = true; + } else { + updatedHostFiles.add(hostFile); + } + } + + if (!updated) return; + + // 更新 hostConfigs + List> updatedConfigs = []; + for (var config in hostConfigs) { + if (config['fileName'] == fileName) { + updatedConfigs.add({'fileName': fileName, 'remark': newRemark}); + } else { + updatedConfigs.add(config); + } + } + + // 保存到 settings + await _settingsManager.setList(settingKeyHostConfigs, updatedConfigs); + + // 更新状态 + emit( + HomeInitial( + state.data.copyWith( + hostFiles: updatedHostFiles, + ), + ), + ); + } + + /// 删除host文件 + /// [fileName] 要删除的文件名 + Future deleteHostFile(String fileName) async { + if (fileName.isEmpty || fileName == "system") return; + + // 判断是否删除使用的 Host 文件 + final bool isDeleteUse = state.data.useHostFiles.contains(fileName); + // 判断是否删除选择的 Host 文件 + final bool isDeleteSelect = state.data.selectHostFile == fileName; + + if (isDeleteUse) { + // TODO + // final String path = + // await _fileManager.getHostsFilePath("system"); + // + // if (!await widget + // .onClickUse(File(path).readAsStringSync())) { + // return; + // } + // await _settingsManager.setString( + // settingKeyUseHostFile, "system"); + // useHostFile = "system"; + // selectHostFile = "system"; + } + + if (isDeleteSelect) { + selectHost( + state.data.selectHostFile.isNotEmpty + ? state.data.selectHostFile + : "system", + ); + } + + List updatedHostFiles = state.data.hostFiles + .where((file) => file.fileName != fileName) + .toList(); + + List hostConfigs = + await _settingsManager.getList(settingKeyHostConfigs); + + hostConfigs.removeWhere((config) => config['fileName'] == fileName); + + // 保存到 settings + await _settingsManager.setList(settingKeyHostConfigs, hostConfigs); + + // 删除实际文件 + await _fileManager.deleteFiles([fileName]); + + // 更新状态 + emit( + HomeInitial( + state.data.copyWith( + hostFiles: updatedHostFiles, + ), + ), + ); + } + + /// 选择host文件 + /// [fileId] 要选择的文件ID + Future selectHost(String fileId) async { + emit( + HomeSelectHostFileChanged( + state.data.copyWith( + selectHostFile: fileId, + ), + ), + ); + } + + /// 使用host文件 + /// [fileName] 要使用的文件名 + Future useHost(String fileName) async {} + + /// 切换编辑模式 + /// [editMode] 新的编辑模式(表格/文本) + Future toggleEditMode(EditMode editMode) async { + emit( + HomeEditMode( + state.data.copyWith( + editMode: editMode, + ), + ), + ); + } + + /// 切换高级设置状态 + /// [advancedSettings] 新的高级设置状态 + Future toggleAdvancedSettings( + AdvancedSettingsEnum advancedSettings) async { + emit( + HomeInitial( + state.data.copyWith( + advancedSettingsEnum: advancedSettings, + ), + ), + ); + } + + /// 切换高级设置开关状态 + /// 在Open和Close之间切换 + Future toggleAdvancedSettingsSwitch() async { + final AdvancedSettingsEnum newSettings = + state.data.advancedSettingsEnum == AdvancedSettingsEnum.Close + ? AdvancedSettingsEnum.Open + : AdvancedSettingsEnum.Close; + + await toggleAdvancedSettings(newSettings); + } +} diff --git a/lib/home/cubit/home_state.dart b/lib/home/cubit/home_state.dart new file mode 100644 index 0000000..3acd93a --- /dev/null +++ b/lib/home/cubit/home_state.dart @@ -0,0 +1,96 @@ +part of 'home_cubit.dart'; + +/// 首页状态数据类 +/// 保存所有与首页相关的状态属性 +/// 首页状态数据模型 +class HomeStateData { + /// 所有host文件列表 + final List hostFiles; + + /// 当前使用的host文件列表 + final List useHostFiles; + + /// 当前选中的host文件 + final String selectHostFile; + + /// 编辑模式(表格/文本) + final EditMode editMode; + + /// 高级设置状态 + final AdvancedSettingsEnum advancedSettingsEnum; + + /// 搜索文本 + final String searchText; + + /// 构造函数 + const HomeStateData( + {this.hostFiles = const [], + this.useHostFiles = const [], + this.selectHostFile = "", + this.editMode = EditMode.Table, + this.advancedSettingsEnum = AdvancedSettingsEnum.Close, + this.searchText = ""}); + + /// 复制方法 + /// 用于基于当前状态创建新状态 + HomeStateData copyWith({ + List? hostFiles, + List? useHostFiles, + String? selectHostFile, + EditMode? editMode, + AdvancedSettingsEnum? advancedSettingsEnum, + String? searchText, + }) { + return HomeStateData( + hostFiles: hostFiles ?? this.hostFiles, + useHostFiles: useHostFiles ?? this.useHostFiles, + selectHostFile: selectHostFile ?? this.selectHostFile, + editMode: editMode ?? this.editMode, + advancedSettingsEnum: advancedSettingsEnum ?? this.advancedSettingsEnum, + searchText: searchText ?? this.searchText, + ); + } +} + +/// 首页状态基类 +/// 使用密封类设计模式限制状态类型 +/// 不可变状态基类 +@immutable +sealed class HomeState { + final HomeStateData data; + + const HomeState(this.data); +} + +/// 初始状态 +class HomeInitial extends HomeState { + const HomeInitial(super.data); +} + +/// 编辑模式变更状态 +/// 当用户切换编辑模式(表格/文本)时触发 +class HomeEditMode extends HomeState { + const HomeEditMode(super.data); +} + +/// 数据加载完成状态 +class HomeLoaded extends HomeState { + const HomeLoaded(super.data); +} + +/// 数据加载中状态 +class HomeLoading extends HomeState { + const HomeLoading(super.data); +} + +/// 错误状态 +/// 包含错误信息 +class HomeError extends HomeState { + final String message; + const HomeError(super.data, this.message); +} + +/// 选择的host文件变更状态 +class HomeSelectHostFileChanged extends HomeState { + const HomeSelectHostFileChanged(super.data); +} diff --git a/lib/home/cubit/host_cubit.dart b/lib/home/cubit/host_cubit.dart new file mode 100644 index 0000000..cec9b27 --- /dev/null +++ b/lib/home/cubit/host_cubit.dart @@ -0,0 +1,612 @@ +/// 主机管理模块的Cubit实现 +/// +/// 负责管理主机文件的状态和业务逻辑 +library; + +import 'dart:io'; + +import 'package:bloc/bloc.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hosts/enums.dart'; +import 'package:hosts/home/cubit/home_cubit.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/host_file.dart'; +import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/util/file_manager.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +part 'host_state.dart'; + +// 确保已正确导入HostSort状态类 +/// 主机管理Cubit类 +/// +/// 继承自Cubit,管理主机文件的各种状态变更 +class HostCubit extends Cubit { + /// 构造函数 + /// + /// 初始化状态为HostInitial + HostCubit() : super(HostInitial(HostStateData())); + + /// 文件管理器实例 + final FileManager _fileManager = FileManager(); + + /// 更新搜索文本 + /// + /// [text]: 新的搜索文本 + void updateSearchText(String text) { + emit( + HostFilter( + state.data.copyWith( + searchText: text, + filterHosts: + state.data.hosts.where((host) => host.filter(text)).toList(), + ), + ), + ); + } + + /// 更新选中的主机列表 + /// + /// [selectHosts]: 新的选中主机列表 + void updateSelectHosts(List selectHosts) { + emit( + HostInitial( + state.data.copyWith( + selectHosts: selectHosts, + ), + ), + ); + } + + /// 更新全选状态 + /// + /// [isCheckedAll]: 是否全选 + void updateCheckedAll(bool? isCheckedAll) { + emit( + HostInitial( + state.data.copyWith( + selectHosts: isCheckedAll == true ? state.data.filterHosts : [], + ), + ), + ); + } + + /// 更新主机文件内容 + /// + /// [fileId]: 文件ID + void updateHost(String fileId) async { + final content = await _fileManager.readAsString(fileId); + final hosts = _fileManager.parseHosts(content.split("\n")); + final history = await _fileManager.getHistory(fileId); + emit( + HostInitial( + HostStateData( + fileId: fileId, + fileContent: content, + defaultFileContent: content, + hosts: hosts, + defaultHosts: hosts, + filterHosts: hosts, + history: history, + ), + ), + ); + } + + /// 更新编辑模式 + /// + /// [editMode]: 新的编辑模式(表格/文本) + /// 根据编辑模式切换显示方式 + void updateEditMode(EditMode editMode) { + if (editMode == EditMode.Table) { + final hosts = _fileManager.parseHosts(state.data.fileContent.split("\n")); + emit( + HostEditMode( + state.data.copyWith( + hosts: hosts, + filterHosts: hosts, + ), + ), + ); + } + if (editMode == EditMode.Text) { + emit( + HostEditMode( + state.data.copyWith( + fileContent: toString(), + ), + ), + ); + } + } + + /// 编辑主机项 + /// + /// [index]: 主机索引 + /// [host]: 新的主机数据 + void onEdit(int index, HostsModel host) { + final List updatedHosts = + List.from(state.data.hosts); + + HostsModel oldHost = updatedHosts[index]; + + host.descLine ??= oldHost.descLine; + host.hostLine = oldHost.hostLine; + + final lines = state.data.fileContent.split("\n"); + + final List newLine = host.toString().split("\n"); + if (host.descLine != null && + host.descLine! > -1 && + [2].contains(newLine.length)) { + lines[host.descLine!] = newLine[0]; + } + + if (host.hostLine != null && + host.hostLine! > -1 && + [1, 2].contains(newLine.length)) { + lines[host.hostLine!] = newLine.length == 2 ? newLine[1] : newLine[0]; + } + + // 新增备注 + if (host.descLine == null && host.description.isNotEmpty) { + lines.insert(host.hostLine!, "# ${host.description}"); + } + + // 移除备注 + if (host.descLine != null && host.description.isEmpty) { + lines.removeAt(host.descLine!); + } + + updatedHosts[index] = host; + + emit( + HostEdit( + state.data.copyWith( + hosts: updatedHosts, + filterHosts: updatedHosts + .where((host) => host.filter(state.data.searchText)) + .toList(), + isSave: isUpdate(updatedHosts), + fileContent: lines.join("\n"), + ), + ), + ); + } + + /// 切换主机使用状态 + /// + /// [hostsMap]: 主机映射关系 + void onToggleUse(Map hostsMap) { + final List updatedHosts = state.data.hosts.map((host) { + return hostsMap.containsKey(host) ? hostsMap[host]! : host; + }).toList(); + + emit( + HostToggleUse( + state.data.copyWith( + hosts: updatedHosts, + filterHosts: updatedHosts + .where((host) => host.filter(state.data.searchText)) + .toList(), + selectHosts: + state.data.selectHosts.isNotEmpty ? hostsMap.values.toList() : [], + isSave: isUpdate(updatedHosts), + ), + ), + ); + } + + /// 删除主机项 + /// + /// [hosts]: 要删除的主机列表 + void onDelete(List hosts) { + final newHosts = + state.data.hosts.where((host) => !hosts.contains(host)).toList(); + hosts.sort((a, b) => a.hostLine?.compareTo(b.hostLine ?? -1) ?? 1); + final lines = state.data.fileContent.split("\n"); + + int removeCount = 0; + for (var host in hosts) { + if (host.descLine != null && host.descLine == host.hostLine) { + lines.removeAt(host.descLine! - removeCount); + removeCount++; + continue; + } + + if (host.descLine != null && host.descLine! > -1) { + lines.removeAt(host.descLine! - removeCount); + removeCount++; + } + if (host.hostLine != null && host.hostLine! > -1) { + lines.removeAt(host.hostLine! - removeCount); + removeCount++; + } + } + emit( + HostDelete( + state.data.copyWith( + hosts: newHosts, + filterHosts: newHosts + .where((host) => host.filter(state.data.searchText)) + .toList(), + selectHosts: state.data.selectHosts + .where((host) => !hosts.contains(host)) + .toList(), + isSave: isUpdate(newHosts), + fileContent: lines.join("\n"), + ), + ), + ); + } + + /// 选中/取消选中主机项 + /// + /// [index]: 主机索引 + /// [host]: 主机数据 + void onChecked(int index, HostsModel host) { + final List updatedSelectHosts = + List.from(state.data.selectHosts); + if (updatedSelectHosts.contains(host)) { + updatedSelectHosts.remove(host); + } else { + updatedSelectHosts.add(host); + } + + emit( + HostChecked( + state.data.copyWith( + selectHosts: updatedSelectHosts, + ), + ), + ); + } + + /// 排序主机列表 + /// + /// [columnName]: 列名 + /// [sortDirection]: 排序方向 + void onSort(String columnName) { + // 获取当前排序状态 + final currentDirection = state.data.sortStatus[columnName]; + SortDirection newDirection; + + // 确定新的排序方向(两态循环) + if (currentDirection == SortDirection.ascending) { + newDirection = SortDirection.descending; + } else { + newDirection = SortDirection.ascending; + } + + // 排序当前列表 + List sortedHosts = List.from(state.data.hosts); + sortedHosts.sort((a, b) { + int result; + switch (columnName) { + case 'host': + result = a.host.compareTo(b.host); + break; + case 'use': + result = a.isUse == b.isUse ? 0 : (a.isUse ? 1 : -1); + break; + case 'hosts': + result = a.hosts.join(',').compareTo(b.hosts.join(',')); + break; + case 'description': + result = a.description.compareTo(b.description); + break; + default: + result = 0; + } + return newDirection == SortDirection.ascending ? result : -result; + }); + + emit( + HostSort( + state.data.copyWith( + hosts: sortedHosts, + filterHosts: sortedHosts + .where((host) => host.filter(state.data.searchText)) + .toList(), + sortStatus: {columnName: newDirection}, + ), + ), + ); + } + + /// 添加主机项 + /// + /// [hostsModels]: 要添加的主机列表 + void addHosts(List hostsModels) { + final List hosts = List.from(state.data.hosts); + final lines = state.data.fileContent.split("\n"); + + for (var host in hostsModels) { + final newLine = host.toString().split("\n"); + if (newLine.isEmpty) { + continue; + } + if (newLine.length == 2) { + hosts.add( + host.withCopy( + descLine: lines.length, + hostLine: lines.length + 1, + ), + ); + } + if (newLine.length == 1) { + hosts.add( + host.withCopy( + hostLine: lines.length, + ), + ); + } + lines.addAll(newLine); + } + + emit( + HostAdd( + state.data.copyWith( + hosts: hosts, + filterHosts: hosts + .where((host) => host.filter(state.data.searchText)) + .toList(), + isSave: isUpdate(hosts), + fileContent: lines.join("\n"), + ), + ), + ); + } + + /// 撤销操作 + void undoHost() { + emit( + HostUndo( + state.data.copyWith( + hosts: state.data.defaultHosts, + filterHosts: state.data.defaultHosts, + selectHosts: [], + fileContent: state.data.defaultFileContent, + isSave: true, + ), + ), + ); + } + + /// 历史记录变更处理 + /// + /// [history]: 选中的历史记录 + void onHistoryChanged(SimpleHostFileHistory? history) async { + final resultHistory = await _fileManager.getHistory(state.data.fileId); + if (history != null) { + final historyContent = _fileManager.readHistoryFile(history.path); + final hosts = _fileManager.parseHosts(historyContent.split("\n")); + emit( + HostHistory( + state.data.copyWith( + selectHistory: history, + history: resultHistory, + hosts: hosts, + filterHosts: hosts, + isSave: false, + fileContent: historyContent, + ), + ), + ); + + return; + } + + emit( + HostHistory( + state.data.copyWith( + selectHistory: null, + history: resultHistory, + ), + ), + ); + } + + /// 更新文件内容 + /// + /// [text]: 新的文件内容 + void updateFileContent(String text) { + if (text == state.data.defaultFileContent) { + return; + } + emit( + HostFileContent( + state.data.copyWith( + fileContent: text, + isSave: text == state.data.defaultFileContent, + ), + ), + ); + } + + /// 检查是否有更新 + /// + /// [hosts]: 当前主机列表 + /// 返回: 是否有更新 + bool isUpdate(List hosts) { + if (hosts.length != state.data.defaultHosts.length) return false; + + for (int i = 0; i < hosts.length; i++) { + if (hosts[i].toString() != state.data.defaultHosts[i].toString()) { + return false; + } + } + + return true; + } + + void onTableSave( + BuildContext context, + HomeStateData homeStateData, + bool isHistory, + ) async { + if (homeStateData.useHostFiles.contains(state.data.fileId)) { + if (!await saveHost( + context, + FileManager.systemHostFilePath, + state.data.fileContent, + )) { + return; + } + } + save(isHistory); + } + + // TODO 保存到文件中。 + void onTextSave() { + // state.data.fileContent + } + + void saveToFile() {} + + void save([bool isHistory = false]) async { + final fileId = state.data.fileId; + final String content = toString(); + final filePath = await _fileManager.getHostsFilePath(fileId); + File(filePath).writeAsStringSync(content); + final List history = []; + if (isHistory) { + await _fileManager.saveHistory(fileId, content); + history.addAll(await _fileManager.getHistory(fileId)); + } + + emit( + HostSave( + state.data.copyWith( + history: isHistory ? history : state.data.history, + defaultHosts: state.data.hosts, + defaultFileContent: state.data.fileContent, + isSave: true, + ), + ), + ); + } + + Future saveHost( + BuildContext context, String filePath, String hostContent) async { + if (kIsWeb) { + final String tempContent = hostContent.replaceAll("\"", "\\\""); + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text("保存"), + content: SizedBox( + width: MediaQuery.of(context).size.width * 0.5, + child: SelectableText(hostContent), + ), + actions: [ + TextButton( + onPressed: () => writeClipboard( + 'echo "$tempContent" > /etc/hosts', + tempContent, + context, + ), + child: const Text("Linux(echo)")), + TextButton( + onPressed: () { + final String systemHostPath = p.joinAll([ + "C:", + "Windows", + "System32", + "drivers", + "etc", + "hosts" + ]); + final String content = hostContent + .split("\n") + .map((item) => 'echo $item') + .join("\n"); + writeClipboard( + '(\n$content\n) > $systemHostPath', + hostContent, + context, + ); + }, + child: const Text("Windows(echo)")), + TextButton( + onPressed: () => writeClipboard( + 'echo "$tempContent" > /etc/hosts', + tempContent, + context, + ), + child: const Text("MacOS(echo)")), + ], + )); + return true; + } + + final File file = File(filePath); + try { + await file.writeAsString(hostContent); + } catch (e) { + try { + final Directory cacheDirectory = await getApplicationCacheDirectory(); + final File cacheFile = File(p.join(cacheDirectory.path, 'hosts')); + await cacheFile.writeAsString(hostContent); + + await _fileManager.writeFileWithAdminPrivileges( + cacheFile.path, filePath); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.error_save_fail))); + return false; + } + } + + // emit( + // HostSave( + // state.data.copyWith( + // defaultHosts: state.data.hosts, + // defaultFileContent: state.data.fileContent, + // isSave: true, + // ), + // ), + // ); + // setState(() { + // hostsFile.defaultContent = hostContent; + // hostsFile.isUpdateHost(); + // }); + return true; + } + + void writeClipboard( + String hostContent, String defaultContent, BuildContext context) { + Clipboard.setData(ClipboardData(text: hostContent)).then((_) { + // emit( + // HostSave( + // state.data.copyWith( + // defaultHosts: state.data.hosts, + // defaultFileContent: state.data.fileContent, + // isSave: true, + // ), + // ), + // ); + // setState(() { + // hostsFile.defaultContent = defaultContent; + // hostsFile.isUpdateHost(); + // }); + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.copy_to_tip), + ), + ); + }); + } + + /// 转换为字符串 + /// + /// 返回当前文件内容字符串 + @override + String toString() { + return state.data.fileContent; + } +} diff --git a/lib/home/cubit/host_state.dart b/lib/home/cubit/host_state.dart new file mode 100644 index 0000000..01cd6f5 --- /dev/null +++ b/lib/home/cubit/host_state.dart @@ -0,0 +1,209 @@ +part of 'host_cubit.dart'; + +/// 包含主机文件状态的所有数据 +class HostStateData { + /// 当前文件的唯一标识符 + final String fileId; + + /// 文件内容文本 + final String fileContent; + + /// 默认文件内容文本 + final String defaultFileContent; + + /// 标识文件是否已保存 + final bool isSave; + + /// 过滤后的主机列表 + final List filterHosts; + + /// 当前显示的主机列表 + final List hosts; + + /// 默认主机列表 + final List defaultHosts; + + /// 用户选择的主机列表 + final List selectHosts; + + /// 当前选择的历史记录 + final SimpleHostFileHistory? selectHistory; + + /// 所有历史记录列表 + final List history; + + /// 搜索文本 + final String searchText; + + /// 各列排序状态 + final Map sortStatus; + + /// 创建 HostStateData 实例 + /// + /// [fileId]: 文件ID,默认为空字符串 + /// [fileContent]: 文件内容,默认为空字符串 + /// [isSave]: 是否已保存,默认为true + /// [filterHosts]: 过滤后的主机列表,默认为空列表 + /// [hosts]: 主机列表,默认为空列表 + /// [defaultHosts]: 默认主机列表,默认为空列表 + /// [selectHosts]: 选中的主机列表,默认为空列表 + /// [selectHistory]: 选中的历史记录,默认为null + /// [history]: 历史记录列表,默认为空列表 + /// [searchText]: 搜索文本,默认为空字符串 + HostStateData({ + this.fileId = "", + this.fileContent = "", + this.defaultFileContent = "", + this.isSave = true, + this.filterHosts = const [], + this.hosts = const [], + this.defaultHosts = const [], + this.selectHosts = const [], + this.selectHistory, + this.history = const [], + this.searchText = "", + this.sortStatus = const {}, + }); + + /// 创建当前状态的副本,可选择更新部分字段 + /// + /// 返回一个新的 HostStateData 实例,其中未提供的参数将保留原值 + HostStateData copyWith({ + String? fileId, + String? fileContent, + String? defaultFileContent, + bool? isSave, + List? filterHosts, + List? hosts, + List? defaultHosts, + List? selectHosts, + SimpleHostFileHistory? selectHistory, + List? history, + String? searchText, + Map? sortStatus, + }) { + return HostStateData( + fileId: fileId ?? this.fileId, + fileContent: fileContent ?? this.fileContent, + defaultFileContent: defaultFileContent ?? this.defaultFileContent, + isSave: isSave ?? this.isSave, + filterHosts: filterHosts ?? this.filterHosts, + hosts: hosts ?? this.hosts, + defaultHosts: defaultHosts ?? this.defaultHosts, + selectHosts: selectHosts ?? this.selectHosts, + selectHistory: selectHistory ?? this.selectHistory, + history: history ?? this.history, + searchText: searchText ?? this.searchText, + sortStatus: sortStatus ?? this.sortStatus, + ); + } +} + +/// 主机状态的基类,所有具体状态都必须继承此类 +/// +/// 使用 sealed 修饰确保所有子类都在同一文件中定义 +@immutable +sealed class HostState { + /// 当前状态关联的数据 + final HostStateData data; + + /// 创建 HostState 实例 + const HostState(this.data); +} + +/// 初始状态,表示应用刚启动时的状态 +final class HostInitial extends HostState { + /// 创建初始状态 + const HostInitial(super.data); +} + +/// 切换主机使用状态时的状态 +final class HostToggleUse extends HostState { + /// 创建切换使用状态 + const HostToggleUse(super.data); +} + +/// 编辑主机时的状态 +final class HostEdit extends HostState { + /// 创建编辑状态 + const HostEdit(super.data); +} + +/// 过滤主机时的状态 +final class HostFilter extends HostState { + /// 创建过滤状态 + const HostFilter(super.data); +} + +/// 主机被选中时的状态 +final class HostChecked extends HostState { + /// 创建选中状态 + const HostChecked(super.data); +} + +/// 删除主机时的状态 +final class HostDelete extends HostState { + /// 创建删除状态 + const HostDelete(super.data); +} + +/// 添加主机时的状态 +final class HostAdd extends HostState { + /// 创建添加状态 + const HostAdd(super.data); +} + +/// 撤销操作时的状态 +final class HostUndo extends HostState { + /// 创建撤销状态 + const HostUndo(super.data); +} + +/// 历史记录变更时的状态 +final class HostHistory extends HostState { + /// 创建历史记录状态 + const HostHistory(super.data); +} + +/// 文件内容变更时的状态 +final class HostFileContent extends HostState { + /// 创建文件内容状态 + const HostFileContent(super.data); +} + +/// 保存操作完成时的状态 +/// +/// 当主机文件保存成功后触发此状态 +final class HostSave extends HostState { + /// 创建保存状态 + const HostSave(super.data); +} + +final class HostSort extends HostState { + const HostSort(super.data); +} + +final class HostEditMode extends HostState { + const HostEditMode(super.data); +} + +/// 主机数据加载完成时的状态 +final class HostLoaded extends HostState { + /// 创建加载完成状态 + const HostLoaded(super.data); +} + +/// 正在加载主机数据时的状态 +final class HostLoading extends HostState { + /// 创建加载中状态 + const HostLoading(super.data); +} + +/// 发生错误时的状态 +final class HostError extends HostState { + /// 错误信息 + final String message; + + /// 创建错误状态 + const HostError(super.data, this.message); +} diff --git a/lib/home/view/home_app_bar.dart b/lib/home/view/home_app_bar.dart new file mode 100644 index 0000000..9c5d9fb --- /dev/null +++ b/lib/home/view/home_app_bar.dart @@ -0,0 +1,312 @@ +import 'dart:typed_data'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hosts/enums.dart'; +import 'package:hosts/home/cubit/home_cubit.dart'; +import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/global_settings.dart'; +import 'package:hosts/model/host_file.dart'; +import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/page/history_page.dart'; +import 'package:hosts/widget/dialog/copy_multiple_dialog.dart'; +import 'package:hosts/widget/snakbar.dart'; +import 'package:hosts/widget/text_field/search_text_field.dart'; + +/// 首页应用栏组件 +/// +/// 包含文件操作、搜索、历史记录等功能的顶部工具栏 +class HomeAppBar extends StatelessWidget { + /// 构造函数 + const HomeAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final homeCubit = context.read(); + final homeStateData = state.data; + return Column( + children: [ + Container( + height: 58, + padding: const EdgeInsets.symmetric(horizontal: 5), + child: Row( + children: [ + Expanded( + child: Row( + children: [ + if (GlobalSettings().isSimple) + IconButton( + onPressed: () async { + FilePickerResult? result = + await FilePicker.platform.pickFiles(); + if (result == null) return; + if (!context.read().state.data.isSave) { + ScaffoldMessenger.of(context) + .removeCurrentSnackBar(); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)! + .error_not_save), + action: SnackBarAction( + label: AppLocalizations.of(context)!.abort, + onPressed: () => pickFile(context, result), + ), + )); + + return; + } + + pickFile(context, result); + }, + icon: const Icon(Icons.file_open_outlined), + tooltip: AppLocalizations.of(context)!.open_file, + ) + else + IconButton( + onPressed: homeCubit.toggleAdvancedSettingsSwitch, + icon: const Icon(Icons.menu), + tooltip: + AppLocalizations.of(context)!.advanced_settings, + ), + _buildEditModeButton(homeCubit, context), + const SizedBox(width: 10), + if (homeStateData.editMode == EditMode.Table) + Flexible( + child: Container( + constraints: const BoxConstraints( + maxWidth: 430, + minWidth: 100, + ), + child: BlocBuilder( + builder: (context, state) { + return SearchTextField( + text: state.data.searchText, + onChanged: context + .read() + .updateSearchText, + ); + }, + ), + ), + ), + ], + )), + const SizedBox(width: 32), + BlocBuilder( + builder: (context, state) { + final hostStateData = state.data; + final hostCubit = context.read(); + return Row( + children: [ + batchGroupButton(homeCubit), + if (hostStateData.history.isNotEmpty) + IconButton( + onPressed: () async { + SimpleHostFileHistory? resultHistory = + await showModalBottomSheet( + context: context, + builder: (BuildContext context) => + HistoryPage( + selectHistory: hostStateData.selectHistory, + history: hostStateData.history, + ), + ); + if (resultHistory == null) { + hostCubit.onHistoryChanged(null); + return; + } + + if (!hostStateData.isSave) { + ScaffoldMessenger.of(context) + .removeCurrentSnackBar(); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)! + .error_not_save), + action: SnackBarAction( + label: + AppLocalizations.of(context)!.abort, + onPressed: () => + hostCubit.onHistoryChanged(resultHistory), + ), + )); + + return; + } + hostCubit.onHistoryChanged(resultHistory); + }, + icon: const Icon(Icons.history), + ), + if (!hostStateData.isSave) + IconButton( + onPressed: context.read().undoHost, + icon: const Icon(Icons.undo), + tooltip: AppLocalizations.of(context)!.reduction, + ), + buildMoreButton(context) + ], + ); + }, + ) + ], + ), + ) + ], + ); + }, + ); + } + + IconButton _buildEditModeButton(HomeCubit homeCubit, BuildContext context) { + return IconButton( + onPressed: () { + if (homeCubit.state.data.editMode == EditMode.Text) { + homeCubit.toggleEditMode(EditMode.Table); + } else { + homeCubit.toggleEditMode(EditMode.Text); + } + }, + tooltip: homeCubit.state.data.editMode == EditMode.Text + ? AppLocalizations.of(context)!.table + : AppLocalizations.of(context)!.text, + icon: Icon( + homeCubit.state.data.editMode == EditMode.Text + ? Icons.table_rows_outlined + : Icons.text_snippet_outlined, + ), + ); + } + + Widget batchGroupButton(HomeCubit homeCubit) { + return BlocBuilder( + builder: (BuildContext context, state) { + final selectHosts = state.data.selectHosts; + final hostCubit = context.read(); + return Row( + children: [ + if (selectHosts.isNotEmpty && + homeCubit.state.data.editMode == EditMode.Table) + Switch( + value: true, + onChanged: (value) { + final Map hostsMap = {}; + for (var host in selectHosts) { + hostsMap[host] = host.withCopy(isUse: true); + } + hostCubit.onToggleUse(hostsMap); + }, + ), + if (selectHosts.isNotEmpty && + homeCubit.state.data.editMode == EditMode.Table) + Switch( + value: false, + onChanged: (value) { + final Map hostsMap = {}; + for (var host in selectHosts) { + hostsMap[host] = host.withCopy(isUse: false); + } + hostCubit.onToggleUse(hostsMap); + }, + ), + if (selectHosts.isNotEmpty && + homeCubit.state.data.editMode == EditMode.Table) + IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => + CopyMultipleDialog(hosts: selectHosts)); + }, + tooltip: AppLocalizations.of(context)!.copy_selected, + icon: const Icon(Icons.copy)), + if (selectHosts.isNotEmpty && + homeCubit.state.data.editMode == EditMode.Table) + IconButton( + onPressed: () { + deleteMultiple( + context, selectHosts.map((it) => it.host).toList(), () { + hostCubit.onDelete(selectHosts); + }); + }, + tooltip: AppLocalizations.of(context)!.delete_selected, + icon: const Icon(Icons.delete_outline)), + ], + ); + }, + ); + } + + void pickFile(BuildContext context, FilePickerResult result) { + if (result.files.first.size > 10 * 1024 * 1024) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.error_open_file_size))); + return; + } + + try { + String path = ""; + try { + path = result.files.single.path ?? ""; + } catch (e) { + path = ""; + } + final Uint8List? bytes = result.files.first.bytes; + if (path.isNotEmpty && bytes == null) { + // onOpenFile(File(path).readAsStringSync()); + } + + if (path.isEmpty && bytes != null) { + // onOpenFile(utf8.decode(bytes)); + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.error_open_file))); + } + } + + Widget buildMoreButton(BuildContext context) { + return PopupMenuButton(onSelected: (value) { + switch (value) { + case 1: + showAboutDialog( + context: context, + applicationVersion: '1.5.0', + applicationIcon: Image.asset( + "assets/icon/logo.png", + width: 50, + height: 50, + ), + children: [ + Text(AppLocalizations.of(context)!.about_description), + const SizedBox(height: 10), + const Text('Developed by Webb.'), + ], + ); + break; + default: + break; + } + }, itemBuilder: (BuildContext context) { + final List> list = [ + {"text": AppLocalizations.of(context)!.about, "value": 1}, + ]; + + return list.map((item) { + return PopupMenuItem( + value: int.parse(item["value"].toString()), + child: Row( + children: [ + if (item["icon"] != null) Icon(item["icon"]! as IconData), + SizedBox(width: item["icon"] != null ? 8 : 32), + Text(item["text"]!.toString()), + ], + ), + ); + }).toList(); + }); + } +} diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart new file mode 100644 index 0000000..a46e6ae --- /dev/null +++ b/lib/home/view/home_drawer.dart @@ -0,0 +1,215 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hosts/home/cubit/home_cubit.dart'; +import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/widget/dialog/dialog.dart'; +import 'package:hosts/widget/snakbar.dart'; + +class HomeDrawer extends StatelessWidget { + const HomeDrawer({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Drawer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Text( + AppLocalizations.of(context)!.app_name, + style: Theme.of(context).textTheme.titleLarge, + ), + const Expanded(child: SizedBox()), + IconButton( + onPressed: () async { + String? remark = await hostConfigDialog(context); + if (remark == null || remark.isEmpty) return; + context.read().addHostFile(remark); + }, + icon: const Icon(Icons.add)) + ], + ), + ), + Expanded( + child: ListView.builder( + itemCount: state.data.hostFiles.length, + itemBuilder: (context, index) { + final hostFile = state.data.hostFiles[index]; + return ListTile( + title: Text(hostFile.remark), + leading: IconButton( + tooltip: AppLocalizations.of(context)!.use, + style: OutlinedButton.styleFrom( + minimumSize: Size.zero, + padding: EdgeInsets.zero, + ), + onPressed: + state.data.useHostFiles.contains(hostFile.fileName) + ? null + : () async { + // final String path = await _fileManager + // .getHostsFilePath(hostFile.fileName); + // + // if (!await widget + // .onClickUse(File(path).readAsStringSync())) { + // return; + // } + + // setState(() { + // useHostFile = hostFile.fileName; + // }); + // _settingsManager.setString( + // settingKeyUseHostFile, hostFile.fileName); + }, + icon: Icon( + state.data.useHostFiles.contains(hostFile.fileName) + ? Icons.star + : Icons.star_border), + ), + selectedTileColor: + Theme.of(context).colorScheme.primaryContainer, + selected: state.data.selectHostFile == hostFile.fileName, + trailing: buildMoreButton(hostFile), + onTap: () { + if (state.data.selectHostFile == hostFile.fileName) { + return; + } + if (!context.read().state.data.isSave) { + ScaffoldMessenger.of(context).removeCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.error_not_save), + action: SnackBarAction( + label: AppLocalizations.of(context)!.abort, + onPressed: () { + context + .read() + .selectHost(hostFile.fileName); + }, + ), + ), + ); + return; + } + + context.read().selectHost(hostFile.fileName); + }, + ); + }, + ), + ) + ], + ), + ); + }, + ); + } + + Widget buildMoreButton(SimpleHostFile hostFile) { + if (hostFile.fileName == "system") { + return const SizedBox(); + } + + return BlocBuilder( + builder: (context, state) { + final homeCubit = context.read(); + + return PopupMenuButton( + style: OutlinedButton.styleFrom( + minimumSize: Size.zero, + padding: EdgeInsets.zero, + ), + onSelected: (value) async { + switch (value) { + case 1: + String result = + (await hostConfigDialog(context, hostFile.remark) ?? ""); + if (result.isEmpty) return; + homeCubit.updateHostFileRemark(hostFile.fileName, result); + break; + case 2: + deleteMultiple(context, [hostFile.remark], () async { + homeCubit.deleteHostFile(hostFile.fileName); + // 判断是否删除使用的 Host 文件 + // final bool isDeleteUse = list + // .where( + // (host) => state.useHostFiles.contains(host.fileName)) + // .isNotEmpty; + // 判断是否删除选择的 Host 文件 + // final bool isDeleteSelect = list + // .where((host) => state.selectHostFile == host.fileName) + // .isNotEmpty; + // + // if (isDeleteUse) { + // final String path = + // await _fileManager.getHostsFilePath("system"); + // + // if (!await widget + // .onClickUse(File(path).readAsStringSync())) { + // return; + // } + // await _settingsManager.setString( + // settingKeyUseHostFile, "system"); + // useHostFile = "system"; + // selectHostFile = "system"; + // } + // + // if (isDeleteSelect) { + // homeCubit.selectHost("system"); + // } + + // homeCubit.deleteHostFile(hostFile.fileName); + // + // setState(() { + // hostFiles.removeWhere((hostFile) => list.contains(hostFile)); + // }); + // await _settingsManager.setList(settingKeyHostConfigs, hostFiles); + // widget.onChanged( + // await _fileManager.getHostsFilePath(selectHostFile!), + // selectHostFile!); + // _fileManager + // .deleteFiles(list.map((file) => file.fileName).toList()); + }); + break; + } + }, + itemBuilder: (BuildContext context) { + List> list = [ + { + "icon": Icons.edit, + "text": AppLocalizations.of(context)!.edit, + "value": 1 + }, + { + "icon": Icons.delete_outline, + "text": AppLocalizations.of(context)!.remove, + "value": 2 + }, + ]; + + return list.map((item) { + return PopupMenuItem( + value: int.parse(item["value"].toString()), + child: Row( + children: [ + Icon(item["icon"]! as IconData), + const SizedBox(width: 8), + Text(item["text"]!.toString()), + ], + ), + ); + }).toList(); + }, + ); + }, + ); + } +} diff --git a/lib/home/view/home_page.dart b/lib/home/view/home_page.dart new file mode 100644 index 0000000..81582f2 --- /dev/null +++ b/lib/home/view/home_page.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hosts/home/cubit/home_cubit.dart'; +import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/home/view/home_view.dart'; + +/// 首页页面组件 +/// +/// 负责初始化和管理首页相关的Cubit状态 +/// 包含HomeCubit和HostCubit的初始化 +class HomePage extends StatelessWidget { + /// 构造函数 + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider(create: (context) => HomeCubit()), + BlocProvider(create: (context) => HostCubit()), + ], + child: const HomeView(), + ); + } +} diff --git a/lib/home/view/home_view.dart b/lib/home/view/home_view.dart new file mode 100644 index 0000000..12c92da --- /dev/null +++ b/lib/home/view/home_view.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hosts/enums.dart'; +import 'package:hosts/home/cubit/home_cubit.dart'; +import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/home/view/home_app_bar.dart'; +import 'package:hosts/home/view/host_view.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/host_file.dart'; +import 'package:hosts/page/host_page.dart'; +import 'package:hosts/home/view/home_drawer.dart'; + +class HomeView extends StatefulWidget { + const HomeView({super.key}); + + @override + State createState() => _HomeViewState(); +} + +class _HomeViewState extends State { + @override + void initState() { + // 初始化时加载 hostFiles + context.read().loadHostFiles(context, true); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + drawer: + MediaQuery.of(context).size.width < 600 ? const HomeDrawer() : null, + floatingActionButton: BlocBuilder( + builder: (context, state) { + if (state.data.editMode == EditMode.Table) { + return FloatingActionButton( + onPressed: () async { + List? hostsModels = await Navigator.of(context) + .push(MaterialPageRoute( + builder: (context) => const HostPage())); + if (hostsModels == null) return; + context.read().addHosts(hostsModels); + }, + child: const Icon(Icons.add), + ); + } + return const SizedBox(); + }, + ), + body: BlocBuilder( + builder: (context, state) { + if (state is HomeSelectHostFileChanged) { + context.read().updateHost(state.data.selectHostFile); + } + + if(state is HomeEditMode) { + context.read().updateEditMode(state.data.editMode); + } + + return Row( + children: [ + if (state.data.advancedSettingsEnum == + AdvancedSettingsEnum.Close && + MediaQuery.of(context).size.width > 600) + const HomeDrawer(), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const HomeAppBar(), + saveTipMessage(state.data), + HostView(state.data) + ], + )) + ], + ); + }, + ), + ); + } + + Widget saveTipMessage(HomeStateData data) { + return BlocBuilder(builder: (context, state) { + final hostCubit = context.read(); + if (state.data.isSave) { + return const SizedBox(); + } + + final homeCubit = context.read(); + + final String updateSaveTip = + AppLocalizations.of(context)!.error_not_update_save_tip; + final String updateSavePermissionTip = data.selectHostFile == + state.data.fileId + ? '\n${AppLocalizations.of(context)!.error_not_update_save_permission_tip}' + : ''; + return MaterialBanner( + content: Text("$updateSaveTip$updateSavePermissionTip"), + leading: const Icon(Icons.error_outline), + actions: [ + TextButton( + onPressed: () => + hostCubit.onTableSave(context, homeCubit.state.data, true), + child: Text(AppLocalizations.of(context)!.save_create_history), + ), + TextButton( + onPressed: () => + hostCubit.onTableSave(context, homeCubit.state.data, false), + child: Text(AppLocalizations.of(context)!.save), + ), + ], + ); + }); + } +} \ No newline at end of file diff --git a/lib/home/view/host_list.dart b/lib/home/view/host_list.dart new file mode 100644 index 0000000..11faf33 --- /dev/null +++ b/lib/home/view/host_list.dart @@ -0,0 +1,383 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hosts/enums.dart'; +import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/host_file.dart'; +import 'package:hosts/page/host_page.dart'; +import 'package:hosts/widget/dialog/copy_dialog.dart'; +import 'package:hosts/widget/dialog/link_dialog.dart'; +import 'package:hosts/widget/dialog/test_dialog.dart'; +import 'package:hosts/widget/snakbar.dart'; + +/// 主机列表组件 +/// +/// 显示主机列表并提供交互功能,包括: +/// - 主机项的增删改查 +/// - 状态切换 +/// - 排序功能 +/// - 批量操作 +class HostList extends StatelessWidget { + /// 构造函数 + const HostList({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (BuildContext context, state) { + final hostCubit = context.read(); + final hostStateData = state.data; + return Column( + children: [ + // 表头行 + Row( + children: [ + Container( + width: 50, + margin: EdgeInsets.symmetric(horizontal: 4), + child: Checkbox( + value: state.data.selectHosts.isNotEmpty && + state.data.selectHosts.length == + state.data.filterHosts.length, + onChanged: context.read().updateCheckedAll, + ), + ), + HeaderColumn( + columnName: 'host', + text: AppLocalizations.of(context)!.ip_address, + cubit: hostCubit, + ), + SizedBox(width: 16), + HeaderColumn( + columnName: 'use', + text: AppLocalizations.of(context)!.status, + cubit: hostCubit, + ), + SizedBox(width: 16), + HeaderColumn( + columnName: 'hosts', + text: AppLocalizations.of(context)!.domain, + cubit: hostCubit, + ), + SizedBox(width: 16), + HeaderColumn( + columnName: 'description', + text: AppLocalizations.of(context)!.remark, + cubit: hostCubit, + ), + SizedBox(width: 16), + SizedBox( + width: 100, + child: Text(AppLocalizations.of(context)!.action, + style: const TextStyle(fontWeight: FontWeight.bold)), + ), + ], + ), + Expanded( + child: ListView.builder( + itemCount: hostStateData.filterHosts.length, + itemBuilder: (context, index) { + final HostsModel host = hostStateData.filterHosts[index]; + return InkWell( + onTap: () async { + List? hostsModels = + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => HostPage(hostModel: host), + ), + ); + if (hostsModels == null) return; + hostCubit.onEdit(index, hostsModels.first); + }, + child: ListItem( + host: host, + onSwitchChanged: (value) { + // Map<旧, 新> + final Map updateUseHosts = { + host: host.withCopy(isUse: value) + }; + void updateHostStates( + List hostNames, bool isUse) { + for (var tempHost in hostStateData.filterHosts + .where((item) => hostNames.contains(item.host))) { + updateUseHosts[tempHost] = + tempHost.withCopy(isUse: isUse); + } + } + + if (host.config["same"] != null) { + updateHostStates( + (host.config["same"] as List) + .cast(), + value); + } + if (host.config["contrary"] != null) { + updateHostStates( + (host.config["contrary"] as List) + .cast(), + !value); + } + + hostCubit.onToggleUse(updateUseHosts); + }, + onCheckChanged: (value) => + hostCubit.onChecked(index, host), + trailing: buildMoreButton( + context, hostCubit, hostStateData, index, host), + isChecked: hostStateData.selectHosts.contains(host), + ), + ); + }, + ), + ) + ], + ); + }, + ); + } + + Widget buildMoreButton(BuildContext context, HostCubit hostCubit, + HostStateData hostStateData, int index, HostsModel host) { + return PopupMenuButton( + onSelected: (value) async { + switch (value) { + case 1: + final Map>? result = + await linkDialog(context, hostStateData.filterHosts, host); + if (result == null) return; + hostCubit.onEdit(index, host.withCopy(config: result)); + break; + case 2: + testDialog(context, host); + break; + case 3: + copyDialog(context, hostStateData.filterHosts, index); + break; + case 4: + deleteMultiple( + context, + hostStateData.filterHosts.map((it) => it.host).toList(), + () => hostCubit.onDelete([host])); + break; + } + }, + itemBuilder: (BuildContext context) { + List> list = [ + { + "icon": Icons.link, + "text": AppLocalizations.of(context)!.link, + "value": 1 + }, + { + "icon": Icons.sensors, + "text": AppLocalizations.of(context)!.test, + "value": 2 + }, + { + "icon": Icons.copy, + "text": AppLocalizations.of(context)!.copy, + "value": 3 + }, + { + "icon": Icons.delete_outline, + "text": AppLocalizations.of(context)!.delete, + "value": 4 + }, + ]; + + return list + .where((item) => !(item["value"] == 2 && kIsWeb)) + .map((item) { + return PopupMenuItem( + value: int.parse(item["value"].toString()), + child: Row( + children: [ + Icon(item["icon"]! as IconData), + const SizedBox(width: 8), + Text(item["text"]!.toString()), + ], + ), + ); + }).toList(); + }, + ); + } +} + +/// 主机列表项组件 +/// +/// 显示单个主机项的详细信息,包括: +/// - 复选框状态 +/// - 开关状态 +/// - 主机信息 +/// - 操作按钮 +class ListItem extends StatelessWidget { + /// 是否选中 + final bool isChecked; + + /// 主机数据模型 + final HostsModel host; + + /// 开关状态变更回调 + final ValueChanged onSwitchChanged; + + /// 复选框状态变更回调 + final ValueChanged onCheckChanged; + + /// 右侧操作按钮组件 + final Widget trailing; + + /// 构造函数 + const ListItem( + {super.key, + required this.host, + required this.onSwitchChanged, + required this.onCheckChanged, + required this.isChecked, + required this.trailing}); + + @override + Widget build(BuildContext context) { + bool isLink = false; + if (host.config.isNotEmpty) { + isLink = host.config["same"] != null && host.config["contrary"] != null; + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 9), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Checkbox( + value: isChecked, + onChanged: onCheckChanged, + ), + const SizedBox(width: 16), + Switch( + value: host.isUse, + onChanged: onSwitchChanged, + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (host.description.isNotEmpty) + Text( + host.description, + style: Theme.of(context).textTheme.labelSmall, + ), + const SizedBox(height: 4.0), + Text.rich(TextSpan( + children: [ + if (isLink) + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 4), + child: Icon( + Icons.link, + color: Theme.of(context).colorScheme.primary, + size: 18, + ), + )), + TextSpan( + text: host.host, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold), + ) + ], + )), + const SizedBox(height: 4.0), + Text.rich(TextSpan( + children: _buildTextSpans(host.hosts, context), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.6), + fontWeight: FontWeight.bold))), + ], + ), + ), + const SizedBox(width: 16), + trailing, + ], + ), + ); + } + + List _buildTextSpans(List hosts, BuildContext context) { + List textSpans = []; + + for (int i = 0; i < hosts.length; i++) { + textSpans.add(TextSpan( + text: hosts[i], + recognizer: TapGestureRecognizer() + ..onTap = () { + // onLaunchUrl(hosts[i]); + }, + )); + + if (i < hosts.length - 1) { + textSpans.add(TextSpan( + text: ' - ', + style: TextStyle( + color: Theme.of(context).colorScheme.inverseSurface, + fontWeight: FontWeight.w900))); + } + } + + return textSpans; + } +} + +class HeaderColumn extends StatelessWidget { + final String columnName; + final String text; + final HostCubit cubit; + + const HeaderColumn({ + super.key, + required this.columnName, + required this.text, + required this.cubit, + }); + + @override + Widget build(BuildContext context) { + return Expanded( + child: InkWell( + onTap: () => cubit.onSort(columnName), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + text, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + BlocBuilder( + builder: (context, state) { + final direction = state.data.sortStatus[columnName]; + return Icon( + direction == SortDirection.ascending + ? Icons.arrow_upward + : direction == SortDirection.descending + ? Icons.arrow_downward + : Icons.unfold_more, + size: 16, + ); + }, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/home/view/host_table.dart b/lib/home/view/host_table.dart new file mode 100644 index 0000000..690a39e --- /dev/null +++ b/lib/home/view/host_table.dart @@ -0,0 +1,445 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/host_file.dart'; +import 'package:hosts/page/host_page.dart'; +import 'package:hosts/widget/dialog/copy_dialog.dart'; +import 'package:hosts/widget/dialog/link_dialog.dart'; +import 'package:hosts/widget/dialog/test_dialog.dart'; +import 'package:hosts/widget/snakbar.dart'; +import 'package:syncfusion_flutter_datagrid/datagrid.dart'; + +/// 主机表格组件 +/// +/// 使用SfDataGrid显示主机数据表格,提供: +/// - 排序功能 +/// - 分页显示 +/// - 交互操作 +class HostTable extends StatelessWidget { + /// 构造函数 + const HostTable({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final hostCubit = context.read(); + + return SfDataGrid( + allowSorting: true, + columnWidthMode: ColumnWidthMode.fill, + gridLinesVisibility: GridLinesVisibility.none, + headerGridLinesVisibility: GridLinesVisibility.none, + source: HostDataSource( + hosts: state.data.filterHosts, + selectHosts: state.data.selectHosts, + onEdit: (index, host) async { + List? hostsModels = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => HostPage(hostModel: host), + ), + ); + if (hostsModels == null) return; + hostCubit.onEdit(index, hostsModels.first); + }, + onLink: (index, host) async { + final Map>? result = + await linkDialog(context, state.data.hosts, host); + if (result == null) return; + hostCubit.onEdit(index, host.withCopy(config: result)); + }, + onChecked: hostCubit.onChecked, + onDelete: (hosts) { + deleteMultiple(context, hosts.map((it) => it.host).toList(), + () => hostCubit.onDelete(hosts)); + }, + onToggleUse: hostCubit.onToggleUse, + onLaunchUrl: (host) { + print("onLaunchUrl, $host"); + }, + context: context, + ), + columns: [ + GridColumn( + columnName: 'checkbox', + allowSorting: false, + label: Checkbox( + value: state.data.selectHosts.isNotEmpty && + state.data.selectHosts.length == state.data.filterHosts.length, + onChanged: context.read().updateCheckedAll, + ), + width: 50, + ), + GridColumn( + columnName: 'host', + allowSorting: true, + label: Container( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(AppLocalizations.of(context)!.ip_address, + style: const TextStyle(fontWeight: FontWeight.bold)), + ), + ), + ), + GridColumn( + columnName: 'use', + allowSorting: true, + label: Container( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(AppLocalizations.of(context)!.status, + style: const TextStyle(fontWeight: FontWeight.bold)), + ), + ), + ), + GridColumn( + columnName: 'hosts', + allowSorting: true, + label: Container( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(AppLocalizations.of(context)!.domain, + style: const TextStyle(fontWeight: FontWeight.bold)), + ), + ), + ), + GridColumn( + columnName: 'description', + allowSorting: true, + label: Container( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(AppLocalizations.of(context)!.remark, + style: const TextStyle(fontWeight: FontWeight.bold)), + ), + ), + ), + GridColumn( + columnName: 'actions', + allowSorting: false, + label: Container( + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(AppLocalizations.of(context)!.action, + style: const TextStyle(fontWeight: FontWeight.bold)), + ), + ), + ), + ], + ); + }, + ); + } +} + +/// 主机表格数据源 +/// +/// 负责管理表格数据并提供排序功能 +class HostDataSource extends DataGridSource { + /// 构造函数 + /// + /// [hosts]: 主机数据列表 + /// [selectHosts]: 已选中的主机列表 + /// [onEdit]: 编辑回调函数 + /// [onLink]: 链接回调函数 + /// [onChecked]: 选中状态变更回调 + /// [onDelete]: 删除回调函数 + /// [onToggleUse]: 使用状态切换回调 + /// [onLaunchUrl]: URL跳转回调 + /// [context]: 构建上下文 + HostDataSource({ + required this.hosts, + required this.selectHosts, + required this.onEdit, + required this.onLink, + required this.onChecked, + required this.onDelete, + required this.onToggleUse, + required this.onLaunchUrl, + required this.context, + }); + + final List hosts; + final List selectHosts; + final Function(int, HostsModel) onEdit; + final Function(int, HostsModel) onLink; + final Function(int, HostsModel) onChecked; + final Function(List) onDelete; + final Function(Map) onToggleUse; + final Function(String) onLaunchUrl; + final BuildContext context; + + @override + List get rows => hosts.map((host) { + bool isLink = false; + if (host.config.isNotEmpty) { + isLink = + host.config["same"] != null && host.config["contrary"] != null; + } + + return DataGridRow(cells: [ + DataGridCell( + columnName: 'checkbox', + value: Checkbox( + value: selectHosts.contains(host), + onChanged: (bool? newValue) => + onChecked(hosts.indexOf(host), host), + ), + ), + DataGridCell( + columnName: 'host', + value: MyDataGridCell( + value: host.host, + child: GestureDetector( + onTap: () => onLaunchUrl(host.host), + child: Container( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + children: [ + if (isLink) + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 4), + child: Icon( + Icons.link, + color: Theme.of(context).colorScheme.primary, + size: 18, + ), + ), + ), + TextSpan( + text: host.host, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + )), + ), + DataGridCell( + columnName: 'use', + value: MyDataGridCell( + value: host.isUse, + child: Switch( + value: host.isUse, + onChanged: (value) { + // Map<旧, 新> + final Map updateUseHosts = { + host: host.withCopy(isUse: value) + }; + void updateHostStates(List hostNames, bool isUse) { + for (var tempHost in hosts + .where((item) => hostNames.contains(item.host))) { + updateUseHosts[tempHost] = + tempHost.withCopy(isUse: isUse); + } + } + + if (host.config["same"] != null) { + updateHostStates( + (host.config["same"] as List).cast(), + value); + } + if (host.config["contrary"] != null) { + updateHostStates( + (host.config["contrary"] as List) + .cast(), + !value); + } + onToggleUse(updateUseHosts); + }, + ), + ), + ), + DataGridCell( + columnName: 'hosts', + value: MyDataGridCell( + value: host.hosts, + child: Container( + alignment: Alignment.centerLeft, + child: Text.rich( + TextSpan( + children: _buildTextSpans(host.hosts, context), + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + DataGridCell( + columnName: 'description', + value: MyDataGridCell( + value: host.description, + child: Container( + alignment: Alignment.centerLeft, + child: SelectableText(host.description)), + ), + ), + DataGridCell( + columnName: 'actions', + value: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + onPressed: () => onEdit(hosts.indexOf(host), host), + icon: const Icon(Icons.edit), + ), + const SizedBox(width: 8), + IconButton( + onPressed: () => onDelete([host]), + icon: const Icon(Icons.delete_outline), + ), + const SizedBox(width: 8), + buildMoreButton(context, hosts.indexOf(host), host), + ], + ), + ), + ]); + }).toList(); + + @override + Future performSorting(List rows) async { + // sortedColumns 是父类提供的属性,里面包含排序列及排序方向等信息。 + if (sortedColumns.isNotEmpty) { + final sortColumn = sortedColumns.first; + // SortColumnDetails 通常包含 columnName 和 sortDirection + final String columnName = sortColumn.name; + final bool isAscending = + sortColumn.sortDirection == DataGridSortDirection.ascending; + if (!["checkbox", "actions"].contains(columnName)) { + rows.sort((a, b) { + final valueA = a + .getCells() + .firstWhere((cell) => cell.columnName == columnName) + .value + .toString(); + final valueB = b + .getCells() + .firstWhere((cell) => cell.columnName == columnName) + .value + .toString(); + + return isAscending + ? valueA.compareTo(valueB) + : valueB.compareTo(valueA); + }); + } + } + // notifyListeners(); + } + + List _buildTextSpans(List hosts, BuildContext context) { + List textSpans = []; + for (int i = 0; i < hosts.length; i++) { + textSpans.add(TextSpan( + text: hosts[i], + recognizer: TapGestureRecognizer() + ..onTap = () { + onLaunchUrl(hosts[i]); + }, + )); + if (i < hosts.length - 1) { + textSpans.add(TextSpan( + text: ' - ', + style: TextStyle( + color: Theme.of(context).colorScheme.inverseSurface, + fontWeight: FontWeight.w900, + ), + )); + } + } + return textSpans; + } + + Widget buildMoreButton(BuildContext context, int index, HostsModel host) { + return PopupMenuButton( + onSelected: (value) async { + switch (value) { + case 1: + onLink(index, host); + break; + case 2: + testDialog(context, host); + break; + case 3: + copyDialog(context, hosts, index); + break; + } + }, + itemBuilder: (BuildContext context) { + List> list = [ + { + "icon": Icons.link, + "text": AppLocalizations.of(context)!.link, + "value": 1 + }, + { + "icon": Icons.sensors, + "text": AppLocalizations.of(context)!.test, + "value": 2 + }, + { + "icon": Icons.copy, + "text": AppLocalizations.of(context)!.copy, + "value": 3 + }, + ]; + return list + .where((item) => !(item["value"] == 2 && kIsWeb)) + .map((item) { + return PopupMenuItem( + value: int.parse(item["value"].toString()), + child: Row( + children: [ + Icon(item["icon"]! as IconData), + const SizedBox(width: 8), + Text(item["text"]!.toString()), + ], + ), + ); + }).toList(); + }, + ); + } + + @override + DataGridRowAdapter? buildRow(DataGridRow row) { + return DataGridRowAdapter( + cells: + row.getCells().map((dataCell) => dataCell.value as Widget).toList(), + ); + } +} + +class MyDataGridCell extends StatelessWidget { + final Widget child; + final dynamic value; + + const MyDataGridCell({super.key, required this.child, required this.value}); + + @override + Widget build(BuildContext context) { + return child; + } + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return value.toString(); + } +} diff --git a/lib/home/view/host_text.dart b/lib/home/view/host_text.dart new file mode 100644 index 0000000..06867ab --- /dev/null +++ b/lib/home/view/host_text.dart @@ -0,0 +1,145 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/widget/host_text_editing_controller.dart'; +import 'package:hosts/widget/row_line_widget.dart'; + +/// 主机文本编辑组件 +/// +/// 提供主机文件的文本编辑功能,包括: +/// - 行号显示 +/// - 快捷键操作 +/// - 文本内容同步 +class HostText extends StatefulWidget { + /// 构造函数 + const HostText({super.key}); + + @override + State createState() => _HostTextState(); +} + +/// HostText组件的状态类 +/// +/// 管理文本编辑器的状态和交互逻辑,包括: +/// - 文本控制器初始化 +/// - 快捷键处理 +/// - 滚动同步 +class _HostTextState extends State { + HostTextEditingController textEditingController = HostTextEditingController(); + final FocusNode _focusNode = FocusNode(); + bool isControl = false; + final ScrollController _scrollController = ScrollController(); + final ScrollController _textScrollController = ScrollController(); + final GlobalKey _textFieldContainerKey = GlobalKey(); + + @override + void initState() { + final hostCubit = context.read(); + textEditingController + ..text = hostCubit.state.data.fileContent + ..addListener(() { + hostCubit.updateFileContent(textEditingController.text); + }); + super.initState(); + } + + @override + void dispose() { + textEditingController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (BuildContext context, state) { + if (state is HostUndo || state is HostInitial || state is HostHistory) { + textEditingController.text = state.data.fileContent; + } + final hostCubit = context.read(); + return Column( + children: [ + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RowLineWidget( + textEditingController: textEditingController, + context: context, + textFieldContainerKey: _textFieldContainerKey, + scrollController: _scrollController, + ), + Expanded( + key: _textFieldContainerKey, + child: KeyboardListener( + focusNode: _focusNode, + onKeyEvent: (event) { + List logicalKeys = []; + if (Platform.isMacOS) { + logicalKeys = [ + LogicalKeyboardKey.metaLeft, + LogicalKeyboardKey.metaRight + ]; + } else { + logicalKeys = [ + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.controlRight + ]; + } + if (logicalKeys.contains(event.logicalKey)) { + if (isControl) { + isControl = false; + } else { + isControl = true; + } + } + if (event.logicalKey == LogicalKeyboardKey.slash && + isControl && + event is KeyDownEvent) { + textEditingController + .updateUseStatus(textEditingController.selection); + } + + if (event.logicalKey == LogicalKeyboardKey.keyS && + isControl && + event is KeyDownEvent && + !state.data.isSave) { + hostCubit.onTextSave(); + } + }, + child: TextField( + controller: textEditingController, + scrollController: _textScrollController, + maxLines: null, + decoration: + const InputDecoration(border: InputBorder.none), + ), + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Row( + children: [ + Text( + "当前行:${textEditingController.countNewlines(textEditingController.text.substring(0, textEditingController.selection.start > 0 ? textEditingController.selection.start : 0)) + 1}", + ), + const SizedBox( + width: 8, + ), + Text( + "总行数:${textEditingController.countNewlines(textEditingController.text) + 1}"), + ], + ), + ) + ], + ); + }, + ); + } +} diff --git a/lib/home/view/host_view.dart b/lib/home/view/host_view.dart new file mode 100644 index 0000000..af0e08d --- /dev/null +++ b/lib/home/view/host_view.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hosts/enums.dart'; +import 'package:hosts/home/cubit/home_cubit.dart'; +import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/home/view/host_list.dart'; +import 'package:hosts/home/view/host_table.dart'; +import 'package:hosts/home/view/host_text.dart'; +import 'package:hosts/widget/error/error_empty.dart'; + +/// 主机视图组件 +/// +/// 根据编辑模式和屏幕尺寸显示不同的主机视图: +/// - 文本编辑模式显示HostText +/// - 表格模式显示HostTable +/// - 列表模式显示HostList +class HostView extends StatelessWidget { + /// 主页状态数据 + final HomeStateData data; + + /// 构造函数 + /// + /// [data]: 主页状态数据 + /// [key]: 组件key + const HostView(this.data, {super.key}); + + /// 构建组件布局 + /// + /// [context]: 构建上下文 + /// 返回: 根据编辑模式和屏幕尺寸返回对应的视图组件 + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (BuildContext context, state) { + if (data.editMode == EditMode.Text) { + return Expanded( + child: HostText(), + ); + } + + if (state.data.filterHosts.isEmpty) { + return Expanded( + child: Container( + alignment: Alignment.center, + width: double.maxFinite, + height: double.maxFinite, + child: const ErrorEmpty(), + ), + ); + } + + if (MediaQuery.of(context).size.width >= 1000) { + return Expanded(child: HostTable()); + } + + return Expanded(child: HostList()); + }); + } +} diff --git a/lib/host_observer.dart b/lib/host_observer.dart new file mode 100644 index 0000000..8d58279 --- /dev/null +++ b/lib/host_observer.dart @@ -0,0 +1,16 @@ +import 'package:bloc/bloc.dart'; + +/// 主机观察者类 +/// 用于观察应用中所有BLoC状态变化的观察者 +class HostObserver extends BlocObserver { + /// 构造函数 + const HostObserver(); + + @override + void onChange(BlocBase bloc, Change change) { + super.onChange(bloc, change); + // 打印状态变化日志(忽略避免打印的警告) + // ignore: avoid_print + print('${bloc.runtimeType} $change'); + } +} From df6a10e73db661110a96d4f4449f969459efb103 Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Mon, 30 Jun 2025 09:07:07 +0800 Subject: [PATCH 06/54] =?UTF-8?q?feat(=E8=A7=86=E5=9B=BE):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E7=AE=80=E5=8D=95=E4=B8=BB=E9=A1=B5=E9=9D=A2=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=B9=B6=E4=BC=98=E5=8C=96=E6=96=87=E4=BB=B6=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 `SimpleHomePage` 和 `SimpleHomeView` 组件,完善主页面的简化视图展示。优化文件读取逻辑,通过 `HostCubit` 管理从文件读取的数据流。更新 `.gitignore` 增加多平台相关的缓存文件排除规则。同步更新依赖项以支持新功能和修复。 --- .gitignore | 5 + lib/app.dart | 2 - lib/home/cubit/host_cubit.dart | 18 +- lib/home/view/home_app_bar.dart | 8 +- lib/home/view/home_page.dart | 15 ++ lib/home/view/host_list.dart | 6 - lib/home/view/simple_home_view.dart | 87 +++++++ lib/main.dart | 78 +----- lib/model/host_file.dart | 57 ++++- lib/util/file_manager.dart | 122 ++++++++- macos/Flutter/Flutter-Debug.xcconfig | 1 + macos/Flutter/Flutter-Release.xcconfig | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + macos/Runner.xcodeproj/project.pbxproj | 98 ++++++- .../xcshareddata/xcschemes/Runner.xcscheme | 1 + .../contents.xcworkspacedata | 3 + .../AppIcon.appiconset/app_icon_1024.png | Bin 102994 -> 23998 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 5680 -> 4444 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 520 -> 673 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 14142 -> 10118 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 1066 -> 1207 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 36406 -> 24003 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 2218 -> 2224 bytes pubspec.lock | 242 +++++++++++------- pubspec.yaml | 82 ++---- 25 files changed, 581 insertions(+), 247 deletions(-) create mode 100644 lib/home/view/simple_home_view.dart diff --git a/.gitignore b/.gitignore index 29a3a50..6ebb677 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related @@ -41,3 +43,6 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +/android/app/.cxx/ + +/macos/DerivedData/ \ No newline at end of file diff --git a/lib/app.dart b/lib/app.dart index bbe60a1..ce285d0 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -6,8 +6,6 @@ import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/home/view/home_page.dart'; import 'package:hosts/model/global_settings.dart'; import 'package:hosts/model/simple_host_file.dart'; -// import 'package:hosts/page/home_page.dart'; -import 'package:hosts/page/simple_home_page.dart'; import 'package:hosts/theme.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/util/settings_manager.dart'; diff --git a/lib/home/cubit/host_cubit.dart b/lib/home/cubit/host_cubit.dart index cec9b27..6bd41e9 100644 --- a/lib/home/cubit/host_cubit.dart +++ b/lib/home/cubit/host_cubit.dart @@ -427,6 +427,22 @@ class HostCubit extends Cubit { ); } + void fromText(String content) { + final hosts = _fileManager.parseHosts(content.split("\n")); + + emit( + HostInitial( + HostStateData( + fileContent: content, + defaultFileContent: content, + hosts: hosts, + defaultHosts: hosts, + filterHosts: hosts, + ), + ), + ); + } + /// 检查是否有更新 /// /// [hosts]: 当前主机列表 @@ -603,7 +619,7 @@ class HostCubit extends Cubit { } /// 转换为字符串 - /// + /// /// 返回当前文件内容字符串 @override String toString() { diff --git a/lib/home/view/home_app_bar.dart b/lib/home/view/home_app_bar.dart index 9c5d9fb..b68a1bb 100644 --- a/lib/home/view/home_app_bar.dart +++ b/lib/home/view/home_app_bar.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; +import 'dart:io'; import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; @@ -256,11 +258,11 @@ class HomeAppBar extends StatelessWidget { } final Uint8List? bytes = result.files.first.bytes; if (path.isNotEmpty && bytes == null) { - // onOpenFile(File(path).readAsStringSync()); + context.read().fromText(File(path).readAsStringSync()); } - if (path.isEmpty && bytes != null) { - // onOpenFile(utf8.decode(bytes)); + if (bytes != null) { + context.read().fromText(utf8.decode(bytes)); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( diff --git a/lib/home/view/home_page.dart b/lib/home/view/home_page.dart index 81582f2..0dbf607 100644 --- a/lib/home/view/home_page.dart +++ b/lib/home/view/home_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/home/view/home_view.dart'; +import 'package:hosts/home/view/simple_home_view.dart'; /// 首页页面组件 /// @@ -23,3 +24,17 @@ class HomePage extends StatelessWidget { ); } } + +class SimpleHomePage extends StatelessWidget { + final String filePath; + + const SimpleHomePage({super.key, required this.filePath}); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider(providers: [ + BlocProvider(create: (context) => HomeCubit()), + BlocProvider(create: (context) => HostCubit()), + ], child: SimpleHomeView()); + } +} diff --git a/lib/home/view/host_list.dart b/lib/home/view/host_list.dart index 11faf33..230c900 100644 --- a/lib/home/view/host_list.dart +++ b/lib/home/view/host_list.dart @@ -67,12 +67,6 @@ class HostList extends StatelessWidget { text: AppLocalizations.of(context)!.remark, cubit: hostCubit, ), - SizedBox(width: 16), - SizedBox( - width: 100, - child: Text(AppLocalizations.of(context)!.action, - style: const TextStyle(fontWeight: FontWeight.bold)), - ), ], ), Expanded( diff --git a/lib/home/view/simple_home_view.dart b/lib/home/view/simple_home_view.dart new file mode 100644 index 0000000..504171a --- /dev/null +++ b/lib/home/view/simple_home_view.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hosts/enums.dart'; +import 'package:hosts/home/cubit/home_cubit.dart'; +import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/home/view/home_app_bar.dart'; +import 'package:hosts/home/view/host_view.dart'; +import 'package:hosts/model/host_file.dart'; +import 'package:hosts/page/host_page.dart'; +import 'package:hosts/l10n/app_localizations.dart'; + +class SimpleHomeView extends StatelessWidget { + const SimpleHomeView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: BlocBuilder( + builder: (context, state) { + if (state.data.editMode == EditMode.Table) { + return FloatingActionButton( + onPressed: () async { + List? hostsModels = await Navigator.of(context) + .push(MaterialPageRoute( + builder: (context) => const HostPage())); + if (hostsModels == null) return; + context.read().addHosts(hostsModels); + }, + child: const Icon(Icons.add), + ); + } + return const SizedBox(); + }, + ), + body: BlocBuilder( + builder: (context, state) { + if(state is HomeEditMode) { + context.read().updateEditMode(state.data.editMode); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const HomeAppBar(), + saveTipMessage(state.data), + HostView(state.data) + ], + ); + }, + ), + ); + } + + Widget saveTipMessage(HomeStateData data) { + return BlocBuilder(builder: (context, state) { + final hostCubit = context.read(); + if (state.data.isSave) { + return const SizedBox(); + } + + final homeCubit = context.read(); + + final String updateSaveTip = + AppLocalizations.of(context)!.error_not_update_save_tip; + final String updateSavePermissionTip = data.selectHostFile == + state.data.fileId + ? '\n${AppLocalizations.of(context)!.error_not_update_save_permission_tip}' + : ''; + return MaterialBanner( + content: Text("$updateSaveTip$updateSavePermissionTip"), + leading: const Icon(Icons.error_outline), + actions: [ + TextButton( + onPressed: () => + hostCubit.onTableSave(context, homeCubit.state.data, true), + child: Text(AppLocalizations.of(context)!.save_create_history), + ), + TextButton( + onPressed: () => + hostCubit.onTableSave(context, homeCubit.state.data, false), + child: Text(AppLocalizations.of(context)!.save), + ), + ], + ); + }); + } +} diff --git a/lib/main.dart b/lib/main.dart index 8669ead..a9917e9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,83 +1,17 @@ import 'dart:io'; -import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:hosts/model/global_settings.dart'; -import 'package:hosts/model/simple_host_file.dart'; -import 'package:hosts/page/home_page.dart'; -import 'package:hosts/page/simple_home_page.dart'; -import 'package:hosts/theme.dart'; -import 'package:hosts/util/file_manager.dart'; -import 'package:hosts/util/settings_manager.dart'; +import 'package:hosts/app.dart'; +import 'package:hosts/host_observer.dart'; void main(List args) async { WidgetsFlutterBinding.ensureInitialized(); + Bloc.observer = const HostObserver(); final Iterable files = args.where((path) => File(path).existsSync()); if (files.isNotEmpty) { - runApp(MyApp(filePath: files.first)); + runApp(HostsApp(files.first)); } else { - runApp(const MyApp(filePath: "")); - } -} - -class MyApp extends StatelessWidget { - final String filePath; - - const MyApp({super.key, required this.filePath}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - onGenerateTitle: (context) => AppLocalizations.of(context)!.app_name, - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - theme: ThemeData( - brightness: Brightness.light, - colorScheme: MaterialTheme.lightScheme(), - useMaterial3: true, - ), - darkTheme: ThemeData( - brightness: Brightness.dark, - colorScheme: MaterialTheme.darkScheme(), - useMaterial3: true, - ), - themeMode: ThemeMode.system, - home: platformSpecificWidget(context), - ); - } - - Widget platformSpecificWidget(BuildContext context) { - GlobalSettings().isSimple = kIsWeb || filePath.isNotEmpty; - if (GlobalSettings().isSimple) { - return SimpleHomePage(filePath: filePath); - } else { - return FutureBuilder( - future: initializeApp(context), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else { - return const HomePage(); - } - }, - ); - } - } - - Future initializeApp(BuildContext context) async { - SettingsManager settingsManager = SettingsManager(); - FileManager fileManager = FileManager(); - bool firstOpenApp = await settingsManager.getBool(settingKeyFirstOpenApp); - if (!firstOpenApp) { - const String fileName = "system"; - await fileManager.createHosts(fileName); - await settingsManager.setList(settingKeyHostConfigs, - [SimpleHostFile(fileName: fileName, remark: "")]); - await settingsManager.setString(settingKeyUseHostFile, fileName); - File(FileManager.systemHostFilePath) - .copy(await fileManager.getHostsFilePath(fileName)); - settingsManager.setBool(settingKeyFirstOpenApp, true); - } + runApp(HostsApp("")); } } diff --git a/lib/model/host_file.dart b/lib/model/host_file.dart index 40608b6..543fde0 100644 --- a/lib/model/host_file.dart +++ b/lib/model/host_file.dart @@ -30,14 +30,14 @@ class HostsModel { if (text.isEmpty && host.isEmpty && - hosts.where((it) => it.trim().isNotEmpty).isEmpty) { + hosts.where((it) => it.trim().isNotEmpty).isEmpty) { return ""; } return "$text${isUse ? "" : "# "}$host ${hosts.join(" ")} ${config.isNotEmpty ? '# - config ${json.encode(config)}' : ''}"; } - filter(String searchQuery) { + bool filter(String searchQuery) { if (searchQuery.isEmpty) return true; return host.contains(searchQuery) || description.contains(searchQuery) || @@ -64,6 +64,59 @@ class HostsModel { descLine: descLine ?? this.descLine, ); } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is HostsModel && + other.host == host && + other.isUse == isUse && + other.description == description && + _listEquals(other.hosts, hosts) && + _mapEquals(other.config, config); + } + @override + int get hashCode { + return host.hashCode ^ + isUse.hashCode ^ + description.hashCode ^ + _listHash(hosts) ^ + _mapHash(config); + } + + static bool _listEquals(List? list1, List? list2) { + if (identical(list1, list2)) return true; + if (list1 == null || list2 == null) return false; + if (list1.length != list2.length) return false; + for (int i = 0; i < list1.length; i++) { + if (list1[i] != list2[i]) return false; + } + return true; + } + static int _listHash(List list) { + int hash = 0; + for (var item in list) { + hash ^= item.hashCode; + } + return hash; + } + static bool _mapEquals(Map? map1, Map? map2) { + if (identical(map1, map2)) return true; + if (map1 == null || map2 == null) return false; + if (map1.length != map2.length) return false; + for (var key in map1.keys) { + if (map1[key] != map2[key]) return false; + } + return true; + } + static int _mapHash(Map map) { + int hash = 0; + for (var key in map.keys) { + hash ^= key.hashCode ^ map[key].hashCode; + } + return hash; + } } class HostsFile { diff --git a/lib/util/file_manager.dart b/lib/util/file_manager.dart index a60a05a..cab38b2 100644 --- a/lib/util/file_manager.dart +++ b/lib/util/file_manager.dart @@ -1,7 +1,10 @@ import 'dart:convert'; import 'dart:io'; +import 'package:flutter/services.dart'; +import 'package:hosts/model/host_file.dart'; import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/util/regexp_util.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; @@ -18,6 +21,9 @@ class FileManager { // 静态变量保存单例实例 static final FileManager _instance = FileManager._internal(); + static const MethodChannel _channel = + MethodChannel('top.webb_l.hosts/system'); + // 工厂构造函数返回单例实例 factory FileManager() => _instance; @@ -82,6 +88,16 @@ class FileManager { } } + Future readAsString(String fileId) async { + final path = await getHostsFilePath(fileId); + return File(path).readAsString(); + } + + Future> readAsLines(String fileId) async { + final path = await getHostsFilePath(fileId); + return await File(path).readAsLines(); + } + // 删除文件 Future deleteFiles(List fileNames) async { if (_cachedDirectory == null) await _initializeDirectory(); @@ -137,7 +153,7 @@ class FileManager { .toList(); } - void saveHistory(String fileId, String content) async { + Future saveHistory(String fileId, String content) async { if (_cachedDirectory == null) await _initializeDirectory(); if (fileId.isEmpty) return; @@ -145,18 +161,20 @@ class FileManager { final safeFileName = p.basename(fileId); // 只保留文件名,不允许路径 final filePath = p.join(_cachedDirectory!.path, safeFileName); Directory rootDirectory = Directory(filePath); - if (!rootDirectory.existsSync()) { + if (!(await rootDirectory.exists())) { rootDirectory.create(recursive: true); } Directory historyDirectory = Directory(p.join(rootDirectory.path, "history")); - if (!historyDirectory.existsSync()) { + if (!(await historyDirectory.exists())) { historyDirectory.create(recursive: true); } - File( + final File file = File( p.join(historyDirectory.path, DateTime.now().millisecondsSinceEpoch.toString()), - ).writeAsString(content); + ); + + await file.writeAsString(content); } void deleteFile(String path) { @@ -227,6 +245,100 @@ class FileManager { } } + if (Platform.isMacOS) { + try { + final result = await _channel.invokeMethod('modifyHostsFile', + {'content': File(cacheFilePath).readAsStringSync()}); + + if (result == null) { + throw Exception("修改hosts文件失败"); + } + } on PlatformException catch (e) { + print('修改hosts文件失败: ${e.message}'); + throw e; // 重新抛出异常,让调用者处理 + } + } + return result; } + + List parseHosts(List lines) { + List tempHosts = []; + + for (int i = 0; i < lines.length; i++) { + final line = lines[i].trim(); + if (line.isNotEmpty && (isValidIPv4(line) || isValidIPv6(line))) { + final parts = line + .replaceFirst("#", "") + .split(RegExp(r"\s+")) + .where((it) => it.trim().isNotEmpty) + .toList(); + + if (parts.length < 2) continue; + + String host = parts.first; + List hosts = parts.sublist(1); + + int? descLine; + String description = ""; + + Map config = {}; + + List lineDescription = line.contains(RegExp(r"\s+#\s?")) + ? line + .split(RegExp(r"\s+#\s?")) + .where((it) => it.trim().isNotEmpty) + .toList() + : []; + + if (lineDescription.isNotEmpty) { + print(lineDescription); + final List tempLineDescription = lineDescription.sublist(1); + final String temp = tempLineDescription.length > 1 + ? tempLineDescription.join("# ") + : "# ${tempLineDescription.join("")}"; + + final RegExp regExp = RegExp(r"# - config \{([^{}]*)\}"); + final String tempDescription = temp.contains(regExp) + ? temp.replaceAll(regExp, "") + : temp.replaceFirst("# ", ""); + if (tempDescription.trim().isNotEmpty) { + description = tempDescription; + } + + final RegExpMatch? match = regExp.firstMatch(temp); + if (match != null) { + try { + config = jsonDecode("{${match.group(1)}}"); + } catch (e) { + print("错误:解析配置失败"); + } + } + + hosts = lineDescription.first + .replaceFirst("#", "") + .split(RegExp(r"\s+")) + .where((it) => it.trim().isNotEmpty) + .toList() + .sublist(1); + } + + if (i > 0 && description.isEmpty) { + final prevLine = lines[i - 1].trim(); + if (prevLine.isNotEmpty && + prevLine.startsWith("#") && + !(isValidIPv4(prevLine) || isValidIPv6(prevLine))) { + description = prevLine.replaceFirst(RegExp(r"^#\s?"), ""); + descLine = i - 1; + } + } + + tempHosts.add(HostsModel(host, !line.startsWith(RegExp(r"^\s?#")), + description, hosts, config, + hostLine: i, descLine: descLine)); + } + } + + return tempHosts; + } } diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b..4b81f9b 100644 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig index c2efd0b..5caa9d1 100644 --- a/macos/Flutter/Flutter-Release.xcconfig +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b8e2b22..4712381 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,12 @@ import FlutterMacOS import Foundation +import file_picker import path_provider_foundation import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index f37c5a1..0118e0c 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 3F55E16061778D6F20835434 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E889EC860D2F7BA9D9722B31 /* Pods_Runner.framework */; }; + F19DFA0754E4847621807A92 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 297457D0F304F0F853517024 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -60,11 +62,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 286CDE1F1D210D69780895EA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 297457D0F304F0F853517024 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* hosts.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "hosts.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* hosts.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = hosts.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,8 +80,14 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 49ECC88EAD1B645FFA58948D /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 4E7B105E9F539AFB3D6B7B58 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8B41A85B58DED4695D296251 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + CAE52630D3CE470C54F4E04E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + DFED14FADB1C32FF7E538D04 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + E889EC860D2F7BA9D9722B31 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F19DFA0754E4847621807A92 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3F55E16061778D6F20835434 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 707625401C7FAFA8334CAAED /* Pods */, ); sourceTree = ""; }; @@ -172,9 +185,25 @@ path = Runner; sourceTree = ""; }; + 707625401C7FAFA8334CAAED /* Pods */ = { + isa = PBXGroup; + children = ( + 8B41A85B58DED4695D296251 /* Pods-Runner.debug.xcconfig */, + CAE52630D3CE470C54F4E04E /* Pods-Runner.release.xcconfig */, + 286CDE1F1D210D69780895EA /* Pods-Runner.profile.xcconfig */, + 49ECC88EAD1B645FFA58948D /* Pods-RunnerTests.debug.xcconfig */, + 4E7B105E9F539AFB3D6B7B58 /* Pods-RunnerTests.release.xcconfig */, + DFED14FADB1C32FF7E538D04 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + E889EC860D2F7BA9D9722B31 /* Pods_Runner.framework */, + 297457D0F304F0F853517024 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + E87E959E47DB7B36A0BEB3D4 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 787AADE9E303C852C45269B9 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + BE46BD8567808E3E601E4A37 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -329,6 +361,67 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 787AADE9E303C852C45269B9 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + BE46BD8567808E3E601E4A37 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E87E959E47DB7B36A0BEB3D4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -380,6 +473,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 49ECC88EAD1B645FFA58948D /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -394,6 +488,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 4E7B105E9F539AFB3D6B7B58 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -408,6 +503,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = DFED14FADB1C32FF7E538D04 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index dbc8bee..8f7c4cc 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 82b6f9d9a33e198f5747104729e1fcef999772a5..ad18f3b33881e10a90c041f2dedec36ce7c04690 100644 GIT binary patch literal 23998 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 Qy?8*dzeIm=HB4~){~Z#)egFUf literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z 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 Pe7HEdAEg|=c=LY&YVNkY diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index 13b35eba55c6dabc3aac36f33d859266c18fa0d0..62a3c89a8bd7cc6218ee4e51097e8d4759a7da8f 100644 GIT binary patch delta 4370 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 JPDHLkV1jL2y`=yE diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bdb57226d5f2bd20f11934f4903f16459cf52379..0c5bcc2f2e80c02794f096aa7cf4e9d6c0daebf1 100644 GIT binary patch literal 10118 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 literal 14142 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 delta 965 zcmV;$13LV-391N?Bnkm@Qb$4nuFf3kkzG@Nlu1NER9Fe^SItioK@|V(ZWmgLZT;Xw zPgVuWM>O%^|Dc$VK;n&?9!&g5)aVsG8cjs5UbtxVVnQNOV~7Mrg3+jnU;rhE6fhW6 zP)R>_eXrXo-RW*y6RQ_qc@}2Y@>0Q!$ilStXQ-=H=A?c;3K&txp!X7!P|jTxj;7LSC9Mu!=gho6q2efk2g@Dvo;{tK z-H-{`2D1(MoxvEqcQ$U@@BxpClMx;M?2|%vUT@nN$^_QU9Nq?^*2*~rEKDeW>{D^J zNiP(3%faD)Q3SZ!2dUuhq{A-2n<(cT_B;jW0BP?kNPu%bvS{GuTpUa!CG^}yD`)T! z+C`cd@RN$M}@H3uF6~p(y>?{JD7nQf_ z*`T^YL06-O>T(s$bi5v~_fWMfnE7Vn%2*tqV|?~m;wSJEVGkNMD>+xCu#um(7}0so zSEu7?_=Q64Q5D+fz~T=Rr=G_!L*P|(-iOK*@X8r{-?oBlnxMNNgCVDXARS1VC$QCL zagDB+=p2n6;Dc&taUr|3y$YDeNX#{=)#1Zk{;&VW800000NkvXXu0mjf>tWTf diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index 326c0e72c9d820600887813b3b98d0dd69c5d4e8..486d7e49d45321f8bcbb3e8a6218dcf95f30a5ff 100644 GIT binary patch literal 24003 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 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 2f1632cfddf3d9dade342351e627a0a75609fb46..b0fb75ea79c4fcc2db80f3c1b6868dae59d1c548 100644 GIT binary patch delta 2133 zcmV-b2&(t05wH=EBsm0UK}|sb0I`n?{9y$E001CkNK#Dz0D2|>0Dy!50Qvv`0D$NK z0Cg|`0P0`>06Lfe02gqax=}olE>eF7>q$gGRCod9TUlsTNf52X7T_Bz~xfGWhGCq9`K7fcPUWKQf^BA#udGU>p@32Y*D3xS)btGVa@( zI<5V<@4l|?e%?GX?^#gEy;arSRp(Z(_r4tRqarpdVJ>5?VJ=|)KTvQ{mj-4%$XLnyXRY-kKG$K`YDh96f=v#&zd!hcJACs!-fqbej0xa88AzvH*Zka z_fflc?P%Y=eKdRaY#K0N0P&Mfs_4aw7xdx7ha`DK0b(u&0Ohud`t|EarKL%OW+9*pjT__zfNcsw-*g2KiZ|)jH4FIGkA1`D&6}yRvXZ)Y z@1E2S1Z8Dqv~1ZjHEsb2CjbcE1^`B9Uk8mIvCsv8PX;Vqx|DY9+C_r~4U(EkkYj^2 zXU-h=H0T3>D}aB5mJK&909^oV*sy^PA3p3Ie9#4F2H*)}Ae0CIwIu*+*RG`ojaS)d?EvYCopyDR6|&; zCkg{FckW!ms4vll3m0hr{{3|M@@0DY@}s}MV7fnh_H4XBTU@MKwJPr8 zlqpkUJn(-S-8g#mXxz>ehn8*Hv?*TRIS6v0$fG>!h(f_GfK?v)beJ%ZZrQRWEH>WP z*4EOUJ73?NXA}U~)Xbkh-x$=DD_7|H_3K7mSco&oW53n56K*?U~pFMjfnGk>8F!%4@U(|;c)22;RA}0VyyL1EK$^fmu zefxGej`%&kUBFJyhmf(07cW+(P5@wR0|04+ZV-iVY;7Z*0aveHwV4rba?~-n)rghY zN>7|PQJFaba0Li}q5_};lpNQ39Ame%v`A*E3miIhNPV^=8BLlrDd8BcRRA!3)*^p7 zV3NBsKzD%$4<68|Q>RQ-#AP^~X=rGm)vH%4ATUPZ0bvofZrwUMdGaI;95^s;?_3I_ zFe;GP943A2w39T^?+E_3D3R?1q>T z@PhOB@#DcNt=D*0aOB7l_3E5otTba5*vtI8RO;^CyR>`vZb_}=*REZ&>TODl=J?b$ zI5mas@ujG=CC5q7qD70;snp7qD{01z8R~c~IB?_O196-_eVPs)JV=)=UD8eL&EcyG z=0CI~)Bj_t8#;6-_3hhN&0c>99_fyAOT*FLF94v3+4`|`rh_8*uRfN>AlSs*C>Uk4 z7%?{i035j0WpkuLY>4^mVIyB)rE~t90K#l%#G16aD`g?JTG<{vVnnP)s$%I6`?EQX z2!33M#Zk6p0%AgJUi3r%bXFtuL;QFe3n3rEVyT3=j5(`*KK~zi@;DK@a-2kg00000 LNkvXXu0mjfY&G-& delta 2127 zcmV-V2(b6C5vmc8Bnkm@Qb$4nuFf3kkzG=M2oy;~K~#7F?OSVX6jc;HJG-py|c5InY-=IY-!`3OG*I1PedcfpaTVeyujRs#C(u&N)%XKl>`91hDwK_^Q}+L#Dp`!10~I{udIVa+2|G#V0cEH-*RHY~<>3g?j?-cK^;&$8cpYTHEnP(3 zixu>PseUTRQ<~jf%ed6Q17;aHDPh|Z+@II^^V0LKWD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5A^;M{I8oD7Lp^7cb+fR6L@YvzuR5#}?#=HP7u8s)cnE$Rj=B?a+UI6FL^w}OG&!;+b z%ya1c1n+6-inU<0VM-Y_s5iTElq)ThyFdwi~+=Vj&vf)1)r+H~|Zp@L`V6$o%`85T1?(Ku& zD1Fr!rkb1a6+?g(ONhvOHI3F!^wP9^_uSshfFlp!h5|S;y)i|jqP)at(${&oJs)hq zgCSi2|M{sfmEVZFy#OnN4`AfqnFNA|IX2ViUrwl6#^Tn0Gs(uQn`INcR5&C?0*^c| zb+X%c1f#U6B1GR_3{qS7q-XTd&4M5!->=cei9Q{?i9h3Uw-PD>dez5ZKhet=t$hZfA&!@XBwdb_G06#>FlRej|>cKdhBgR~P{ zWYHL3&1oj#B1njWD1HN+;vYVgcBpQu2vF7Q(LHv5?B-ys3mLdM&)ex!hbt+ol|T6C zTS+Sc0jv(&7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBI zo*981BU%%U@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M zvs!lAvqf?6tSD1t*y5KBjx{~&;KB9p@;yjZ%A&Yr*Ouvepc zN(6Kn)m=*PK4I%T9~HOM)8+a`>dXyL$mdOe2n+6tt*vVBqGvblp}MAvZuD-8MKuSs zyV0}U1|Og{JUxxxer&7`l)>_->tVrZJHf7}ex3-E;6h6{S$!4H2gWGN~F(zYfI z&I!<4C6Cj}86!!HVKso`!wlU}od~3=LBi5c_EG(5M{~kuOY|sI5x`$yrcH9Pv#9ic z%|*0$Qa(nXCqO8AW98XbrvX1)&Lk`=-AiZAT}joRdb^6Yrww#G29;c2k7Pss?YK=J ze|dmP%VM#v1II%QoLSKG-Ph@epI<&IrZEo;mzznJpNvmr8m%6(C4e7>5#_iPSydcB zWwii5)6QO12ymUPb`=Luxe`M%4`70SjzZJ=sazV#tVY{&-~q97xJ5@w4?snpkCsAP zE$a67B3{OqsK7G-B?0^Z8sOF7N$5Z#fx$lo+fU-El%xc3Z8UTkm-)~N&i8X+w_gaG z54vtARs}C~IkXDe-{d3=S_Q>kv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2jAq-(-k z0-?OOZ-ep=3.5.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 52c4d6f..7b8b80c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,59 +1,43 @@ name: hosts description: "Hosts Editor" -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. +publish_to: 'none' # 防止意外发布到pub.dev version: 1.5.0 environment: sdk: ^3.5.0 -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. +# 依赖项管理提示: 使用`flutter pub upgrade --major-versions`自动升级 dependencies: + # Flutter SDK flutter: sdk: flutter + flutter_localizations: + sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. + # UI组件 cupertino_icons: ^1.0.8 - path_provider: ^2.1.4 + syncfusion_flutter_datagrid: ^29.2.7+1 + + # 状态管理 + flutter_bloc: ^9.1.1 + bloc: ^9.0.0 + + # 本地存储 shared_preferences: ^2.3.2 + + # 文件操作 + path_provider: ^2.1.4 path: ^1.9.0 - flutter_localizations: - sdk: flutter + file_picker: ^10.1.9 + + # 国际化 intl: any - file_picker: ^8.1.2 - syncfusion_flutter_datagrid: ^28.2.6 dev_dependencies: flutter_test: sdk: flutter - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^4.0.0 + flutter_lints: ^6.0.0 flutter_native_splash: ^2.4.1 flutter_native_splash: @@ -76,29 +60,3 @@ flutter: assets: # - images/a_dot_burr.jpeg - assets/icon/ - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package From b1288092a7c012b80db3fbcf2b88daba89d12592 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Wed, 9 Jul 2025 01:10:46 +0800 Subject: [PATCH 07/54] =?UTF-8?q?feat(=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86?= =?UTF-8?q?):=20=E6=94=AF=E6=8C=81=E4=B8=BB=E6=9C=BA=E8=A1=8C=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=AE=9E=E6=97=B6=E6=9B=B4=E6=96=B0=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 `onToggleUse` 中新增对主机行内容的更新逻辑,通过实时更新文件内容增强操作的一致性。重构 `toHostString` 方法,提升代码复用性并简化主机字符串生成规则。修复部分逻辑中的格式处理问题。 --- lib/home/cubit/host_cubit.dart | 26 ++++++++++++++++++-------- lib/model/host_file.dart | 23 ++++++++++++++++------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/lib/home/cubit/host_cubit.dart b/lib/home/cubit/host_cubit.dart index 6bd41e9..95d7cc8 100644 --- a/lib/home/cubit/host_cubit.dart +++ b/lib/home/cubit/host_cubit.dart @@ -181,21 +181,31 @@ class HostCubit extends Cubit { /// /// [hostsMap]: 主机映射关系 void onToggleUse(Map hostsMap) { + final lines = state.data.fileContent.split("\n"); + final List updatedHosts = state.data.hosts.map((host) { return hostsMap.containsKey(host) ? hostsMap[host]! : host; }).toList(); + for (var host in updatedHosts) { + if (host.hostLine == null) { + continue; + } + lines[host.hostLine!] = host.toHostString(); + } + emit( HostToggleUse( state.data.copyWith( - hosts: updatedHosts, - filterHosts: updatedHosts - .where((host) => host.filter(state.data.searchText)) - .toList(), - selectHosts: - state.data.selectHosts.isNotEmpty ? hostsMap.values.toList() : [], - isSave: isUpdate(updatedHosts), - ), + hosts: updatedHosts, + filterHosts: updatedHosts + .where((host) => host.filter(state.data.searchText)) + .toList(), + selectHosts: state.data.selectHosts.isNotEmpty + ? hostsMap.values.toList() + : [], + isSave: isUpdate(updatedHosts), + fileContent: lines.join("\n")), ), ); } diff --git a/lib/model/host_file.dart b/lib/model/host_file.dart index 543fde0..f90bb99 100644 --- a/lib/model/host_file.dart +++ b/lib/model/host_file.dart @@ -30,11 +30,15 @@ class HostsModel { if (text.isEmpty && host.isEmpty && - hosts.where((it) => it.trim().isNotEmpty).isEmpty) { + hosts.where((it) => it.trim().isNotEmpty).isEmpty) { return ""; } - return "$text${isUse ? "" : "# "}$host ${hosts.join(" ")} ${config.isNotEmpty ? '# - config ${json.encode(config)}' : ''}"; + return "$text${toHostString()}"; + } + + String toHostString() { + return "${isUse ? "" : "# "}$host ${hosts.join(" ")} ${config.isNotEmpty ? '# - config ${json.encode(config)}' : ''}"; } bool filter(String searchQuery) { @@ -76,13 +80,14 @@ class HostsModel { _listEquals(other.hosts, hosts) && _mapEquals(other.config, config); } + @override int get hashCode { return host.hashCode ^ - isUse.hashCode ^ - description.hashCode ^ - _listHash(hosts) ^ - _mapHash(config); + isUse.hashCode ^ + description.hashCode ^ + _listHash(hosts) ^ + _mapHash(config); } static bool _listEquals(List? list1, List? list2) { @@ -94,6 +99,7 @@ class HostsModel { } return true; } + static int _listHash(List list) { int hash = 0; for (var item in list) { @@ -101,7 +107,9 @@ class HostsModel { } return hash; } - static bool _mapEquals(Map? map1, Map? map2) { + + static bool _mapEquals( + Map? map1, Map? map2) { if (identical(map1, map2)) return true; if (map1 == null || map2 == null) return false; if (map1.length != map2.length) return false; @@ -110,6 +118,7 @@ class HostsModel { } return true; } + static int _mapHash(Map map) { int hash = 0; for (var key in map.keys) { From f53e93e1f116bc1eb6b5af370d4a74cf4a91180a Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Wed, 9 Jul 2025 23:50:16 +0800 Subject: [PATCH 08/54] =?UTF-8?q?feat(=E8=A7=86=E5=9B=BE&=E9=80=BB?= =?UTF-8?q?=E8=BE=91):=20=E6=94=AF=E6=8C=81=E9=80=9A=E8=BF=87=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E5=8A=A0=E8=BD=BD=E5=92=8C=E4=BF=9D=E5=AD=98=E4=B8=BB?= =?UTF-8?q?=E6=9C=BA=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 `filePath` 参数到 `SimpleHomeView`,支持根据文件路径加载主机文件内容。实现文件保存功能,包含多平台支持,并在 Web 平台上新增通过剪贴板保存的选项。优化编辑模式逻辑,修复部分注释内容不准确的问题。 --- lib/app.dart | 2 +- lib/home/view/home_page.dart | 11 ++- lib/home/view/simple_home_view.dart | 131 ++++++++++++++++++++++++---- lib/util/file_manager.dart | 2 +- 4 files changed, 121 insertions(+), 25 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index ce285d0..40f3091 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -2,8 +2,8 @@ import 'dart:io'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; -import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/home/view/home_page.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/global_settings.dart'; import 'package:hosts/model/simple_host_file.dart'; import 'package:hosts/theme.dart'; diff --git a/lib/home/view/home_page.dart b/lib/home/view/home_page.dart index 0dbf607..363aafd 100644 --- a/lib/home/view/home_page.dart +++ b/lib/home/view/home_page.dart @@ -32,9 +32,12 @@ class SimpleHomePage extends StatelessWidget { @override Widget build(BuildContext context) { - return MultiBlocProvider(providers: [ - BlocProvider(create: (context) => HomeCubit()), - BlocProvider(create: (context) => HostCubit()), - ], child: SimpleHomeView()); + return MultiBlocProvider( + providers: [ + BlocProvider(create: (context) => HomeCubit()), + BlocProvider(create: (context) => HostCubit()), + ], + child: SimpleHomeView(filePath: filePath), + ); } } diff --git a/lib/home/view/simple_home_view.dart b/lib/home/view/simple_home_view.dart index 504171a..b4b7179 100644 --- a/lib/home/view/simple_home_view.dart +++ b/lib/home/view/simple_home_view.dart @@ -1,19 +1,32 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hosts/enums.dart'; import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/home/view/home_app_bar.dart'; import 'package:hosts/home/view/host_view.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; import 'package:hosts/page/host_page.dart'; -import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/util/file_manager.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; class SimpleHomeView extends StatelessWidget { - const SimpleHomeView({super.key}); + final String filePath; + + const SimpleHomeView({super.key, required this.filePath}); @override Widget build(BuildContext context) { + if (!kIsWeb && filePath.isNotEmpty && File(filePath).existsSync()) { + context.read().fromText(File(filePath).readAsStringSync()); + } + return Scaffold( floatingActionButton: BlocBuilder( builder: (context, state) { @@ -34,7 +47,7 @@ class SimpleHomeView extends StatelessWidget { ), body: BlocBuilder( builder: (context, state) { - if(state is HomeEditMode) { + if (state is HomeEditMode) { context.read().updateEditMode(state.data.editMode); } @@ -58,30 +71,110 @@ class SimpleHomeView extends StatelessWidget { return const SizedBox(); } - final homeCubit = context.read(); - - final String updateSaveTip = - AppLocalizations.of(context)!.error_not_update_save_tip; - final String updateSavePermissionTip = data.selectHostFile == - state.data.fileId - ? '\n${AppLocalizations.of(context)!.error_not_update_save_permission_tip}' - : ''; return MaterialBanner( - content: Text("$updateSaveTip$updateSavePermissionTip"), + content: Text(AppLocalizations.of(context)!.error_not_update_save_tip), leading: const Icon(Icons.error_outline), actions: [ TextButton( - onPressed: () => - hostCubit.onTableSave(context, homeCubit.state.data, true), - child: Text(AppLocalizations.of(context)!.save_create_history), - ), - TextButton( - onPressed: () => - hostCubit.onTableSave(context, homeCubit.state.data, false), + onPressed: () { + saveHost(context, hostCubit.state.data.fileContent); + }, child: Text(AppLocalizations.of(context)!.save), ), ], ); }); } + + Future saveHost(BuildContext context, String hostContent) async { + final hostCubit = context.read(); + + if (kIsWeb) { + final String tempContent = hostContent.replaceAll("\"", "\\\""); + await showDialog( + context: context, + builder: (dialogContext) => AlertDialog( + title: const Text("保存"), + content: SizedBox( + width: MediaQuery.of(dialogContext).size.width * 0.5, + child: SelectableText(hostContent), + ), + actions: [ + TextButton( + onPressed: () => writeClipboard( + 'echo "$tempContent" > /etc/hosts', + tempContent, + context, + hostCubit, + ), + child: const Text("Linux(echo)")), + TextButton( + onPressed: () { + final String systemHostPath = p.joinAll([ + "C:", + "Windows", + "System32", + "drivers", + "etc", + "hosts" + ]); + final String content = hostContent + .split("\n") + .map((item) => 'echo $item') + .join("\n"); + writeClipboard( + '(\n$content\n) > $systemHostPath', + hostContent, + context, + hostCubit, + ); + }, + child: const Text("Windows(echo)")), + TextButton( + onPressed: () => writeClipboard( + 'echo "$tempContent" > /etc/hosts', + tempContent, + context, + hostCubit, + ), + child: const Text("MacOS(echo)")), + ], + )); + return true; + } + + final File file = File(filePath); + try { + await file.writeAsString(hostContent); + } catch (e) { + try { + final Directory cacheDirectory = await getApplicationCacheDirectory(); + final File cacheFile = File(p.join(cacheDirectory.path, 'hosts')); + await cacheFile.writeAsString(hostContent); + + await FileManager() + .writeFileWithAdminPrivileges(cacheFile.path, filePath); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.error_save_fail))); + return false; + } + } + + hostCubit.fromText(hostContent); + return true; + } + + void writeClipboard(String hostContent, String defaultContent, + BuildContext context, HostCubit hostCubit) { + Clipboard.setData(ClipboardData(text: hostContent)).then((_) { + hostCubit.fromText(defaultContent); + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.copy_to_tip), + ), + ); + }); + } } diff --git a/lib/util/file_manager.dart b/lib/util/file_manager.dart index cab38b2..c66d6bc 100644 --- a/lib/util/file_manager.dart +++ b/lib/util/file_manager.dart @@ -216,7 +216,7 @@ class FileManager { String cacheFilePath, String systemHostFilePath) async { String result = ""; - // TODO Windows Mac + // TODO Windows if (Platform.isLinux) { final Process process = await Process.start( "pkexec", From 9530643083ecf234492be34674a58a57a41e7fe1 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Thu, 10 Jul 2025 00:50:08 +0800 Subject: [PATCH 09/54] =?UTF-8?q?feat(=E5=8A=9F=E8=83=BD):=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=AF=BC=E5=87=BA=20Hosts=20=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=BA=20ZIP=20=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增导出功能,允许用户将 Hosts 文件压缩为 ZIP 包并保存到指定路径。更新本地化文件,添加导出相关文本提示,同时调整依赖以引入 `archive` 和 `file_picker` 包。 --- lib/home/view/home_drawer.dart | 18 +++++++++ lib/l10n/app_en.arb | 3 ++ lib/l10n/app_localizations.dart | 18 +++++++++ lib/l10n/app_localizations_en.dart | 9 +++++ lib/l10n/app_localizations_zh.dart | 9 +++++ lib/l10n/app_zh.arb | 3 ++ lib/util/file_manager.dart | 63 ++++++++++++++++++++++++++++++ pubspec.lock | 22 ++++------- pubspec.yaml | 1 + 9 files changed, 131 insertions(+), 15 deletions(-) diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart index a46e6ae..8cc2f08 100644 --- a/lib/home/view/home_drawer.dart +++ b/lib/home/view/home_drawer.dart @@ -4,6 +4,7 @@ import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/util/file_manager.dart'; import 'package:hosts/widget/dialog/dialog.dart'; import 'package:hosts/widget/snakbar.dart'; @@ -179,6 +180,18 @@ class HomeDrawer extends StatelessWidget { // .deleteFiles(list.map((file) => file.fileName).toList()); }); break; + case 3: + final FileManager fileManager = FileManager(); + final bool success = await fileManager.exportHostFile( + hostFile, + AppLocalizations.of(context)!.export_data + ); + if (success) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalizations.of(context)!.export_success)), + ); + } + break; } }, itemBuilder: (BuildContext context) { @@ -193,6 +206,11 @@ class HomeDrawer extends StatelessWidget { "text": AppLocalizations.of(context)!.remove, "value": 2 }, + { + "icon": Icons.file_download, + "text": AppLocalizations.of(context)!.export, + "value": 3 + }, ]; return list.map((item) { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4a6a83c..7238a27 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -56,6 +56,9 @@ "link": "Link", "delete": "Delete", "open_file": "Open file", + "export": "Export", + "export_data": "Export hosts data", + "export_success": "File exported successfully", "error_open_file": "Failed to read the file", "error_open_file_size": "The file size cannot exceed 10MB", "about": "About", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index bfc45ac..7c2e4a9 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -440,6 +440,24 @@ abstract class AppLocalizations { /// **'打开文件'** String get open_file; + /// No description provided for @export. + /// + /// In zh, this message translates to: + /// **'导出'** + String get export; + + /// No description provided for @export_data. + /// + /// In zh, this message translates to: + /// **'导出 Hosts 数据'** + String get export_data; + + /// No description provided for @export_success. + /// + /// In zh, this message translates to: + /// **'文件导出成功'** + String get export_success; + /// No description provided for @error_open_file. /// /// In zh, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f305b4b..9777561 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -190,6 +190,15 @@ class AppLocalizationsEn extends AppLocalizations { @override String get open_file => 'Open file'; + @override + String get export => 'Export'; + + @override + String get export_data => 'Export hosts data'; + + @override + String get export_success => 'File exported successfully'; + @override String get error_open_file => 'Failed to read the file'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 7437689..de6c5fe 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -185,6 +185,15 @@ class AppLocalizationsZh extends AppLocalizations { @override String get open_file => '打开文件'; + @override + String get export => '导出'; + + @override + String get export_data => '导出 Hosts 数据'; + + @override + String get export_success => '文件导出成功'; + @override String get error_open_file => '文件读取失败'; diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9b9867d..0d30cf6 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -56,6 +56,9 @@ "link": "关联", "delete": "删除", "open_file": "打开文件", + "export": "导出", + "export_data": "导出 Hosts 数据", + "export_success": "文件导出成功", "error_open_file": "文件读取失败", "error_open_file_size": "读取文件不能大于10MB", "about": "关于", diff --git a/lib/util/file_manager.dart b/lib/util/file_manager.dart index c66d6bc..4707fc9 100644 --- a/lib/util/file_manager.dart +++ b/lib/util/file_manager.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:io'; +import 'package:archive/archive.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/services.dart'; import 'package:hosts/model/host_file.dart'; import 'package:hosts/model/simple_host_file.dart'; @@ -262,6 +264,67 @@ class FileManager { return result; } + Future exportHostFile(SimpleHostFile hostFile, String dialogTitle) async { + try { + if (_cachedDirectory == null) await _initializeDirectory(); + + // 获取要导出的目录路径 + final String directoryPath = p.join(_cachedDirectory!.path, hostFile.fileName); + final Directory exportDirectory = Directory(directoryPath); + + if (!await exportDirectory.exists()) { + print('Directory does not exist: $directoryPath'); + return false; + } + + // 让用户选择保存路径 + String? outputFilePath = await FilePicker.platform.saveFile( + dialogTitle: dialogTitle, + fileName: '${hostFile.remark}_${hostFile.fileName}.zip', + type: FileType.custom, + allowedExtensions: ['zip'], + ); + + if (outputFilePath != null) { + // 创建压缩包 + final Archive archive = Archive(); + + // 递归添加目录中的所有文件到压缩包 + await _addDirectoryToArchive(exportDirectory, archive, hostFile.fileName); + + // 编码压缩包 + final List zipData = ZipEncoder().encode(archive)!; + + // 写入文件 + await File(outputFilePath).writeAsBytes(zipData); + return true; + } + return false; + } catch (e) { + print('Export failed: $e'); + return false; + } + } + + // 递归添加目录到压缩包的辅助方法 + Future _addDirectoryToArchive( + Directory directory, Archive archive, String baseName) async { + final List entities = directory.listSync(); + + for (FileSystemEntity entity in entities) { + if (entity is File) { + final String relativePath = p.relative(entity.path, + from: p.join(_cachedDirectory!.path, baseName)); + final List fileBytes = await entity.readAsBytes(); + final ArchiveFile file = + ArchiveFile(relativePath, fileBytes.length, fileBytes); + archive.addFile(file); + } else if (entity is Directory) { + await _addDirectoryToArchive(entity, archive, baseName); + } + } + } + List parseHosts(List lines) { List tempHosts = []; diff --git a/pubspec.lock b/pubspec.lock index 5ecfadd..6cc955f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -10,13 +10,13 @@ packages: source: hosted version: "2.0.3" archive: - dependency: transitive + dependency: "direct main" description: name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "4.0.7" + version: "3.6.1" args: dependency: transitive description: @@ -167,10 +167,10 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: "8321a6d11a8d13977fa780c89de8d257cce3d841eecfb7a4cadffcc4f12d82dc" + sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.4" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -201,10 +201,10 @@ packages: dependency: transitive description: name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d url: "https://pub.dev" source: hosted - version: "4.5.4" + version: "4.3.0" intl: dependency: "direct main" description: @@ -357,14 +357,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - posix: - dependency: transitive - description: - name: posix - sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 - url: "https://pub.dev" - source: hosted - version: "6.0.2" provider: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7b8b80c..db15ed4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,6 +29,7 @@ dependencies: path_provider: ^2.1.4 path: ^1.9.0 file_picker: ^10.1.9 + archive: ^3.6.1 # 国际化 intl: any From 2324fffaa0a7c3009011a2bab1bc3bcca3c3e3b0 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Thu, 10 Jul 2025 20:30:20 +0800 Subject: [PATCH 10/54] =?UTF-8?q?feat(=E6=9C=8D=E5=8A=A1=E5=99=A8&?= =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96):=20=E6=96=B0=E5=A2=9EHTTP=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E5=8A=9F=E8=83=BD=E5=92=8C=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • 新增服务器模块 (lib/server/) - HostsServer: 提供RESTful API管理hosts文件 - ServerManager: 服务器生命周期管理 - ServerSettingsPage: 服务器配置界面 - 支持CORS、日志、错误处理 • 完善导入导出功能 - ExportHostsDialog: 支持多选导出hosts文件 - ImportHostsDialog: 支持ZIP文件导入 - 在主菜单中集成导入/导出/远程同步选项 • 国际化增强 - 新增50+本地化字符串 - 服务器相关界面完全支持中英文 - API端点描述支持多语言 - 错误信息本地化 • 界面优化 - 使用主题颜色替代硬编码颜色 - 改进用户体验和视觉一致性 - 添加服务器状态指示器 • 依赖更新 - 添加 shelf 和 shelf_router 依赖 - 支持HTTP服务器功能 --- .../java/top/webb_l/hosts/HostsVpnService.kt | 57 ++++ lib/home/cubit/home_cubit.dart | 30 ++ lib/home/view/home_drawer.dart | 110 ++++-- lib/l10n/app_en.arb | 56 ++- lib/l10n/app_localizations.dart | 300 ++++++++++++++++ lib/l10n/app_localizations_en.dart | 153 +++++++++ lib/l10n/app_localizations_zh.dart | 150 ++++++++ lib/l10n/app_zh.arb | 56 ++- lib/server/README.md | 202 +++++++++++ lib/server/hosts_server.dart | 319 ++++++++++++++++++ lib/server/index.dart | 4 + lib/server/server_manager.dart | 185 ++++++++++ lib/server/server_settings_page.dart | 294 ++++++++++++++++ lib/util/file_manager.dart | 239 +++++++++++++ lib/widget/dialog/export_hosts_dialog.dart | 156 +++++++++ lib/widget/dialog/import_hosts_dialog.dart | 312 +++++++++++++++++ 16 files changed, 2598 insertions(+), 25 deletions(-) create mode 100644 android/app/src/main/java/top/webb_l/hosts/HostsVpnService.kt mode change 100644 => 100755 lib/home/cubit/home_cubit.dart mode change 100644 => 100755 lib/l10n/app_en.arb mode change 100644 => 100755 lib/l10n/app_localizations.dart mode change 100644 => 100755 lib/l10n/app_localizations_en.dart mode change 100644 => 100755 lib/l10n/app_localizations_zh.dart mode change 100644 => 100755 lib/l10n/app_zh.arb create mode 100755 lib/server/README.md create mode 100755 lib/server/hosts_server.dart create mode 100755 lib/server/index.dart create mode 100755 lib/server/server_manager.dart create mode 100755 lib/server/server_settings_page.dart mode change 100644 => 100755 lib/util/file_manager.dart create mode 100755 lib/widget/dialog/export_hosts_dialog.dart create mode 100755 lib/widget/dialog/import_hosts_dialog.dart diff --git a/android/app/src/main/java/top/webb_l/hosts/HostsVpnService.kt b/android/app/src/main/java/top/webb_l/hosts/HostsVpnService.kt new file mode 100644 index 0000000..cb60a37 --- /dev/null +++ b/android/app/src/main/java/top/webb_l/hosts/HostsVpnService.kt @@ -0,0 +1,57 @@ +package top.webb_l.hosts + +import android.content.Intent +import android.net.VpnService +import android.os.ParcelFileDescriptor +import android.util.Log +import java.io.FileInputStream +import java.io.FileOutputStream +import kotlin.concurrent.thread + +class HostsVpnService : VpnService() { + + private var vpnInterface: ParcelFileDescriptor? = null + + override fun onDestroy() { + super.onDestroy() + // 清理资源 + vpnInterface?.close() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + // 启动 VPN + startVpn() + return START_STICKY + } + + private fun startVpn() { + // 配置 VPN + val builder = Builder() + builder.setSession("MyVPN") + .addAddress("10.0.0.2", 24) // VPN 地址 + .addRoute("0.0.0.0", 0) // 路由 + + vpnInterface = builder.establish() + + // 这里是处理网络流量的逻辑 + // 你需要实现读取数据包、解析 DNS 请求、根据 hosts 文件进行重定向等 + Log.e("TAG", "run: ${vpnInterface}") + val inputStream = FileInputStream(vpnInterface!!.fileDescriptor) + val outputStream = FileOutputStream(vpnInterface!!.fileDescriptor) + + val buffer = ByteArray(32767) + thread { + while (true) { + // 读取数据包 + val length = inputStream.read(buffer) + if (length > 0) { + // 处理数据包 + // 这里可以添加 DNS 解析和 hosts 文件的处理逻辑 + Log.e("TAG", "startVpn: ${String(buffer, 0, length)}", ) + // 将数据包写回输出流 + outputStream.write(buffer, 0, length) + } + } + } + } +} diff --git a/lib/home/cubit/home_cubit.dart b/lib/home/cubit/home_cubit.dart old mode 100644 new mode 100755 index c7c4d03..95d8e30 --- a/lib/home/cubit/home_cubit.dart +++ b/lib/home/cubit/home_cubit.dart @@ -258,4 +258,34 @@ class HomeCubit extends Cubit { await toggleAdvancedSettings(newSettings); } + + /// 刷新hosts文件列表 + /// [context] 可选的context参数,用于system文件的本地化 + Future refreshHostFiles([BuildContext? context]) async { + List tempHostFiles = []; + List hostConfigs = + await _settingsManager.getList(settingKeyHostConfigs); + + for (Map config in hostConfigs) { + SimpleHostFile hostFile = SimpleHostFile.fromJson(config); + tempHostFiles.add(hostFile); + + // 特殊处理system文件的remark + if (hostFile.fileName == "system") { + if (context != null) { + hostFile.remark = gen.AppLocalizations.of(context)!.default_hosts_text; + } else { + hostFile.remark = "默认"; // 后备文本 + } + } + } + + emit( + HomeInitial( + state.data.copyWith( + hostFiles: tempHostFiles, + ), + ), + ); + } } diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart index 8cc2f08..609020e 100644 --- a/lib/home/view/home_drawer.dart +++ b/lib/home/view/home_drawer.dart @@ -4,8 +4,11 @@ import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/server/server_settings_page.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/widget/dialog/dialog.dart'; +import 'package:hosts/widget/dialog/export_hosts_dialog.dart'; +import 'package:hosts/widget/dialog/import_hosts_dialog.dart'; import 'package:hosts/widget/snakbar.dart'; class HomeDrawer extends StatelessWidget { @@ -34,7 +37,68 @@ class HomeDrawer extends StatelessWidget { if (remark == null || remark.isEmpty) return; context.read().addHostFile(remark); }, - icon: const Icon(Icons.add)) + icon: const Icon(Icons.add)), + PopupMenuButton( + icon: const Icon(Icons.more_vert), + onSelected: (value) async { + switch (value) { + case 1: + // Import functionality + await importHostsDialog(context, state.data.hostFiles, + onImportSuccess: () { + context.read().refreshHostFiles(context); + }); + break; + case 2: + // Export functionality + await exportHostsDialog(context, state.data.hostFiles); + break; + case 3: + // Remote sync functionality + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ServerSettingsPage(), + ), + ); + break; + } + }, + itemBuilder: (BuildContext context) { + return [ + PopupMenuItem( + value: 1, + child: Row( + children: [ + const Icon(Icons.file_upload), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.import), + ], + ), + ), + PopupMenuItem( + value: 2, + child: Row( + children: [ + const Icon(Icons.file_download), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.export), + ], + ), + ), + PopupMenuItem( + value: 3, + child: Row( + children: [ + const Icon(Icons.cloud_sync), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.remote_sync), + ], + ), + ), + ]; + }, + ) ], ), ), @@ -52,30 +116,30 @@ class HomeDrawer extends StatelessWidget { padding: EdgeInsets.zero, ), onPressed: - state.data.useHostFiles.contains(hostFile.fileName) - ? null - : () async { - // final String path = await _fileManager - // .getHostsFilePath(hostFile.fileName); - // - // if (!await widget - // .onClickUse(File(path).readAsStringSync())) { - // return; - // } + state.data.useHostFiles.contains(hostFile.fileName) + ? null + : () async { + // final String path = await _fileManager + // .getHostsFilePath(hostFile.fileName); + // + // if (!await widget + // .onClickUse(File(path).readAsStringSync())) { + // return; + // } - // setState(() { - // useHostFile = hostFile.fileName; - // }); - // _settingsManager.setString( - // settingKeyUseHostFile, hostFile.fileName); - }, + // setState(() { + // useHostFile = hostFile.fileName; + // }); + // _settingsManager.setString( + // settingKeyUseHostFile, hostFile.fileName); + }, icon: Icon( state.data.useHostFiles.contains(hostFile.fileName) ? Icons.star : Icons.star_border), ), selectedTileColor: - Theme.of(context).colorScheme.primaryContainer, + Theme.of(context).colorScheme.primaryContainer, selected: state.data.selectHostFile == hostFile.fileName, trailing: buildMoreButton(hostFile), onTap: () { @@ -132,7 +196,7 @@ class HomeDrawer extends StatelessWidget { switch (value) { case 1: String result = - (await hostConfigDialog(context, hostFile.remark) ?? ""); + (await hostConfigDialog(context, hostFile.remark) ?? ""); if (result.isEmpty) return; homeCubit.updateHostFileRemark(hostFile.fileName, result); break; @@ -182,9 +246,9 @@ class HomeDrawer extends StatelessWidget { break; case 3: final FileManager fileManager = FileManager(); - final bool success = await fileManager.exportHostFile( - hostFile, - AppLocalizations.of(context)!.export_data + final bool success = await fileManager.exportMultipleHostFiles( + [hostFile], + AppLocalizations.of(context)!.export_data ); if (success) { ScaffoldMessenger.of(context).showSnackBar( @@ -230,4 +294,4 @@ class HomeDrawer extends StatelessWidget { }, ); } -} +} \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb old mode 100644 new mode 100755 index 7238a27..6161123 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -68,5 +68,59 @@ "link_and_description": "When ", "link_status_update_description": " the status changes, the following data switches to", "link_status_description": "Status:", - "form": "Form" + "form": "Form", + "import_data": "Import Hosts Data", + "import_success": "Import successful", + "select_all": "Select All", + "loading": "Loading", + "file_processing": "Processing file", + "import_file": "Import file", + "will_overwrite": "Will overwrite existing file", + "remote_sync": "Remote Sync", + "import": "Import", + "server_settings": "Server Settings", + "server_status": "Server Status", + "server_config": "Server Configuration", + "server_running": "Running", + "server_stopped": "Stopped", + "server_start": "Start", + "server_stop": "Stop", + "server_restart": "Restart", + "server_host": "Host Address", + "server_port": "Port", + "server_auto_start": "Auto Start", + "server_auto_start_desc": "Automatically start HTTP server when app launches", + "server_save_config": "Save Configuration", + "server_copy_url": "Copy URL", + "server_url_copied": "URL copied to clipboard", + "server_started": "Server started", + "server_stopped_msg": "Server stopped", + "server_config_saved": "Configuration saved successfully", + "server_operation_failed": "Operation failed", + "server_invalid_port": "Port must be between 1-65535", + "server_invalid_host": "Host address cannot be empty", + "api_docs": "API Documentation", + "api_endpoints": "Available API endpoints", + "refresh_status": "Refresh status", + "server_address": "Server address", + "copy_url": "Copy URL", + "operation_failed": "Operation failed", + "load_server_settings_failed": "Failed to load server settings", + "get_all_hosts_files": "Get all hosts files", + "get_specific_hosts_file": "Get specific hosts file content (plain text)", + "get_hosts_file_history": "Get hosts file history", + "get_specific_history_content": "Get specific history content (plain text)", + "server_already_running": "Server is already running", + "http_server_start_success": "HTTP server started successfully", + "http_server_start_failed": "Failed to start HTTP server", + "http_server_stopped": "HTTP server stopped", + "missing_file_id": "Missing file ID", + "read_file_failed": "Failed to read file", + "missing_file_id_or_history_id": "Missing file ID or history ID", + "history_not_found": "History not found", + "read_history_failed": "Failed to read history", + "select_hosts_to_export": "Please select hosts files to export", + "select_all": "Select All", + "selected_count": "Selected", + "export_failed": "Export failed" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart old mode 100644 new mode 100755 index 7c2e4a9..a36296a --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -517,6 +517,306 @@ abstract class AppLocalizations { /// In zh, this message translates to: /// **'表单'** String get form; + + /// No description provided for @import_data. + /// + /// In zh, this message translates to: + /// **'导入 Hosts 数据'** + String get import_data; + + /// No description provided for @import_success. + /// + /// In zh, this message translates to: + /// **'导入成功'** + String get import_success; + + /// No description provided for @select_all. + /// + /// In zh, this message translates to: + /// **'全选'** + String get select_all; + + /// No description provided for @loading. + /// + /// In zh, this message translates to: + /// **'加载中'** + String get loading; + + /// No description provided for @file_processing. + /// + /// In zh, this message translates to: + /// **'文件处理中'** + String get file_processing; + + /// No description provided for @import_file. + /// + /// In zh, this message translates to: + /// **'导入文件'** + String get import_file; + + /// No description provided for @will_overwrite. + /// + /// In zh, this message translates to: + /// **'将覆盖现有文件'** + String get will_overwrite; + + /// No description provided for @remote_sync. + /// + /// In zh, this message translates to: + /// **'远程同步'** + String get remote_sync; + + /// No description provided for @import. + /// + /// In zh, this message translates to: + /// **'导入'** + String get import; + + /// No description provided for @server_settings. + /// + /// In zh, this message translates to: + /// **'服务器设置'** + String get server_settings; + + /// No description provided for @server_status. + /// + /// In zh, this message translates to: + /// **'服务器状态'** + String get server_status; + + /// No description provided for @server_config. + /// + /// In zh, this message translates to: + /// **'服务器配置'** + String get server_config; + + /// No description provided for @server_running. + /// + /// In zh, this message translates to: + /// **'运行中'** + String get server_running; + + /// No description provided for @server_stopped. + /// + /// In zh, this message translates to: + /// **'已停止'** + String get server_stopped; + + /// No description provided for @server_start. + /// + /// In zh, this message translates to: + /// **'启动'** + String get server_start; + + /// No description provided for @server_stop. + /// + /// In zh, this message translates to: + /// **'停止'** + String get server_stop; + + /// No description provided for @server_restart. + /// + /// In zh, this message translates to: + /// **'重启'** + String get server_restart; + + /// No description provided for @server_host. + /// + /// In zh, this message translates to: + /// **'主机地址'** + String get server_host; + + /// No description provided for @server_port. + /// + /// In zh, this message translates to: + /// **'端口'** + String get server_port; + + /// No description provided for @server_auto_start. + /// + /// In zh, this message translates to: + /// **'自动启动'** + String get server_auto_start; + + /// No description provided for @server_auto_start_desc. + /// + /// In zh, this message translates to: + /// **'应用启动时自动启动HTTP服务器'** + String get server_auto_start_desc; + + /// No description provided for @server_save_config. + /// + /// In zh, this message translates to: + /// **'保存配置'** + String get server_save_config; + + /// No description provided for @server_copy_url. + /// + /// In zh, this message translates to: + /// **'复制URL'** + String get server_copy_url; + + /// No description provided for @server_url_copied. + /// + /// In zh, this message translates to: + /// **'URL已复制到剪贴板'** + String get server_url_copied; + + /// No description provided for @server_started. + /// + /// In zh, this message translates to: + /// **'服务器已启动'** + String get server_started; + + /// No description provided for @server_stopped_msg. + /// + /// In zh, this message translates to: + /// **'服务器已停止'** + String get server_stopped_msg; + + /// No description provided for @server_config_saved. + /// + /// In zh, this message translates to: + /// **'配置保存成功'** + String get server_config_saved; + + /// No description provided for @server_operation_failed. + /// + /// In zh, this message translates to: + /// **'操作失败'** + String get server_operation_failed; + + /// No description provided for @server_invalid_port. + /// + /// In zh, this message translates to: + /// **'端口号必须在1-65535之间'** + String get server_invalid_port; + + /// No description provided for @server_invalid_host. + /// + /// In zh, this message translates to: + /// **'主机地址不能为空'** + String get server_invalid_host; + + /// No description provided for @api_docs. + /// + /// In zh, this message translates to: + /// **'API文档'** + String get api_docs; + + /// No description provided for @api_endpoints. + /// + /// In zh, this message translates to: + /// **'可用的API端点'** + String get api_endpoints; + + /// No description provided for @refresh_status. + /// + /// In zh, this message translates to: + /// **'刷新状态'** + String get refresh_status; + + /// No description provided for @server_address. + /// + /// In zh, this message translates to: + /// **'服务器地址'** + String get server_address; + + /// No description provided for @copy_url. + /// + /// In zh, this message translates to: + /// **'复制URL'** + String get copy_url; + + /// No description provided for @operation_failed. + /// + /// In zh, this message translates to: + /// **'操作失败'** + String get operation_failed; + + /// No description provided for @load_server_settings_failed. + /// + /// In zh, this message translates to: + /// **'加载服务器设置失败'** + String get load_server_settings_failed; + + /// No description provided for @get_all_hosts_files. + /// + /// In zh, this message translates to: + /// **'获取所有hosts文件'** + String get get_all_hosts_files; + + /// No description provided for @get_specific_hosts_file. + /// + /// In zh, this message translates to: + /// **'获取特定hosts文件内容(纯文本)'** + String get get_specific_hosts_file; + + /// No description provided for @get_hosts_file_history. + /// + /// In zh, this message translates to: + /// **'获取hosts文件历史记录'** + String get get_hosts_file_history; + + /// No description provided for @get_specific_history_content. + /// + /// In zh, this message translates to: + /// **'获取特定历史记录内容(纯文本)'** + String get get_specific_history_content; + + /// No description provided for @server_already_running. + /// + /// In zh, this message translates to: + /// **'服务器已经在运行中'** + String get server_already_running; + + /// No description provided for @http_server_start_success. + /// + /// In zh, this message translates to: + /// **'HTTP服务器启动成功'** + String get http_server_start_success; + + /// No description provided for @http_server_start_failed. + /// + /// In zh, this message translates to: + /// **'启动HTTP服务器失败'** + String get http_server_start_failed; + + /// No description provided for @http_server_stopped. + /// + /// In zh, this message translates to: + /// **'HTTP服务器已停止'** + String get http_server_stopped; + + /// No description provided for @missing_file_id. + /// + /// In zh, this message translates to: + /// **'缺少文件ID'** + String get missing_file_id; + + /// No description provided for @read_file_failed. + /// + /// In zh, this message translates to: + /// **'读取文件失败'** + String get read_file_failed; + + /// No description provided for @missing_file_id_or_history_id. + /// + /// In zh, this message translates to: + /// **'缺少文件ID或历史记录ID'** + String get missing_file_id_or_history_id; + + /// No description provided for @history_not_found. + /// + /// In zh, this message translates to: + /// **'历史记录不存在'** + String get history_not_found; + + /// No description provided for @read_history_failed. + /// + /// In zh, this message translates to: + /// **'读取历史记录失败'** + String get read_history_failed; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart old mode 100644 new mode 100755 index 9777561..3ab3f88 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -230,4 +230,157 @@ class AppLocalizationsEn extends AppLocalizations { @override String get form => 'Form'; + + @override + String get import_data => 'Import Hosts Data'; + + @override + String get import_success => 'Import successful'; + + @override + String get select_all => 'Select All'; + + @override + String get loading => 'Loading'; + + @override + String get file_processing => 'Processing file'; + + @override + String get import_file => 'Import file'; + + @override + String get will_overwrite => 'Will overwrite existing file'; + + @override + String get remote_sync => 'Remote Sync'; + + @override + String get import => 'Import'; + + @override + String get server_settings => 'Server Settings'; + + @override + String get server_status => 'Server Status'; + + @override + String get server_config => 'Server Configuration'; + + @override + String get server_running => 'Running'; + + @override + String get server_stopped => 'Stopped'; + + @override + String get server_start => 'Start'; + + @override + String get server_stop => 'Stop'; + + @override + String get server_restart => 'Restart'; + + @override + String get server_host => 'Host Address'; + + @override + String get server_port => 'Port'; + + @override + String get server_auto_start => 'Auto Start'; + + @override + String get server_auto_start_desc => + 'Automatically start HTTP server when app launches'; + + @override + String get server_save_config => 'Save Configuration'; + + @override + String get server_copy_url => 'Copy URL'; + + @override + String get server_url_copied => 'URL copied to clipboard'; + + @override + String get server_started => 'Server started'; + + @override + String get server_stopped_msg => 'Server stopped'; + + @override + String get server_config_saved => 'Configuration saved successfully'; + + @override + String get server_operation_failed => 'Operation failed'; + + @override + String get server_invalid_port => 'Port must be between 1-65535'; + + @override + String get server_invalid_host => 'Host address cannot be empty'; + + @override + String get api_docs => 'API Documentation'; + + @override + String get api_endpoints => 'Available API endpoints'; + + @override + String get refresh_status => 'Refresh status'; + + @override + String get server_address => 'Server address'; + + @override + String get copy_url => 'Copy URL'; + + @override + String get operation_failed => 'Operation failed'; + + @override + String get load_server_settings_failed => 'Failed to load server settings'; + + @override + String get get_all_hosts_files => 'Get all hosts files'; + + @override + String get get_specific_hosts_file => + 'Get specific hosts file content (plain text)'; + + @override + String get get_hosts_file_history => 'Get hosts file history'; + + @override + String get get_specific_history_content => + 'Get specific history content (plain text)'; + + @override + String get server_already_running => 'Server is already running'; + + @override + String get http_server_start_success => 'HTTP server started successfully'; + + @override + String get http_server_start_failed => 'Failed to start HTTP server'; + + @override + String get http_server_stopped => 'HTTP server stopped'; + + @override + String get missing_file_id => 'Missing file ID'; + + @override + String get read_file_failed => 'Failed to read file'; + + @override + String get missing_file_id_or_history_id => 'Missing file ID or history ID'; + + @override + String get history_not_found => 'History not found'; + + @override + String get read_history_failed => 'Failed to read history'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart old mode 100644 new mode 100755 index de6c5fe..81b3ccd --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -224,4 +224,154 @@ class AppLocalizationsZh extends AppLocalizations { @override String get form => '表单'; + + @override + String get import_data => '导入 Hosts 数据'; + + @override + String get import_success => '导入成功'; + + @override + String get select_all => '全选'; + + @override + String get loading => '加载中'; + + @override + String get file_processing => '文件处理中'; + + @override + String get import_file => '导入文件'; + + @override + String get will_overwrite => '将覆盖现有文件'; + + @override + String get remote_sync => '远程同步'; + + @override + String get import => '导入'; + + @override + String get server_settings => '服务器设置'; + + @override + String get server_status => '服务器状态'; + + @override + String get server_config => '服务器配置'; + + @override + String get server_running => '运行中'; + + @override + String get server_stopped => '已停止'; + + @override + String get server_start => '启动'; + + @override + String get server_stop => '停止'; + + @override + String get server_restart => '重启'; + + @override + String get server_host => '主机地址'; + + @override + String get server_port => '端口'; + + @override + String get server_auto_start => '自动启动'; + + @override + String get server_auto_start_desc => '应用启动时自动启动HTTP服务器'; + + @override + String get server_save_config => '保存配置'; + + @override + String get server_copy_url => '复制URL'; + + @override + String get server_url_copied => 'URL已复制到剪贴板'; + + @override + String get server_started => '服务器已启动'; + + @override + String get server_stopped_msg => '服务器已停止'; + + @override + String get server_config_saved => '配置保存成功'; + + @override + String get server_operation_failed => '操作失败'; + + @override + String get server_invalid_port => '端口号必须在1-65535之间'; + + @override + String get server_invalid_host => '主机地址不能为空'; + + @override + String get api_docs => 'API文档'; + + @override + String get api_endpoints => '可用的API端点'; + + @override + String get refresh_status => '刷新状态'; + + @override + String get server_address => '服务器地址'; + + @override + String get copy_url => '复制URL'; + + @override + String get operation_failed => '操作失败'; + + @override + String get load_server_settings_failed => '加载服务器设置失败'; + + @override + String get get_all_hosts_files => '获取所有hosts文件'; + + @override + String get get_specific_hosts_file => '获取特定hosts文件内容(纯文本)'; + + @override + String get get_hosts_file_history => '获取hosts文件历史记录'; + + @override + String get get_specific_history_content => '获取特定历史记录内容(纯文本)'; + + @override + String get server_already_running => '服务器已经在运行中'; + + @override + String get http_server_start_success => 'HTTP服务器启动成功'; + + @override + String get http_server_start_failed => '启动HTTP服务器失败'; + + @override + String get http_server_stopped => 'HTTP服务器已停止'; + + @override + String get missing_file_id => '缺少文件ID'; + + @override + String get read_file_failed => '读取文件失败'; + + @override + String get missing_file_id_or_history_id => '缺少文件ID或历史记录ID'; + + @override + String get history_not_found => '历史记录不存在'; + + @override + String get read_history_failed => '读取历史记录失败'; } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb old mode 100644 new mode 100755 index 0d30cf6..a02247d --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -68,5 +68,59 @@ "link_and_description": "当 ", "link_status_update_description": " 状态变化时,下列数据切换为", "link_status_description": "状态:", - "form": "表单" + "form": "表单", + "import_data": "导入 Hosts 数据", + "import_success": "导入成功", + "select_all": "全选", + "loading": "加载中", + "file_processing": "文件处理中", + "import_file": "导入文件", + "will_overwrite": "将覆盖现有文件", + "remote_sync": "远程同步", + "import": "导入", + "server_settings": "服务器设置", + "server_status": "服务器状态", + "server_config": "服务器配置", + "server_running": "运行中", + "server_stopped": "已停止", + "server_start": "启动", + "server_stop": "停止", + "server_restart": "重启", + "server_host": "主机地址", + "server_port": "端口", + "server_auto_start": "自动启动", + "server_auto_start_desc": "应用启动时自动启动HTTP服务器", + "server_save_config": "保存配置", + "server_copy_url": "复制URL", + "server_url_copied": "URL已复制到剪贴板", + "server_started": "服务器已启动", + "server_stopped_msg": "服务器已停止", + "server_config_saved": "配置保存成功", + "server_operation_failed": "操作失败", + "server_invalid_port": "端口号必须在1-65535之间", + "server_invalid_host": "主机地址不能为空", + "api_docs": "API文档", + "api_endpoints": "可用的API端点", + "refresh_status": "刷新状态", + "server_address": "服务器地址", + "copy_url": "复制URL", + "operation_failed": "操作失败", + "load_server_settings_failed": "加载服务器设置失败", + "get_all_hosts_files": "获取所有hosts文件", + "get_specific_hosts_file": "获取特定hosts文件内容(纯文本)", + "get_hosts_file_history": "获取hosts文件历史记录", + "get_specific_history_content": "获取特定历史记录内容(纯文本)", + "server_already_running": "服务器已经在运行中", + "http_server_start_success": "HTTP服务器启动成功", + "http_server_start_failed": "启动HTTP服务器失败", + "http_server_stopped": "HTTP服务器已停止", + "missing_file_id": "缺少文件ID", + "read_file_failed": "读取文件失败", + "missing_file_id_or_history_id": "缺少文件ID或历史记录ID", + "history_not_found": "历史记录不存在", + "read_history_failed": "读取历史记录失败", + "select_hosts_to_export": "请选择要导出hosts文件", + "select_all": "全选", + "selected_count": "已选择", + "export_failed": "导出失败" } \ No newline at end of file diff --git a/lib/server/README.md b/lib/server/README.md new file mode 100755 index 0000000..34058b5 --- /dev/null +++ b/lib/server/README.md @@ -0,0 +1,202 @@ +# HTTP服务器模块 + +这个模块为Hosts Editor应用提供了内置的HTTP服务器功能,允许通过RESTful API远程管理hosts文件。 + +## 功能特性 + +- 🚀 内置HTTP服务器,支持RESTful API +- 📝 完整的hosts文件CRUD操作 +- 🔧 灵活的服务器配置管理 +- 🎯 CORS支持,允许跨域访问 +- 📱 集成的Flutter设置页面 +- 🔄 自动启动功能 +- 📋 完整的API文档 + +## 模块结构 + +``` +lib/server/ +├── hosts_server.dart # HTTP服务器核心实现 +├── server_manager.dart # 服务器生命周期管理 +├── server_settings_page.dart # Flutter设置页面 +├── example.dart # 使用示例 +├── index.dart # 模块导出 +└── README.md # 模块文档 +``` + +## 快速开始 + +### 1. 基本使用 + +```dart +import 'package:hosts/server/index.dart'; + +// 创建并启动服务器 +final serverManager = ServerManager(); +await serverManager.initialize(); +await serverManager.startServer(); + +// 服务器现在运行在 http://localhost:1204 +print('服务器URL: ${serverManager.server.serverUrl}'); +``` + +### 2. 自定义配置 + +```dart +// 更新服务器配置 +await serverManager.updateServerConfig( + port: 9090, + host: '0.0.0.0', + autoStart: true, +); + +// 启动服务器 +await serverManager.startServer(); +``` + +### 3. 在Flutter应用中集成 + +```dart +// 在main函数中初始化 +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + final serverManager = ServerManager(); + await serverManager.initialize(); + + runApp(MyApp()); +} + +// 在设置页面中添加服务器设置 +Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ServerSettingsPage(), + ), +); +``` + +## API接口 + +### 服务器状态 +- `GET /` - 获取服务器状态和API文档 + +### Hosts文件管理 +- `GET /api/hosts` - 获取所有hosts文件列表(JSON格式) +- `GET /api/hosts/{fileId}` - 获取特定hosts文件内容(纯文本格式) + +### 历史记录管理 +- `GET /api/hosts/{fileId}/history` - 获取hosts文件历史记录(JSON格式) +- `GET /api/hosts/{fileId}/history/{historyId}` - 获取特定历史记录内容(纯文本格式) + +## API示例 + +### 获取所有hosts文件 +```bash +curl http://localhost:1204/api/hosts +``` + +响应: +```json +{ + "success": true, + "data": [ + {"fileName": "system", "remark": "系统默认"}, + {"fileName": "custom", "remark": "自定义配置"} + ] +} +``` + +### 获取hosts文件内容(纯文本) +```bash +curl http://localhost:1204/api/hosts/system +``` + +响应(纯文本): +``` +127.0.0.1 localhost +::1 localhost +# 这里是hosts文件的实际内容 +``` + +### 获取hosts文件历史记录(JSON) +```bash +curl http://localhost:1204/api/hosts/system/history +``` + +响应(JSON): +```json +{ + "success": true, + "data": [ + { + "id": "1672531200000", + "fileName": "1672531200000", + "timestamp": "1672531200000", + "createTime": "2023-01-01T00:00:00.000Z" + } + ] +} +``` + +### 获取特定历史记录内容(纯文本) +```bash +curl http://localhost:1204/api/hosts/system/history/1672531200000 +``` + +响应(纯文本): +``` +127.0.0.1 localhost +::1 localhost +# 这里是历史记录的实际内容 +``` + +## 配置选项 + +| 选项 | 类型 | 默认值 | 描述 | +|------|------|--------|------| +| port | int | 1204 | 服务器端口号 | +| host | String | 'localhost' | 服务器绑定地址 | +| autoStart | bool | false | 应用启动时自动启动服务器 | + +## 安全注意事项 + +1. **网络访问**: 默认只绑定到localhost,如需远程访问请谨慎设置host为0.0.0.0 +2. **端口冲突**: 确保选择的端口未被其他应用占用 +3. **文件权限**: 系统hosts文件操作可能需要管理员权限 +4. **CORS策略**: 当前配置允许所有来源的跨域请求,生产环境请根据需要调整 + +## 故障排除 + +### 服务器启动失败 +- 检查端口是否被占用 +- 确认防火墙设置 +- 验证host地址格式 + +### API请求失败 +- 确认服务器正在运行 +- 检查请求URL和参数格式 +- 查看服务器日志输出 + +### 权限错误 +- 系统hosts文件操作可能需要提升权限 +- macOS可能需要授权访问系统文件 + +## 依赖项 + +```yaml +dependencies: + shelf: ^1.4.1 + shelf_router: ^1.1.4 +``` + +## 版本历史 + +- v1.0.0 - 初始版本,基本HTTP服务器功能 +- 支持完整的hosts文件CRUD操作 +- 集成Flutter设置页面 +- 提供自动启动和配置管理功能 + +## 贡献 + +欢迎提交Issue和Pull Request来改进这个模块! \ No newline at end of file diff --git a/lib/server/hosts_server.dart b/lib/server/hosts_server.dart new file mode 100755 index 0000000..1bf570e --- /dev/null +++ b/lib/server/hosts_server.dart @@ -0,0 +1,319 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/util/file_manager.dart'; +import 'package:hosts/util/settings_manager.dart'; +import 'package:shelf/shelf.dart'; +import 'package:shelf/shelf_io.dart' as shelf_io; +import 'package:shelf_router/shelf_router.dart'; + +/// HTTP服务器管理类 +/// 提供RESTful API来管理hosts文件 +class HostsServer { + static const int _defaultPort = 1204; + static const String _defaultHost = '0.0.0.0'; + + HttpServer? _server; + final FileManager _fileManager = FileManager(); + + final SettingsManager _settingsManager = SettingsManager(); + + // 本地化字符串映射 + Map _i18nStrings = {}; + + int _port = _defaultPort; + String _host = _defaultHost; + + /// 获取当前服务器端口 + int get port => _port; + + /// 获取当前服务器主机 + String get host => _host; + + /// 检查服务器是否正在运行 + bool get isRunning => _server != null; + + /// 获取服务器URL + String get serverUrl => 'http://$_host:$_port'; + + /// 设置本地化字符串 + void setI18nStrings(Map strings) { + _i18nStrings = strings; + } + + /// 启动HTTP服务器 + /// [port] 端口号,默认1204 + /// [host] 主机地址,默认0.0.0.0 + /// [i18nStrings] 本地化字符串映射 + Future start( + {int port = _defaultPort, + String host = _defaultHost, + Map? i18nStrings}) async { + if (_server != null) { + throw Exception(_i18nStrings['server_already_running'] ?? + 'Server is already running'); + } + + if (i18nStrings != null) { + _i18nStrings = i18nStrings; + } + + _port = port; + _host = host; + + final router = _setupRouter(); + + // 添加CORS中间件 + final handler = Pipeline() + .addMiddleware(corsMiddleware()) + .addMiddleware(logRequests()) + .addHandler(router); + + try { + _server = await shelf_io.serve(handler, _host, _port); + print( + '${_i18nStrings['http_server_start_success'] ?? 'HTTP server started successfully'}: ${serverUrl}'); + } catch (e) { + print( + '${_i18nStrings['http_server_start_failed'] ?? 'Failed to start HTTP server'}: $e'); + rethrow; + } + } + + /// 停止HTTP服务器 + Future stop() async { + if (_server != null) { + await _server!.close(); + _server = null; + print(_i18nStrings['http_server_stopped'] ?? 'HTTP server stopped'); + } + } + + /// 重启HTTP服务器 + Future restart() async { + final currentPort = _port; + final currentHost = _host; + await stop(); + await start(port: currentPort, host: currentHost); + } + + /// 设置路由 + Router _setupRouter() { + final router = Router(); + + // 根路径 - 服务器状态 + router.get('/', _handleStatus); + + // 获取所有hosts文件列表 + router.get('/api/hosts', _handleGetHosts); + + // 获取特定hosts文件内容 + router.get('/api/hosts/', _handleGetHostFile); + + // 获取hosts文件的历史记录 + router.get('/api/hosts//history', _handleGetHostHistory); + + // 获取特定历史记录内容 + router.get( + '/api/hosts//history/', _handleGetHistoryContent); + + return router; + } + + /// CORS中间件 + Middleware corsMiddleware() { + return (Handler innerHandler) { + return (Request request) async { + if (request.method == 'OPTIONS') { + return Response.ok('', headers: _corsHeaders); + } + + final response = await innerHandler(request); + return response.change(headers: _corsHeaders); + }; + }; + } + + /// CORS头部 + Map get _corsHeaders => { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'Content-Type': 'application/json; charset=utf-8', + }; + + /// 处理服务器状态请求 + Future _handleStatus(Request request) async { + final status = { + 'status': 'running', + 'version': '1.0.0', + 'timestamp': DateTime.now().toIso8601String(), + 'endpoints': [ + 'GET /api/hosts - ${_i18nStrings['get_all_hosts_files'] ?? 'Get all hosts files (JSON)'}', + 'GET /api/hosts/{fileId} - ${_i18nStrings['get_specific_hosts_file'] ?? 'Get specific hosts file content (plain text)'}', + 'GET /api/hosts/{fileId}/history - ${_i18nStrings['get_hosts_file_history'] ?? 'Get hosts file history (JSON)'}', + 'GET /api/hosts/{fileId}/history/{historyId} - ${_i18nStrings['get_specific_history_content'] ?? 'Get specific history content (plain text)'}', + ] + }; + + return Response.ok( + jsonEncode(status), + headers: _corsHeaders, + ); + } + + /// 处理获取所有hosts文件请求 + Future _handleGetHosts(Request request) async { + try { + List hostConfigs = + await _settingsManager.getList(settingKeyHostConfigs); + // 获取所有hosts文件列表 + final List hostFiles = []; + + for (Map config in hostConfigs) { + SimpleHostFile hostFile = SimpleHostFile.fromJson(config); + hostFiles.add(hostFile); + } + + // 转换为API响应格式 + final hosts = hostFiles + .map((hostFile) => { + 'fileName': hostFile.fileName, + 'remark': hostFile.remark, + }) + .toList(); + + return Response.ok( + jsonEncode({'success': true, 'data': hosts}), + headers: _corsHeaders, + ); + } catch (e) { + return Response.internalServerError( + body: jsonEncode({'success': false, 'error': e.toString()}), + headers: _corsHeaders, + ); + } + } + + /// 处理获取特定hosts文件请求 + Future _handleGetHostFile(Request request) async { + final fileId = request.params['fileId']; + if (fileId == null) { + return Response.badRequest( + body: _i18nStrings['missing_file_id'] ?? 'Missing file ID', + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + }, + ); + } + + try { + final content = await _fileManager.readAsString(fileId); + return Response.ok( + content, + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + }, + ); + } catch (e) { + return Response.internalServerError( + body: + '${_i18nStrings['read_file_failed'] ?? 'Failed to read file'}: $e', + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + }, + ); + } + } + + /// 处理获取hosts文件历史记录请求 + Future _handleGetHostHistory(Request request) async { + final fileId = request.params['fileId']; + if (fileId == null) { + return Response.badRequest( + body: jsonEncode({ + 'success': false, + 'error': _i18nStrings['missing_file_id'] ?? 'Missing file ID' + }), + headers: _corsHeaders, + ); + } + + try { + final historyList = await _fileManager.getHistory(fileId); + + // 转换为API响应格式 + final historyData = historyList + .map((history) => { + 'id': history.fileName, + 'createTime': DateTime.fromMillisecondsSinceEpoch( + int.tryParse(history.fileName) ?? 0) + .toIso8601String(), + }) + .toList(); + + // 按时间倒序排列 + historyData.sort((a, b) => + b['timestamp'].toString().compareTo(a['timestamp'].toString())); + + return Response.ok( + jsonEncode({'success': true, 'data': historyData}), + headers: _corsHeaders, + ); + } catch (e) { + return Response.internalServerError( + body: jsonEncode({'success': false, 'error': e.toString()}), + headers: _corsHeaders, + ); + } + } + + /// 处理获取特定历史记录内容请求 + Future _handleGetHistoryContent(Request request) async { + final fileId = request.params['fileId']; + final historyId = request.params['historyId']; + + if (fileId == null || historyId == null) { + return Response.badRequest( + body: _i18nStrings['missing_file_id_or_history_id'] ?? + 'Missing file ID or history ID', + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + }, + ); + } + + try { + final historyList = await _fileManager.getHistory(fileId); + final historyItem = historyList.firstWhere( + (h) => h.fileName == historyId, + orElse: () => throw Exception( + _i18nStrings['history_not_found'] ?? 'History not found'), + ); + + final content = _fileManager.readHistoryFile(historyItem.path); + + return Response.ok( + content, + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + }, + ); + } catch (e) { + return Response.internalServerError( + body: + '${_i18nStrings['read_history_failed'] ?? 'Failed to read history'}: $e', + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + }, + ); + } + } +} diff --git a/lib/server/index.dart b/lib/server/index.dart new file mode 100755 index 0000000..0db615c --- /dev/null +++ b/lib/server/index.dart @@ -0,0 +1,4 @@ +// Server module exports +export 'hosts_server.dart'; +export 'server_manager.dart'; +export 'server_settings_page.dart'; \ No newline at end of file diff --git a/lib/server/server_manager.dart b/lib/server/server_manager.dart new file mode 100755 index 0000000..1c5f7cd --- /dev/null +++ b/lib/server/server_manager.dart @@ -0,0 +1,185 @@ +import 'dart:async'; +import 'package:hosts/server/hosts_server.dart'; +import 'package:hosts/util/settings_manager.dart'; + +/// 服务器管理器 +/// 负责管理HTTP服务器的生命周期和配置 +class ServerManager { + static final ServerManager _instance = ServerManager._internal(); + factory ServerManager() => _instance; + ServerManager._internal(); + + final HostsServer _server = HostsServer(); + final SettingsManager _settingsManager = SettingsManager(); + + // 配置键 + static const String _serverEnabledKey = 'server_enabled'; + static const String _serverPortKey = 'server_port'; + static const String _serverHostKey = 'server_host'; + static const String _serverAutoStartKey = 'server_auto_start'; + + // 默认配置 + static const int _defaultPort = 1204; + static const String _defaultHost = '0.0.0.0'; + + /// 获取服务器实例 + HostsServer get server => _server; + + /// 检查服务器是否启用 + Future isServerEnabled() async { + return await _settingsManager.getBool(_serverEnabledKey) ?? false; + } + + /// 设置服务器启用状态 + Future setServerEnabled(bool enabled) async { + await _settingsManager.setBool(_serverEnabledKey, enabled); + } + + /// 获取服务器端口 + Future getServerPort() async { + return await _settingsManager.getInt(_serverPortKey) ?? _defaultPort; + } + + /// 设置服务器端口 + Future setServerPort(int port) async { + await _settingsManager.setInt(_serverPortKey, port); + } + + /// 获取服务器主机 + Future getServerHost() async { + return await _settingsManager.getString(_serverHostKey) ?? _defaultHost; + } + + /// 设置服务器主机 + Future setServerHost(String host) async { + await _settingsManager.setString(_serverHostKey, host); + } + + /// 获取自动启动设置 + Future isAutoStartEnabled() async { + return await _settingsManager.getBool(_serverAutoStartKey) ?? false; + } + + /// 设置自动启动 + Future setAutoStart(bool enabled) async { + await _settingsManager.setBool(_serverAutoStartKey, enabled); + } + + /// 初始化服务器管理器 + Future initialize() async { + // 如果启用了自动启动,则启动服务器 + if (await isAutoStartEnabled() && await isServerEnabled()) { + await startServer(); + } + } + + /// 启动服务器 + Future startServer() async { + try { + if (_server.isRunning) { + return true; + } + + final port = await getServerPort(); + final host = await getServerHost(); + + await _server.start(port: port, host: host); + await setServerEnabled(true); + + return true; + } catch (e) { + print('启动服务器失败: $e'); + return false; + } + } + + /// 停止服务器 + Future stopServer() async { + try { + await _server.stop(); + await setServerEnabled(false); + } catch (e) { + print('停止服务器失败: $e'); + } + } + + /// 重启服务器 + Future restartServer() async { + try { + await stopServer(); + return await startServer(); + } catch (e) { + print('重启服务器失败: $e'); + return false; + } + } + + /// 获取服务器状态信息 + Future> getServerStatus() async { + return { + 'isRunning': _server.isRunning, + 'isEnabled': await isServerEnabled(), + 'port': await getServerPort(), + 'host': await getServerHost(), + 'autoStart': await isAutoStartEnabled(), + 'url': _server.serverUrl, + }; + } + + /// 更新服务器配置 + Future updateServerConfig({ + int? port, + String? host, + bool? autoStart, + }) async { + try { + bool needRestart = false; + + if (port != null) { + final currentPort = await getServerPort(); + if (currentPort != port) { + await setServerPort(port); + needRestart = true; + } + } + + if (host != null) { + final currentHost = await getServerHost(); + if (currentHost != host) { + await setServerHost(host); + needRestart = true; + } + } + + if (autoStart != null) { + await setAutoStart(autoStart); + } + + // 如果服务器正在运行且配置有变化,需要重启 + if (_server.isRunning && needRestart) { + return await restartServer(); + } + + return true; + } catch (e) { + print('更新服务器配置失败: $e'); + return false; + } + } + + /// 验证端口是否可用 + Future isPortAvailable(int port) async { + try { + // 这里可以添加端口检查逻辑 + // 暂时返回true + return true; + } catch (e) { + return false; + } + } + + /// 清理资源 + Future dispose() async { + await stopServer(); + } +} \ No newline at end of file diff --git a/lib/server/server_settings_page.dart b/lib/server/server_settings_page.dart new file mode 100755 index 0000000..cee9126 --- /dev/null +++ b/lib/server/server_settings_page.dart @@ -0,0 +1,294 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/server/server_manager.dart'; + +/// 服务器设置页面 +class ServerSettingsPage extends StatefulWidget { + const ServerSettingsPage({super.key}); + + @override + State createState() => _ServerSettingsPageState(); +} + +class _ServerSettingsPageState extends State { + final ServerManager _serverManager = ServerManager(); + + bool _isServerEnabled = false; + bool _isLoading = true; + Map? _serverStatus; + + @override + void initState() { + super.initState(); + _loadServerSettings(); + } + + + /// 加载服务器设置 + Future _loadServerSettings() async { + try { + final status = await _serverManager.getServerStatus(); + setState(() { + _serverStatus = status; + _isServerEnabled = status['isEnabled'] ?? false; + _isLoading = false; + }); + } catch (e) { + setState(() { + _isLoading = false; + }); + _showError('${AppLocalizations.of(context)!.load_server_settings_failed}: $e'); + } + } + + /// 切换服务器状态 + Future _toggleServer() async { + setState(() { + _isLoading = true; + }); + + try { + bool success; + if (_isServerEnabled) { + await _serverManager.stopServer(); + success = true; + } else { + success = await _serverManager.startServer(); + } + + if (success) { + await _loadServerSettings(); + _showSuccess(_isServerEnabled ? AppLocalizations.of(context)!.server_started : AppLocalizations.of(context)!.server_stopped_msg); + } else { + _showError(AppLocalizations.of(context)!.operation_failed); + } + } catch (e) { + _showError('${AppLocalizations.of(context)!.operation_failed}: $e'); + } finally { + setState(() { + _isLoading = false; + }); + } + } + + + /// 复制服务器URL + void _copyServerUrl() { + if (_serverStatus != null) { + final url = _serverStatus!['url']; + Clipboard.setData(ClipboardData(text: url)); + _showSuccess(AppLocalizations.of(context)!.server_url_copied); + } + } + + /// 显示成功消息 + void _showSuccess(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Theme.of(context).colorScheme.primary, + ), + ); + } + + /// 显示错误消息 + void _showError(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.remote_sync), + actions: [ + IconButton( + icon: const Icon(Icons.refresh), + onPressed: _loadServerSettings, + tooltip: AppLocalizations.of(context)!.refresh_status, + ), + ], + ), + body: _isLoading + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 服务器状态卡片 + _buildStatusCard(), + const SizedBox(height: 16), + + // API文档卡片 + _buildApiDocsCard(), + ], + ), + ), + ); + } + + /// 构建状态卡片 + Widget _buildStatusCard() { + final isRunning = _serverStatus?['isRunning'] ?? false; + final url = _serverStatus?['url'] ?? ''; + + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.server_status, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 16), + Row( + children: [ + Icon( + isRunning ? Icons.radio_button_checked : Icons.radio_button_off, + color: isRunning ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.outline, + ), + const SizedBox(width: 8), + Text( + isRunning ? AppLocalizations.of(context)!.server_running : AppLocalizations.of(context)!.server_stopped, + style: TextStyle( + color: isRunning ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.outline, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + ElevatedButton( + onPressed: _toggleServer, + child: Text(isRunning ? AppLocalizations.of(context)!.server_stop : AppLocalizations.of(context)!.server_start), + ), + ], + ), + if (isRunning) ...[ + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: Text( + '${AppLocalizations.of(context)!.server_address}: $url', + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + IconButton( + icon: const Icon(Icons.copy), + onPressed: _copyServerUrl, + tooltip: AppLocalizations.of(context)!.copy_url, + ), + ], + ), + ], + ], + ), + ), + ); + } + + + /// 构建API文档卡片 + Widget _buildApiDocsCard() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.api_docs, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 16), + Text( + '${AppLocalizations.of(context)!.api_endpoints}:', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + _buildApiEndpoint('GET', '/', AppLocalizations.of(context)!.server_status), + _buildApiEndpoint('GET', '/api/hosts', AppLocalizations.of(context)!.get_all_hosts_files), + _buildApiEndpoint('GET', '/api/hosts/{fileId}', AppLocalizations.of(context)!.get_specific_hosts_file), + _buildApiEndpoint('GET', '/api/hosts/{fileId}/history', AppLocalizations.of(context)!.get_hosts_file_history), + _buildApiEndpoint('GET', '/api/hosts/{fileId}/history/{historyId}', AppLocalizations.of(context)!.get_specific_history_content), + ], + ), + ), + ); + } + + /// 构建API端点项 + Widget _buildApiEndpoint(String method, String path, String description) { + Color methodColor; + switch (method) { + case 'GET': + methodColor = Theme.of(context).colorScheme.primary; + break; + case 'POST': + methodColor = Theme.of(context).colorScheme.secondary; + break; + case 'PUT': + methodColor = Theme.of(context).colorScheme.tertiary; + break; + case 'DELETE': + methodColor = Theme.of(context).colorScheme.error; + break; + default: + methodColor = Theme.of(context).colorScheme.outline; + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: methodColor, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + method, + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimary, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + path, + style: const TextStyle( + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + ), + ), + Text( + description, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/util/file_manager.dart b/lib/util/file_manager.dart old mode 100644 new mode 100755 index 4707fc9..0d5e1e8 --- a/lib/util/file_manager.dart +++ b/lib/util/file_manager.dart @@ -10,6 +10,21 @@ import 'package:hosts/util/regexp_util.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; +// 导入hosts文件的数据模型 +class ImportableHost { + final String remark; + final String fileName; + final String folderPath; + final bool hasContent; + + ImportableHost({ + required this.remark, + required this.fileName, + required this.folderPath, + required this.hasContent, + }); +} + class FileManager { // 私有构造函数 FileManager._internal() { @@ -325,6 +340,230 @@ class FileManager { } } + // 递归添加目录到压缩包的辅助方法(自定义文件夹名称) + Future _addDirectoryToArchiveWithCustomName( + Directory directory, Archive archive, String baseName, String customFolderName) async { + final List entities = directory.listSync(); + + for (FileSystemEntity entity in entities) { + if (entity is File) { + final String relativePath = p.relative(entity.path, + from: p.join(_cachedDirectory!.path, baseName)); + final String customPath = p.join(customFolderName, relativePath); + final List fileBytes = await entity.readAsBytes(); + final ArchiveFile file = + ArchiveFile(customPath, fileBytes.length, fileBytes); + archive.addFile(file); + } else if (entity is Directory) { + await _addDirectoryToArchiveWithCustomName(entity, archive, baseName, customFolderName); + } + } + } + + Future exportMultipleHostFiles(List hostFiles, String dialogTitle) async { + try { + if (_cachedDirectory == null) await _initializeDirectory(); + + if (hostFiles.isEmpty) { + return false; + } + + // 让用户选择保存路径 + String defaultFileName; + if (hostFiles.length == 1) { + // 单个文件时使用该文件的备注作为文件名 + defaultFileName = '${hostFiles.first.remark}_${hostFiles.first.fileName}.zip'; + } else { + // 多个文件时使用通用名称 + defaultFileName = 'hosts_batch_export.zip'; + } + + String? outputFilePath = await FilePicker.platform.saveFile( + dialogTitle: dialogTitle, + fileName: defaultFileName, + type: FileType.custom, + allowedExtensions: ['zip'], + ); + + if (outputFilePath != null) { + // 创建压缩包 + final Archive archive = Archive(); + + // 为每个host文件添加到压缩包 + for (SimpleHostFile hostFile in hostFiles) { + final String directoryPath = p.join(_cachedDirectory!.path, hostFile.fileName); + final Directory exportDirectory = Directory(directoryPath); + + if (await exportDirectory.exists()) { + // 创建以 {remark}_{fileName} 命名的文件夹 + final String folderName = '${hostFile.remark}_${hostFile.fileName}'; + await _addDirectoryToArchiveWithCustomName(exportDirectory, archive, hostFile.fileName, folderName); + } + } + + // 编码压缩包 + final List zipData = ZipEncoder().encode(archive)!; + + // 写入文件 + await File(outputFilePath).writeAsBytes(zipData); + return true; + } + return false; + } catch (e) { + print('Batch export failed: $e'); + return false; + } + } + + // 解析导入文件,返回可导入的hosts列表 + Future> parseImportFile(String filePath) async { + List importableHosts = []; + + try { + if (filePath.toLowerCase().endsWith('.zip')) { + // 解析ZIP文件 + final bytes = await File(filePath).readAsBytes(); + final archive = ZipDecoder().decodeBytes(bytes); + + // 查找所有可能的hosts文件夹 + Map hostFolders = {}; + + for (final file in archive) { + if (file.isFile && file.name.endsWith('/hosts')) { + // 获取文件夹名称 + final parts = file.name.split('/'); + if (parts.length >= 2) { + final folderName = parts[parts.length - 2]; + hostFolders[folderName] = file.name; + } + } + } + + // 为每个hosts文件夹创建ImportableHost + for (final entry in hostFolders.entries) { + final folderName = entry.key; + final hostFilePath = entry.value; + + // 尝试解析文件夹名称为remark和fileName + String remark = folderName; + String fileName = folderName; + + // 如果文件夹名称包含下划线,尝试分割 + if (folderName.contains('_')) { + final lastUnderscoreIndex = folderName.lastIndexOf('_'); + remark = folderName.substring(0, lastUnderscoreIndex); + fileName = folderName.substring(lastUnderscoreIndex + 1); + } + + importableHosts.add(ImportableHost( + remark: remark, + fileName: fileName, + folderPath: hostFilePath.substring(0, hostFilePath.lastIndexOf('/')), + hasContent: true, + )); + } + } else { + // 单个hosts文件 + final fileName = p.basenameWithoutExtension(filePath); + importableHosts.add(ImportableHost( + remark: fileName, + fileName: fileName, + folderPath: filePath, + hasContent: await File(filePath).exists(), + )); + } + } catch (e) { + print('解析导入文件失败: $e'); + } + + return importableHosts; + } + + // 导入选中的hosts文件 + Future> importSelectedHosts(String filePath, List selectedHosts, List existingFileNames) async { + List importedFiles = []; + + try { + if (_cachedDirectory == null) await _initializeDirectory(); + + if (filePath.toLowerCase().endsWith('.zip')) { + // 从ZIP文件导入 + final bytes = await File(filePath).readAsBytes(); + final archive = ZipDecoder().decodeBytes(bytes); + + for (final selectedHost in selectedHosts) { + // 直接使用原文件名,如果存在则覆盖 + String fileName = selectedHost.fileName; + String targetDir = p.join(_cachedDirectory!.path, fileName); + + // 如果目录已存在,先删除 + if (await Directory(targetDir).exists()) { + await Directory(targetDir).delete(recursive: true); + } + + // 创建目标目录 + await Directory(targetDir).create(recursive: true); + await Directory(p.join(targetDir, 'history')).create(recursive: true); + + // 提取文件 + for (final file in archive) { + if (file.isFile && file.name.startsWith(selectedHost.folderPath)) { + final relativePath = file.name.substring(selectedHost.folderPath.length + 1); + final targetPath = p.join(targetDir, relativePath); + + // 确保目标目录存在 + await Directory(p.dirname(targetPath)).create(recursive: true); + + // 写入文件 + await File(targetPath).writeAsBytes(file.content as List); + } + } + + // 创建SimpleHostFile对象 + final importedFile = SimpleHostFile( + fileName: fileName, + remark: selectedHost.remark, + ); + importedFiles.add(importedFile); + existingFileNames.add(fileName); + } + } else { + // 单个文件导入 + if (selectedHosts.isNotEmpty) { + final selectedHost = selectedHosts.first; + String fileName = selectedHost.fileName; + String targetDir = p.join(_cachedDirectory!.path, fileName); + + // 如果目录已存在,先删除 + if (await Directory(targetDir).exists()) { + await Directory(targetDir).delete(recursive: true); + } + + // 创建目标目录 + await Directory(targetDir).create(recursive: true); + await Directory(p.join(targetDir, 'history')).create(recursive: true); + + // 复制文件 + final sourceFile = File(filePath); + final targetFile = File(p.join(targetDir, 'hosts')); + await sourceFile.copy(targetFile.path); + + // 创建SimpleHostFile对象 + final importedFile = SimpleHostFile( + fileName: fileName, + remark: selectedHost.remark, + ); + importedFiles.add(importedFile); + } + } + + return importedFiles; + } catch (e) { + print('导入失败: $e'); + return []; + } + } + List parseHosts(List lines) { List tempHosts = []; diff --git a/lib/widget/dialog/export_hosts_dialog.dart b/lib/widget/dialog/export_hosts_dialog.dart new file mode 100755 index 0000000..29648ab --- /dev/null +++ b/lib/widget/dialog/export_hosts_dialog.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/util/file_manager.dart'; + +Future exportHostsDialog( + BuildContext context, List hostFiles) { + return showDialog( + context: context, + builder: (BuildContext context) { + return ExportHostsDialog(hostFiles: hostFiles); + }, + ); +} + +class ExportHostsDialog extends StatefulWidget { + final List hostFiles; + + const ExportHostsDialog({super.key, required this.hostFiles}); + + @override + State createState() => _ExportHostsDialogState(); +} + +class _ExportHostsDialogState extends State { + late List selectedItems; + bool isAllSelected = false; + + @override + void initState() { + super.initState(); + selectedItems = List.generate(widget.hostFiles.length, (index) => false); + } + + void _toggleSelectAll() { + setState(() { + isAllSelected = !isAllSelected; + for (int i = 0; i < selectedItems.length; i++) { + selectedItems[i] = isAllSelected; + } + }); + } + + void _toggleItem(int index) { + setState(() { + selectedItems[index] = !selectedItems[index]; + isAllSelected = selectedItems.every((item) => item); + }); + } + + Future _exportSelected() async { + final List selectedHostFiles = []; + for (int i = 0; i < widget.hostFiles.length; i++) { + if (selectedItems[i]) { + selectedHostFiles.add(widget.hostFiles[i]); + } + } + + if (selectedHostFiles.isEmpty) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalizations.of(context)!.select_hosts_to_export)), + ); + } + return; + } + + // 获取本地化字符串,在关闭对话框之前 + final String exportDataTitle = AppLocalizations.of(context)!.export_data; + final String exportSuccessMessage = AppLocalizations.of(context)!.export_success; + + // 获取ScaffoldMessenger,在关闭对话框之前 + final ScaffoldMessengerState scaffoldMessenger = ScaffoldMessenger.of(context); + + Navigator.of(context).pop(); + + final FileManager fileManager = FileManager(); + bool success = false; + + try { + // 统一使用批量导出方法 + success = await fileManager.exportMultipleHostFiles( + selectedHostFiles, + exportDataTitle, + ); + + if (success) { + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(exportSuccessMessage)), + ); + } + } catch (e) { + scaffoldMessenger.showSnackBar( + SnackBar(content: Text('${AppLocalizations.of(context)!.export_failed}: $e')), + ); + } + } + + @override + Widget build(BuildContext context) { + final selectedCount = selectedItems.where((item) => item).length; + + return AlertDialog( + title: Text(AppLocalizations.of(context)!.export_data), + content: SizedBox( + width: 500, + height: 400, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 全选/反选按钮和统计 + Row( + children: [ + Checkbox( + value: isAllSelected, + onChanged: (bool? value) => _toggleSelectAll(), + ), + Text(AppLocalizations.of(context)!.select_all), + const Spacer(), + Text('${AppLocalizations.of(context)!.selected_count}: $selectedCount/${widget.hostFiles.length}'), + ], + ), + const Divider(), + // hosts文件列表 + Expanded( + child: ListView.builder( + itemCount: widget.hostFiles.length, + itemBuilder: (context, index) { + final hostFile = widget.hostFiles[index]; + return ListTile( + trailing: Checkbox( + value: selectedItems[index], + onChanged: (bool? value) => _toggleItem(index), + ), + title: Text(hostFile.remark), + onTap: () => _toggleItem(index), + ); + }, + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(AppLocalizations.of(context)!.cancel), + ), + FilledButton( + onPressed: selectedCount > 0 ? _exportSelected : null, + child: Text(AppLocalizations.of(context)!.export), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/widget/dialog/import_hosts_dialog.dart b/lib/widget/dialog/import_hosts_dialog.dart new file mode 100755 index 0000000..d3b0834 --- /dev/null +++ b/lib/widget/dialog/import_hosts_dialog.dart @@ -0,0 +1,312 @@ +import 'package:flutter/material.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/util/file_manager.dart'; +import 'package:hosts/util/settings_manager.dart'; + +Future importHostsDialog( + BuildContext context, List existingHostFiles, {VoidCallback? onImportSuccess}) { + return showDialog( + context: context, + builder: (BuildContext context) { + return ImportHostsDialog(existingHostFiles: existingHostFiles, onImportSuccess: onImportSuccess); + }, + ); +} + +class ImportHostsDialog extends StatefulWidget { + final List existingHostFiles; + final VoidCallback? onImportSuccess; + + const ImportHostsDialog({super.key, required this.existingHostFiles, this.onImportSuccess}); + + @override + State createState() => _ImportHostsDialogState(); +} + +class _ImportHostsDialogState extends State { + List importableHosts = []; + late List selectedItems; + bool isAllSelected = false; + bool isLoading = false; + String? selectedFilePath; + + @override + void initState() { + super.initState(); + selectedItems = []; + } + + Future _selectFile() async { + try { + FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['zip'], + allowMultiple: false, + ); + + if (result != null && result.files.single.path != null) { + setState(() { + isLoading = true; + selectedFilePath = result.files.single.path; + importableHosts = []; + selectedItems = []; + }); + + final FileManager fileManager = FileManager(); + final List hosts = + await fileManager.parseImportFile(selectedFilePath!); + + setState(() { + importableHosts = hosts; + selectedItems = List.generate(hosts.length, (index) => false); + isLoading = false; + }); + } + } catch (e) { + setState(() { + isLoading = false; + }); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${AppLocalizations.of(context)!.error_open_file}: $e')), + ); + } + } + } + + void _toggleSelectAll() { + setState(() { + isAllSelected = !isAllSelected; + for (int i = 0; i < selectedItems.length; i++) { + selectedItems[i] = isAllSelected; + } + }); + } + + void _toggleItem(int index) { + setState(() { + selectedItems[index] = !selectedItems[index]; + isAllSelected = selectedItems.every((item) => item); + }); + } + + Future _importSelected() async { + if (selectedFilePath == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalizations.of(context)!.error_null_data)), + ); + return; + } + + final List selectedHosts = []; + for (int i = 0; i < importableHosts.length; i++) { + if (selectedItems[i]) { + selectedHosts.add(importableHosts[i]); + } + } + + if (selectedHosts.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalizations.of(context)!.error_null_data)), + ); + return; + } + + // 获取本地化字符串和ScaffoldMessenger,在关闭对话框之前 + final String importSuccessMessage = AppLocalizations.of(context)!.import_success; + final String importFailMessage = AppLocalizations.of(context)!.error_save_fail; + final ScaffoldMessengerState scaffoldMessenger = ScaffoldMessenger.of(context); + + Navigator.of(context).pop(); + + try { + + final FileManager fileManager = FileManager(); + final SettingsManager settingsManager = SettingsManager(); + final List existingFileNames = + widget.existingHostFiles.map((e) => e.fileName).toList(); + + // 导入文件 + final List importedFiles = await fileManager.importSelectedHosts( + selectedFilePath!, + selectedHosts, + existingFileNames, + ); + + if (importedFiles.isNotEmpty) { + // 获取当前配置 + List hostConfigs = await settingsManager.getList(settingKeyHostConfigs); + + // 添加或更新导入的文件到配置中 + for (final importedFile in importedFiles) { + // 查找是否已存在相同fileName的配置 + int existingIndex = hostConfigs.indexWhere((config) => + config['fileName'] == importedFile.fileName); + + if (existingIndex >= 0) { + // 如果存在,则覆盖 + hostConfigs[existingIndex] = importedFile.toJson(); + } else { + // 如果不存在,则添加 + hostConfigs.add(importedFile.toJson()); + } + } + + // 保存到settingsManager + await settingsManager.setList(settingKeyHostConfigs, hostConfigs); + + scaffoldMessenger.showSnackBar( + SnackBar(content: Text('$importSuccessMessage ${importedFiles.length}')), + ); + + // 调用成功回调来刷新数据 + widget.onImportSuccess?.call(); + } else { + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(importFailMessage)), + ); + } + } catch (e) { + scaffoldMessenger.showSnackBar( + SnackBar(content: Text('$importFailMessage: $e')), + ); + } + } + + @override + Widget build(BuildContext context) { + final selectedCount = selectedItems.where((item) => item).length; + + return AlertDialog( + title: Text(AppLocalizations.of(context)!.import_data), + content: SizedBox( + width: 500, + height: 450, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 文件选择按钮 + Row( + children: [ + ElevatedButton.icon( + onPressed: isLoading ? null : _selectFile, + icon: Icon(Icons.file_open), + label: Text(AppLocalizations.of(context)!.open_file), + ), + const SizedBox(width: 16), + Expanded( + child: Text( + selectedFilePath != null + ? selectedFilePath!.split('/').last + : AppLocalizations.of(context)!.error_null_data, + style: Theme.of(context).textTheme.bodyMedium, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 16), + + if (isLoading) + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('${AppLocalizations.of(context)!.file_processing}...'), + ], + ), + ), + ) + else if (importableHosts.isNotEmpty) ...[ + // 全选/反选按钮和统计 + Row( + children: [ + Checkbox( + value: isAllSelected, + onChanged: (bool? value) => _toggleSelectAll(), + ), + Text(AppLocalizations.of(context)!.select_all), + const Spacer(), + Text('$selectedCount/${importableHosts.length}'), + ], + ), + const Divider(), + // hosts文件列表 + Expanded( + child: ListView.builder( + itemCount: importableHosts.length, + itemBuilder: (context, index) { + final host = importableHosts[index]; + final bool isExisting = widget.existingHostFiles.any((existingFile) => + existingFile.fileName == host.fileName); + + return ListTile( + trailing: Checkbox( + value: selectedItems[index], + onChanged: (bool? value) => _toggleItem(index), + ), + title: Text(host.remark), + subtitle: isExisting + ? Text( + AppLocalizations.of(context)!.will_overwrite, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 12, + ), + ) + : null, + onTap: () => _toggleItem(index), + ); + }, + ), + ), + ] else if (selectedFilePath != null) ...[ + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.warning, size: 48, color: Colors.orange), + SizedBox(height: 16), + Text(AppLocalizations.of(context)!.error_null_data), + Text(AppLocalizations.of(context)!.error_open_file, + style: TextStyle(color: Colors.grey[600])), + ], + ), + ), + ), + ] else ...[ + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.file_upload, size: 48, color: Colors.grey), + SizedBox(height: 16), + Text(AppLocalizations.of(context)!.import_file), + ], + ), + ), + ), + ], + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(AppLocalizations.of(context)!.cancel), + ), + FilledButton( + onPressed: (selectedCount > 0 && !isLoading) ? _importSelected : null, + child: Text(AppLocalizations.of(context)!.import), + ), + ], + ); + } +} \ No newline at end of file From 099723197a3e584e491a2b5fe554ee8e9d51824f Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Thu, 10 Jul 2025 20:33:55 +0800 Subject: [PATCH 11/54] =?UTF-8?q?feat(=E4=BE=9D=E8=B5=96):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0HTTP=E6=9C=8D=E5=8A=A1=E5=99=A8=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • 添加 shelf ^1.4.2 - HTTP服务器框架 • 添加 shelf_router ^1.1.4 - 路由管理 • 更新 pubspec.lock 以固定依赖版本 --- pubspec.lock | 32 ++++++++++++++++++++++++++++++++ pubspec.yaml | 4 ++++ 2 files changed, 36 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index 6cc955f..f13c563 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -197,6 +197,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.15.6" + http_methods: + dependency: transitive + description: + name: http_methods + sha256: "6bccce8f1ec7b5d701e7921dca35e202d425b57e317ba1a37f2638590e29e566" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" image: dependency: transitive description: @@ -421,6 +437,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + shelf: + dependency: "direct main" + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_router: + dependency: "direct main" + description: + name: shelf_router + sha256: f5e5d492440a7fb165fe1e2e1a623f31f734d3370900070b2b1e0d0428d59864 + url: "https://pub.dev" + source: hosted + version: "1.1.4" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index db15ed4..1c7b8c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,10 @@ dependencies: # 国际化 intl: any + # 服务器 + shelf: ^1.4.2 + shelf_router: ^1.1.4 + dev_dependencies: flutter_test: sdk: flutter From 33cfb56b250204cbbfa54d9e302563005a470e6a Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Thu, 10 Jul 2025 20:49:00 +0800 Subject: [PATCH 12/54] =?UTF-8?q?fix(API&=E5=9B=BD=E9=99=85=E5=8C=96):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8DAPI=E8=B7=AF=E5=BE=84=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E5=92=8C=E5=9B=BD=E9=99=85=E5=8C=96=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E9=87=8D=E5=A4=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • API路径优化 - 将路径参数从 {fileId} 改为 {fileName} 提高语义清晰度 - 更新所有相关的路由和处理函数 - 保持API文档与实际实现一致 • 国际化修复 - 移除重复的 select_all 字符串定义 - 重新组织导出相关的本地化字符串 - 确保所有本地化文件结构一致 • 代码一致性 - 统一使用 fileName 作为参数名 - 更新服务器设置页面的API文档显示 - 保持前后端接口命名规范 --- lib/l10n/app_en.arb | 1 - lib/l10n/app_localizations.dart | 30 ++++++++++++++++++++++------ lib/l10n/app_localizations_en.dart | 15 +++++++++++--- lib/l10n/app_localizations_zh.dart | 15 +++++++++++--- lib/l10n/app_zh.arb | 1 - lib/server/hosts_server.dart | 30 ++++++++++++++-------------- lib/server/server_settings_page.dart | 6 +++--- 7 files changed, 66 insertions(+), 32 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6161123..05183e0 100755 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -71,7 +71,6 @@ "form": "Form", "import_data": "Import Hosts Data", "import_success": "Import successful", - "select_all": "Select All", "loading": "Loading", "file_processing": "Processing file", "import_file": "Import file", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index a36296a..58959d3 100755 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -530,12 +530,6 @@ abstract class AppLocalizations { /// **'导入成功'** String get import_success; - /// No description provided for @select_all. - /// - /// In zh, this message translates to: - /// **'全选'** - String get select_all; - /// No description provided for @loading. /// /// In zh, this message translates to: @@ -817,6 +811,30 @@ abstract class AppLocalizations { /// In zh, this message translates to: /// **'读取历史记录失败'** String get read_history_failed; + + /// No description provided for @select_hosts_to_export. + /// + /// In zh, this message translates to: + /// **'请选择要导出hosts文件'** + String get select_hosts_to_export; + + /// No description provided for @select_all. + /// + /// In zh, this message translates to: + /// **'全选'** + String get select_all; + + /// No description provided for @selected_count. + /// + /// In zh, this message translates to: + /// **'已选择'** + String get selected_count; + + /// No description provided for @export_failed. + /// + /// In zh, this message translates to: + /// **'导出失败'** + String get export_failed; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 3ab3f88..e7afb9e 100755 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -237,9 +237,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get import_success => 'Import successful'; - @override - String get select_all => 'Select All'; - @override String get loading => 'Loading'; @@ -383,4 +380,16 @@ class AppLocalizationsEn extends AppLocalizations { @override String get read_history_failed => 'Failed to read history'; + + @override + String get select_hosts_to_export => 'Please select hosts files to export'; + + @override + String get select_all => 'Select All'; + + @override + String get selected_count => 'Selected'; + + @override + String get export_failed => 'Export failed'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 81b3ccd..bc5e87c 100755 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -231,9 +231,6 @@ class AppLocalizationsZh extends AppLocalizations { @override String get import_success => '导入成功'; - @override - String get select_all => '全选'; - @override String get loading => '加载中'; @@ -374,4 +371,16 @@ class AppLocalizationsZh extends AppLocalizations { @override String get read_history_failed => '读取历史记录失败'; + + @override + String get select_hosts_to_export => '请选择要导出hosts文件'; + + @override + String get select_all => '全选'; + + @override + String get selected_count => '已选择'; + + @override + String get export_failed => '导出失败'; } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index a02247d..24a315a 100755 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -71,7 +71,6 @@ "form": "表单", "import_data": "导入 Hosts 数据", "import_success": "导入成功", - "select_all": "全选", "loading": "加载中", "file_processing": "文件处理中", "import_file": "导入文件", diff --git a/lib/server/hosts_server.dart b/lib/server/hosts_server.dart index 1bf570e..dd0585c 100755 --- a/lib/server/hosts_server.dart +++ b/lib/server/hosts_server.dart @@ -109,14 +109,14 @@ class HostsServer { router.get('/api/hosts', _handleGetHosts); // 获取特定hosts文件内容 - router.get('/api/hosts/', _handleGetHostFile); + router.get('/api/hosts/', _handleGetHostFile); // 获取hosts文件的历史记录 - router.get('/api/hosts//history', _handleGetHostHistory); + router.get('/api/hosts//history', _handleGetHostHistory); // 获取特定历史记录内容 router.get( - '/api/hosts//history/', _handleGetHistoryContent); + '/api/hosts//history/', _handleGetHistoryContent); return router; } @@ -151,9 +151,9 @@ class HostsServer { 'timestamp': DateTime.now().toIso8601String(), 'endpoints': [ 'GET /api/hosts - ${_i18nStrings['get_all_hosts_files'] ?? 'Get all hosts files (JSON)'}', - 'GET /api/hosts/{fileId} - ${_i18nStrings['get_specific_hosts_file'] ?? 'Get specific hosts file content (plain text)'}', - 'GET /api/hosts/{fileId}/history - ${_i18nStrings['get_hosts_file_history'] ?? 'Get hosts file history (JSON)'}', - 'GET /api/hosts/{fileId}/history/{historyId} - ${_i18nStrings['get_specific_history_content'] ?? 'Get specific history content (plain text)'}', + 'GET /api/hosts/{fileName} - ${_i18nStrings['get_specific_hosts_file'] ?? 'Get specific hosts file content (plain text)'}', + 'GET /api/hosts/{fileName}/history - ${_i18nStrings['get_hosts_file_history'] ?? 'Get hosts file history (JSON)'}', + 'GET /api/hosts/{fileName}/history/{historyId} - ${_i18nStrings['get_specific_history_content'] ?? 'Get specific history content (plain text)'}', ] }; @@ -198,8 +198,8 @@ class HostsServer { /// 处理获取特定hosts文件请求 Future _handleGetHostFile(Request request) async { - final fileId = request.params['fileId']; - if (fileId == null) { + final fileName = request.params['fileName']; + if (fileName == null) { return Response.badRequest( body: _i18nStrings['missing_file_id'] ?? 'Missing file ID', headers: { @@ -210,7 +210,7 @@ class HostsServer { } try { - final content = await _fileManager.readAsString(fileId); + final content = await _fileManager.readAsString(fileName); return Response.ok( content, headers: { @@ -232,8 +232,8 @@ class HostsServer { /// 处理获取hosts文件历史记录请求 Future _handleGetHostHistory(Request request) async { - final fileId = request.params['fileId']; - if (fileId == null) { + final fileName = request.params['fileName']; + if (fileName == null) { return Response.badRequest( body: jsonEncode({ 'success': false, @@ -244,7 +244,7 @@ class HostsServer { } try { - final historyList = await _fileManager.getHistory(fileId); + final historyList = await _fileManager.getHistory(fileName); // 转换为API响应格式 final historyData = historyList @@ -274,10 +274,10 @@ class HostsServer { /// 处理获取特定历史记录内容请求 Future _handleGetHistoryContent(Request request) async { - final fileId = request.params['fileId']; + final fileName = request.params['fileName']; final historyId = request.params['historyId']; - if (fileId == null || historyId == null) { + if (fileName == null || historyId == null) { return Response.badRequest( body: _i18nStrings['missing_file_id_or_history_id'] ?? 'Missing file ID or history ID', @@ -289,7 +289,7 @@ class HostsServer { } try { - final historyList = await _fileManager.getHistory(fileId); + final historyList = await _fileManager.getHistory(fileName); final historyItem = historyList.firstWhere( (h) => h.fileName == historyId, orElse: () => throw Exception( diff --git a/lib/server/server_settings_page.dart b/lib/server/server_settings_page.dart index cee9126..30d9625 100755 --- a/lib/server/server_settings_page.dart +++ b/lib/server/server_settings_page.dart @@ -216,9 +216,9 @@ class _ServerSettingsPageState extends State { const SizedBox(height: 8), _buildApiEndpoint('GET', '/', AppLocalizations.of(context)!.server_status), _buildApiEndpoint('GET', '/api/hosts', AppLocalizations.of(context)!.get_all_hosts_files), - _buildApiEndpoint('GET', '/api/hosts/{fileId}', AppLocalizations.of(context)!.get_specific_hosts_file), - _buildApiEndpoint('GET', '/api/hosts/{fileId}/history', AppLocalizations.of(context)!.get_hosts_file_history), - _buildApiEndpoint('GET', '/api/hosts/{fileId}/history/{historyId}', AppLocalizations.of(context)!.get_specific_history_content), + _buildApiEndpoint('GET', '/api/hosts/{fileName}', AppLocalizations.of(context)!.get_specific_hosts_file), + _buildApiEndpoint('GET', '/api/hosts/{fileName}/history', AppLocalizations.of(context)!.get_hosts_file_history), + _buildApiEndpoint('GET', '/api/hosts/{fileName}/history/{historyId}', AppLocalizations.of(context)!.get_specific_history_content), ], ), ), From 81188191edf083d7b6805fc732ecc71dc449c262 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Fri, 11 Jul 2025 00:26:02 +0800 Subject: [PATCH 13/54] =?UTF-8?q?feat(=E7=BB=84=E4=BB=B6=E5=8C=96&?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E6=A3=80=E6=B5=8B):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E7=8A=B6=E6=80=81=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E5=92=8C=E4=BA=8C=E7=BB=B4=E7=A0=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 新增功能 - 添加network_info_plus依赖,实现可靠的WiFi网络检测 - 新增QrCodeDialog独立组件,支持透明背景和居中显示 - 新增ServerStatusCard组件,完整的服务器状态管理 ## 网络检测优化 - 使用network_info_plus库优先检测WiFi信息 - 结合传统NetworkInterface.list()作为补充 - 完整显示192.168.x.x等私有网络地址 - 根据IP地址类型使用不同颜色主题 ## 界面增强 - 地址芯片支持二维码和复制功能 - 移除手动刷新功能,简化用户界面 - 优化视觉层次,提高用户体验 ## 代码重构 - 提取独立的二维码对话框组件 - 提取独立的服务器状态卡片组件 - 简化ServerSettingsPage主文件逻辑 - 提高代码复用性和可维护性 --- lib/server/server_settings_page.dart | 357 +++++++++++++++++++------- lib/widget/dialog/qr_code_dialog.dart | 114 ++++++++ lib/widget/server_status_card.dart | 232 +++++++++++++++++ 3 files changed, 605 insertions(+), 98 deletions(-) create mode 100644 lib/widget/dialog/qr_code_dialog.dart create mode 100644 lib/widget/server_status_card.dart diff --git a/lib/server/server_settings_page.dart b/lib/server/server_settings_page.dart index 30d9625..28544bd 100755 --- a/lib/server/server_settings_page.dart +++ b/lib/server/server_settings_page.dart @@ -1,7 +1,10 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/server/server_manager.dart'; +import 'package:hosts/widget/server_status_card.dart'; +import 'package:network_info_plus/network_info_plus.dart'; /// 服务器设置页面 class ServerSettingsPage extends StatefulWidget { @@ -13,18 +16,19 @@ class ServerSettingsPage extends StatefulWidget { class _ServerSettingsPageState extends State { final ServerManager _serverManager = ServerManager(); - + bool _isServerEnabled = false; bool _isLoading = true; Map? _serverStatus; - + List> _networkInterfaces = []; + @override void initState() { super.initState(); _loadServerSettings(); + _getNetworkInterfaces(); } - - + /// 加载服务器设置 Future _loadServerSettings() async { try { @@ -38,16 +42,231 @@ class _ServerSettingsPageState extends State { setState(() { _isLoading = false; }); - _showError('${AppLocalizations.of(context)!.load_server_settings_failed}: $e'); + _showError( + '${AppLocalizations.of(context)!.load_server_settings_failed}: $e'); } } - + + /// 获取所有网络接口 + Future _getNetworkInterfaces() async { + final List> networkList = []; + final info = NetworkInfo(); + + try { + print('开始获取网络信息...'); + + // 1. 使用 network_info_plus 获取WiFi信息 + try { + final wifiIP = await info.getWifiIP(); + final wifiName = await info.getWifiName(); + final wifiBSSID = await info.getWifiBSSID(); + + print('WiFi IP: $wifiIP'); + print('WiFi Name: $wifiName'); + print('WiFi BSSID: $wifiBSSID'); + + if (wifiIP != null && wifiIP.isNotEmpty && wifiIP != '0.0.0.0') { + networkList.add({ + 'name': 'WiFi', + 'address': wifiIP, + 'description': + 'WiFi${wifiName != null ? ' ($wifiName)' : ''} (IPv4) Private', + 'icon': Icons.wifi.codePoint.toString(), + 'type': 'ipv4', + 'isMain': 'true', + }); + } + } catch (e) { + print('获取WiFi信息失败: $e'); + } + + // 2. 使用传统方法作为补充 + try { + final interfaces = await NetworkInterface.list( + includeLoopback: true, + includeLinkLocal: false, + type: InternetAddressType.any, + ); + + print('发现的系统网络接口数量: ${interfaces.length}'); + + // 收集所有已知IP,避免重复 + final knownIPs = networkList.map((e) => e['address']).toSet(); + + for (final interface in interfaces) { + print('接口: ${interface.name}, 地址数量: ${interface.addresses.length}'); + + if (interface.addresses.isNotEmpty) { + for (final address in interface.addresses) { + final ip = address.address; + + // 跳过已知的IP地址 + if (knownIPs.contains(ip)) { + continue; + } + + print(' 地址: $ip, 类型: ${address.type}'); + + // 根据接口类型和地址类型确定图标和描述 + String description; + IconData icon; + bool isMainInterface = false; + + // 更精确的接口识别 + final interfaceName = interface.name.toLowerCase(); + + if (interfaceName.contains('lo') || interfaceName == 'loopback') { + description = 'Loopback'; + icon = Icons.loop; + } else if (interfaceName.contains('wlan') || + interfaceName.contains('wifi') || + interfaceName.contains('wi-fi') || + interfaceName.startsWith('wl')) { + description = 'WiFi (${interface.name})'; + icon = Icons.wifi; + isMainInterface = true; + } else if (interfaceName.contains('eth') || + interfaceName.contains('en') || + interfaceName.startsWith('enp') || + interfaceName.startsWith('ens') || + interfaceName.startsWith('eno')) { + description = 'Ethernet (${interface.name})'; + icon = Icons.settings_ethernet; + isMainInterface = true; + } else if (interfaceName.contains('docker') || + interfaceName.contains('br-') || + interfaceName.startsWith('docker')) { + description = 'Docker (${interface.name})'; + icon = Icons.developer_board; + } else if (interfaceName.contains('vmnet') || + interfaceName.contains('vbox') || + interfaceName.contains('virtual')) { + description = 'Virtual (${interface.name})'; + icon = Icons.computer; + } else if (interfaceName.contains('tun') || + interfaceName.contains('tap')) { + description = 'VPN/Tunnel (${interface.name})'; + icon = Icons.vpn_key; + } else { + description = interface.name; + icon = Icons.device_hub; + // 如果是未知接口但有局域网IP,也标记为主要接口 + if (address.type == InternetAddressType.IPv4) { + if (ip.startsWith('192.168.') || + ip.startsWith('10.') || + ip.startsWith('172.')) { + isMainInterface = true; + } + } + } + + // 添加地址类型和网络范围标识 + if (address.type == InternetAddressType.IPv6) { + description += ' (IPv6)'; + if (ip.startsWith('fe80')) { + description += ' Link-Local'; + } + } else { + description += ' (IPv4)'; + if (ip.startsWith('192.168.')) { + description += ' Private'; + isMainInterface = true; + } else if (ip.startsWith('10.')) { + description += ' Private'; + isMainInterface = true; + } else if (ip.startsWith('172.')) { + final second = int.tryParse(ip.split('.')[1]) ?? 0; + if (second >= 16 && second <= 31) { + description += ' Private'; + isMainInterface = true; + } + } else if (ip.startsWith('127.')) { + description += ' Loopback'; + } else { + description += ' Public'; + isMainInterface = true; + } + } + + networkList.add({ + 'name': interface.name, + 'address': ip, + 'description': description, + 'icon': icon.codePoint.toString(), + 'type': + address.type == InternetAddressType.IPv4 ? 'ipv4' : 'ipv6', + 'isMain': isMainInterface.toString(), + }); + + knownIPs.add(ip); + } + } + } + } catch (e) { + print('获取系统网络接口失败: $e'); + } + + // 3. 如果都没有获取到,添加默认的localhost + if (networkList.isEmpty) { + networkList.add({ + 'name': 'localhost', + 'address': '127.0.0.1', + 'description': 'Localhost (IPv4) Loopback', + 'icon': Icons.computer.codePoint.toString(), + 'type': 'ipv4', + 'isMain': 'false', + }); + } + + // 按优先级排序:主要接口 > IPv4 > 接口名称 + networkList.sort((a, b) { + final aIsMain = a['isMain'] == 'true'; + final bIsMain = b['isMain'] == 'true'; + + if (aIsMain && !bIsMain) return -1; + if (!aIsMain && bIsMain) return 1; + + final aIsIPv4 = a['type'] == 'ipv4'; + final bIsIPv4 = b['type'] == 'ipv4'; + + if (aIsIPv4 && !bIsIPv4) return -1; + if (!aIsIPv4 && bIsIPv4) return 1; + + return a['name']!.compareTo(b['name']!); + }); + + print('最终网络接口列表: ${networkList.length}'); + for (final interface in networkList) { + print(' ${interface['description']}: ${interface['address']}'); + } + + setState(() { + _networkInterfaces = networkList; + }); + } catch (e) { + print('获取网络接口失败: $e'); + // 最后的备用方案 + setState(() { + _networkInterfaces = [ + { + 'name': 'localhost', + 'address': '127.0.0.1', + 'description': 'Localhost (IPv4) Loopback', + 'icon': Icons.computer.codePoint.toString(), + 'type': 'ipv4', + 'isMain': 'false', + } + ]; + }); + } + } + /// 切换服务器状态 Future _toggleServer() async { setState(() { _isLoading = true; }); - + try { bool success; if (_isServerEnabled) { @@ -56,10 +275,12 @@ class _ServerSettingsPageState extends State { } else { success = await _serverManager.startServer(); } - + if (success) { await _loadServerSettings(); - _showSuccess(_isServerEnabled ? AppLocalizations.of(context)!.server_started : AppLocalizations.of(context)!.server_stopped_msg); + _showSuccess(_isServerEnabled + ? AppLocalizations.of(context)!.server_started + : AppLocalizations.of(context)!.server_stopped_msg); } else { _showError(AppLocalizations.of(context)!.operation_failed); } @@ -71,17 +292,10 @@ class _ServerSettingsPageState extends State { }); } } - - - /// 复制服务器URL - void _copyServerUrl() { - if (_serverStatus != null) { - final url = _serverStatus!['url']; - Clipboard.setData(ClipboardData(text: url)); - _showSuccess(AppLocalizations.of(context)!.server_url_copied); - } - } - + + + + /// 显示成功消息 void _showSuccess(String message) { ScaffoldMessenger.of(context).showSnackBar( @@ -91,7 +305,7 @@ class _ServerSettingsPageState extends State { ), ); } - + /// 显示错误消息 void _showError(String message) { ScaffoldMessenger.of(context).showSnackBar( @@ -101,19 +315,12 @@ class _ServerSettingsPageState extends State { ), ); } - + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.remote_sync), - actions: [ - IconButton( - icon: const Icon(Icons.refresh), - onPressed: _loadServerSettings, - tooltip: AppLocalizations.of(context)!.refresh_status, - ), - ], ), body: _isLoading ? const Center(child: CircularProgressIndicator()) @@ -125,7 +332,7 @@ class _ServerSettingsPageState extends State { // 服务器状态卡片 _buildStatusCard(), const SizedBox(height: 16), - + // API文档卡片 _buildApiDocsCard(), ], @@ -133,69 +340,16 @@ class _ServerSettingsPageState extends State { ), ); } - + /// 构建状态卡片 Widget _buildStatusCard() { - final isRunning = _serverStatus?['isRunning'] ?? false; - final url = _serverStatus?['url'] ?? ''; - - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.server_status, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 16), - Row( - children: [ - Icon( - isRunning ? Icons.radio_button_checked : Icons.radio_button_off, - color: isRunning ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.outline, - ), - const SizedBox(width: 8), - Text( - isRunning ? AppLocalizations.of(context)!.server_running : AppLocalizations.of(context)!.server_stopped, - style: TextStyle( - color: isRunning ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.outline, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - ElevatedButton( - onPressed: _toggleServer, - child: Text(isRunning ? AppLocalizations.of(context)!.server_stop : AppLocalizations.of(context)!.server_start), - ), - ], - ), - if (isRunning) ...[ - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: Text( - '${AppLocalizations.of(context)!.server_address}: $url', - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - IconButton( - icon: const Icon(Icons.copy), - onPressed: _copyServerUrl, - tooltip: AppLocalizations.of(context)!.copy_url, - ), - ], - ), - ], - ], - ), - ), + return ServerStatusCard( + serverStatus: _serverStatus, + networkInterfaces: _networkInterfaces, + onToggleServer: _toggleServer, ); } - - + /// 构建API文档卡片 Widget _buildApiDocsCard() { return Card( @@ -214,17 +368,24 @@ class _ServerSettingsPageState extends State { style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), - _buildApiEndpoint('GET', '/', AppLocalizations.of(context)!.server_status), - _buildApiEndpoint('GET', '/api/hosts', AppLocalizations.of(context)!.get_all_hosts_files), - _buildApiEndpoint('GET', '/api/hosts/{fileName}', AppLocalizations.of(context)!.get_specific_hosts_file), - _buildApiEndpoint('GET', '/api/hosts/{fileName}/history', AppLocalizations.of(context)!.get_hosts_file_history), - _buildApiEndpoint('GET', '/api/hosts/{fileName}/history/{historyId}', AppLocalizations.of(context)!.get_specific_history_content), + _buildApiEndpoint( + 'GET', '/', AppLocalizations.of(context)!.server_status), + _buildApiEndpoint('GET', '/api/hosts', + AppLocalizations.of(context)!.get_all_hosts_files), + _buildApiEndpoint('GET', '/api/hosts/{fileName}', + AppLocalizations.of(context)!.get_specific_hosts_file), + _buildApiEndpoint('GET', '/api/hosts/{fileName}/history', + AppLocalizations.of(context)!.get_hosts_file_history), + _buildApiEndpoint( + 'GET', + '/api/hosts/{fileName}/history/{historyId}', + AppLocalizations.of(context)!.get_specific_history_content), ], ), ), ); } - + /// 构建API端点项 Widget _buildApiEndpoint(String method, String path, String description) { Color methodColor; @@ -244,7 +405,7 @@ class _ServerSettingsPageState extends State { default: methodColor = Theme.of(context).colorScheme.outline; } - + return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( @@ -291,4 +452,4 @@ class _ServerSettingsPageState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/widget/dialog/qr_code_dialog.dart b/lib/widget/dialog/qr_code_dialog.dart new file mode 100644 index 0000000..50ac33b --- /dev/null +++ b/lib/widget/dialog/qr_code_dialog.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:qr_flutter/qr_flutter.dart'; + +/// 二维码显示对话框 +class QrCodeDialog extends StatelessWidget { + final String url; + final String? title; + + const QrCodeDialog({ + super.key, + required this.url, + this.title, + }); + + /// 显示二维码对话框 + static void show( + BuildContext context, { + required String url, + String? title, + }) { + showDialog( + context: context, + builder: (BuildContext context) { + return QrCodeDialog( + url: url, + title: title, + ); + }, + ); + } + + /// 复制URL到剪贴板 + void _copyUrl(BuildContext context) { + Clipboard.setData(ClipboardData(text: url)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.server_url_copied), + backgroundColor: Theme.of(context).colorScheme.primary, + ), + ); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(title ?? AppLocalizations.of(context)!.copy_url), + content: SizedBox( + width: 250, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 二维码显示区域 + SizedBox( + width: 200, + height: 200, + child: Center( + child: QrImageView( + data: url, + version: QrVersions.auto, + size: 180.0, + backgroundColor: Colors.transparent, + eyeStyle: QrEyeStyle( + eyeShape: QrEyeShape.square, + color: Theme.of(context).colorScheme.primary, + ), + dataModuleStyle: QrDataModuleStyle( + dataModuleShape: QrDataModuleShape.square, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ), + const SizedBox(height: 16), + // URL文本显示 + Container( + width: double.infinity, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + url, + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 12, + ), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(AppLocalizations.of(context)!.cancel), + ), + FilledButton.icon( + onPressed: () { + _copyUrl(context); + Navigator.of(context).pop(); + }, + icon: const Icon(Icons.copy, size: 16), + label: Text(AppLocalizations.of(context)!.copy), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/widget/server_status_card.dart b/lib/widget/server_status_card.dart new file mode 100644 index 0000000..517fb4c --- /dev/null +++ b/lib/widget/server_status_card.dart @@ -0,0 +1,232 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/widget/dialog/qr_code_dialog.dart'; + +/// 服务器状态卡片组件 +class ServerStatusCard extends StatelessWidget { + final Map? serverStatus; + final List> networkInterfaces; + final VoidCallback onToggleServer; + + const ServerStatusCard({ + super.key, + required this.serverStatus, + required this.networkInterfaces, + required this.onToggleServer, + }); + + /// 复制URL到剪贴板 + void _copyUrl(BuildContext context, String url) { + Clipboard.setData(ClipboardData(text: url)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.server_url_copied), + backgroundColor: Theme.of(context).colorScheme.primary, + ), + ); + } + + /// 显示二维码对话框 + void _showQrCodeDialog(BuildContext context, String url) { + QrCodeDialog.show(context, url: url); + } + + /// 构建地址芯片 + Widget _buildAddressChip( + BuildContext context, { + required String label, + required String url, + }) { + // 不省略任何IP信息,全部展示 + final displayLabel = label; + + // 根据IP地址类型确定颜色主题 + final isLocalhost = label.contains('127.0.0.1'); + final isPrivateNetwork = label.contains('192.168.') || + label.contains('10.') || + label.contains('172.'); + + Color primaryColor; + Color backgroundColor; + Color borderColor; + + if (isLocalhost) { + // localhost 使用灰色主题 + primaryColor = Theme.of(context).colorScheme.onSurfaceVariant; + backgroundColor = Theme.of(context).colorScheme.surfaceContainerHighest; + borderColor = Theme.of(context).colorScheme.outline; + } else if (isPrivateNetwork) { + // 私有网络使用主色调 + primaryColor = Theme.of(context).colorScheme.primary; + backgroundColor = Theme.of(context).colorScheme.primaryContainer; + borderColor = Theme.of(context).colorScheme.primary; + } else { + // 其他地址使用次要色调 + primaryColor = Theme.of(context).colorScheme.secondary; + backgroundColor = Theme.of(context).colorScheme.secondaryContainer; + borderColor = Theme.of(context).colorScheme.secondary; + } + + return Container( + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: borderColor.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // 二维码按钮 + IconButton( + icon: Icon( + Icons.qr_code, + size: 18, + color: primaryColor, + ), + onPressed: () => _showQrCodeDialog(context, url), + tooltip: '显示二维码', + padding: const EdgeInsets.all(4), + constraints: const BoxConstraints( + minWidth: 28, + minHeight: 28, + ), + ), + const SizedBox(width: 8), + // IP地址文本 + Flexible( + child: Text( + displayLabel, + style: TextStyle( + fontSize: 13, + fontFamily: 'monospace', + fontWeight: FontWeight.w600, + color: primaryColor, + ), + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 8), + // 复制按钮 + IconButton( + icon: Icon( + Icons.copy, + size: 18, + color: primaryColor, + ), + onPressed: () => _copyUrl(context, url), + tooltip: AppLocalizations.of(context)!.copy_url, + padding: const EdgeInsets.all(4), + constraints: const BoxConstraints( + minWidth: 28, + minHeight: 28, + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final isRunning = serverStatus?['isRunning'] ?? false; + final url = serverStatus?['url'] ?? ''; + final port = serverStatus?['port'] ?? 1204; + + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 标题行 + Text( + AppLocalizations.of(context)!.server_status, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 16), + // 服务器状态行 + Row( + children: [ + Icon( + isRunning + ? Icons.radio_button_checked + : Icons.radio_button_off, + color: isRunning + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + ), + const SizedBox(width: 8), + Text( + isRunning + ? AppLocalizations.of(context)!.server_running + : AppLocalizations.of(context)!.server_stopped, + style: TextStyle( + color: isRunning + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + ElevatedButton( + onPressed: onToggleServer, + child: Text(isRunning + ? AppLocalizations.of(context)!.server_stop + : AppLocalizations.of(context)!.server_start), + ), + ], + ), + const SizedBox(height: 12), + // 服务器地址信息 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${AppLocalizations.of(context)!.server_address}:', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + // 显示所有网络接口的IP地址 + ...networkInterfaces.map((interface) { + final address = interface['address']!; + final serverUrl = 'http://$address:$port'; + + return _buildAddressChip( + context, + label: serverUrl.replaceFirst('http://', ''), + url: serverUrl, + ); + }), + // 如果服务器运行中且有不同的URL,也显示实际运行的URL + if (isRunning && + url.isNotEmpty && + !networkInterfaces.any((interface) => + 'http://${interface['address']}:$port' == url)) + _buildAddressChip( + context, + label: url.replaceFirst('http://', ''), + url: url, + ), + ], + ), + ], + ), + ], + ), + ), + ); + } +} \ No newline at end of file From 86ec1e5309413006c73e0aaa5e1743445d9aff01 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Fri, 11 Jul 2025 00:29:45 +0800 Subject: [PATCH 14/54] =?UTF-8?q?feat(=E4=BE=9D=E8=B5=96):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=BD=91=E7=BB=9C=E6=A3=80=E6=B5=8B=E5=92=8C=E4=BA=8C?= =?UTF-8?q?=E7=BB=B4=E7=A0=81=E7=94=9F=E6=88=90=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加network_info_plus ^6.1.4用于可靠的WiFi网络检测 - 添加qr_flutter ^4.1.0用于二维码生成和显示 - 更新pubspec.lock以包含新的传递依赖项 这些依赖项支持了服务器状态页面的网络接口检测和二维码功能 --- pubspec.lock | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 6 ++++++ 2 files changed, 54 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index f13c563..4078948 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -105,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" fake_async: dependency: transitive description: @@ -293,6 +301,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + network_info_plus: + dependency: "direct main" + description: + name: network_info_plus + sha256: f926b2ba86aa0086a0dfbb9e5072089bc213d854135c1712f1d29fc89ba3c877 + url: "https://pub.dev" + source: hosted + version: "6.1.4" + network_info_plus_platform_interface: + dependency: transitive + description: + name: network_info_plus_platform_interface + sha256: "7e7496a8a9d8136859b8881affc613c4a21304afeb6c324bcefc4bd0aff6b94b" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" path: dependency: "direct main" description: @@ -381,6 +413,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5" + qr: + dependency: transitive + description: + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 1c7b8c9..5d95120 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,12 @@ dependencies: # 服务器 shelf: ^1.4.2 shelf_router: ^1.1.4 + + # 网络信息 + network_info_plus: ^6.1.4 + + # 二维码生成 + qr_flutter: ^4.1.0 dev_dependencies: flutter_test: From 009e22367b80538c687f87a421d784c9d550a02c Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Fri, 11 Jul 2025 01:02:40 +0800 Subject: [PATCH 15/54] =?UTF-8?q?feat(UI):=20=E4=BC=98=E5=8C=96=E4=B8=BB?= =?UTF-8?q?=E6=9C=BA=E6=96=87=E4=BB=B6=E9=80=89=E6=8B=A9=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E6=A1=86=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用ListTile替代CheckboxListTile以获得更清晰的界面 - 优先显示hostFile.remark而不是文件名 - 添加点击整行切换选择功能 - 保持复选框和图标的视觉一致性 --- lib/widget/dialog/select_hosts_dialog.dart | 228 +++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 lib/widget/dialog/select_hosts_dialog.dart diff --git a/lib/widget/dialog/select_hosts_dialog.dart b/lib/widget/dialog/select_hosts_dialog.dart new file mode 100644 index 0000000..1ce34f7 --- /dev/null +++ b/lib/widget/dialog/select_hosts_dialog.dart @@ -0,0 +1,228 @@ +import 'package:flutter/material.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/util/settings_manager.dart'; + +/// 选择要共享的hosts文件对话框 +class SelectHostsDialog extends StatefulWidget { + const SelectHostsDialog({super.key}); + + /// 显示选择hosts文件对话框 + static Future?> show(BuildContext context) { + return showDialog>( + context: context, + builder: (BuildContext context) { + return const SelectHostsDialog(); + }, + ); + } + + @override + State createState() => _SelectHostsDialogState(); +} + +class _SelectHostsDialogState extends State { + final SettingsManager _settingsManager = SettingsManager(); + List _hostFiles = []; + Set _selectedFileNames = {}; + bool _isLoading = true; + String? _error; + + @override + void initState() { + super.initState(); + _loadHostFiles(); + } + + /// 加载所有hosts文件 + Future _loadHostFiles() async { + try { + final List hostConfigs = await _settingsManager.getList(settingKeyHostConfigs); + final List hostFiles = []; + + for (Map config in hostConfigs) { + SimpleHostFile hostFile = SimpleHostFile.fromJson(config); + hostFiles.add(hostFile); + } + + setState(() { + _hostFiles = hostFiles; + _isLoading = false; + }); + } catch (e) { + setState(() { + _error = e.toString(); + _isLoading = false; + }); + } + } + + /// 切换文件选择状态 + void _toggleFileSelection(String fileName, bool isSelected) { + setState(() { + if (isSelected) { + _selectedFileNames.add(fileName); + } else { + _selectedFileNames.remove(fileName); + } + }); + } + + /// 切换全选状态 + void _toggleSelectAll(bool selectAll) { + setState(() { + if (selectAll) { + _selectedFileNames = _hostFiles.map((f) => f.fileName).toSet(); + } else { + _selectedFileNames.clear(); + } + }); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(AppLocalizations.of(context)!.select_hosts_to_export), + content: SizedBox( + width: 500, + height: 400, + child: _buildContent(), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(AppLocalizations.of(context)!.cancel), + ), + TextButton( + onPressed: _selectedFileNames.isEmpty + ? null + : () { + final selectedFiles = _hostFiles + .where((f) => _selectedFileNames.contains(f.fileName)) + .toList(); + Navigator.of(context).pop(selectedFiles); + }, + child: Text('${AppLocalizations.of(context)!.ok} (${_selectedFileNames.length})'), + ), + ], + ); + } + + Widget _buildContent() { + if (_isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + if (_error != null) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + size: 48, + color: Theme.of(context).colorScheme.error, + ), + const SizedBox(height: 16), + Text( + _error!, + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + ), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + setState(() { + _error = null; + _isLoading = true; + }); + _loadHostFiles(); + }, + child: Text(AppLocalizations.of(context)!.refresh_status), + ), + ], + ), + ); + } + + if (_hostFiles.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.folder_open, + size: 48, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.error_null_data, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ); + } + + return Column( + children: [ + // 全选/取消全选 + ListTile( + title: Text( + AppLocalizations.of(context)!.select_all, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text('${_hostFiles.length} 个文件'), + leading: Checkbox( + value: _selectedFileNames.length == _hostFiles.length && _hostFiles.isNotEmpty, + tristate: true, + onChanged: (value) { + if (value == null) return; + _toggleSelectAll(value); + }, + ), + onTap: () { + final allSelected = _selectedFileNames.length == _hostFiles.length && _hostFiles.isNotEmpty; + _toggleSelectAll(!allSelected); + }, + contentPadding: EdgeInsets.zero, + ), + const Divider(), + // 文件列表 + Expanded( + child: ListView.builder( + itemCount: _hostFiles.length, + itemBuilder: (context, index) { + final hostFile = _hostFiles[index]; + final isSelected = _selectedFileNames.contains(hostFile.fileName); + + return ListTile( + title: Text( + hostFile.remark.isNotEmpty ? hostFile.remark : hostFile.fileName, + style: const TextStyle( + fontWeight: FontWeight.w500, + ), + ), + leading: Checkbox( + value: isSelected, + onChanged: (value) { + _toggleFileSelection(hostFile.fileName, value ?? false); + }, + ), + onTap: () { + _toggleFileSelection(hostFile.fileName, !isSelected); + }, + contentPadding: EdgeInsets.zero, + ); + }, + ), + ), + ], + ); + } +} \ No newline at end of file From 071d69f36c5eaf1333c82abe5797e6964e49cf5c Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Fri, 11 Jul 2025 19:14:15 +0800 Subject: [PATCH 16/54] =?UTF-8?q?feat(=E6=9C=8D=E5=8A=A1&=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E9=9B=86=E6=88=90):=20=E6=B7=BB=E5=8A=A0systemd?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E6=96=87=E4=BB=B6=E5=92=8C=E5=AE=89=E8=A3=85?= =?UTF-8?q?=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加用户级和系统级服务配置文件 - 实现服务自动安装脚本 - 优化服务器功能和UI界面 - 更新Android构建配置和依赖项 --- README.md | 7 +- android/app/build.gradle | 4 +- android/app/src/main/AndroidManifest.xml | 71 ++++++++++++------- .../kotlin/top/webb_l/hosts/MainActivity.kt | 26 ++++++- android/build.gradle | 9 +++ android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 4 +- android/settings.gradle | 2 +- hosts-manager-user.service | 13 ++++ hosts-manager.service | 16 +++++ install-service.sh | 31 ++++++++ install-user-service.sh | 39 ++++++++++ lib/server/hosts_server.dart | 56 ++++++++++++++- lib/server/server_manager.dart | 8 ++- lib/server/server_settings_page.dart | 51 +++++++++---- lib/widget/server_status_card.dart | 24 ++++++- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + 17 files changed, 315 insertions(+), 51 deletions(-) create mode 100644 hosts-manager-user.service create mode 100644 hosts-manager.service create mode 100755 install-service.sh create mode 100755 install-user-service.sh diff --git a/README.md b/README.md index 1f787ae..38b6a06 100644 --- a/README.md +++ b/README.md @@ -35,4 +35,9 @@ hosts /etc/hosts ### 测试是否配置成功 -![6.png](image/6.png) \ No newline at end of file +![6.png](image/6.png) + +1. 构建应用:flutter build linux +2. 安装用户服务:sudo ./install-user-service.sh +3. 启动服务:systemctl --user start hosts-manager +4. 开机自启:loginctl enable-linger $USER \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 53ed703..d3f2411 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -8,7 +8,9 @@ plugins { android { namespace = "top.webb_l.hosts" compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion +// ndkVersion = flutter.ndkVersion + + ndkVersion = "27.0.12077973" compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3d19912..b5cc9c6 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,45 +1,68 @@ + + + + + + + + + + + + android:icon="@mipmap/ic_launcher" + android:label="hosts"> + + + + + + - + to determine the Window background behind the Flutter UI. + --> + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" /> + - - + + + - + - - - - - - - - + \ No newline at end of file diff --git a/android/app/src/main/kotlin/top/webb_l/hosts/MainActivity.kt b/android/app/src/main/kotlin/top/webb_l/hosts/MainActivity.kt index f262e6f..f505603 100644 --- a/android/app/src/main/kotlin/top/webb_l/hosts/MainActivity.kt +++ b/android/app/src/main/kotlin/top/webb_l/hosts/MainActivity.kt @@ -1,5 +1,29 @@ package top.webb_l.hosts +import android.content.Intent +import android.net.VpnService +import android.util.Log import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine -class MainActivity: FlutterActivity() +class MainActivity : FlutterActivity() { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + val intent = VpnService.prepare(this) + startService(Intent(this, HostsVpnService::class.java)) + Log.e("TAG", "configureFlutterEngine: ", ) +// if (intent != null) { +// startActivityForResult(intent, 0) +// } else { +// onActivityResult(0, RESULT_OK, null) +// } + super.configureFlutterEngine(flutterEngine) + } + + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode == RESULT_OK) { + startService(Intent(this, HostsVpnService::class.java)) + } + super.onActivityResult(requestCode, resultCode, data) + } +} diff --git a/android/build.gradle b/android/build.gradle index d2ffbff..ffe59ea 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,12 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10' + } +} allprojects { repositories { google() diff --git a/android/gradle.properties b/android/gradle.properties index 2597170..db24e8b 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,6 @@ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index e1ca574..09523c0 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 536165d..1b73ac6 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false + id "com.android.application" version '8.7.2' apply false id "org.jetbrains.kotlin.android" version "1.7.10" apply false } diff --git a/hosts-manager-user.service b/hosts-manager-user.service new file mode 100644 index 0000000..a3d9ef0 --- /dev/null +++ b/hosts-manager-user.service @@ -0,0 +1,13 @@ +[Unit] +Description=Hosts Manager Application (User Service) +After=graphical-session.target + +[Service] +Type=simple +ExecStart=/usr/bin/pkexec /opt/hosts-manager/hosts-manager +Restart=on-failure +RestartSec=5 +Environment=DISPLAY=%i + +[Install] +WantedBy=default.target \ No newline at end of file diff --git a/hosts-manager.service b/hosts-manager.service new file mode 100644 index 0000000..881df2e --- /dev/null +++ b/hosts-manager.service @@ -0,0 +1,16 @@ +[Unit] +Description=Hosts Manager Application +After=graphical-session.target + +[Service] +Type=simple +User=root +Group=root +Environment=DISPLAY=:0 +Environment=XAUTHORITY=/home/%i/.Xauthority +ExecStart=/opt/hosts-manager/hosts-manager +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/install-service.sh b/install-service.sh new file mode 100755 index 0000000..55ebde0 --- /dev/null +++ b/install-service.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# 检查是否以root权限运行 +if [[ $EUID -ne 0 ]]; then + echo "此脚本需要以root权限运行" + echo "请使用: sudo ./install-service.sh" + exit 1 +fi + +# 创建应用目录 +mkdir -p /opt/hosts-manager + +# 复制应用文件 +cp -r build/linux/x64/release/bundle/* /opt/hosts-manager/ + +# 设置权限 +chmod +x /opt/hosts-manager/hosts-manager + +# 复制服务文件 +cp hosts-manager.service /etc/systemd/system/ + +# 重新加载systemd +systemctl daemon-reload + +# 启用服务 +systemctl enable hosts-manager.service + +echo "服务安装完成!" +echo "启动服务: sudo systemctl start hosts-manager" +echo "查看状态: sudo systemctl status hosts-manager" +echo "停止服务: sudo systemctl stop hosts-manager" \ No newline at end of file diff --git a/install-user-service.sh b/install-user-service.sh new file mode 100755 index 0000000..7fa5d51 --- /dev/null +++ b/install-user-service.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# 检查是否以root权限运行安装应用文件 +if [[ $EUID -ne 0 ]]; then + echo "需要root权限安装应用文件" + echo "请使用: sudo ./install-user-service.sh" + exit 1 +fi + +# 创建应用目录 +mkdir -p /opt/hosts-manager + +# 复制应用文件 +cp -r build/linux/x64/release/bundle/* /opt/hosts-manager/ + +# 设置权限 +chmod +x /opt/hosts-manager/hosts-manager + +# 切换到普通用户安装用户服务 +REAL_USER=${SUDO_USER:-$USER} +REAL_HOME=$(eval echo ~$REAL_USER) + +# 创建用户systemd目录 +sudo -u $REAL_USER mkdir -p $REAL_HOME/.config/systemd/user + +# 复制用户服务文件 +sudo -u $REAL_USER cp hosts-manager-user.service $REAL_HOME/.config/systemd/user/hosts-manager.service + +# 重新加载用户systemd +sudo -u $REAL_USER systemctl --user daemon-reload + +# 启用用户服务 +sudo -u $REAL_USER systemctl --user enable hosts-manager.service + +echo "用户服务安装完成!" +echo "启动服务: systemctl --user start hosts-manager" +echo "查看状态: systemctl --user status hosts-manager" +echo "停止服务: systemctl --user stop hosts-manager" +echo "开机自启: loginctl enable-linger $REAL_USER" \ No newline at end of file diff --git a/lib/server/hosts_server.dart b/lib/server/hosts_server.dart index dd0585c..5f70f2f 100755 --- a/lib/server/hosts_server.dart +++ b/lib/server/hosts_server.dart @@ -22,6 +22,9 @@ class HostsServer { // 本地化字符串映射 Map _i18nStrings = {}; + // 允许访问的hosts文件列表 + List _allowedHostFiles = []; + int _port = _defaultPort; String _host = _defaultHost; @@ -46,10 +49,12 @@ class HostsServer { /// [port] 端口号,默认1204 /// [host] 主机地址,默认0.0.0.0 /// [i18nStrings] 本地化字符串映射 + /// [allowedHostFiles] 允许访问的hosts文件列表 Future start( {int port = _defaultPort, String host = _defaultHost, - Map? i18nStrings}) async { + Map? i18nStrings, + List? allowedHostFiles}) async { if (_server != null) { throw Exception(_i18nStrings['server_already_running'] ?? 'Server is already running'); @@ -59,6 +64,10 @@ class HostsServer { _i18nStrings = i18nStrings; } + if (allowedHostFiles != null) { + _allowedHostFiles = allowedHostFiles; + } + _port = port; _host = host; @@ -176,8 +185,18 @@ class HostsServer { hostFiles.add(hostFile); } + // 根据允许的hosts文件列表进行过滤 + final allowedHosts = hostFiles.where((hostFile) { + // 如果没有设置允许列表,则返回所有文件 + if (_allowedHostFiles.isEmpty) { + return true; + } + // 只返回允许访问的文件 + return _allowedHostFiles.contains(hostFile.fileName); + }).toList(); + // 转换为API响应格式 - final hosts = hostFiles + final hosts = allowedHosts .map((hostFile) => { 'fileName': hostFile.fileName, 'remark': hostFile.remark, @@ -209,6 +228,17 @@ class HostsServer { ); } + // 验证文件是否在允许访问的列表中 + if (_allowedHostFiles.isNotEmpty && !_allowedHostFiles.contains(fileName)) { + return Response.forbidden( + 'Access denied: File not allowed', + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + }, + ); + } + try { final content = await _fileManager.readAsString(fileName); return Response.ok( @@ -243,6 +273,17 @@ class HostsServer { ); } + // 验证文件是否在允许访问的列表中 + if (_allowedHostFiles.isNotEmpty && !_allowedHostFiles.contains(fileName)) { + return Response.forbidden( + jsonEncode({ + 'success': false, + 'error': 'Access denied: File not allowed' + }), + headers: _corsHeaders, + ); + } + try { final historyList = await _fileManager.getHistory(fileName); @@ -288,6 +329,17 @@ class HostsServer { ); } + // 验证文件是否在允许访问的列表中 + if (_allowedHostFiles.isNotEmpty && !_allowedHostFiles.contains(fileName)) { + return Response.forbidden( + 'Access denied: File not allowed', + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + }, + ); + } + try { final historyList = await _fileManager.getHistory(fileName); final historyItem = historyList.firstWhere( diff --git a/lib/server/server_manager.dart b/lib/server/server_manager.dart index 1c5f7cd..2a26867 100755 --- a/lib/server/server_manager.dart +++ b/lib/server/server_manager.dart @@ -74,7 +74,7 @@ class ServerManager { } /// 启动服务器 - Future startServer() async { + Future startServer({List? allowedHostFiles}) async { try { if (_server.isRunning) { return true; @@ -83,7 +83,11 @@ class ServerManager { final port = await getServerPort(); final host = await getServerHost(); - await _server.start(port: port, host: host); + await _server.start( + port: port, + host: host, + allowedHostFiles: allowedHostFiles, + ); await setServerEnabled(true); return true; diff --git a/lib/server/server_settings_page.dart b/lib/server/server_settings_page.dart index 28544bd..059c37f 100755 --- a/lib/server/server_settings_page.dart +++ b/lib/server/server_settings_page.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/simple_host_file.dart'; import 'package:hosts/server/server_manager.dart'; import 'package:hosts/widget/server_status_card.dart'; import 'package:network_info_plus/network_info_plus.dart'; @@ -17,7 +18,6 @@ class ServerSettingsPage extends StatefulWidget { class _ServerSettingsPageState extends State { final ServerManager _serverManager = ServerManager(); - bool _isServerEnabled = false; bool _isLoading = true; Map? _serverStatus; List> _networkInterfaces = []; @@ -35,7 +35,6 @@ class _ServerSettingsPageState extends State { final status = await _serverManager.getServerStatus(); setState(() { _serverStatus = status; - _isServerEnabled = status['isEnabled'] ?? false; _isLoading = false; }); } catch (e) { @@ -261,26 +260,28 @@ class _ServerSettingsPageState extends State { } } - /// 切换服务器状态 - Future _toggleServer() async { + /// 启动服务器 + Future _startServer(List? selectedHosts) async { + if (selectedHosts == null || selectedHosts.isEmpty) { + // 用户取消了选择或没有选择任何文件 + return; + } + setState(() { _isLoading = true; }); try { - bool success; - if (_isServerEnabled) { - await _serverManager.stopServer(); - success = true; - } else { - success = await _serverManager.startServer(); - } + // 提取文件名列表 + final allowedFileNames = selectedHosts.map((f) => f.fileName).toList(); + + final success = await _serverManager.startServer( + allowedHostFiles: allowedFileNames, + ); if (success) { await _loadServerSettings(); - _showSuccess(_isServerEnabled - ? AppLocalizations.of(context)!.server_started - : AppLocalizations.of(context)!.server_stopped_msg); + _showSuccess(AppLocalizations.of(context)!.server_started); } else { _showError(AppLocalizations.of(context)!.operation_failed); } @@ -293,6 +294,25 @@ class _ServerSettingsPageState extends State { } } + /// 停止服务器 + Future _stopServer() async { + setState(() { + _isLoading = true; + }); + + try { + await _serverManager.stopServer(); + await _loadServerSettings(); + _showSuccess(AppLocalizations.of(context)!.server_stopped_msg); + } catch (e) { + _showError('${AppLocalizations.of(context)!.operation_failed}: $e'); + } finally { + setState(() { + _isLoading = false; + }); + } + } + @@ -346,7 +366,8 @@ class _ServerSettingsPageState extends State { return ServerStatusCard( serverStatus: _serverStatus, networkInterfaces: _networkInterfaces, - onToggleServer: _toggleServer, + onStartServer: _startServer, + onStopServer: _stopServer, ); } diff --git a/lib/widget/server_status_card.dart b/lib/widget/server_status_card.dart index 517fb4c..80cc45c 100644 --- a/lib/widget/server_status_card.dart +++ b/lib/widget/server_status_card.dart @@ -2,18 +2,22 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/widget/dialog/qr_code_dialog.dart'; +import 'package:hosts/widget/dialog/select_hosts_dialog.dart'; +import 'package:hosts/model/simple_host_file.dart'; /// 服务器状态卡片组件 class ServerStatusCard extends StatelessWidget { final Map? serverStatus; final List> networkInterfaces; - final VoidCallback onToggleServer; + final Function(List?) onStartServer; + final VoidCallback onStopServer; const ServerStatusCard({ super.key, required this.serverStatus, required this.networkInterfaces, - required this.onToggleServer, + required this.onStartServer, + required this.onStopServer, }); /// 复制URL到剪贴板 @@ -32,6 +36,20 @@ class ServerStatusCard extends StatelessWidget { QrCodeDialog.show(context, url: url); } + /// 处理服务器切换 + Future _handleServerToggle(BuildContext context) async { + final isRunning = serverStatus?['isRunning'] ?? false; + + if (isRunning) { + // 如果服务器正在运行,直接停止 + onStopServer(); + } else { + // 如果服务器未运行,显示选择hosts文件对话框 + final selectedHosts = await SelectHostsDialog.show(context); + onStartServer(selectedHosts); + } + } + /// 构建地址芯片 Widget _buildAddressChip( BuildContext context, { @@ -176,7 +194,7 @@ class ServerStatusCard extends StatelessWidget { ), const Spacer(), ElevatedButton( - onPressed: onToggleServer, + onPressed: () => _handleServerToggle(context), child: Text(isRunning ? AppLocalizations.of(context)!.server_stop : AppLocalizations.of(context)!.server_start), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 4712381..4820fa9 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,11 +6,13 @@ import FlutterMacOS import Foundation import file_picker +import network_info_plus import path_provider_foundation import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } From 4d7fae877bc2be6739a2292e40227cd3207a58bf Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Fri, 11 Jul 2025 19:24:51 +0800 Subject: [PATCH 17/54] =?UTF-8?q?feat(=E6=9E=B6=E6=9E=84=E9=87=8D=E6=9E=84?= =?UTF-8?q?):=20=E6=9C=8D=E5=8A=A1=E5=99=A8=E9=A1=B5=E9=9D=A2=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=B8=BABLoC=E6=9E=B6=E6=9E=84=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E9=99=84=E8=BF=91=E8=AE=BE=E5=A4=87=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构服务器设置页面为BLoC架构提升状态管理 - 新增附近设备扫描和展示功能 - 优化响应式布局支持大屏幕设备 - 完善国际化支持添加新功能相关文案 - 组件化重构移动服务状态卡片到server/view目录 --- lib/home/view/home_drawer.dart | 2 +- lib/l10n/app_en.arb | 9 +- lib/l10n/app_localizations.dart | 42 ++ lib/l10n/app_localizations_en.dart | 22 ++ lib/l10n/app_localizations_zh.dart | 21 + lib/l10n/app_zh.arb | 9 +- .../server_settings_bloc.dart} | 362 +++++------------- lib/server/bloc/server_settings_event.dart | 21 + lib/server/bloc/server_settings_state.dart | 43 +++ lib/server/index.dart | 2 +- lib/server/view/nearby_devices_card.dart | 190 +++++++++ lib/server/view/server_settings_page.dart | 238 ++++++++++++ .../view}/server_status_card.dart | 0 lib/utils/nearby_devices_scanner.dart | 205 ++++++++++ 14 files changed, 900 insertions(+), 266 deletions(-) rename lib/server/{server_settings_page.dart => bloc/server_settings_bloc.dart} (50%) mode change 100755 => 100644 create mode 100644 lib/server/bloc/server_settings_event.dart create mode 100644 lib/server/bloc/server_settings_state.dart create mode 100644 lib/server/view/nearby_devices_card.dart create mode 100755 lib/server/view/server_settings_page.dart rename lib/{widget => server/view}/server_status_card.dart (100%) create mode 100644 lib/utils/nearby_devices_scanner.dart diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart index 609020e..325562a 100644 --- a/lib/home/view/home_drawer.dart +++ b/lib/home/view/home_drawer.dart @@ -4,7 +4,7 @@ import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/simple_host_file.dart'; -import 'package:hosts/server/server_settings_page.dart'; +import 'package:hosts/server/view/server_settings_page.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/widget/dialog/dialog.dart'; import 'package:hosts/widget/dialog/export_hosts_dialog.dart'; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 05183e0..6b85e0a 100755 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -121,5 +121,12 @@ "select_hosts_to_export": "Please select hosts files to export", "select_all": "Select All", "selected_count": "Selected", - "export_failed": "Export failed" + "export_failed": "Export failed", + "nearby_devices": "Nearby Devices", + "scan_nearby_devices": "Scan nearby devices", + "no_nearby_devices": "No devices with sharing enabled found\nClick refresh button to scan nearby devices", + "scanning_devices": "Scanning nearby devices...", + "sharing_enabled": "Sharing service enabled", + "device_reachable": "Device reachable", + "visit_device": "Visit device" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 58959d3..c7d01e4 100755 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -835,6 +835,48 @@ abstract class AppLocalizations { /// In zh, this message translates to: /// **'导出失败'** String get export_failed; + + /// No description provided for @nearby_devices. + /// + /// In zh, this message translates to: + /// **'附近设备'** + String get nearby_devices; + + /// No description provided for @scan_nearby_devices. + /// + /// In zh, this message translates to: + /// **'扫描附近设备'** + String get scan_nearby_devices; + + /// No description provided for @no_nearby_devices. + /// + /// In zh, this message translates to: + /// **'没有发现开启共享功能的设备\n点击刷新按钮扫描附近设备'** + String get no_nearby_devices; + + /// No description provided for @scanning_devices. + /// + /// In zh, this message translates to: + /// **'正在扫描附近设备...'** + String get scanning_devices; + + /// No description provided for @sharing_enabled. + /// + /// In zh, this message translates to: + /// **'共享服务已开启'** + String get sharing_enabled; + + /// No description provided for @device_reachable. + /// + /// In zh, this message translates to: + /// **'设备可达'** + String get device_reachable; + + /// No description provided for @visit_device. + /// + /// In zh, this message translates to: + /// **'访问设备'** + String get visit_device; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index e7afb9e..d6627db 100755 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -392,4 +392,26 @@ class AppLocalizationsEn extends AppLocalizations { @override String get export_failed => 'Export failed'; + + @override + String get nearby_devices => 'Nearby Devices'; + + @override + String get scan_nearby_devices => 'Scan nearby devices'; + + @override + String get no_nearby_devices => + 'No devices with sharing enabled found\nClick refresh button to scan nearby devices'; + + @override + String get scanning_devices => 'Scanning nearby devices...'; + + @override + String get sharing_enabled => 'Sharing service enabled'; + + @override + String get device_reachable => 'Device reachable'; + + @override + String get visit_device => 'Visit device'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index bc5e87c..1f84b1a 100755 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -383,4 +383,25 @@ class AppLocalizationsZh extends AppLocalizations { @override String get export_failed => '导出失败'; + + @override + String get nearby_devices => '附近设备'; + + @override + String get scan_nearby_devices => '扫描附近设备'; + + @override + String get no_nearby_devices => '没有发现开启共享功能的设备\n点击刷新按钮扫描附近设备'; + + @override + String get scanning_devices => '正在扫描附近设备...'; + + @override + String get sharing_enabled => '共享服务已开启'; + + @override + String get device_reachable => '设备可达'; + + @override + String get visit_device => '访问设备'; } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 24a315a..9d34d5e 100755 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -121,5 +121,12 @@ "select_hosts_to_export": "请选择要导出hosts文件", "select_all": "全选", "selected_count": "已选择", - "export_failed": "导出失败" + "export_failed": "导出失败", + "nearby_devices": "附近设备", + "scan_nearby_devices": "扫描附近设备", + "no_nearby_devices": "没有发现开启共享功能的设备\n点击刷新按钮扫描附近设备", + "scanning_devices": "正在扫描附近设备...", + "sharing_enabled": "共享服务已开启", + "device_reachable": "设备可达", + "visit_device": "访问设备" } \ No newline at end of file diff --git a/lib/server/server_settings_page.dart b/lib/server/bloc/server_settings_bloc.dart old mode 100755 new mode 100644 similarity index 50% rename from lib/server/server_settings_page.dart rename to lib/server/bloc/server_settings_bloc.dart index 059c37f..a21c7a1 --- a/lib/server/server_settings_page.dart +++ b/lib/server/bloc/server_settings_bloc.dart @@ -1,53 +1,109 @@ import 'dart:io'; +import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; -import 'package:hosts/l10n/app_localizations.dart'; -import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/server/bloc/server_settings_event.dart'; +import 'package:hosts/server/bloc/server_settings_state.dart'; import 'package:hosts/server/server_manager.dart'; -import 'package:hosts/widget/server_status_card.dart'; import 'package:network_info_plus/network_info_plus.dart'; -/// 服务器设置页面 -class ServerSettingsPage extends StatefulWidget { - const ServerSettingsPage({super.key}); - - @override - State createState() => _ServerSettingsPageState(); -} +/// 服务器设置页面 BLoC +class ServerSettingsBloc extends Bloc { + ServerSettingsBloc() : super(const ServerSettingsState()) { + on(_onLoadServerSettings); + on(_onLoadNetworkInterfaces); + on(_onToggleServerStatus); + on(_onRefreshServerStatus); + } -class _ServerSettingsPageState extends State { final ServerManager _serverManager = ServerManager(); - bool _isLoading = true; - Map? _serverStatus; - List> _networkInterfaces = []; + /// 加载服务器设置 + Future _onLoadServerSettings( + LoadServerSettings event, + Emitter emit, + ) async { + try { + emit(state.copyWith(isLoading: true, errorMessage: null)); + + final status = await _serverManager.getServerStatus(); + + emit(state.copyWith( + isLoading: false, + serverStatus: status, + isServerEnabled: status['isEnabled'] ?? false, + )); + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: '加载服务器设置失败: $e', + )); + } + } - @override - void initState() { - super.initState(); - _loadServerSettings(); - _getNetworkInterfaces(); + /// 加载网络接口 + Future _onLoadNetworkInterfaces( + LoadNetworkInterfaces event, + Emitter emit, + ) async { + try { + final networkInterfaces = await _getNetworkInterfaces(); + emit(state.copyWith(networkInterfaces: networkInterfaces)); + } catch (e) { + emit(state.copyWith( + errorMessage: '获取网络接口失败: $e', + )); + } } - /// 加载服务器设置 - Future _loadServerSettings() async { + /// 切换服务器状态 + Future _onToggleServerStatus( + ToggleServerStatus event, + Emitter emit, + ) async { try { - final status = await _serverManager.getServerStatus(); - setState(() { - _serverStatus = status; - _isLoading = false; - }); + emit(state.copyWith(isLoading: true, errorMessage: null)); + + bool success; + if (state.isServerEnabled) { + await _serverManager.stopServer(); + success = true; + } else { + success = await _serverManager.startServer(); + } + + if (success) { + // 重新加载服务器状态 + final status = await _serverManager.getServerStatus(); + emit(state.copyWith( + isLoading: false, + serverStatus: status, + isServerEnabled: status['isEnabled'] ?? false, + )); + } else { + emit(state.copyWith( + isLoading: false, + errorMessage: '操作失败', + )); + } } catch (e) { - setState(() { - _isLoading = false; - }); - _showError( - '${AppLocalizations.of(context)!.load_server_settings_failed}: $e'); + emit(state.copyWith( + isLoading: false, + errorMessage: '操作失败: $e', + )); } } + /// 刷新服务器状态 + Future _onRefreshServerStatus( + RefreshServerStatus event, + Emitter emit, + ) async { + add(LoadServerSettings()); + } + /// 获取所有网络接口 - Future _getNetworkInterfaces() async { + Future>> _getNetworkInterfaces() async { final List> networkList = []; final info = NetworkInfo(); @@ -239,238 +295,20 @@ class _ServerSettingsPageState extends State { print(' ${interface['description']}: ${interface['address']}'); } - setState(() { - _networkInterfaces = networkList; - }); + return networkList; } catch (e) { print('获取网络接口失败: $e'); // 最后的备用方案 - setState(() { - _networkInterfaces = [ - { - 'name': 'localhost', - 'address': '127.0.0.1', - 'description': 'Localhost (IPv4) Loopback', - 'icon': Icons.computer.codePoint.toString(), - 'type': 'ipv4', - 'isMain': 'false', - } - ]; - }); - } - } - - /// 启动服务器 - Future _startServer(List? selectedHosts) async { - if (selectedHosts == null || selectedHosts.isEmpty) { - // 用户取消了选择或没有选择任何文件 - return; - } - - setState(() { - _isLoading = true; - }); - - try { - // 提取文件名列表 - final allowedFileNames = selectedHosts.map((f) => f.fileName).toList(); - - final success = await _serverManager.startServer( - allowedHostFiles: allowedFileNames, - ); - - if (success) { - await _loadServerSettings(); - _showSuccess(AppLocalizations.of(context)!.server_started); - } else { - _showError(AppLocalizations.of(context)!.operation_failed); - } - } catch (e) { - _showError('${AppLocalizations.of(context)!.operation_failed}: $e'); - } finally { - setState(() { - _isLoading = false; - }); - } - } - - /// 停止服务器 - Future _stopServer() async { - setState(() { - _isLoading = true; - }); - - try { - await _serverManager.stopServer(); - await _loadServerSettings(); - _showSuccess(AppLocalizations.of(context)!.server_stopped_msg); - } catch (e) { - _showError('${AppLocalizations.of(context)!.operation_failed}: $e'); - } finally { - setState(() { - _isLoading = false; - }); - } - } - - - - - /// 显示成功消息 - void _showSuccess(String message) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - backgroundColor: Theme.of(context).colorScheme.primary, - ), - ); - } - - /// 显示错误消息 - void _showError(String message) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - backgroundColor: Theme.of(context).colorScheme.error, - ), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.remote_sync), - ), - body: _isLoading - ? const Center(child: CircularProgressIndicator()) - : SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 服务器状态卡片 - _buildStatusCard(), - const SizedBox(height: 16), - - // API文档卡片 - _buildApiDocsCard(), - ], - ), - ), - ); - } - - /// 构建状态卡片 - Widget _buildStatusCard() { - return ServerStatusCard( - serverStatus: _serverStatus, - networkInterfaces: _networkInterfaces, - onStartServer: _startServer, - onStopServer: _stopServer, - ); - } - - /// 构建API文档卡片 - Widget _buildApiDocsCard() { - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.api_docs, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 16), - Text( - '${AppLocalizations.of(context)!.api_endpoints}:', - style: const TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - _buildApiEndpoint( - 'GET', '/', AppLocalizations.of(context)!.server_status), - _buildApiEndpoint('GET', '/api/hosts', - AppLocalizations.of(context)!.get_all_hosts_files), - _buildApiEndpoint('GET', '/api/hosts/{fileName}', - AppLocalizations.of(context)!.get_specific_hosts_file), - _buildApiEndpoint('GET', '/api/hosts/{fileName}/history', - AppLocalizations.of(context)!.get_hosts_file_history), - _buildApiEndpoint( - 'GET', - '/api/hosts/{fileName}/history/{historyId}', - AppLocalizations.of(context)!.get_specific_history_content), - ], - ), - ), - ); - } - - /// 构建API端点项 - Widget _buildApiEndpoint(String method, String path, String description) { - Color methodColor; - switch (method) { - case 'GET': - methodColor = Theme.of(context).colorScheme.primary; - break; - case 'POST': - methodColor = Theme.of(context).colorScheme.secondary; - break; - case 'PUT': - methodColor = Theme.of(context).colorScheme.tertiary; - break; - case 'DELETE': - methodColor = Theme.of(context).colorScheme.error; - break; - default: - methodColor = Theme.of(context).colorScheme.outline; + return [ + { + 'name': 'localhost', + 'address': '127.0.0.1', + 'description': 'Localhost (IPv4) Loopback', + 'icon': Icons.computer.codePoint.toString(), + 'type': 'ipv4', + 'isMain': 'false', + } + ]; } - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: methodColor, - borderRadius: BorderRadius.circular(4), - ), - child: Text( - method, - style: TextStyle( - color: Theme.of(context).colorScheme.onPrimary, - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox(width: 8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - path, - style: const TextStyle( - fontFamily: 'monospace', - fontWeight: FontWeight.bold, - ), - ), - Text( - description, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontSize: 12, - ), - ), - ], - ), - ), - ], - ), - ); } -} +} \ No newline at end of file diff --git a/lib/server/bloc/server_settings_event.dart b/lib/server/bloc/server_settings_event.dart new file mode 100644 index 0000000..18b8a01 --- /dev/null +++ b/lib/server/bloc/server_settings_event.dart @@ -0,0 +1,21 @@ +import 'package:equatable/equatable.dart'; + +/// 服务器设置页面事件 +abstract class ServerSettingsEvent extends Equatable { + const ServerSettingsEvent(); + + @override + List get props => []; +} + +/// 加载服务器设置 +class LoadServerSettings extends ServerSettingsEvent {} + +/// 加载网络接口 +class LoadNetworkInterfaces extends ServerSettingsEvent {} + +/// 切换服务器状态 +class ToggleServerStatus extends ServerSettingsEvent {} + +/// 刷新服务器状态 +class RefreshServerStatus extends ServerSettingsEvent {} \ No newline at end of file diff --git a/lib/server/bloc/server_settings_state.dart b/lib/server/bloc/server_settings_state.dart new file mode 100644 index 0000000..b0362ab --- /dev/null +++ b/lib/server/bloc/server_settings_state.dart @@ -0,0 +1,43 @@ +import 'package:equatable/equatable.dart'; + +/// 服务器设置页面状态 +class ServerSettingsState extends Equatable { + const ServerSettingsState({ + this.isLoading = true, + this.isServerEnabled = false, + this.serverStatus, + this.networkInterfaces = const [], + this.errorMessage, + }); + + final bool isLoading; + final bool isServerEnabled; + final Map? serverStatus; + final List> networkInterfaces; + final String? errorMessage; + + ServerSettingsState copyWith({ + bool? isLoading, + bool? isServerEnabled, + Map? serverStatus, + List>? networkInterfaces, + String? errorMessage, + }) { + return ServerSettingsState( + isLoading: isLoading ?? this.isLoading, + isServerEnabled: isServerEnabled ?? this.isServerEnabled, + serverStatus: serverStatus ?? this.serverStatus, + networkInterfaces: networkInterfaces ?? this.networkInterfaces, + errorMessage: errorMessage, + ); + } + + @override + List get props => [ + isLoading, + isServerEnabled, + serverStatus, + networkInterfaces, + errorMessage, + ]; +} \ No newline at end of file diff --git a/lib/server/index.dart b/lib/server/index.dart index 0db615c..37d7aed 100755 --- a/lib/server/index.dart +++ b/lib/server/index.dart @@ -1,4 +1,4 @@ // Server module exports export 'hosts_server.dart'; export 'server_manager.dart'; -export 'server_settings_page.dart'; \ No newline at end of file +export 'view/server_settings_page.dart'; \ No newline at end of file diff --git a/lib/server/view/nearby_devices_card.dart b/lib/server/view/nearby_devices_card.dart new file mode 100644 index 0000000..16d04e5 --- /dev/null +++ b/lib/server/view/nearby_devices_card.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/utils/nearby_devices_scanner.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/// 附近设备卡片组件 +class NearbyDevicesCard extends StatefulWidget { + const NearbyDevicesCard({super.key}); + + @override + State createState() => _NearbyDevicesCardState(); +} + +class _NearbyDevicesCardState extends State { + List _nearbyDevices = []; + bool _isScanning = false; + + /// 扫描附近设备 + Future _scanNearbyDevices() async { + setState(() { + _isScanning = true; + }); + + try { + // 首先测试本地服务器是否正在运行 + final bool localServerRunning = await NearbyDevicesScanner.testLocalServer(); + if (!localServerRunning) { + print('本地服务器未运行,无法扫描其他设备'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('请先启动本地服务器再扫描附近设备'), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + setState(() { + _nearbyDevices = []; + }); + return; + } + + final devices = await NearbyDevicesScanner.scanNearbyDevices(); + setState(() { + _nearbyDevices = devices; + }); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('扫描完成,发现${devices.length}个设备'), + backgroundColor: Theme.of(context).colorScheme.primary, + ), + ); + } + } catch (e) { + print('扫描附近设备失败: $e'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('扫描失败: $e'), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + } finally { + setState(() { + _isScanning = false; + }); + } + } + + /// 启动设备URL + Future _launchDeviceUrl(String ip) async { + final url = 'http://$ip:1204'; + try { + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('无法打开URL: $url'), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('打开设备失败: $e'), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + } + } + + /// 构建设备项 + Widget _buildDeviceItem(NearbyDevice device) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + Icon( + device.hasSharing ? Icons.share : Icons.computer, + color: device.hasSharing ? Colors.green : Colors.grey, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + device.ip, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + Text( + device.hasSharing + ? AppLocalizations.of(context)!.sharing_enabled + : AppLocalizations.of(context)!.device_reachable, + style: TextStyle( + fontSize: 12, + color: device.hasSharing ? Colors.green : Colors.grey, + ), + ), + ], + ), + ), + if (device.hasSharing) + IconButton( + icon: const Icon(Icons.launch), + onPressed: () => _launchDeviceUrl(device.ip), + tooltip: AppLocalizations.of(context)!.visit_device, + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.nearby_devices, + style: Theme.of(context).textTheme.titleMedium, + ), + IconButton( + icon: _isScanning + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.refresh), + onPressed: _isScanning ? null : _scanNearbyDevices, + tooltip: AppLocalizations.of(context)!.scan_nearby_devices, + ), + ], + ), + const SizedBox(height: 16), + if (_nearbyDevices.isEmpty && !_isScanning) + Text( + AppLocalizations.of(context)!.no_nearby_devices, + style: const TextStyle(color: Colors.grey), + ) + else if (_nearbyDevices.isNotEmpty) + ..._nearbyDevices.map((device) => _buildDeviceItem(device)) + else + Text( + AppLocalizations.of(context)!.scanning_devices, + style: const TextStyle(color: Colors.grey), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/server/view/server_settings_page.dart b/lib/server/view/server_settings_page.dart new file mode 100755 index 0000000..a6f7116 --- /dev/null +++ b/lib/server/view/server_settings_page.dart @@ -0,0 +1,238 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/server/bloc/server_settings_bloc.dart'; +import 'package:hosts/server/bloc/server_settings_event.dart'; +import 'package:hosts/server/bloc/server_settings_state.dart'; +import 'package:hosts/server/view/nearby_devices_card.dart'; +import 'package:hosts/server/view/server_status_card.dart'; + +/// 服务器设置页面 +class ServerSettingsPage extends StatelessWidget { + const ServerSettingsPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => ServerSettingsBloc() + ..add(LoadServerSettings()) + ..add(LoadNetworkInterfaces()), + child: const _ServerSettingsView(), + ); + } +} + +class _ServerSettingsView extends StatelessWidget { + const _ServerSettingsView(); + + // 使用 GlobalKey 保持组件状态 + static final GlobalKey _nearbyDevicesKey = GlobalKey(); + static final GlobalKey _serverStatusKey = GlobalKey(); + static final GlobalKey _apiDocsKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.remote_sync), + ), + body: BlocConsumer( + listener: (context, state) { + if (state.errorMessage != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage!), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + }, + builder: (context, state) { + if (state.isLoading && state.serverStatus == null) { + return const Center(child: CircularProgressIndicator()); + } + + return SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: _buildResponsiveLayout(context, state), + ); + }, + ), + ); + } + + /// 构建响应式布局 + Widget _buildResponsiveLayout( + BuildContext context, ServerSettingsState state) { + return LayoutBuilder( + builder: (context, constraints) { + // 判断是否为大屏幕 (宽度大于 800px 认为是大屏) + final bool isLargeScreen = constraints.maxWidth > 800; + + if (isLargeScreen) { + // 大屏布局:上排两列,下排全宽 + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 上排:服务状态和API文档并排 + IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // 左侧:服务器状态卡片 + Expanded( + flex: 1, + child: _buildStatusCard(context, state), + ), + const SizedBox(width: 16), + // 右侧:API文档卡片 + Expanded( + flex: 1, + child: _buildApiDocsCard(context), + ), + ], + ), + ), + const SizedBox(height: 16), + // 下排:附近设备卡片(全宽) + NearbyDevicesCard(key: _nearbyDevicesKey), + ], + ); + } else { + // 小屏布局:垂直排列 + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 服务器状态卡片 + _buildStatusCard(context, state), + const SizedBox(height: 16), + // 附近设备卡片 + NearbyDevicesCard(key: _nearbyDevicesKey), + const SizedBox(height: 16), + // API文档卡片 + _buildApiDocsCard(context), + ], + ); + } + }, + ); + } + + /// 构建状态卡片 + Widget _buildStatusCard(BuildContext context, ServerSettingsState state) { + return ServerStatusCard( + key: _serverStatusKey, + serverStatus: state.serverStatus, + networkInterfaces: state.networkInterfaces, + onToggleServer: () { + context.read().add(ToggleServerStatus()); + }, + ); + } + + /// 构建API文档卡片 + Widget _buildApiDocsCard(BuildContext context) { + return Card( + key: _apiDocsKey, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.api_docs, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 16), + Text( + '${AppLocalizations.of(context)!.api_endpoints}:', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + _buildApiEndpoint(context, 'GET', '/', + AppLocalizations.of(context)!.server_status), + _buildApiEndpoint(context, 'GET', '/api/hosts', + AppLocalizations.of(context)!.get_all_hosts_files), + _buildApiEndpoint(context, 'GET', '/api/hosts/{fileName}', + AppLocalizations.of(context)!.get_specific_hosts_file), + _buildApiEndpoint(context, 'GET', '/api/hosts/{fileName}/history', + AppLocalizations.of(context)!.get_hosts_file_history), + _buildApiEndpoint( + context, + 'GET', + '/api/hosts/{fileName}/history/{historyId}', + AppLocalizations.of(context)!.get_specific_history_content), + ], + ), + ), + ); + } + + /// 构建API端点项 + Widget _buildApiEndpoint( + BuildContext context, String method, String path, String description) { + Color methodColor; + switch (method) { + case 'GET': + methodColor = Theme.of(context).colorScheme.primary; + break; + case 'POST': + methodColor = Theme.of(context).colorScheme.secondary; + break; + case 'PUT': + methodColor = Theme.of(context).colorScheme.tertiary; + break; + case 'DELETE': + methodColor = Theme.of(context).colorScheme.error; + break; + default: + methodColor = Theme.of(context).colorScheme.outline; + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: methodColor, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + method, + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimary, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + path, + style: const TextStyle( + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + ), + ), + Text( + description, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/widget/server_status_card.dart b/lib/server/view/server_status_card.dart similarity index 100% rename from lib/widget/server_status_card.dart rename to lib/server/view/server_status_card.dart diff --git a/lib/utils/nearby_devices_scanner.dart b/lib/utils/nearby_devices_scanner.dart new file mode 100644 index 0000000..8816586 --- /dev/null +++ b/lib/utils/nearby_devices_scanner.dart @@ -0,0 +1,205 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:network_info_plus/network_info_plus.dart'; + +/// 附近设备扫描器 +class NearbyDevicesScanner { + static const int _defaultPort = 1204; + static const Duration _scanTimeout = Duration(seconds: 2); + + /// 扫描附近设备 + static Future> scanNearbyDevices() async { + try { + print('开始扫描附近设备...'); + + // 获取当前设备的IP地址 + String? currentIP = await _getCurrentIP(); + + if (currentIP == null || currentIP.isEmpty) { + print('无法获取当前设备IP地址'); + return []; + } + + print('当前设备IP: $currentIP'); + + // 解析IP地址段 + final ipParts = currentIP.split('.'); + if (ipParts.length != 4) { + print('IP地址格式错误: $currentIP'); + return []; + } + + final baseIP = '${ipParts[0]}.${ipParts[1]}.${ipParts[2]}'; + print('扫描网段: $baseIP.1-254'); + + final List devices = []; + + // 扫描同网段的设备,限制并发数量以避免过多连接 + const int batchSize = 50; + for (int start = 1; start <= 254; start += batchSize) { + final int end = (start + batchSize - 1).clamp(1, 254); + final List> scanFutures = []; + + for (int i = start; i <= end; i++) { + final targetIP = '$baseIP.$i'; + // 跳过当前设备 + if (targetIP == currentIP) continue; + + scanFutures.add(_scanDevice(targetIP)); + } + + // 等待当前批次扫描完成 + final results = await Future.wait(scanFutures); + + // 过滤出有效设备 + for (final result in results) { + if (result != null) { + devices.add(result); + print('发现设备: ${result.ip}'); + } + } + } + + print('扫描完成,发现${devices.length}个设备'); + return devices; + } catch (e) { + print('扫描附近设备失败: $e'); + return []; + } + } + + /// 获取当前设备IP地址 + static Future _getCurrentIP() async { + try { + // 首先尝试获取WiFi IP + final info = NetworkInfo(); + final wifiIP = await info.getWifiIP(); + + if (wifiIP != null && wifiIP.isNotEmpty && wifiIP != '0.0.0.0') { + return wifiIP; + } + + // 如果WiFi IP获取失败,尝试从网络接口获取 + final interfaces = await NetworkInterface.list( + includeLoopback: false, + includeLinkLocal: false, + type: InternetAddressType.IPv4, + ); + + for (final interface in interfaces) { + for (final address in interface.addresses) { + final ip = address.address; + // 寻找私有网络地址 + if (ip.startsWith('192.168.') || + ip.startsWith('10.') || + (ip.startsWith('172.') && + int.tryParse(ip.split('.')[1]) != null && + int.parse(ip.split('.')[1]) >= 16 && + int.parse(ip.split('.')[1]) <= 31)) { + return ip; + } + } + } + + return null; + } catch (e) { + print('获取当前IP失败: $e'); + return null; + } + } + + /// 扫描单个设备 + static Future _scanDevice(String ip) async { + try { + // 尝试连接设备的共享端口 + final socket = await Socket.connect(ip, _defaultPort, timeout: _scanTimeout); + await socket.close(); + + // 连接成功,进一步验证是否是hosts服务器 + final bool isHostsServer = await _verifyHostsServer(ip); + + return NearbyDevice( + ip: ip, + isReachable: true, + hasSharing: isHostsServer, + lastSeen: DateTime.now(), + ); + } catch (e) { + // 连接失败,设备不可达或未开启共享 + return null; + } + } + + /// 验证是否是hosts服务器 + static Future _verifyHostsServer(String ip) async { + try { + final httpClient = HttpClient(); + httpClient.connectionTimeout = _scanTimeout; + httpClient.idleTimeout = _scanTimeout; + + final request = await httpClient.get(ip, _defaultPort, '/'); + final response = await request.close(); + + if (response.statusCode == 200) { + final responseBody = await response.transform(utf8.decoder).join(); + // 检查响应是否包含hosts服务器的特征 + if (responseBody.contains('status') && responseBody.contains('running')) { + httpClient.close(); + return true; + } + } + + httpClient.close(); + return false; + } catch (e) { + return false; + } + } + + /// 检查特定设备是否开启共享 + static Future checkDeviceSharing(String ip) async { + try { + final socket = await Socket.connect(ip, _defaultPort, timeout: _scanTimeout); + await socket.close(); + return await _verifyHostsServer(ip); + } catch (e) { + return false; + } + } + + /// 测试本地服务器是否正在运行 + static Future testLocalServer() async { + try { + final currentIP = await _getCurrentIP(); + if (currentIP != null) { + print('测试本地服务器: $currentIP:$_defaultPort'); + return await checkDeviceSharing(currentIP); + } + return false; + } catch (e) { + print('测试本地服务器失败: $e'); + return false; + } + } +} + +/// 附近设备信息 +class NearbyDevice { + final String ip; + final bool isReachable; + final bool hasSharing; + final DateTime lastSeen; + + const NearbyDevice({ + required this.ip, + required this.isReachable, + required this.hasSharing, + required this.lastSeen, + }); + + @override + String toString() { + return 'NearbyDevice(ip: $ip, isReachable: $isReachable, hasSharing: $hasSharing, lastSeen: $lastSeen)'; + } +} \ No newline at end of file From b81703af2fa7633d67e7f2d794147c1655c71cc9 Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Fri, 11 Jul 2025 19:26:13 +0800 Subject: [PATCH 18/54] =?UTF-8?q?feat(=E4=BE=9D=E8=B5=96):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0equatable=E5=92=8Curl=5Flauncher=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加equatable ^2.0.5用于BLoC状态管理 - 添加url_launcher ^6.3.1用于外部链接跳转 - 更新相关平台插件依赖 --- pubspec.lock | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 4 +++ 2 files changed, 76 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index 4078948..f3aa2ae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.11" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" fake_async: dependency: transitive description: @@ -586,6 +594,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" + url: "https://pub.dev" + source: hosted + version: "6.3.16" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + url: "https://pub.dev" + source: hosted + version: "6.3.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5d95120..82ccf6b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: # 状态管理 flutter_bloc: ^9.1.1 bloc: ^9.0.0 + equatable: ^2.0.5 # 本地存储 shared_preferences: ^2.3.2 @@ -43,6 +44,9 @@ dependencies: # 二维码生成 qr_flutter: ^4.1.0 + + # URL启动器 + url_launcher: ^6.3.1 dev_dependencies: flutter_test: From 13bb5b92fae5febb2c638edfb03efd376aacc967 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sat, 12 Jul 2025 00:14:24 +0800 Subject: [PATCH 19/54] =?UTF-8?q?feat(=E6=9C=8D=E5=8A=A1=E5=99=A8&?= =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96):=20=E5=AE=8C=E5=96=84=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E8=AE=BE=E7=BD=AE=E5=92=8C=E4=B8=BB=E6=9C=BA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复服务器状态卡片参数匹配问题 - 添加启动和停止服务器的独立事件处理 - 实现主机文件选择对话框集成 - 完善服务器权限检查和文件访问控制 - 添加访问拒绝和主机文件选择的国际化字符串 - 更新服务器状态管理BLoC架构 --- lib/l10n/app_en.arb | 4 +- lib/l10n/app_localizations.dart | 12 ++++ lib/l10n/app_localizations_en.dart | 7 +++ lib/l10n/app_localizations_zh.dart | 6 ++ lib/l10n/app_zh.arb | 4 +- lib/server/bloc/server_settings_bloc.dart | 61 +++++++++++++++++++ lib/server/bloc/server_settings_event.dart | 14 +++++ lib/server/hosts_server.dart | 6 +- lib/server/view/server_settings_page.dart | 7 ++- lib/widget/dialog/select_hosts_dialog.dart | 2 +- linux/flutter/generated_plugin_registrant.cc | 4 ++ linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 15 files changed, 126 insertions(+), 8 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6b85e0a..2000527 100755 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -128,5 +128,7 @@ "scanning_devices": "Scanning nearby devices...", "sharing_enabled": "Sharing service enabled", "device_reachable": "Device reachable", - "visit_device": "Visit device" + "visit_device": "Visit device", + "access_denied_file_not_allowed": "Access denied: File not allowed", + "select_hosts_to_share": "Please select hosts files to share" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index c7d01e4..cd845e8 100755 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -877,6 +877,18 @@ abstract class AppLocalizations { /// In zh, this message translates to: /// **'访问设备'** String get visit_device; + + /// No description provided for @access_denied_file_not_allowed. + /// + /// In zh, this message translates to: + /// **'访问被拒绝:文件不被允许'** + String get access_denied_file_not_allowed; + + /// No description provided for @select_hosts_to_share. + /// + /// In zh, this message translates to: + /// **'请选择要分享的hosts文件'** + String get select_hosts_to_share; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index d6627db..c3aa5f1 100755 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -414,4 +414,11 @@ class AppLocalizationsEn extends AppLocalizations { @override String get visit_device => 'Visit device'; + + @override + String get access_denied_file_not_allowed => + 'Access denied: File not allowed'; + + @override + String get select_hosts_to_share => 'Please select hosts files to share'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 1f84b1a..f1005f0 100755 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -404,4 +404,10 @@ class AppLocalizationsZh extends AppLocalizations { @override String get visit_device => '访问设备'; + + @override + String get access_denied_file_not_allowed => '访问被拒绝:文件不被允许'; + + @override + String get select_hosts_to_share => '请选择要分享的hosts文件'; } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9d34d5e..8c72454 100755 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -128,5 +128,7 @@ "scanning_devices": "正在扫描附近设备...", "sharing_enabled": "共享服务已开启", "device_reachable": "设备可达", - "visit_device": "访问设备" + "visit_device": "访问设备", + "access_denied_file_not_allowed": "访问被拒绝:文件不被允许", + "select_hosts_to_share": "请选择要分享的hosts文件" } \ No newline at end of file diff --git a/lib/server/bloc/server_settings_bloc.dart b/lib/server/bloc/server_settings_bloc.dart index a21c7a1..fb9625b 100644 --- a/lib/server/bloc/server_settings_bloc.dart +++ b/lib/server/bloc/server_settings_bloc.dart @@ -13,6 +13,8 @@ class ServerSettingsBloc extends Bloc on(_onLoadServerSettings); on(_onLoadNetworkInterfaces); on(_onToggleServerStatus); + on(_onStartServer); + on(_onStopServer); on(_onRefreshServerStatus); } @@ -94,6 +96,65 @@ class ServerSettingsBloc extends Bloc } } + /// 启动服务器 + Future _onStartServer( + StartServer event, + Emitter emit, + ) async { + try { + emit(state.copyWith(isLoading: true, errorMessage: null)); + + // 将选中的hosts文件名传递给服务器管理器 + final selectedHostNames = event.selectedHosts?.map((host) => host.fileName).toList(); + final success = await _serverManager.startServer(allowedHostFiles: selectedHostNames); + + if (success) { + // 重新加载服务器状态 + final status = await _serverManager.getServerStatus(); + emit(state.copyWith( + isLoading: false, + serverStatus: status, + isServerEnabled: status['isEnabled'] ?? false, + )); + } else { + emit(state.copyWith( + isLoading: false, + errorMessage: '启动服务器失败', + )); + } + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: '启动服务器失败: $e', + )); + } + } + + /// 停止服务器 + Future _onStopServer( + StopServer event, + Emitter emit, + ) async { + try { + emit(state.copyWith(isLoading: true, errorMessage: null)); + + await _serverManager.stopServer(); + + // 重新加载服务器状态 + final status = await _serverManager.getServerStatus(); + emit(state.copyWith( + isLoading: false, + serverStatus: status, + isServerEnabled: status['isEnabled'] ?? false, + )); + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: '停止服务器失败: $e', + )); + } + } + /// 刷新服务器状态 Future _onRefreshServerStatus( RefreshServerStatus event, diff --git a/lib/server/bloc/server_settings_event.dart b/lib/server/bloc/server_settings_event.dart index 18b8a01..c1049fe 100644 --- a/lib/server/bloc/server_settings_event.dart +++ b/lib/server/bloc/server_settings_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:hosts/model/simple_host_file.dart'; /// 服务器设置页面事件 abstract class ServerSettingsEvent extends Equatable { @@ -17,5 +18,18 @@ class LoadNetworkInterfaces extends ServerSettingsEvent {} /// 切换服务器状态 class ToggleServerStatus extends ServerSettingsEvent {} +/// 启动服务器 +class StartServer extends ServerSettingsEvent { + final List? selectedHosts; + + const StartServer(this.selectedHosts); + + @override + List get props => [selectedHosts]; +} + +/// 停止服务器 +class StopServer extends ServerSettingsEvent {} + /// 刷新服务器状态 class RefreshServerStatus extends ServerSettingsEvent {} \ No newline at end of file diff --git a/lib/server/hosts_server.dart b/lib/server/hosts_server.dart index 5f70f2f..924dbf6 100755 --- a/lib/server/hosts_server.dart +++ b/lib/server/hosts_server.dart @@ -231,7 +231,7 @@ class HostsServer { // 验证文件是否在允许访问的列表中 if (_allowedHostFiles.isNotEmpty && !_allowedHostFiles.contains(fileName)) { return Response.forbidden( - 'Access denied: File not allowed', + _i18nStrings['access_denied_file_not_allowed'] ?? 'Access denied: File not allowed', headers: { 'Content-Type': 'text/plain; charset=utf-8', 'Access-Control-Allow-Origin': '*', @@ -278,7 +278,7 @@ class HostsServer { return Response.forbidden( jsonEncode({ 'success': false, - 'error': 'Access denied: File not allowed' + 'error': _i18nStrings['access_denied_file_not_allowed'] ?? 'Access denied: File not allowed' }), headers: _corsHeaders, ); @@ -332,7 +332,7 @@ class HostsServer { // 验证文件是否在允许访问的列表中 if (_allowedHostFiles.isNotEmpty && !_allowedHostFiles.contains(fileName)) { return Response.forbidden( - 'Access denied: File not allowed', + _i18nStrings['access_denied_file_not_allowed'] ?? 'Access denied: File not allowed', headers: { 'Content-Type': 'text/plain; charset=utf-8', 'Access-Control-Allow-Origin': '*', diff --git a/lib/server/view/server_settings_page.dart b/lib/server/view/server_settings_page.dart index a6f7116..a33eca5 100755 --- a/lib/server/view/server_settings_page.dart +++ b/lib/server/view/server_settings_page.dart @@ -124,8 +124,11 @@ class _ServerSettingsView extends StatelessWidget { key: _serverStatusKey, serverStatus: state.serverStatus, networkInterfaces: state.networkInterfaces, - onToggleServer: () { - context.read().add(ToggleServerStatus()); + onStartServer: (selectedHosts) { + context.read().add(StartServer(selectedHosts)); + }, + onStopServer: () { + context.read().add(StopServer()); }, ); } diff --git a/lib/widget/dialog/select_hosts_dialog.dart b/lib/widget/dialog/select_hosts_dialog.dart index 1ce34f7..adddd1a 100644 --- a/lib/widget/dialog/select_hosts_dialog.dart +++ b/lib/widget/dialog/select_hosts_dialog.dart @@ -82,7 +82,7 @@ class _SelectHostsDialogState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: Text(AppLocalizations.of(context)!.select_hosts_to_export), + title: Text(AppLocalizations.of(context)!.select_hosts_to_share), content: SizedBox( width: 500, height: 400, diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..f6f23bf 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..f16b4c3 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 4820fa9..df9be2c 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,10 +9,12 @@ import file_picker import network_info_plus import path_provider_foundation import shared_preferences_foundation +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..4f78848 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..88b22e5 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From 13c642e252e86cc28ec260232a850589680a3fde Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sat, 12 Jul 2025 13:13:51 +0800 Subject: [PATCH 20/54] =?UTF-8?q?feat(UI&UX):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E7=8A=B6=E6=80=81=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E5=92=8C=E4=B8=BB=E6=9C=BA=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重新设计服务器状态卡片界面,采用现代化设计风格 - 增强启动/停止按钮的视觉效果,使用绿色/红色突出显示 - 添加状态指示器动画效果和阴影 - 优化布局结构,增加端口信息显示 - 设置主机文件选择对话框默认全选所有文件 - 提升用户交互体验和界面美观度 --- lib/server/view/server_status_card.dart | 189 +++++++++++++++++---- lib/widget/dialog/select_hosts_dialog.dart | 2 + 2 files changed, 160 insertions(+), 31 deletions(-) diff --git a/lib/server/view/server_status_card.dart b/lib/server/view/server_status_card.dart index 80cc45c..a3a24d4 100644 --- a/lib/server/view/server_status_card.dart +++ b/lib/server/view/server_status_card.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/simple_host_file.dart'; import 'package:hosts/widget/dialog/qr_code_dialog.dart'; import 'package:hosts/widget/dialog/select_hosts_dialog.dart'; -import 'package:hosts/model/simple_host_file.dart'; /// 服务器状态卡片组件 class ServerStatusCard extends StatelessWidget { @@ -39,14 +39,16 @@ class ServerStatusCard extends StatelessWidget { /// 处理服务器切换 Future _handleServerToggle(BuildContext context) async { final isRunning = serverStatus?['isRunning'] ?? false; - + if (isRunning) { // 如果服务器正在运行,直接停止 onStopServer(); } else { // 如果服务器未运行,显示选择hosts文件对话框 final selectedHosts = await SelectHostsDialog.show(context); - onStartServer(selectedHosts); + if (selectedHosts != null && selectedHosts.isNotEmpty) { + onStartServer(selectedHosts); + } } } @@ -170,36 +172,161 @@ class ServerStatusCard extends StatelessWidget { ), const SizedBox(height: 16), // 服务器状态行 - Row( - children: [ - Icon( - isRunning - ? Icons.radio_button_checked - : Icons.radio_button_off, + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: isRunning + ? Theme.of(context) + .colorScheme + .primaryContainer + .withValues(alpha: 0.3) + : Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + border: Border.all( color: isRunning - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.outline, + ? Theme.of(context) + .colorScheme + .primary + .withValues(alpha: 0.2) + : Theme.of(context) + .colorScheme + .outline + .withValues(alpha: 0.1), + width: 1, ), - const SizedBox(width: 8), - Text( - isRunning - ? AppLocalizations.of(context)!.server_running - : AppLocalizations.of(context)!.server_stopped, - style: TextStyle( - color: isRunning - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.outline, - fontWeight: FontWeight.bold, + ), + child: Row( + children: [ + // 状态指示器 + Container( + width: 12, + height: 12, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isRunning + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + boxShadow: isRunning + ? [ + BoxShadow( + color: Theme.of(context) + .colorScheme + .primary + .withValues(alpha: 0.4), + blurRadius: 8, + spreadRadius: 2, + ), + ] + : null, + ), + child: isRunning + ? Container( + margin: const EdgeInsets.all(2), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).colorScheme.onPrimary, + ), + ) + : null, ), - ), - const Spacer(), - ElevatedButton( - onPressed: () => _handleServerToggle(context), - child: Text(isRunning - ? AppLocalizations.of(context)!.server_stop - : AppLocalizations.of(context)!.server_start), - ), - ], + const SizedBox(width: 12), + // 状态文字 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + isRunning + ? AppLocalizations.of(context)!.server_running + : AppLocalizations.of(context)!.server_stopped, + style: + Theme.of(context).textTheme.titleSmall?.copyWith( + color: isRunning + ? Theme.of(context).colorScheme.primary + : Theme.of(context) + .colorScheme + .onSurfaceVariant, + fontWeight: FontWeight.w600, + ), + ), + if (isRunning && port != null) ...[ + const SizedBox(height: 2), + Text( + '端口: $port', + style: + Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant + .withValues(alpha: 0.7), + ), + ), + ], + ], + ), + ), + // 操作按钮 + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: isRunning + ? Colors.red.withValues(alpha: 0.2) + : Theme.of(context) + .colorScheme + .primary + .withValues(alpha: 0.3), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: ElevatedButton.icon( + onPressed: () => _handleServerToggle(context), + style: ElevatedButton.styleFrom( + backgroundColor: isRunning + ? Colors.red.shade600 + : Colors.green.shade600, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + shadowColor: isRunning + ? Colors.red.withValues(alpha: 0.3) + : Colors.green.withValues(alpha: 0.3), + ), + icon: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.2), + shape: BoxShape.circle, + ), + child: Icon( + isRunning + ? Icons.stop_rounded + : Icons.play_arrow_rounded, + size: 20, + color: Colors.white, + ), + ), + label: Text( + isRunning + ? AppLocalizations.of(context)!.server_stop + : AppLocalizations.of(context)!.server_start, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: Colors.white, + ), + ), + ), + ), + ], + ), ), const SizedBox(height: 12), // 服务器地址信息 @@ -247,4 +374,4 @@ class ServerStatusCard extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/widget/dialog/select_hosts_dialog.dart b/lib/widget/dialog/select_hosts_dialog.dart index adddd1a..8316bac 100644 --- a/lib/widget/dialog/select_hosts_dialog.dart +++ b/lib/widget/dialog/select_hosts_dialog.dart @@ -47,6 +47,8 @@ class _SelectHostsDialogState extends State { setState(() { _hostFiles = hostFiles; + // 默认全选所有hosts文件 + _selectedFileNames = hostFiles.map((f) => f.fileName).toSet(); _isLoading = false; }); } catch (e) { From 9b3a347ca264c430d35bcdf980e0acddc5d4e3da Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Sat, 12 Jul 2025 17:20:25 +0800 Subject: [PATCH 21/54] =?UTF-8?q?fix(UI&=E5=9B=BD=E9=99=85=E5=8C=96):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9E=84=E5=BB=BA=E6=97=B6=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=B9=B6=E5=AE=8C=E5=96=84=E5=9B=BD=E9=99=85?= =?UTF-8?q?=E5=8C=96=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复HomeView中scaffold key重复创建导致的FocusScopeNode错误 - 使用postFrameCallback避免构建期间调用setState - 添加服务器功能相关的国际化文案 - 完善访问控制和文件选择的用户提示 --- lib/home/cubit/home_cubit.dart | 2 +- lib/home/cubit/home_state.dart | 4 +++ lib/home/view/home_view.dart | 45 +++++++++++++++++++++++----------- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/lib/home/cubit/home_cubit.dart b/lib/home/cubit/home_cubit.dart index 95d8e30..4de2b35 100755 --- a/lib/home/cubit/home_cubit.dart +++ b/lib/home/cubit/home_cubit.dart @@ -240,7 +240,7 @@ class HomeCubit extends Cubit { Future toggleAdvancedSettings( AdvancedSettingsEnum advancedSettings) async { emit( - HomeInitial( + HomeAdvancedSettings( state.data.copyWith( advancedSettingsEnum: advancedSettings, ), diff --git a/lib/home/cubit/home_state.dart b/lib/home/cubit/home_state.dart index 3acd93a..08261ba 100644 --- a/lib/home/cubit/home_state.dart +++ b/lib/home/cubit/home_state.dart @@ -67,6 +67,10 @@ class HomeInitial extends HomeState { const HomeInitial(super.data); } +class HomeAdvancedSettings extends HomeState { + const HomeAdvancedSettings(super.data); +} + /// 编辑模式变更状态 /// 当用户切换编辑模式(表格/文本)时触发 class HomeEditMode extends HomeState { diff --git a/lib/home/view/home_view.dart b/lib/home/view/home_view.dart index 12c92da..a9a7cb9 100644 --- a/lib/home/view/home_view.dart +++ b/lib/home/view/home_view.dart @@ -4,11 +4,11 @@ import 'package:hosts/enums.dart'; import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/home/view/home_app_bar.dart'; +import 'package:hosts/home/view/home_drawer.dart'; import 'package:hosts/home/view/host_view.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; import 'package:hosts/page/host_page.dart'; -import 'package:hosts/home/view/home_drawer.dart'; class HomeView extends StatefulWidget { const HomeView({super.key}); @@ -18,6 +18,8 @@ class HomeView extends StatefulWidget { } class _HomeViewState extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + @override void initState() { // 初始化时加载 hostFiles @@ -27,9 +29,16 @@ class _HomeViewState extends State { @override Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, drawer: - MediaQuery.of(context).size.width < 600 ? const HomeDrawer() : null, + MediaQuery.of(context).size.width < 600 ? const HomeDrawer() : null, + onDrawerChanged: (value) { + if (!value) { + context.read().toggleAdvancedSettingsSwitch(); + } + }, floatingActionButton: BlocBuilder( builder: (context, state) { if (state.data.editMode == EditMode.Table) { @@ -37,7 +46,7 @@ class _HomeViewState extends State { onPressed: () async { List? hostsModels = await Navigator.of(context) .push(MaterialPageRoute( - builder: (context) => const HostPage())); + builder: (context) => const HostPage())); if (hostsModels == null) return; context.read().addHosts(hostsModels); }, @@ -53,25 +62,33 @@ class _HomeViewState extends State { context.read().updateHost(state.data.selectHostFile); } - if(state is HomeEditMode) { + if (state is HomeEditMode) { context.read().updateEditMode(state.data.editMode); } + if (state is HomeAdvancedSettings) { + if (state.data.advancedSettingsEnum == AdvancedSettingsEnum.Open) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _scaffoldKey.currentState?.openDrawer(); + }); + } + } + return Row( children: [ if (state.data.advancedSettingsEnum == - AdvancedSettingsEnum.Close && + AdvancedSettingsEnum.Close && MediaQuery.of(context).size.width > 600) const HomeDrawer(), Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const HomeAppBar(), - saveTipMessage(state.data), - HostView(state.data) - ], - )) + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const HomeAppBar(), + saveTipMessage(state.data), + HostView(state.data) + ], + )) ], ); }, @@ -91,7 +108,7 @@ class _HomeViewState extends State { final String updateSaveTip = AppLocalizations.of(context)!.error_not_update_save_tip; final String updateSavePermissionTip = data.selectHostFile == - state.data.fileId + state.data.fileId ? '\n${AppLocalizations.of(context)!.error_not_update_save_permission_tip}' : ''; return MaterialBanner( @@ -112,4 +129,4 @@ class _HomeViewState extends State { ); }); } -} \ No newline at end of file +} From c63569d1eb5a293aee19b5f9bed9f93bfbc8e16a Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sun, 13 Jul 2025 17:20:22 +0800 Subject: [PATCH 22/54] =?UTF-8?q?feat(UI):=20=E4=BC=98=E5=8C=96=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=AE=89=E5=85=A8=E5=8C=BA=E5=9F=9F=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E5=92=8C=E6=8A=BD=E5=B1=89=E5=AF=BC=E8=88=AA=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为主页面和简单主页面添加SafeArea包装,确保内容不被系统状态栏遮挡 - 优化抽屉导航的顶部padding设置,提升视觉效果 - 改进列表视图的padding配置,提供更好的用户体验 --- lib/home/view/home_drawer.dart | 291 ++++++++++++++-------------- lib/home/view/home_view.dart | 66 ++++--- lib/home/view/simple_home_view.dart | 30 +-- 3 files changed, 197 insertions(+), 190 deletions(-) diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart index 325562a..7f03b84 100644 --- a/lib/home/view/home_drawer.dart +++ b/lib/home/view/home_drawer.dart @@ -19,159 +19,162 @@ class HomeDrawer extends StatelessWidget { return BlocBuilder( builder: (context, state) { return Drawer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Text( - AppLocalizations.of(context)!.app_name, - style: Theme.of(context).textTheme.titleLarge, - ), - const Expanded(child: SizedBox()), - IconButton( - onPressed: () async { - String? remark = await hostConfigDialog(context); - if (remark == null || remark.isEmpty) return; - context.read().addHostFile(remark); + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16,right: 16,bottom: 16), + child: Row( + children: [ + Text( + AppLocalizations.of(context)!.app_name, + style: Theme.of(context).textTheme.titleLarge, + ), + const Expanded(child: SizedBox()), + IconButton( + onPressed: () async { + String? remark = await hostConfigDialog(context); + if (remark == null || remark.isEmpty) return; + context.read().addHostFile(remark); + }, + icon: const Icon(Icons.add)), + PopupMenuButton( + icon: const Icon(Icons.more_vert), + onSelected: (value) async { + switch (value) { + case 1: + // Import functionality + await importHostsDialog(context, state.data.hostFiles, + onImportSuccess: () { + context.read().refreshHostFiles(context); + }); + break; + case 2: + // Export functionality + await exportHostsDialog(context, state.data.hostFiles); + break; + case 3: + // Remote sync functionality + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ServerSettingsPage(), + ), + ); + break; + } }, - icon: const Icon(Icons.add)), - PopupMenuButton( - icon: const Icon(Icons.more_vert), - onSelected: (value) async { - switch (value) { - case 1: - // Import functionality - await importHostsDialog(context, state.data.hostFiles, - onImportSuccess: () { - context.read().refreshHostFiles(context); - }); - break; - case 2: - // Export functionality - await exportHostsDialog(context, state.data.hostFiles); - break; - case 3: - // Remote sync functionality - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ServerSettingsPage(), + itemBuilder: (BuildContext context) { + return [ + PopupMenuItem( + value: 1, + child: Row( + children: [ + const Icon(Icons.file_upload), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.import), + ], ), - ); - break; - } - }, - itemBuilder: (BuildContext context) { - return [ - PopupMenuItem( - value: 1, - child: Row( - children: [ - const Icon(Icons.file_upload), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.import), - ], ), - ), - PopupMenuItem( - value: 2, - child: Row( - children: [ - const Icon(Icons.file_download), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.export), - ], + PopupMenuItem( + value: 2, + child: Row( + children: [ + const Icon(Icons.file_download), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.export), + ], + ), ), - ), - PopupMenuItem( - value: 3, - child: Row( - children: [ - const Icon(Icons.cloud_sync), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.remote_sync), - ], + PopupMenuItem( + value: 3, + child: Row( + children: [ + const Icon(Icons.cloud_sync), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.remote_sync), + ], + ), ), - ), - ]; - }, - ) - ], + ]; + }, + ) + ], + ), ), - ), - Expanded( - child: ListView.builder( - itemCount: state.data.hostFiles.length, - itemBuilder: (context, index) { - final hostFile = state.data.hostFiles[index]; - return ListTile( - title: Text(hostFile.remark), - leading: IconButton( - tooltip: AppLocalizations.of(context)!.use, - style: OutlinedButton.styleFrom( - minimumSize: Size.zero, - padding: EdgeInsets.zero, - ), - onPressed: - state.data.useHostFiles.contains(hostFile.fileName) - ? null - : () async { - // final String path = await _fileManager - // .getHostsFilePath(hostFile.fileName); - // - // if (!await widget - // .onClickUse(File(path).readAsStringSync())) { - // return; - // } + Expanded( + child: ListView.builder( + itemCount: state.data.hostFiles.length, + padding: EdgeInsets.zero, + itemBuilder: (context, index) { + final hostFile = state.data.hostFiles[index]; + return ListTile( + title: Text(hostFile.remark), + leading: IconButton( + tooltip: AppLocalizations.of(context)!.use, + style: OutlinedButton.styleFrom( + minimumSize: Size.zero, + padding: EdgeInsets.zero, + ), + onPressed: + state.data.useHostFiles.contains(hostFile.fileName) + ? null + : () async { + // final String path = await _fileManager + // .getHostsFilePath(hostFile.fileName); + // + // if (!await widget + // .onClickUse(File(path).readAsStringSync())) { + // return; + // } - // setState(() { - // useHostFile = hostFile.fileName; - // }); - // _settingsManager.setString( - // settingKeyUseHostFile, hostFile.fileName); - }, - icon: Icon( - state.data.useHostFiles.contains(hostFile.fileName) - ? Icons.star - : Icons.star_border), - ), - selectedTileColor: - Theme.of(context).colorScheme.primaryContainer, - selected: state.data.selectHostFile == hostFile.fileName, - trailing: buildMoreButton(hostFile), - onTap: () { - if (state.data.selectHostFile == hostFile.fileName) { - return; - } - if (!context.read().state.data.isSave) { - ScaffoldMessenger.of(context).removeCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - AppLocalizations.of(context)!.error_not_save), - action: SnackBarAction( - label: AppLocalizations.of(context)!.abort, - onPressed: () { - context - .read() - .selectHost(hostFile.fileName); - }, + // setState(() { + // useHostFile = hostFile.fileName; + // }); + // _settingsManager.setString( + // settingKeyUseHostFile, hostFile.fileName); + }, + icon: Icon( + state.data.useHostFiles.contains(hostFile.fileName) + ? Icons.star + : Icons.star_border), + ), + selectedTileColor: + Theme.of(context).colorScheme.primaryContainer, + selected: state.data.selectHostFile == hostFile.fileName, + trailing: buildMoreButton(hostFile), + onTap: () { + if (state.data.selectHostFile == hostFile.fileName) { + return; + } + if (!context.read().state.data.isSave) { + ScaffoldMessenger.of(context).removeCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.error_not_save), + action: SnackBarAction( + label: AppLocalizations.of(context)!.abort, + onPressed: () { + context + .read() + .selectHost(hostFile.fileName); + }, + ), ), - ), - ); - return; - } + ); + return; + } - context.read().selectHost(hostFile.fileName); - }, - ); - }, - ), - ) - ], + context.read().selectHost(hostFile.fileName); + }, + ); + }, + ), + ) + ], + ), ), ); }, diff --git a/lib/home/view/home_view.dart b/lib/home/view/home_view.dart index a9a7cb9..71206df 100644 --- a/lib/home/view/home_view.dart +++ b/lib/home/view/home_view.dart @@ -56,42 +56,44 @@ class _HomeViewState extends State { return const SizedBox(); }, ), - body: BlocBuilder( - builder: (context, state) { - if (state is HomeSelectHostFileChanged) { - context.read().updateHost(state.data.selectHostFile); - } + body: SafeArea( + child: BlocBuilder( + builder: (context, state) { + if (state is HomeSelectHostFileChanged) { + context.read().updateHost(state.data.selectHostFile); + } - if (state is HomeEditMode) { - context.read().updateEditMode(state.data.editMode); - } + if (state is HomeEditMode) { + context.read().updateEditMode(state.data.editMode); + } - if (state is HomeAdvancedSettings) { - if (state.data.advancedSettingsEnum == AdvancedSettingsEnum.Open) { - WidgetsBinding.instance.addPostFrameCallback((_) { - _scaffoldKey.currentState?.openDrawer(); - }); + if (state is HomeAdvancedSettings) { + if (state.data.advancedSettingsEnum == AdvancedSettingsEnum.Open) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _scaffoldKey.currentState?.openDrawer(); + }); + } } - } - return Row( - children: [ - if (state.data.advancedSettingsEnum == - AdvancedSettingsEnum.Close && - MediaQuery.of(context).size.width > 600) - const HomeDrawer(), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const HomeAppBar(), - saveTipMessage(state.data), - HostView(state.data) - ], - )) - ], - ); - }, + return Row( + children: [ + if (state.data.advancedSettingsEnum == + AdvancedSettingsEnum.Close && + MediaQuery.of(context).size.width > 600) + const HomeDrawer(), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const HomeAppBar(), + saveTipMessage(state.data), + HostView(state.data) + ], + )) + ], + ); + }, + ), ), ); } diff --git a/lib/home/view/simple_home_view.dart b/lib/home/view/simple_home_view.dart index b4b7179..a2e2935 100644 --- a/lib/home/view/simple_home_view.dart +++ b/lib/home/view/simple_home_view.dart @@ -45,21 +45,23 @@ class SimpleHomeView extends StatelessWidget { return const SizedBox(); }, ), - body: BlocBuilder( - builder: (context, state) { - if (state is HomeEditMode) { - context.read().updateEditMode(state.data.editMode); - } + body: SafeArea( + child: BlocBuilder( + builder: (context, state) { + if (state is HomeEditMode) { + context.read().updateEditMode(state.data.editMode); + } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const HomeAppBar(), - saveTipMessage(state.data), - HostView(state.data) - ], - ); - }, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const HomeAppBar(), + saveTipMessage(state.data), + HostView(state.data) + ], + ); + }, + ), ), ); } From 0b57384fbca239e65e9adf0d1aa8e99aac0643c2 Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Mon, 14 Jul 2025 00:53:15 +0800 Subject: [PATCH 23/54] =?UTF-8?q?feat(=E9=99=84=E8=BF=91=E8=AE=BE=E5=A4=87?= =?UTF-8?q?&UI):=20=E6=B7=BB=E5=8A=A0=E8=AE=BE=E5=A4=87=E5=8F=91=E7=8E=B0?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=92=8C=E4=BC=98=E5=8C=96=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增附近设备扫描和状态管理功能 - 实现设备在线检测和状态更新 - 优化主页抽屉导航中的设备选择界面 - 添加设备缓存机制和时间扩展工具 - 完善附近设备卡片的交互体验 --- lib/home/view/home_drawer.dart | 238 ++++++++++++--- lib/home/view/home_page.dart | 3 + lib/server/bloc/nearby_devices_cubit.dart | 160 ++++++++++ lib/server/bloc/nearby_devices_state.dart | 56 ++++ lib/server/view/nearby_devices_card.dart | 340 +++++++++++++--------- lib/server/view/server_settings_page.dart | 16 +- lib/utils/datetime_extensions.dart | 20 ++ lib/utils/nearby_devices_scanner.dart | 302 +++++++++++++++++-- 8 files changed, 915 insertions(+), 220 deletions(-) create mode 100644 lib/server/bloc/nearby_devices_cubit.dart create mode 100644 lib/server/bloc/nearby_devices_state.dart create mode 100644 lib/utils/datetime_extensions.dart diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart index 7f03b84..418c1c3 100644 --- a/lib/home/view/home_drawer.dart +++ b/lib/home/view/home_drawer.dart @@ -4,6 +4,7 @@ import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/server/bloc/nearby_devices_cubit.dart'; import 'package:hosts/server/view/server_settings_page.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/widget/dialog/dialog.dart'; @@ -24,14 +25,154 @@ class HomeDrawer extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.only(left: 16,right: 16,bottom: 16), + padding: const EdgeInsets.all(16), child: Row( children: [ - Text( - AppLocalizations.of(context)!.app_name, - style: Theme.of(context).textTheme.titleLarge, + BlocBuilder( + builder: (context, state) { + print(state.devices); + + if (state.devices.isEmpty) { + return Text( + AppLocalizations.of(context)!.app_name, + style: Theme.of(context).textTheme.titleLarge, + ); + } + return PopupMenuButton( + onSelected: (value) { + // 只处理设备点击,不添加其他功能 + if (value.startsWith('device_')) { + final ip = value.substring(7); + final device = + state.devices.firstWhere((d) => d.ip == ip); + context + .read() + .toggleDeviceSelection(device); + } + }, + itemBuilder: (BuildContext context) { + final List> items = []; + items.add( + const PopupMenuItem(child: Text('本地')), + ); + // 只显示设备列表 + for (final device in state.devices) { + items.add( + PopupMenuItem( + value: 'device_${device.ip}', + child: Row( + children: [ + Stack( + children: [ + Icon( + device.hasSharing + ? Icons.share + : Icons.computer, + color: device.isOnline + ? (device.hasSharing + ? Colors.green + : Colors.orange) + : Colors.grey, + size: 20, + ), + if (device.isOnline) + Positioned( + right: 0, + bottom: 0, + child: Container( + width: 6, + height: 6, + decoration: BoxDecoration( + color: Colors.green, + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, + width: 1), + ), + ), + ), + ], + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + device.ip, + style: TextStyle( + fontWeight: FontWeight.w500, + color: device.isOnline + ? null + : Colors.grey, + ), + ), + Text( + device.isOnline + ? (device.hasSharing + ? '可访问' + : '在线') + : '离线', + style: TextStyle( + fontSize: 12, + color: device.isOnline + ? (device.hasSharing + ? Colors.green + : Colors.orange) + : Colors.grey, + ), + ), + ], + ), + ), + if (device.hasSharing && + device.isOnline) + Icon( + state.selectedDevice?.ip == + device.ip + ? Icons.check_circle + : Icons.radio_button_unchecked, + size: 16, + color: state.selectedDevice?.ip == + device.ip + ? Colors.green + : Colors.grey, + ), + ], + ), + ), + ); + } + + return items; + }, + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.app_name, + style: Theme.of(context) + .textTheme + .titleMedium, + ), + Text( + state.selectedDevice?.ip??"本地", + style: + Theme.of(context).textTheme.bodySmall, + ) + ], + ), + const SizedBox(width: 4), + const Icon(Icons.arrow_drop_down), + ], + ), + ); + }, ), - const Expanded(child: SizedBox()), + Spacer(), IconButton( onPressed: () async { String? remark = await hostConfigDialog(context); @@ -44,18 +185,22 @@ class HomeDrawer extends StatelessWidget { onSelected: (value) async { switch (value) { case 1: - // Import functionality - await importHostsDialog(context, state.data.hostFiles, + // Import functionality + await importHostsDialog( + context, state.data.hostFiles, onImportSuccess: () { - context.read().refreshHostFiles(context); - }); + context + .read() + .refreshHostFiles(context); + }); break; case 2: - // Export functionality - await exportHostsDialog(context, state.data.hostFiles); + // Export functionality + await exportHostsDialog( + context, state.data.hostFiles); break; case 3: - // Remote sync functionality + // Remote sync functionality Navigator.push( context, MaterialPageRoute( @@ -93,7 +238,8 @@ class HomeDrawer extends StatelessWidget { children: [ const Icon(Icons.cloud_sync), const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.remote_sync), + Text(AppLocalizations.of(context)! + .remote_sync), ], ), ), @@ -117,43 +263,45 @@ class HomeDrawer extends StatelessWidget { minimumSize: Size.zero, padding: EdgeInsets.zero, ), - onPressed: - state.data.useHostFiles.contains(hostFile.fileName) + onPressed: state.data.useHostFiles + .contains(hostFile.fileName) ? null : () async { - // final String path = await _fileManager - // .getHostsFilePath(hostFile.fileName); - // - // if (!await widget - // .onClickUse(File(path).readAsStringSync())) { - // return; - // } + // final String path = await _fileManager + // .getHostsFilePath(hostFile.fileName); + // + // if (!await widget + // .onClickUse(File(path).readAsStringSync())) { + // return; + // } - // setState(() { - // useHostFile = hostFile.fileName; - // }); - // _settingsManager.setString( - // settingKeyUseHostFile, hostFile.fileName); - }, - icon: Icon( - state.data.useHostFiles.contains(hostFile.fileName) - ? Icons.star - : Icons.star_border), + // setState(() { + // useHostFile = hostFile.fileName; + // }); + // _settingsManager.setString( + // settingKeyUseHostFile, hostFile.fileName); + }, + icon: Icon(state.data.useHostFiles + .contains(hostFile.fileName) + ? Icons.star + : Icons.star_border), ), selectedTileColor: - Theme.of(context).colorScheme.primaryContainer, - selected: state.data.selectHostFile == hostFile.fileName, + Theme.of(context).colorScheme.primaryContainer, + selected: + state.data.selectHostFile == hostFile.fileName, trailing: buildMoreButton(hostFile), onTap: () { if (state.data.selectHostFile == hostFile.fileName) { return; } if (!context.read().state.data.isSave) { - ScaffoldMessenger.of(context).removeCurrentSnackBar(); + ScaffoldMessenger.of(context) + .removeCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text( - AppLocalizations.of(context)!.error_not_save), + content: Text(AppLocalizations.of(context)! + .error_not_save), action: SnackBarAction( label: AppLocalizations.of(context)!.abort, onPressed: () { @@ -167,7 +315,9 @@ class HomeDrawer extends StatelessWidget { return; } - context.read().selectHost(hostFile.fileName); + context + .read() + .selectHost(hostFile.fileName); }, ); }, @@ -199,7 +349,7 @@ class HomeDrawer extends StatelessWidget { switch (value) { case 1: String result = - (await hostConfigDialog(context, hostFile.remark) ?? ""); + (await hostConfigDialog(context, hostFile.remark) ?? ""); if (result.isEmpty) return; homeCubit.updateHostFileRemark(hostFile.fileName, result); break; @@ -250,12 +400,12 @@ class HomeDrawer extends StatelessWidget { case 3: final FileManager fileManager = FileManager(); final bool success = await fileManager.exportMultipleHostFiles( - [hostFile], - AppLocalizations.of(context)!.export_data - ); + [hostFile], AppLocalizations.of(context)!.export_data); if (success) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(AppLocalizations.of(context)!.export_success)), + SnackBar( + content: + Text(AppLocalizations.of(context)!.export_success)), ); } break; @@ -297,4 +447,4 @@ class HomeDrawer extends StatelessWidget { }, ); } -} \ No newline at end of file +} diff --git a/lib/home/view/home_page.dart b/lib/home/view/home_page.dart index 363aafd..d10c310 100644 --- a/lib/home/view/home_page.dart +++ b/lib/home/view/home_page.dart @@ -4,6 +4,8 @@ import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/home/view/home_view.dart'; import 'package:hosts/home/view/simple_home_view.dart'; +import 'package:hosts/server/bloc/nearby_devices_cubit.dart'; +import 'package:hosts/utils/nearby_devices_scanner.dart'; /// 首页页面组件 /// @@ -19,6 +21,7 @@ class HomePage extends StatelessWidget { providers: [ BlocProvider(create: (context) => HomeCubit()), BlocProvider(create: (context) => HostCubit()), + BlocProvider(create: (context) => NearbyDevicesCubit()..loadCachedDevices()), ], child: const HomeView(), ); diff --git a/lib/server/bloc/nearby_devices_cubit.dart b/lib/server/bloc/nearby_devices_cubit.dart new file mode 100644 index 0000000..a35ed88 --- /dev/null +++ b/lib/server/bloc/nearby_devices_cubit.dart @@ -0,0 +1,160 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:hosts/utils/nearby_devices_scanner.dart'; + +part 'nearby_devices_state.dart'; + +/// 附近设备 Cubit +class NearbyDevicesCubit extends Cubit { + NearbyDevicesCubit() : super(const NearbyDevicesState()); + + /// 加载缓存的设备 + Future loadCachedDevices() async { + try { + emit(state.copyWith(isLoading: true, errorMessage: null)); + + final cachedDevices = await NearbyDevicesScanner.getCachedDevices(); + + emit(state.copyWith( + isLoading: false, + devices: cachedDevices, + )); + + // 如果有缓存设备,自动检查在线状态 + if (cachedDevices.isNotEmpty) { + checkDevicesOnlineStatus(); + } + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: '加载缓存设备失败: $e', + )); + } + } + + /// 扫描附近设备 + Future scanNearbyDevices() async { + try { + emit(state.copyWith( + isScanning: true, + errorMessage: null, + devices: [], // 清空现有设备列表 + )); + + // 开始实时扫描 + await NearbyDevicesScanner.scanNearbyDevicesRealTime( + onDeviceFound: (device) { + // 实时更新设备列表 + onDeviceFound(device); + }, + ); + + emit(state.copyWith( + isScanning: false, + successMessage: '扫描完成,发现${state.devices.length}个设备', + )); + } catch (e) { + emit(state.copyWith( + isScanning: false, + errorMessage: '扫描附近设备失败: $e', + )); + } + } + + /// 检查设备在线状态 + Future checkDevicesOnlineStatus() async { + if (state.devices.isEmpty) return; + + try { + emit(state.copyWith(isCheckingStatus: true, errorMessage: null)); + + await NearbyDevicesScanner.checkCachedDevicesOnlineStatus(); + + // 重新加载更新后的设备列表 + final updatedDevices = await NearbyDevicesScanner.getCachedDevices(); + + emit(state.copyWith( + isCheckingStatus: false, + devices: updatedDevices, + )); + } catch (e) { + emit(state.copyWith( + isCheckingStatus: false, + errorMessage: '检查设备在线状态失败: $e', + )); + } + } + + /// 清除设备缓存 + Future clearDeviceCache() async { + try { + await NearbyDevicesScanner.clearCache(); + + emit(state.copyWith( + devices: [], + successMessage: '设备缓存已清除', + )); + } catch (e) { + emit(state.copyWith( + errorMessage: '清除设备缓存失败: $e', + )); + } + } + + /// 设备发现处理 + void onDeviceFound(NearbyDevice device) { + final updatedDevices = List.from(state.devices); + + // 避免重复添加同一IP的设备 + final existingIndex = updatedDevices.indexWhere((d) => d.ip == device.ip); + if (existingIndex >= 0) { + updatedDevices[existingIndex] = device; + } else { + updatedDevices.add(device); + } + + emit(state.copyWith(devices: updatedDevices)); + } + + /// 选择设备 + void selectDevice(NearbyDevice? device) { + emit(state.copyWith( + selectedDevice: device, + clearSelectedDevice: device == null, + )); + } + + /// 切换设备选择状态 + void toggleDeviceSelection(NearbyDevice device) { + if (state.selectedDevice?.ip == device.ip) { + // 如果已选中,则取消选择 + selectDevice(null); + } else { + // 否则选择该设备 + selectDevice(device); + } + } + + /// 检查设备是否被选中 + bool isDeviceSelected(NearbyDevice device) { + return state.selectedDevice?.ip == device.ip; + } + + /// 获取设备状态摘要(供其他组件使用) + Map getDevicesSummary() { + return { + 'total': state.totalDevicesCount, + 'online': state.onlineDevicesCount, + 'offline': state.totalDevicesCount - state.onlineDevicesCount, + }; + } + + /// 清除消息 + void clearMessages() { + emit(state.copyWith( + errorMessage: null, + successMessage: null, + )); + } +} \ No newline at end of file diff --git a/lib/server/bloc/nearby_devices_state.dart b/lib/server/bloc/nearby_devices_state.dart new file mode 100644 index 0000000..c0d23c4 --- /dev/null +++ b/lib/server/bloc/nearby_devices_state.dart @@ -0,0 +1,56 @@ +part of 'nearby_devices_cubit.dart'; + +/// 附近设备状态 +class NearbyDevicesState { + const NearbyDevicesState({ + this.devices = const [], + this.selectedDevice, + this.isScanning = false, + this.isCheckingStatus = false, + this.isLoading = false, + this.errorMessage, + this.successMessage, + }); + + final List devices; + final NearbyDevice? selectedDevice; + final bool isScanning; + final bool isCheckingStatus; + final bool isLoading; + final String? errorMessage; + final String? successMessage; + + /// 获取在线设备数量 + int get onlineDevicesCount => devices.where((device) => device.isOnline).length; + + /// 获取总设备数量 + int get totalDevicesCount => devices.length; + + /// 获取在线设备列表 + List get onlineDevices => devices.where((device) => device.isOnline).toList(); + + /// 获取离线设备列表 + List get offlineDevices => devices.where((device) => !device.isOnline).toList(); + + NearbyDevicesState copyWith({ + List? devices, + NearbyDevice? selectedDevice, + bool clearSelectedDevice = false, + bool? isScanning, + bool? isCheckingStatus, + bool? isLoading, + String? errorMessage, + String? successMessage, + }) { + return NearbyDevicesState( + devices: devices ?? this.devices, + selectedDevice: clearSelectedDevice ? null : (selectedDevice ?? this.selectedDevice), + isScanning: isScanning ?? this.isScanning, + isCheckingStatus: isCheckingStatus ?? this.isCheckingStatus, + isLoading: isLoading ?? this.isLoading, + errorMessage: errorMessage, + successMessage: successMessage, + ); + } + +} \ No newline at end of file diff --git a/lib/server/view/nearby_devices_card.dart b/lib/server/view/nearby_devices_card.dart index 16d04e5..90a92c1 100644 --- a/lib/server/view/nearby_devices_card.dart +++ b/lib/server/view/nearby_devices_card.dart @@ -1,190 +1,246 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/server/bloc/nearby_devices_cubit.dart'; +import 'package:hosts/utils/datetime_extensions.dart'; import 'package:hosts/utils/nearby_devices_scanner.dart'; -import 'package:url_launcher/url_launcher.dart'; /// 附近设备卡片组件 -class NearbyDevicesCard extends StatefulWidget { +class NearbyDevicesCard extends StatelessWidget { const NearbyDevicesCard({super.key}); @override - State createState() => _NearbyDevicesCardState(); -} - -class _NearbyDevicesCardState extends State { - List _nearbyDevices = []; - bool _isScanning = false; - - /// 扫描附近设备 - Future _scanNearbyDevices() async { - setState(() { - _isScanning = true; - }); - - try { - // 首先测试本地服务器是否正在运行 - final bool localServerRunning = await NearbyDevicesScanner.testLocalServer(); - if (!localServerRunning) { - print('本地服务器未运行,无法扫描其他设备'); - if (mounted) { + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + // 处理错误消息 + if (state.errorMessage != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('请先启动本地服务器再扫描附近设备'), + content: Text(state.errorMessage!), backgroundColor: Theme.of(context).colorScheme.error, ), ); } - setState(() { - _nearbyDevices = []; - }); - return; - } - - final devices = await NearbyDevicesScanner.scanNearbyDevices(); - setState(() { - _nearbyDevices = devices; - }); - - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('扫描完成,发现${devices.length}个设备'), - backgroundColor: Theme.of(context).colorScheme.primary, - ), - ); - } - } catch (e) { - print('扫描附近设备失败: $e'); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('扫描失败: $e'), - backgroundColor: Theme.of(context).colorScheme.error, - ), - ); - } - } finally { - setState(() { - _isScanning = false; - }); - } - } - /// 启动设备URL - Future _launchDeviceUrl(String ip) async { - final url = 'http://$ip:1204'; - try { - final uri = Uri.parse(url); - if (await canLaunchUrl(uri)) { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } else { - if (mounted) { + // 处理成功消息 + if (state.successMessage != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('无法打开URL: $url'), - backgroundColor: Theme.of(context).colorScheme.error, + content: Text(state.successMessage!), + backgroundColor: Theme.of(context).colorScheme.primary, ), ); } - } - } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('打开设备失败: $e'), - backgroundColor: Theme.of(context).colorScheme.error, + }, + builder: (context, state) { + print(state.devices); + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.nearby_devices, + style: Theme.of(context).textTheme.titleMedium, + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: state.isCheckingStatus + ? const SizedBox( + width: 16, + height: 16, + child: + CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.wifi_find), + onPressed: + state.devices.isEmpty || state.isCheckingStatus + ? null + : () => context + .read() + .checkDevicesOnlineStatus(), + tooltip: '检查设备在线状态', + ), + IconButton( + icon: const Icon(Icons.clear_all), + onPressed: state.devices.isEmpty + ? null + : () => context + .read() + .clearDeviceCache(), + tooltip: '清除设备缓存', + ), + IconButton( + icon: state.isScanning + ? const SizedBox( + width: 20, + height: 20, + child: + CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.refresh), + onPressed: state.isScanning + ? null + : () => context + .read() + .scanNearbyDevices(), + tooltip: + AppLocalizations.of(context)!.scan_nearby_devices, + ), + ], + ), + ], + ), + const SizedBox(height: 16), + if (state.devices.isEmpty && + !state.isScanning && + !state.isLoading) + Text( + AppLocalizations.of(context)!.no_nearby_devices, + style: const TextStyle(color: Colors.grey), + ) + else if (state.devices.isNotEmpty) + ...state.devices + .map((device) => _buildDeviceItem(context, device)) + else if (state.isScanning || state.isLoading) + Text( + state.isScanning + ? AppLocalizations.of(context)!.scanning_devices + : '加载中...', + style: const TextStyle(color: Colors.grey), + ), + ], + ), ), ); - } - } + }, + ); } /// 构建设备项 - Widget _buildDeviceItem(NearbyDevice device) { + Widget _buildDeviceItem(BuildContext context, NearbyDevice device) { + // 确定设备状态和颜色 + Color statusColor; + IconData statusIcon; + String statusText; + + if (!device.isOnline) { + statusColor = Colors.red; + statusIcon = Icons.offline_bolt; + statusText = '离线'; + } else if (device.hasSharing) { + statusColor = Colors.green; + statusIcon = Icons.share; + statusText = AppLocalizations.of(context)!.sharing_enabled; + } else { + statusColor = Colors.orange; + statusIcon = Icons.computer; + statusText = AppLocalizations.of(context)!.device_reachable; + } + return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ - Icon( - device.hasSharing ? Icons.share : Icons.computer, - color: device.hasSharing ? Colors.green : Colors.grey, + Stack( + children: [ + Icon( + statusIcon, + color: statusColor, + ), + // 在线状态指示器 + if (device.isOnline) + Positioned( + right: 0, + bottom: 0, + child: Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: Colors.green, + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 1), + ), + ), + ), + ], ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - device.ip, - style: const TextStyle(fontWeight: FontWeight.bold), + Row( + children: [ + Text( + device.ip, + style: TextStyle( + fontWeight: FontWeight.bold, + color: device.isOnline ? null : Colors.grey, + ), + ), + if (!device.isOnline) ...[ + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.red.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.red.withValues(alpha: 0.3)), + ), + child: Text( + '离线', + style: TextStyle( + fontSize: 10, + color: Colors.red[700], + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ], ), Text( - device.hasSharing - ? AppLocalizations.of(context)!.sharing_enabled - : AppLocalizations.of(context)!.device_reachable, + statusText, style: TextStyle( fontSize: 12, - color: device.hasSharing ? Colors.green : Colors.grey, + color: statusColor, ), ), - ], - ), - ), - if (device.hasSharing) - IconButton( - icon: const Icon(Icons.launch), - onPressed: () => _launchDeviceUrl(device.ip), - tooltip: AppLocalizations.of(context)!.visit_device, - ), - ], - ), - ); - } - - @override - Widget build(BuildContext context) { - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ + // 显示最后见到时间 Text( - AppLocalizations.of(context)!.nearby_devices, - style: Theme.of(context).textTheme.titleMedium, - ), - IconButton( - icon: _isScanning - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : const Icon(Icons.refresh), - onPressed: _isScanning ? null : _scanNearbyDevices, - tooltip: AppLocalizations.of(context)!.scan_nearby_devices, + '最后见到: ${device.lastSeen.formatLastSeen()}', + style: TextStyle( + fontSize: 10, + color: Colors.grey[600], + ), ), ], ), - const SizedBox(height: 16), - if (_nearbyDevices.isEmpty && !_isScanning) - Text( - AppLocalizations.of(context)!.no_nearby_devices, - style: const TextStyle(color: Colors.grey), - ) - else if (_nearbyDevices.isNotEmpty) - ..._nearbyDevices.map((device) => _buildDeviceItem(device)) - else - Text( - AppLocalizations.of(context)!.scanning_devices, - style: const TextStyle(color: Colors.grey), - ), - ], - ), + ), + IconButton( + icon: const Icon(Icons.launch), + onPressed: device.hasSharing && device.isOnline + ? () { + context + .read() + .toggleDeviceSelection(device); + Navigator.pop(context); + } + : null, + tooltip: AppLocalizations.of(context)!.visit_device, + ) + ], ), ); } -} \ No newline at end of file +} diff --git a/lib/server/view/server_settings_page.dart b/lib/server/view/server_settings_page.dart index a33eca5..6ad55cb 100755 --- a/lib/server/view/server_settings_page.dart +++ b/lib/server/view/server_settings_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/server/bloc/nearby_devices_cubit.dart'; import 'package:hosts/server/bloc/server_settings_bloc.dart'; import 'package:hosts/server/bloc/server_settings_event.dart'; import 'package:hosts/server/bloc/server_settings_state.dart'; @@ -13,10 +14,17 @@ class ServerSettingsPage extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => ServerSettingsBloc() - ..add(LoadServerSettings()) - ..add(LoadNetworkInterfaces()), + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => ServerSettingsBloc() + ..add(LoadServerSettings()) + ..add(LoadNetworkInterfaces()), + ), + BlocProvider( + create: (context) => NearbyDevicesCubit()..loadCachedDevices(), + ), + ], child: const _ServerSettingsView(), ); } diff --git a/lib/utils/datetime_extensions.dart b/lib/utils/datetime_extensions.dart new file mode 100644 index 0000000..a1d34ae --- /dev/null +++ b/lib/utils/datetime_extensions.dart @@ -0,0 +1,20 @@ +/// DateTime 扩展方法 +extension DateTimeExtensions on DateTime { + /// 格式化最后见到时间 + String formatLastSeen() { + final now = DateTime.now(); + final difference = now.difference(this); + + if (difference.inMinutes < 1) { + return '刚刚'; + } else if (difference.inHours < 1) { + return '${difference.inMinutes}分钟前'; + } else if (difference.inDays < 1) { + return '${difference.inHours}小时前'; + } else if (difference.inDays < 7) { + return '${difference.inDays}天前'; + } else { + return '$month/$day'; + } + } +} \ No newline at end of file diff --git a/lib/utils/nearby_devices_scanner.dart b/lib/utils/nearby_devices_scanner.dart index 8816586..1f13cad 100644 --- a/lib/utils/nearby_devices_scanner.dart +++ b/lib/utils/nearby_devices_scanner.dart @@ -2,23 +2,42 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:network_info_plus/network_info_plus.dart'; +import 'package:shared_preferences/shared_preferences.dart'; /// 附近设备扫描器 class NearbyDevicesScanner { static const int _defaultPort = 1204; static const Duration _scanTimeout = Duration(seconds: 2); - /// 扫描附近设备 - static Future> scanNearbyDevices() async { + // SharedPreferences键名 + static const String _deviceCacheKey = 'nearby_devices_cache'; + static const String _priorityIPsKey = 'priority_ips_cache'; + + // 设备缓存,记录之前扫描到的设备 + static final Map _deviceCache = {}; + + // 设备优先级列表,之前扫描到的设备优先扫描 + static final List _priorityIPs = []; + + // 是否已初始化(加载缓存) + static bool _isInitialized = false; + + /// 实时扫描附近设备,发现设备时立即回调 + static Future scanNearbyDevicesRealTime({ + required Function(NearbyDevice) onDeviceFound, + }) async { try { - print('开始扫描附近设备...'); + // 确保缓存已初始化 + await _initializeCache(); + + print('开始实时扫描附近设备...'); // 获取当前设备的IP地址 String? currentIP = await _getCurrentIP(); if (currentIP == null || currentIP.isEmpty) { print('无法获取当前设备IP地址'); - return []; + return; } print('当前设备IP: $currentIP'); @@ -27,47 +46,79 @@ class NearbyDevicesScanner { final ipParts = currentIP.split('.'); if (ipParts.length != 4) { print('IP地址格式错误: $currentIP'); - return []; + return; } final baseIP = '${ipParts[0]}.${ipParts[1]}.${ipParts[2]}'; print('扫描网段: $baseIP.1-254'); - final List devices = []; + // 创建所有需要扫描的IP列表 + final List ipsToScan = []; + + // 首先添加优先级IP(之前扫描到的设备) + for (final ip in _priorityIPs) { + if (ip.startsWith(baseIP) && ip != currentIP) { + ipsToScan.add(ip); + } + } + + // 然后添加其他IP + for (int i = 1; i <= 254; i++) { + final targetIP = '$baseIP.$i'; + if (targetIP != currentIP && !ipsToScan.contains(targetIP)) { + ipsToScan.add(targetIP); + } + } - // 扫描同网段的设备,限制并发数量以避免过多连接 + // 分批扫描,每批最多50个设备,避免过多并发连接 const int batchSize = 50; - for (int start = 1; start <= 254; start += batchSize) { - final int end = (start + batchSize - 1).clamp(1, 254); - final List> scanFutures = []; + for (int start = 0; start < ipsToScan.length; start += batchSize) { + final int end = (start + batchSize).clamp(0, ipsToScan.length); + final batch = ipsToScan.sublist(start, end); - for (int i = start; i <= end; i++) { - final targetIP = '$baseIP.$i'; - // 跳过当前设备 - if (targetIP == currentIP) continue; - - scanFutures.add(_scanDevice(targetIP)); + final List> scanFutures = batch + .map((ip) => _scanDevice(ip)) + .toList(); + + // 使用 forEach 来处理每个完成的扫描结果 + for (final future in scanFutures) { + future.then((device) async { + if (device != null) { + // 更新设备缓存 + _deviceCache[device.ip] = device; + + // 更新优先级列表 + if (!_priorityIPs.contains(device.ip)) { + _priorityIPs.add(device.ip); + } + + // 保存到持久存储 + await _saveCache(); + + print('发现设备: ${device.ip}'); + onDeviceFound(device); + } + }); } // 等待当前批次扫描完成 - final results = await Future.wait(scanFutures); - - // 过滤出有效设备 - for (final result in results) { - if (result != null) { - devices.add(result); - print('发现设备: ${result.ip}'); - } - } + await Future.wait(scanFutures); } - print('扫描完成,发现${devices.length}个设备'); - return devices; + print('实时扫描完成'); } catch (e) { - print('扫描附近设备失败: $e'); - return []; + print('实时扫描附近设备失败: $e'); } } + + /// 扫描附近设备(兼容性保留) + static Future> scanNearbyDevices() async { + final List devices = []; + await scanNearbyDevicesRealTime( + onDeviceFound: (device) => devices.add(device), + ); + return devices; + } /// 获取当前设备IP地址 static Future _getCurrentIP() async { @@ -182,6 +233,156 @@ class NearbyDevicesScanner { return false; } } + + /// 初始化缓存(从持久存储加载) + static Future _initializeCache() async { + if (_isInitialized) return; + + try { + final prefs = await SharedPreferences.getInstance(); + + // 加载设备缓存 + final deviceCacheJson = prefs.getString(_deviceCacheKey); + if (deviceCacheJson != null) { + final Map cacheData = jsonDecode(deviceCacheJson); + _deviceCache.clear(); + cacheData.forEach((ip, deviceData) { + _deviceCache[ip] = NearbyDevice.fromJson(deviceData); + }); + } + + // 加载优先级IP列表 + final priorityIPs = prefs.getStringList(_priorityIPsKey); + if (priorityIPs != null) { + _priorityIPs.clear(); + _priorityIPs.addAll(priorityIPs); + } + + _isInitialized = true; + print('设备缓存初始化完成,加载了${_deviceCache.length}个设备'); + } catch (e) { + print('加载设备缓存失败: $e'); + _isInitialized = true; // 即使失败也标记为已初始化,避免重复尝试 + } + } + + /// 保存缓存到持久存储 + static Future _saveCache() async { + try { + final prefs = await SharedPreferences.getInstance(); + + // 保存设备缓存 + final Map cacheData = {}; + _deviceCache.forEach((ip, device) { + cacheData[ip] = device.toJson(); + }); + await prefs.setString(_deviceCacheKey, jsonEncode(cacheData)); + + // 保存优先级IP列表 + await prefs.setStringList(_priorityIPsKey, _priorityIPs); + + print('设备缓存已保存到存储'); + } catch (e) { + print('保存设备缓存失败: $e'); + } + } + + /// 获取缓存的设备列表 + static Future> getCachedDevices() async { + await _initializeCache(); + return _deviceCache.values.toList(); + } + + /// 检查缓存设备的在线状态 + static Future checkCachedDevicesOnlineStatus() async { + await _initializeCache(); + + if (_deviceCache.isEmpty) return; + + print('开始检查${_deviceCache.length}个缓存设备的在线状态...'); + + final List offlineDevices = []; + final List> checkFutures = []; + + for (final device in _deviceCache.values) { + final future = _checkSingleDeviceOnlineStatus(device).then((isOnline) { + if (isOnline) { + // 设备在线,更新最后见到时间和在线状态 + final updatedDevice = device.copyWith( + lastSeen: DateTime.now(), + isOnline: true, + ); + _deviceCache[device.ip] = updatedDevice; + } else { + // 设备离线,标记为离线 + final updatedDevice = device.copyWith(isOnline: false); + _deviceCache[device.ip] = updatedDevice; + + // 检查是否长时间离线(超过7天),如果是则加入移除列表 + final daysSinceLastSeen = DateTime.now().difference(device.lastSeen).inDays; + if (daysSinceLastSeen > 7) { + offlineDevices.add(device.ip); + } + } + }); + checkFutures.add(future); + } + + // 等待所有检查完成 + await Future.wait(checkFutures); + + // 移除长时间离线的设备 + for (final ip in offlineDevices) { + _deviceCache.remove(ip); + _priorityIPs.remove(ip); + print('移除长时间离线的设备: $ip'); + } + + // 保存更新后的缓存 + await _saveCache(); + + print('设备在线状态检查完成,移除了${offlineDevices.length}个长时间离线的设备'); + } + + /// 检查单个设备的在线状态 + static Future _checkSingleDeviceOnlineStatus(NearbyDevice device) async { + try { + // 使用更短的超时时间进行快速检查 + const quickTimeout = Duration(milliseconds: 500); + final socket = await Socket.connect(device.ip, _defaultPort, timeout: quickTimeout); + await socket.close(); + return true; + } catch (e) { + return false; + } + } + + /// 获取在线的缓存设备列表 + static Future> getOnlineCachedDevices() async { + await _initializeCache(); + return _deviceCache.values.where((device) => device.isOnline).toList(); + } + + /// 获取离线的缓存设备列表 + static Future> getOfflineCachedDevices() async { + await _initializeCache(); + return _deviceCache.values.where((device) => !device.isOnline).toList(); + } + + /// 清除设备缓存 + static Future clearCache() async { + _deviceCache.clear(); + _priorityIPs.clear(); + + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_deviceCacheKey); + await prefs.remove(_priorityIPsKey); + print('设备缓存已清除'); + } catch (e) { + print('清除设备缓存失败: $e'); + } + } } /// 附近设备信息 @@ -190,16 +391,57 @@ class NearbyDevice { final bool isReachable; final bool hasSharing; final DateTime lastSeen; + final bool isOnline; // 当前是否在线 const NearbyDevice({ required this.ip, required this.isReachable, required this.hasSharing, required this.lastSeen, + this.isOnline = true, // 默认为在线 }); + /// 从JSON创建NearbyDevice实例 + factory NearbyDevice.fromJson(Map json) { + return NearbyDevice( + ip: json['ip'] as String, + isReachable: json['isReachable'] as bool, + hasSharing: json['hasSharing'] as bool, + lastSeen: DateTime.parse(json['lastSeen'] as String), + isOnline: json['isOnline'] as bool? ?? true, // 兼容旧数据 + ); + } + + /// 转换为JSON + Map toJson() { + return { + 'ip': ip, + 'isReachable': isReachable, + 'hasSharing': hasSharing, + 'lastSeen': lastSeen.toIso8601String(), + 'isOnline': isOnline, + }; + } + + /// 创建一个更新在线状态的副本 + NearbyDevice copyWith({ + String? ip, + bool? isReachable, + bool? hasSharing, + DateTime? lastSeen, + bool? isOnline, + }) { + return NearbyDevice( + ip: ip ?? this.ip, + isReachable: isReachable ?? this.isReachable, + hasSharing: hasSharing ?? this.hasSharing, + lastSeen: lastSeen ?? this.lastSeen, + isOnline: isOnline ?? this.isOnline, + ); + } + @override String toString() { - return 'NearbyDevice(ip: $ip, isReachable: $isReachable, hasSharing: $hasSharing, lastSeen: $lastSeen)'; + return 'NearbyDevice(ip: $ip, isReachable: $isReachable, hasSharing: $hasSharing, lastSeen: $lastSeen, isOnline: $isOnline)'; } } \ No newline at end of file From ca387b2f36f87fcf5fbf1b363f27c90316c3db25 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Thu, 17 Jul 2025 00:58:39 +0800 Subject: [PATCH 24/54] =?UTF-8?q?feat(=E9=99=84=E8=BF=91=E8=AE=BE=E5=A4=87?= =?UTF-8?q?&=E7=BC=93=E5=AD=98):=20=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87UI?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E5=B9=B6=E6=B7=BB=E5=8A=A0API=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构设备展示界面,采用网格布局和卡片设计 - 优化设备状态指示器和离线状态显示 - 新增设备API缓存功能,提升离线体验 - 添加flutter_staggered_grid_view依赖支持 - 优化设备发现和缓存逻辑 --- lib/server/view/nearby_devices_card.dart | 230 +++++++++++++-------- lib/utils/device_api_cache.dart | 252 +++++++++++++++++++++++ lib/utils/nearby_devices_scanner.dart | 20 +- 3 files changed, 416 insertions(+), 86 deletions(-) create mode 100644 lib/utils/device_api_cache.dart diff --git a/lib/server/view/nearby_devices_card.dart b/lib/server/view/nearby_devices_card.dart index 90a92c1..50cb099 100644 --- a/lib/server/view/nearby_devices_card.dart +++ b/lib/server/view/nearby_devices_card.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/server/bloc/nearby_devices_cubit.dart'; import 'package:hosts/utils/datetime_extensions.dart'; @@ -34,8 +35,8 @@ class NearbyDevicesCard extends StatelessWidget { } }, builder: (context, state) { - print(state.devices); return Card( + elevation: 1, child: Padding( padding: const EdgeInsets.all(16), child: Column( @@ -107,8 +108,7 @@ class NearbyDevicesCard extends StatelessWidget { style: const TextStyle(color: Colors.grey), ) else if (state.devices.isNotEmpty) - ...state.devices - .map((device) => _buildDeviceItem(context, device)) + _buildDeviceGrid(context, state.devices) else if (state.isScanning || state.isLoading) Text( state.isScanning @@ -124,6 +124,43 @@ class NearbyDevicesCard extends StatelessWidget { ); } + /// 构建设备网格 + Widget _buildDeviceGrid(BuildContext context, List devices) { + return LayoutBuilder( + builder: (context, constraints) { + // 根据屏幕宽度确定网格列数 + int crossAxisCount; + if (constraints.maxWidth > 1200) { + crossAxisCount = 4; + } else if (constraints.maxWidth > 800) { + crossAxisCount = 3; + } else if (constraints.maxWidth > 600) { + crossAxisCount = 2; + } else { + crossAxisCount = 1; + } + + return Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(12), + ), + child: StaggeredGrid.count( + crossAxisCount: crossAxisCount, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + children: devices.map((device) { + return StaggeredGridTile.fit( + crossAxisCellCount: 1, + child: _buildDeviceItem(context, device), + ); + }).toList(), + ), + ); + }, + ); + } + /// 构建设备项 Widget _buildDeviceItem(BuildContext context, NearbyDevice device) { // 确定设备状态和颜色 @@ -145,101 +182,124 @@ class NearbyDevicesCard extends StatelessWidget { statusText = AppLocalizations.of(context)!.device_reachable; } - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - children: [ - Stack( - children: [ - Icon( - statusIcon, - color: statusColor, - ), - // 在线状态指示器 - if (device.isOnline) - Positioned( - right: 0, - bottom: 0, - child: Container( - width: 8, - height: 8, - decoration: BoxDecoration( - color: Colors.green, - shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 1), - ), - ), - ), - ], - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + return Card( + elevation: 0, + margin: EdgeInsets.zero, + color: Theme.of(context).colorScheme.surface, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide( + color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.1), + width: 1, + ), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( children: [ - Row( + const SizedBox(width: 8,), + Stack( children: [ - Text( - device.ip, - style: TextStyle( - fontWeight: FontWeight.bold, - color: device.isOnline ? null : Colors.grey, - ), + Icon( + statusIcon, + color: statusColor, + size: 24, ), - if (!device.isOnline) ...[ - const SizedBox(width: 8), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: Colors.red.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Colors.red.withValues(alpha: 0.3)), - ), - child: Text( - '离线', - style: TextStyle( - fontSize: 10, - color: Colors.red[700], - fontWeight: FontWeight.w500, + // 在线状态指示器 + if (device.isOnline) + Positioned( + right: 0, + bottom: 0, + child: Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: Colors.green, + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 1), ), ), ), - ], ], ), - Text( - statusText, - style: TextStyle( - fontSize: 12, - color: statusColor, + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + device.ip, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: device.isOnline ? null : Colors.grey, + ), + overflow: TextOverflow.ellipsis, + ), + ), + if (!device.isOnline) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.red.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.red.withValues(alpha: 0.3)), + ), + child: Text( + '离线', + style: TextStyle( + fontSize: 10, + color: Colors.red[700], + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + statusText, + style: TextStyle( + fontSize: 14, + color: statusColor, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 4), + Text( + '最后见到: ${device.lastSeen.formatLastSeen()}', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + ], ), ), - // 显示最后见到时间 - Text( - '最后见到: ${device.lastSeen.formatLastSeen()}', - style: TextStyle( - fontSize: 10, - color: Colors.grey[600], + if (device.hasSharing && device.isOnline) + IconButton( + icon: const Icon(Icons.launch), + onPressed: () { + context + .read() + .toggleDeviceSelection(device); + Navigator.pop(context); + }, + tooltip: AppLocalizations.of(context)!.visit_device, ), - ), ], ), - ), - IconButton( - icon: const Icon(Icons.launch), - onPressed: device.hasSharing && device.isOnline - ? () { - context - .read() - .toggleDeviceSelection(device); - Navigator.pop(context); - } - : null, - tooltip: AppLocalizations.of(context)!.visit_device, - ) - ], + ], + ), ), ); } diff --git a/lib/utils/device_api_cache.dart b/lib/utils/device_api_cache.dart new file mode 100644 index 0000000..eaef7f8 --- /dev/null +++ b/lib/utils/device_api_cache.dart @@ -0,0 +1,252 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as path; + +/// 设备API缓存工具类 +class DeviceApiCache { + static const int _defaultPort = 1204; + static const Duration _scanTimeout = Duration(seconds: 2); + + /// 缓存设备API响应 + static Future cacheDeviceAPIResponses(String ip) async { + try { + // 创建缓存目录 + final cacheDir = await _getCacheDirectoryForDevice(ip); + await cacheDir.create(recursive: true); + + // 缓存hosts文件列表 + await _cacheHostsFilesList(ip, cacheDir); + + print('设备 $ip 的API响应已缓存'); + } catch (e) { + print('缓存设备 $ip 的API响应失败: $e'); + } + } + + /// 获取设备缓存目录 + static Future _getCacheDirectoryForDevice(String ip) async { + final appCacheDir = await getApplicationCacheDirectory(); + return Directory(path.join(appCacheDir.path, ip)); + } + + /// 缓存hosts文件列表 + static Future _cacheHostsFilesList(String ip, Directory cacheDir) async { + try { + final httpClient = HttpClient(); + httpClient.connectionTimeout = _scanTimeout; + httpClient.idleTimeout = _scanTimeout; + + final request = await httpClient.get(ip, _defaultPort, '/api/hosts'); + final response = await request.close(); + + if (response.statusCode == 200) { + final responseBody = await response.transform(utf8.decoder).join(); + final hostsListFile = File(path.join(cacheDir.path, 'hosts_list')); + + // 解析响应并只保存data数组 + try { + final responseData = jsonDecode(responseBody); + if (responseData['success'] == true) { + final List hostsList = responseData['data']; + + // 只保存data数组 + await hostsListFile.writeAsString(jsonEncode(hostsList)); + print('hosts文件列表已缓存: $ip'); + + // 进一步缓存每个文件的详细信息 + for (final hosts in hostsList) { + final fileName = hosts['fileName']; + if (fileName != null) { + await _cacheHostsFileContent(ip, fileName, cacheDir); + await _cacheHostsFileHistory(ip, fileName, cacheDir); + } + } + } + } catch (e) { + print('解析hosts文件列表失败 $ip: $e'); + } + } + + httpClient.close(); + } catch (e) { + print('缓存hosts文件列表失败 $ip: $e'); + } + } + + /// 缓存hosts文件内容 + static Future _cacheHostsFileContent(String ip, String fileName, Directory cacheDir) async { + try { + final httpClient = HttpClient(); + httpClient.connectionTimeout = _scanTimeout; + httpClient.idleTimeout = _scanTimeout; + + final request = await httpClient.get(ip, _defaultPort, '/api/hosts/$fileName'); + final response = await request.close(); + + if (response.statusCode == 200) { + final responseBody = await response.transform(utf8.decoder).join(); + final fileDir = Directory(path.join(cacheDir.path, fileName)); + await fileDir.create(recursive: true); + + final hostsFile = File(path.join(fileDir.path, 'hosts')); + await hostsFile.writeAsString(responseBody); + print('hosts文件内容已缓存: $ip/$fileName'); + } + + httpClient.close(); + } catch (e) { + print('缓存hosts文件内容失败 $ip/$fileName: $e'); + } + } + + /// 缓存hosts文件历史 + static Future _cacheHostsFileHistory(String ip, String fileName, Directory cacheDir) async { + try { + final httpClient = HttpClient(); + httpClient.connectionTimeout = _scanTimeout; + httpClient.idleTimeout = _scanTimeout; + + final request = await httpClient.get(ip, _defaultPort, '/api/hosts/$fileName/history'); + final response = await request.close(); + + if (response.statusCode == 200) { + final responseBody = await response.transform(utf8.decoder).join(); + final historyDir = Directory(path.join(cacheDir.path, fileName, 'history')); + await historyDir.create(recursive: true); + + print('hosts文件历史已缓存: $ip/$fileName'); + + // 缓存每个历史版本的内容 + try { + final responseData = jsonDecode(responseBody); + if (responseData['success'] == true) { + final List historyList = responseData['data']; + for (final historyItem in historyList) { + final historyId = historyItem['id']; + if (historyId != null) { + await _cacheHostsFileHistoryContent(ip, fileName, historyId, historyDir); + } + } + } + } catch (e) { + print('解析hosts文件历史失败 $ip/$fileName: $e'); + } + } + + httpClient.close(); + } catch (e) { + print('缓存hosts文件历史失败 $ip/$fileName: $e'); + } + } + + /// 缓存hosts文件历史内容 + static Future _cacheHostsFileHistoryContent(String ip, String fileName, String historyId, Directory historyDir) async { + try { + final httpClient = HttpClient(); + httpClient.connectionTimeout = _scanTimeout; + httpClient.idleTimeout = _scanTimeout; + + final request = await httpClient.get(ip, _defaultPort, '/api/hosts/$fileName/history/$historyId'); + final response = await request.close(); + + if (response.statusCode == 200) { + final responseBody = await response.transform(utf8.decoder).join(); + final historyContentFile = File(path.join(historyDir.path, historyId)); + await historyContentFile.writeAsString(responseBody); + print('hosts文件历史内容已缓存: $ip/$fileName/$historyId'); + } + + httpClient.close(); + } catch (e) { + print('缓存hosts文件历史内容失败 $ip/$fileName/$historyId: $e'); + } + } + + /// 获取缓存的设备API响应 + static Future?> getCachedDeviceData(String ip) async { + try { + final cacheDir = await _getCacheDirectoryForDevice(ip); + if (!await cacheDir.exists()) { + return null; + } + + final result = {}; + + // 读取hosts文件列表 + final hostsListFile = File(path.join(cacheDir.path, 'hosts_list')); + if (await hostsListFile.exists()) { + final hostsListContent = await hostsListFile.readAsString(); + result['hostsList'] = hostsListContent; + } + + return result; + } catch (e) { + print('读取设备缓存数据失败 $ip: $e'); + return null; + } + } + + /// 获取特定hosts文件的缓存内容 + static Future getCachedHostsFileContent(String ip, String fileName) async { + try { + final cacheDir = await _getCacheDirectoryForDevice(ip); + final hostsFile = File(path.join(cacheDir.path, fileName, 'hosts')); + + if (await hostsFile.exists()) { + return await hostsFile.readAsString(); + } + + return null; + } catch (e) { + print('读取hosts文件缓存失败 $ip/$fileName: $e'); + return null; + } + } + + /// 获取特定hosts文件的历史版本内容 + static Future getCachedHostsFileHistoryContent(String ip, String fileName, String historyId) async { + try { + final cacheDir = await _getCacheDirectoryForDevice(ip); + final historyFile = File(path.join(cacheDir.path, fileName, 'history', historyId)); + + if (await historyFile.exists()) { + return await historyFile.readAsString(); + } + + return null; + } catch (e) { + print('读取hosts文件历史缓存失败 $ip/$fileName/$historyId: $e'); + return null; + } + } + + /// 清除设备API缓存 + static Future clearDeviceAPICache() async { + try { + final appSupportDir = await getApplicationSupportDirectory(); + final cacheDir = Directory(path.join(appSupportDir.path, 'cache')); + + if (await cacheDir.exists()) { + await cacheDir.delete(recursive: true); + print('设备API缓存已清除'); + } + } catch (e) { + print('清除设备API缓存失败: $e'); + } + } + + /// 清除特定设备的API缓存 + static Future clearDeviceAPIResponseCache(String ip) async { + try { + final cacheDir = await _getCacheDirectoryForDevice(ip); + if (await cacheDir.exists()) { + await cacheDir.delete(recursive: true); + print('设备 $ip 的API缓存已清除'); + } + } catch (e) { + print('清除设备 $ip 的API缓存失败: $e'); + } + } +} \ No newline at end of file diff --git a/lib/utils/nearby_devices_scanner.dart b/lib/utils/nearby_devices_scanner.dart index 1f13cad..19458ae 100644 --- a/lib/utils/nearby_devices_scanner.dart +++ b/lib/utils/nearby_devices_scanner.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:network_info_plus/network_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:hosts/utils/device_api_cache.dart'; /// 附近设备扫描器 class NearbyDevicesScanner { @@ -21,7 +22,7 @@ class NearbyDevicesScanner { // 是否已初始化(加载缓存) static bool _isInitialized = false; - + /// 实时扫描附近设备,发现设备时立即回调 static Future scanNearbyDevicesRealTime({ required Function(NearbyDevice) onDeviceFound, @@ -170,6 +171,11 @@ class NearbyDevicesScanner { // 连接成功,进一步验证是否是hosts服务器 final bool isHostsServer = await _verifyHostsServer(ip); + // 如果是hosts服务器,缓存API响应 + if (isHostsServer) { + await DeviceApiCache.cacheDeviceAPIResponses(ip); + } + return NearbyDevice( ip: ip, isReachable: true, @@ -369,6 +375,14 @@ class NearbyDevicesScanner { return _deviceCache.values.where((device) => !device.isOnline).toList(); } + + + + + + + + /// 清除设备缓存 static Future clearCache() async { _deviceCache.clear(); @@ -378,6 +392,10 @@ class NearbyDevicesScanner { final prefs = await SharedPreferences.getInstance(); await prefs.remove(_deviceCacheKey); await prefs.remove(_priorityIPsKey); + + // 清除API响应缓存 + await DeviceApiCache.clearDeviceAPICache(); + print('设备缓存已清除'); } catch (e) { print('清除设备缓存失败: $e'); From be41fbf6df3312e949da6c0dabb35cd1dc1fef5d Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Thu, 17 Jul 2025 00:59:33 +0800 Subject: [PATCH 25/54] =?UTF-8?q?feat(=E4=BE=9D=E8=B5=96&=E6=9E=84?= =?UTF-8?q?=E5=BB=BA):=20=E5=8D=87=E7=BA=A7Kotlin=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=B9=B6=E6=B7=BB=E5=8A=A0=E7=BD=91=E6=A0=BC=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 升级Kotlin版本至1.8.10 - 添加flutter_staggered_grid_view依赖用于网格布局 - 更新相关构建配置 --- android/build.gradle | 2 +- android/settings.gradle | 2 +- pubspec.lock | 8 ++++++++ pubspec.yaml | 1 + 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index ffe59ea..69776d8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,7 +4,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10' } } allprojects { diff --git a/android/settings.gradle b/android/settings.gradle index 1b73ac6..a4fea4d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,7 +19,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version '8.7.2' apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "org.jetbrains.kotlin.android" version "1.8.10" apply false } include ":app" diff --git a/pubspec.lock b/pubspec.lock index f3aa2ae..6998560 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -195,6 +195,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.28" + flutter_staggered_grid_view: + dependency: "direct main" + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.dev" + source: hosted + version: "0.7.0" flutter_test: dependency: "direct dev" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 82ccf6b..05a9865 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: # UI组件 cupertino_icons: ^1.0.8 syncfusion_flutter_datagrid: ^29.2.7+1 + flutter_staggered_grid_view: ^0.7.0 # 状态管理 flutter_bloc: ^9.1.1 From f12c44e86d3d7ae86b8012df581163779e334ad7 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Thu, 17 Jul 2025 01:21:32 +0800 Subject: [PATCH 26/54] =?UTF-8?q?refactor(=E9=99=84=E8=BF=91=E8=AE=BE?= =?UTF-8?q?=E5=A4=87):=20=E9=87=8D=E6=9E=84=E7=8A=B6=E6=80=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=9E=B6=E6=9E=84=E6=A8=A1=E4=BB=BFHomeCubit=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建独立的NearbyDevicesStateData数据类 - 使用sealed类设计模式定义不同状态类型 - 重构Cubit以使用新的状态架构 - 提供更好的类型安全和状态管理 - 保持与HomeCubit一致的代码结构 --- lib/server/bloc/nearby_devices_cubit.dart | 109 ++++++++++++------- lib/server/bloc/nearby_devices_state.dart | 125 +++++++++++++++++++--- 2 files changed, 181 insertions(+), 53 deletions(-) diff --git a/lib/server/bloc/nearby_devices_cubit.dart b/lib/server/bloc/nearby_devices_cubit.dart index a35ed88..d4774c1 100644 --- a/lib/server/bloc/nearby_devices_cubit.dart +++ b/lib/server/bloc/nearby_devices_cubit.dart @@ -1,24 +1,29 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; import 'package:hosts/utils/nearby_devices_scanner.dart'; part 'nearby_devices_state.dart'; /// 附近设备 Cubit class NearbyDevicesCubit extends Cubit { - NearbyDevicesCubit() : super(const NearbyDevicesState()); + NearbyDevicesCubit() : super(const NearbyDevicesInitial(NearbyDevicesStateData())); /// 加载缓存的设备 Future loadCachedDevices() async { try { - emit(state.copyWith(isLoading: true, errorMessage: null)); + emit(NearbyDevicesLoading( + state.data.copyWith(isLoading: true, errorMessage: null), + )); final cachedDevices = await NearbyDevicesScanner.getCachedDevices(); - emit(state.copyWith( - isLoading: false, - devices: cachedDevices, + emit(NearbyDevicesInitial( + state.data.copyWith( + isLoading: false, + devices: cachedDevices, + ), )); // 如果有缓存设备,自动检查在线状态 @@ -26,9 +31,11 @@ class NearbyDevicesCubit extends Cubit { checkDevicesOnlineStatus(); } } catch (e) { - emit(state.copyWith( - isLoading: false, - errorMessage: '加载缓存设备失败: $e', + emit(NearbyDevicesError( + state.data.copyWith( + isLoading: false, + errorMessage: '加载缓存设备失败: $e', + ), )); } } @@ -36,10 +43,12 @@ class NearbyDevicesCubit extends Cubit { /// 扫描附近设备 Future scanNearbyDevices() async { try { - emit(state.copyWith( - isScanning: true, - errorMessage: null, - devices: [], // 清空现有设备列表 + emit(NearbyDevicesScanning( + state.data.copyWith( + isScanning: true, + errorMessage: null, + devices: [], // 清空现有设备列表 + ), )); // 开始实时扫描 @@ -50,14 +59,18 @@ class NearbyDevicesCubit extends Cubit { }, ); - emit(state.copyWith( - isScanning: false, - successMessage: '扫描完成,发现${state.devices.length}个设备', + emit(NearbyDevicesSuccess( + state.data.copyWith( + isScanning: false, + successMessage: '扫描完成,发现${state.devices.length}个设备', + ), )); } catch (e) { - emit(state.copyWith( - isScanning: false, - errorMessage: '扫描附近设备失败: $e', + emit(NearbyDevicesError( + state.data.copyWith( + isScanning: false, + errorMessage: '扫描附近设备失败: $e', + ), )); } } @@ -67,21 +80,27 @@ class NearbyDevicesCubit extends Cubit { if (state.devices.isEmpty) return; try { - emit(state.copyWith(isCheckingStatus: true, errorMessage: null)); + emit(NearbyDevicesCheckingStatus( + state.data.copyWith(isCheckingStatus: true, errorMessage: null), + )); await NearbyDevicesScanner.checkCachedDevicesOnlineStatus(); // 重新加载更新后的设备列表 final updatedDevices = await NearbyDevicesScanner.getCachedDevices(); - emit(state.copyWith( - isCheckingStatus: false, - devices: updatedDevices, + emit(NearbyDevicesInitial( + state.data.copyWith( + isCheckingStatus: false, + devices: updatedDevices, + ), )); } catch (e) { - emit(state.copyWith( - isCheckingStatus: false, - errorMessage: '检查设备在线状态失败: $e', + emit(NearbyDevicesError( + state.data.copyWith( + isCheckingStatus: false, + errorMessage: '检查设备在线状态失败: $e', + ), )); } } @@ -91,13 +110,17 @@ class NearbyDevicesCubit extends Cubit { try { await NearbyDevicesScanner.clearCache(); - emit(state.copyWith( - devices: [], - successMessage: '设备缓存已清除', + emit(NearbyDevicesSuccess( + state.data.copyWith( + devices: [], + successMessage: '设备缓存已清除', + ), )); } catch (e) { - emit(state.copyWith( - errorMessage: '清除设备缓存失败: $e', + emit(NearbyDevicesError( + state.data.copyWith( + errorMessage: '清除设备缓存失败: $e', + ), )); } } @@ -114,14 +137,18 @@ class NearbyDevicesCubit extends Cubit { updatedDevices.add(device); } - emit(state.copyWith(devices: updatedDevices)); + emit(NearbyDevicesDeviceFound( + state.data.copyWith(devices: updatedDevices), + )); } /// 选择设备 void selectDevice(NearbyDevice? device) { - emit(state.copyWith( - selectedDevice: device, - clearSelectedDevice: device == null, + emit(NearbyDevicesSelectionChanged( + state.data.copyWith( + selectedDevice: device, + clearSelectedDevice: device == null, + ), )); } @@ -144,17 +171,19 @@ class NearbyDevicesCubit extends Cubit { /// 获取设备状态摘要(供其他组件使用) Map getDevicesSummary() { return { - 'total': state.totalDevicesCount, - 'online': state.onlineDevicesCount, - 'offline': state.totalDevicesCount - state.onlineDevicesCount, + 'total': state.data.totalDevicesCount, + 'online': state.data.onlineDevicesCount, + 'offline': state.data.totalDevicesCount - state.data.onlineDevicesCount, }; } /// 清除消息 void clearMessages() { - emit(state.copyWith( - errorMessage: null, - successMessage: null, + emit(NearbyDevicesInitial( + state.data.copyWith( + errorMessage: null, + successMessage: null, + ), )); } } \ No newline at end of file diff --git a/lib/server/bloc/nearby_devices_state.dart b/lib/server/bloc/nearby_devices_state.dart index c0d23c4..84e69ff 100644 --- a/lib/server/bloc/nearby_devices_state.dart +++ b/lib/server/bloc/nearby_devices_state.dart @@ -1,8 +1,31 @@ part of 'nearby_devices_cubit.dart'; -/// 附近设备状态 -class NearbyDevicesState { - const NearbyDevicesState({ +/// 附近设备状态数据类 +/// 保存所有与附近设备相关的状态属性 +class NearbyDevicesStateData { + /// 设备列表 + final List devices; + + /// 当前选中的设备 + final NearbyDevice? selectedDevice; + + /// 是否正在扫描 + final bool isScanning; + + /// 是否正在检查状态 + final bool isCheckingStatus; + + /// 是否正在加载 + final bool isLoading; + + /// 错误信息 + final String? errorMessage; + + /// 成功信息 + final String? successMessage; + + /// 构造函数 + const NearbyDevicesStateData({ this.devices = const [], this.selectedDevice, this.isScanning = false, @@ -12,14 +35,6 @@ class NearbyDevicesState { this.successMessage, }); - final List devices; - final NearbyDevice? selectedDevice; - final bool isScanning; - final bool isCheckingStatus; - final bool isLoading; - final String? errorMessage; - final String? successMessage; - /// 获取在线设备数量 int get onlineDevicesCount => devices.where((device) => device.isOnline).length; @@ -32,7 +47,9 @@ class NearbyDevicesState { /// 获取离线设备列表 List get offlineDevices => devices.where((device) => !device.isOnline).toList(); - NearbyDevicesState copyWith({ + /// 复制方法 + /// 用于基于当前状态创建新状态 + NearbyDevicesStateData copyWith({ List? devices, NearbyDevice? selectedDevice, bool clearSelectedDevice = false, @@ -42,7 +59,7 @@ class NearbyDevicesState { String? errorMessage, String? successMessage, }) { - return NearbyDevicesState( + return NearbyDevicesStateData( devices: devices ?? this.devices, selectedDevice: clearSelectedDevice ? null : (selectedDevice ?? this.selectedDevice), isScanning: isScanning ?? this.isScanning, @@ -52,5 +69,87 @@ class NearbyDevicesState { successMessage: successMessage, ); } +} + +/// 附近设备状态基类 +/// 使用密封类设计模式限制状态类型 +/// 不可变状态基类 +@immutable +sealed class NearbyDevicesState { + final NearbyDevicesStateData data; + + const NearbyDevicesState(this.data); + + /// 获取在线设备数量 + int get onlineDevicesCount => data.onlineDevicesCount; + + /// 获取总设备数量 + int get totalDevicesCount => data.totalDevicesCount; + + /// 获取在线设备列表 + List get onlineDevices => data.onlineDevices; + + /// 获取离线设备列表 + List get offlineDevices => data.offlineDevices; + + /// 设备列表 + List get devices => data.devices; + + /// 当前选中的设备 + NearbyDevice? get selectedDevice => data.selectedDevice; + + /// 是否正在扫描 + bool get isScanning => data.isScanning; + + /// 是否正在检查状态 + bool get isCheckingStatus => data.isCheckingStatus; + + /// 是否正在加载 + bool get isLoading => data.isLoading; + + /// 错误信息 + String? get errorMessage => data.errorMessage; + + /// 成功信息 + String? get successMessage => data.successMessage; +} + +/// 初始状态 +class NearbyDevicesInitial extends NearbyDevicesState { + const NearbyDevicesInitial(super.data); +} + +/// 扫描中状态 +class NearbyDevicesScanning extends NearbyDevicesState { + const NearbyDevicesScanning(super.data); +} + +/// 检查状态中状态 +class NearbyDevicesCheckingStatus extends NearbyDevicesState { + const NearbyDevicesCheckingStatus(super.data); +} + +/// 加载中状态 +class NearbyDevicesLoading extends NearbyDevicesState { + const NearbyDevicesLoading(super.data); +} + +/// 设备发现状态 +class NearbyDevicesDeviceFound extends NearbyDevicesState { + const NearbyDevicesDeviceFound(super.data); +} + +/// 设备选择变更状态 +class NearbyDevicesSelectionChanged extends NearbyDevicesState { + const NearbyDevicesSelectionChanged(super.data); +} + +/// 错误状态 +class NearbyDevicesError extends NearbyDevicesState { + const NearbyDevicesError(super.data); +} +/// 成功状态 +class NearbyDevicesSuccess extends NearbyDevicesState { + const NearbyDevicesSuccess(super.data); } \ No newline at end of file From 607c5dc46377e4d33a1e8834ef6f1ea8feb6b8ec Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Thu, 17 Jul 2025 01:23:05 +0800 Subject: [PATCH 27/54] =?UTF-8?q?refactor(UI):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=8A=BD=E5=B1=89=E5=AF=BC=E8=88=AA=E4=BB=A5=E9=80=82=E9=85=8D?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E9=99=84=E8=BF=91=E8=AE=BE=E5=A4=87=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新BlocBuilder以使用新的NearbyDevicesState架构 - 保持设备选择和显示功能正常工作 - 适配sealed状态类的使用模式 --- lib/home/view/home_drawer.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart index 418c1c3..ea79ba0 100644 --- a/lib/home/view/home_drawer.dart +++ b/lib/home/view/home_drawer.dart @@ -31,6 +31,10 @@ class HomeDrawer extends StatelessWidget { BlocBuilder( builder: (context, state) { print(state.devices); + if (state is NearbyDevicesSelectionChanged) { + print( + "state.selectedDevice = ${state.selectedDevice}"); + } if (state.devices.isEmpty) { return Text( @@ -48,7 +52,13 @@ class HomeDrawer extends StatelessWidget { context .read() .toggleDeviceSelection(device); + + return; } + + context + .read() + .selectDevice(null); }, itemBuilder: (BuildContext context) { final List> items = []; @@ -159,7 +169,7 @@ class HomeDrawer extends StatelessWidget { .titleMedium, ), Text( - state.selectedDevice?.ip??"本地", + state.selectedDevice?.ip ?? "本地", style: Theme.of(context).textTheme.bodySmall, ) From 7e4c7d34efe61b7da4a2f1ed406e46b574524d33 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Fri, 18 Jul 2025 01:10:00 +0800 Subject: [PATCH 28/54] =?UTF-8?q?feat(=E8=BF=9C=E7=A8=8B=E8=AE=BE=E5=A4=87?= =?UTF-8?q?&=E7=BC=93=E5=AD=98):=20=E5=AE=9E=E7=8E=B0=E8=BF=9C=E7=A8=8B?= =?UTF-8?q?=E8=AE=BE=E5=A4=87hosts=E6=96=87=E4=BB=B6=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在HomeCubit中添加loadRemoteHostFiles方法支持远程设备数据加载 - 优化设备API缓存,只保存fileName和remark字段 - 修复设备选择逻辑,避免重复加载相同设备数据 - 添加设备选择状态管理,支持本地/远程模式切换 - 改进缓存数据结构,提高性能和可维护性 --- lib/home/cubit/home_cubit.dart | 52 +++++++-- lib/home/view/home_drawer.dart | 24 ++++- lib/server/bloc/nearby_devices_cubit.dart | 1 + lib/utils/device_api_cache.dart | 124 +++++++++++++--------- 4 files changed, 135 insertions(+), 66 deletions(-) diff --git a/lib/home/cubit/home_cubit.dart b/lib/home/cubit/home_cubit.dart index 4de2b35..aa2af4f 100755 --- a/lib/home/cubit/home_cubit.dart +++ b/lib/home/cubit/home_cubit.dart @@ -6,6 +6,8 @@ import 'package:hosts/model/simple_host_file.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/util/settings_manager.dart'; import 'package:hosts/util/string_util.dart'; +import 'package:hosts/utils/device_api_cache.dart'; +import 'package:hosts/utils/nearby_devices_scanner.dart'; part 'home_state.dart'; @@ -30,7 +32,7 @@ class HomeCubit extends Cubit { List tempHostFiles = []; List tempSelectHostFiles = []; List hostConfigs = - await _settingsManager.getList(settingKeyHostConfigs); + await _settingsManager.getList(settingKeyHostConfigs); if (isInit) { // TODO 支持多个文件选择 @@ -49,7 +51,7 @@ class HomeCubit extends Cubit { } final fileId = - tempSelectHostFiles.isNotEmpty ? tempSelectHostFiles.first : "system"; + tempSelectHostFiles.isNotEmpty ? tempSelectHostFiles.first : "system"; emit( HomeInitial( @@ -72,14 +74,14 @@ class HomeCubit extends Cubit { // 获取当前 hostFiles 列表 List currentHostFiles = List.from(state.data.hostFiles); List hostConfigs = - await _settingsManager.getList(settingKeyHostConfigs); + await _settingsManager.getList(settingKeyHostConfigs); // 生成随机文件名 final String fileName = generateRandomString(18); // 创建新的 hostFile SimpleHostFile newHostFile = - SimpleHostFile(fileName: fileName, remark: remark); + SimpleHostFile(fileName: fileName, remark: remark); // 添加到 hostFiles 列表 currentHostFiles.add(newHostFile); @@ -109,7 +111,7 @@ class HomeCubit extends Cubit { List updatedHostFiles = []; List hostConfigs = - await _settingsManager.getList(settingKeyHostConfigs); + await _settingsManager.getList(settingKeyHostConfigs); bool updated = false; @@ -187,7 +189,7 @@ class HomeCubit extends Cubit { .toList(); List hostConfigs = - await _settingsManager.getList(settingKeyHostConfigs); + await _settingsManager.getList(settingKeyHostConfigs); hostConfigs.removeWhere((config) => config['fileName'] == fileName); @@ -221,7 +223,9 @@ class HomeCubit extends Cubit { /// 使用host文件 /// [fileName] 要使用的文件名 - Future useHost(String fileName) async {} + Future useHost(String fileName) async { + // TODO + } /// 切换编辑模式 /// [editMode] 新的编辑模式(表格/文本) @@ -252,9 +256,9 @@ class HomeCubit extends Cubit { /// 在Open和Close之间切换 Future toggleAdvancedSettingsSwitch() async { final AdvancedSettingsEnum newSettings = - state.data.advancedSettingsEnum == AdvancedSettingsEnum.Close - ? AdvancedSettingsEnum.Open - : AdvancedSettingsEnum.Close; + state.data.advancedSettingsEnum == AdvancedSettingsEnum.Close + ? AdvancedSettingsEnum.Open + : AdvancedSettingsEnum.Close; await toggleAdvancedSettings(newSettings); } @@ -273,7 +277,8 @@ class HomeCubit extends Cubit { // 特殊处理system文件的remark if (hostFile.fileName == "system") { if (context != null) { - hostFile.remark = gen.AppLocalizations.of(context)!.default_hosts_text; + hostFile.remark = + gen.AppLocalizations.of(context)!.default_hosts_text; } else { hostFile.remark = "默认"; // 后备文本 } @@ -288,4 +293,29 @@ class HomeCubit extends Cubit { ), ); } + + void loadRemoteHostFiles(BuildContext context, NearbyDevice device) async { + List tempHostFiles = []; + + final hostConfigs = await DeviceApiCache.getCachedDeviceData(device.ip); + + for (Map config in hostConfigs) { + SimpleHostFile hostFile = SimpleHostFile.fromJson(config); + tempHostFiles.add(hostFile); + + if (hostFile.fileName == "system") { + hostFile.remark = gen.AppLocalizations.of(context)!.default_hosts_text; + } + } + + emit( + HomeInitial( + HomeStateData( + hostFiles: tempHostFiles, + useHostFiles: [], + editMode: EditMode.Table, + ), + ), + ); + } } diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart index ea79ba0..29bcf85 100644 --- a/lib/home/view/home_drawer.dart +++ b/lib/home/view/home_drawer.dart @@ -7,6 +7,7 @@ import 'package:hosts/model/simple_host_file.dart'; import 'package:hosts/server/bloc/nearby_devices_cubit.dart'; import 'package:hosts/server/view/server_settings_page.dart'; import 'package:hosts/util/file_manager.dart'; +import 'package:hosts/utils/nearby_devices_scanner.dart'; import 'package:hosts/widget/dialog/dialog.dart'; import 'package:hosts/widget/dialog/export_hosts_dialog.dart'; import 'package:hosts/widget/dialog/import_hosts_dialog.dart'; @@ -17,6 +18,8 @@ class HomeDrawer extends StatelessWidget { @override Widget build(BuildContext context) { + NearbyDevice? selectedDevice; + return BlocBuilder( builder: (context, state) { return Drawer( @@ -30,10 +33,20 @@ class HomeDrawer extends StatelessWidget { children: [ BlocBuilder( builder: (context, state) { - print(state.devices); + // print(state.devices); if (state is NearbyDevicesSelectionChanged) { - print( - "state.selectedDevice = ${state.selectedDevice}"); + if (state.selectedDevice != null) { + if (selectedDevice != state.selectedDevice) { + context.read().loadRemoteHostFiles( + context, + state.selectedDevice!, + ); + } + } else { + context.read().loadHostFiles(context); + } + + selectedDevice = state.selectedDevice; } if (state.devices.isEmpty) { @@ -63,7 +76,10 @@ class HomeDrawer extends StatelessWidget { itemBuilder: (BuildContext context) { final List> items = []; items.add( - const PopupMenuItem(child: Text('本地')), + const PopupMenuItem( + value: "local", + child: Text('本地'), + ), ); // 只显示设备列表 for (final device in state.devices) { diff --git a/lib/server/bloc/nearby_devices_cubit.dart b/lib/server/bloc/nearby_devices_cubit.dart index d4774c1..be0cc7f 100644 --- a/lib/server/bloc/nearby_devices_cubit.dart +++ b/lib/server/bloc/nearby_devices_cubit.dart @@ -144,6 +144,7 @@ class NearbyDevicesCubit extends Cubit { /// 选择设备 void selectDevice(NearbyDevice? device) { + print("selectDevice = $device"); emit(NearbyDevicesSelectionChanged( state.data.copyWith( selectedDevice: device, diff --git a/lib/utils/device_api_cache.dart b/lib/utils/device_api_cache.dart index eaef7f8..a3bece7 100644 --- a/lib/utils/device_api_cache.dart +++ b/lib/utils/device_api_cache.dart @@ -1,8 +1,9 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:path_provider/path_provider.dart'; + import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; /// 设备API缓存工具类 class DeviceApiCache { @@ -15,10 +16,10 @@ class DeviceApiCache { // 创建缓存目录 final cacheDir = await _getCacheDirectoryForDevice(ip); await cacheDir.create(recursive: true); - + // 缓存hosts文件列表 await _cacheHostsFilesList(ip, cacheDir); - + print('设备 $ip 的API响应已缓存'); } catch (e) { print('缓存设备 $ip 的API响应失败: $e'); @@ -32,29 +33,37 @@ class DeviceApiCache { } /// 缓存hosts文件列表 - static Future _cacheHostsFilesList(String ip, Directory cacheDir) async { + static Future _cacheHostsFilesList( + String ip, Directory cacheDir) async { try { final httpClient = HttpClient(); httpClient.connectionTimeout = _scanTimeout; httpClient.idleTimeout = _scanTimeout; - + final request = await httpClient.get(ip, _defaultPort, '/api/hosts'); final response = await request.close(); - + if (response.statusCode == 200) { final responseBody = await response.transform(utf8.decoder).join(); final hostsListFile = File(path.join(cacheDir.path, 'hosts_list')); - - // 解析响应并只保存data数组 + + // 解析响应并只保存fileName和remark字段 try { final responseData = jsonDecode(responseBody); if (responseData['success'] == true) { final List hostsList = responseData['data']; - - // 只保存data数组 - await hostsListFile.writeAsString(jsonEncode(hostsList)); + + // 只保存fileName和remark字段 + final List> filteredHostsList = hostsList + .map((host) => { + 'fileName': host['fileName'], + 'remark': host['remark'] ?? '', + }) + .toList(); + + await hostsListFile.writeAsString(jsonEncode(filteredHostsList)); print('hosts文件列表已缓存: $ip'); - + // 进一步缓存每个文件的详细信息 for (final hosts in hostsList) { final fileName = hosts['fileName']; @@ -68,7 +77,7 @@ class DeviceApiCache { print('解析hosts文件列表失败 $ip: $e'); } } - + httpClient.close(); } catch (e) { print('缓存hosts文件列表失败 $ip: $e'); @@ -76,25 +85,27 @@ class DeviceApiCache { } /// 缓存hosts文件内容 - static Future _cacheHostsFileContent(String ip, String fileName, Directory cacheDir) async { + static Future _cacheHostsFileContent( + String ip, String fileName, Directory cacheDir) async { try { final httpClient = HttpClient(); httpClient.connectionTimeout = _scanTimeout; httpClient.idleTimeout = _scanTimeout; - - final request = await httpClient.get(ip, _defaultPort, '/api/hosts/$fileName'); + + final request = + await httpClient.get(ip, _defaultPort, '/api/hosts/$fileName'); final response = await request.close(); - + if (response.statusCode == 200) { final responseBody = await response.transform(utf8.decoder).join(); final fileDir = Directory(path.join(cacheDir.path, fileName)); await fileDir.create(recursive: true); - + final hostsFile = File(path.join(fileDir.path, 'hosts')); await hostsFile.writeAsString(responseBody); print('hosts文件内容已缓存: $ip/$fileName'); } - + httpClient.close(); } catch (e) { print('缓存hosts文件内容失败 $ip/$fileName: $e'); @@ -102,22 +113,25 @@ class DeviceApiCache { } /// 缓存hosts文件历史 - static Future _cacheHostsFileHistory(String ip, String fileName, Directory cacheDir) async { + static Future _cacheHostsFileHistory( + String ip, String fileName, Directory cacheDir) async { try { final httpClient = HttpClient(); httpClient.connectionTimeout = _scanTimeout; httpClient.idleTimeout = _scanTimeout; - - final request = await httpClient.get(ip, _defaultPort, '/api/hosts/$fileName/history'); + + final request = await httpClient.get( + ip, _defaultPort, '/api/hosts/$fileName/history'); final response = await request.close(); - + if (response.statusCode == 200) { final responseBody = await response.transform(utf8.decoder).join(); - final historyDir = Directory(path.join(cacheDir.path, fileName, 'history')); + final historyDir = + Directory(path.join(cacheDir.path, fileName, 'history')); await historyDir.create(recursive: true); - + print('hosts文件历史已缓存: $ip/$fileName'); - + // 缓存每个历史版本的内容 try { final responseData = jsonDecode(responseBody); @@ -126,7 +140,8 @@ class DeviceApiCache { for (final historyItem in historyList) { final historyId = historyItem['id']; if (historyId != null) { - await _cacheHostsFileHistoryContent(ip, fileName, historyId, historyDir); + await _cacheHostsFileHistoryContent( + ip, fileName, historyId, historyDir); } } } @@ -134,7 +149,7 @@ class DeviceApiCache { print('解析hosts文件历史失败 $ip/$fileName: $e'); } } - + httpClient.close(); } catch (e) { print('缓存hosts文件历史失败 $ip/$fileName: $e'); @@ -142,22 +157,24 @@ class DeviceApiCache { } /// 缓存hosts文件历史内容 - static Future _cacheHostsFileHistoryContent(String ip, String fileName, String historyId, Directory historyDir) async { + static Future _cacheHostsFileHistoryContent(String ip, String fileName, + String historyId, Directory historyDir) async { try { final httpClient = HttpClient(); httpClient.connectionTimeout = _scanTimeout; httpClient.idleTimeout = _scanTimeout; - - final request = await httpClient.get(ip, _defaultPort, '/api/hosts/$fileName/history/$historyId'); + + final request = await httpClient.get( + ip, _defaultPort, '/api/hosts/$fileName/history/$historyId'); final response = await request.close(); - + if (response.statusCode == 200) { final responseBody = await response.transform(utf8.decoder).join(); final historyContentFile = File(path.join(historyDir.path, historyId)); await historyContentFile.writeAsString(responseBody); print('hosts文件历史内容已缓存: $ip/$fileName/$historyId'); } - + httpClient.close(); } catch (e) { print('缓存hosts文件历史内容失败 $ip/$fileName/$historyId: $e'); @@ -165,39 +182,42 @@ class DeviceApiCache { } /// 获取缓存的设备API响应 - static Future?> getCachedDeviceData(String ip) async { + static Future>> getCachedDeviceData( + String ip) async { try { final cacheDir = await _getCacheDirectoryForDevice(ip); if (!await cacheDir.exists()) { - return null; + return []; } - - final result = {}; - + + List> result = []; + // 读取hosts文件列表 final hostsListFile = File(path.join(cacheDir.path, 'hosts_list')); if (await hostsListFile.exists()) { - final hostsListContent = await hostsListFile.readAsString(); - result['hostsList'] = hostsListContent; + result = (jsonDecode(hostsListFile.readAsStringSync()) as List) + .map((it) => it as Map) + .toList(); } - + return result; } catch (e) { print('读取设备缓存数据失败 $ip: $e'); - return null; + return []; } } /// 获取特定hosts文件的缓存内容 - static Future getCachedHostsFileContent(String ip, String fileName) async { + static Future getCachedHostsFileContent( + String ip, String fileName) async { try { final cacheDir = await _getCacheDirectoryForDevice(ip); final hostsFile = File(path.join(cacheDir.path, fileName, 'hosts')); - + if (await hostsFile.exists()) { return await hostsFile.readAsString(); } - + return null; } catch (e) { print('读取hosts文件缓存失败 $ip/$fileName: $e'); @@ -206,15 +226,17 @@ class DeviceApiCache { } /// 获取特定hosts文件的历史版本内容 - static Future getCachedHostsFileHistoryContent(String ip, String fileName, String historyId) async { + static Future getCachedHostsFileHistoryContent( + String ip, String fileName, String historyId) async { try { final cacheDir = await _getCacheDirectoryForDevice(ip); - final historyFile = File(path.join(cacheDir.path, fileName, 'history', historyId)); - + final historyFile = + File(path.join(cacheDir.path, fileName, 'history', historyId)); + if (await historyFile.exists()) { return await historyFile.readAsString(); } - + return null; } catch (e) { print('读取hosts文件历史缓存失败 $ip/$fileName/$historyId: $e'); @@ -227,7 +249,7 @@ class DeviceApiCache { try { final appSupportDir = await getApplicationSupportDirectory(); final cacheDir = Directory(path.join(appSupportDir.path, 'cache')); - + if (await cacheDir.exists()) { await cacheDir.delete(recursive: true); print('设备API缓存已清除'); @@ -249,4 +271,4 @@ class DeviceApiCache { print('清除设备 $ip 的API缓存失败: $e'); } } -} \ No newline at end of file +} From fdce8d93db202f84c4ce9aaa46b1c80a3b9936ce Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Sun, 20 Jul 2025 19:24:29 +0800 Subject: [PATCH 29/54] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E9=99=84?= =?UTF-8?q?=E8=BF=91=E8=AE=BE=E5=A4=87=E5=8A=9F=E8=83=BD=E5=B9=B6=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E8=AE=BE=E5=A4=87=E7=BC=93=E5=AD=98=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构 NearbyDevicesCubit 和相关组件,移除设备缓存加载和状态检查功能。优化设备扫描逻辑,添加后台服务支持。重构设备API访问方式,改为直接访问而非缓存。新增设备访问对话框,改进用户体验。添加多语言支持并优化UI显示。这些改动简化了代码结构,提高了性能,并改善了设备交互流程。 --- lib/home/cubit/home_cubit.dart | 35 +- lib/home/view/home_drawer.dart | 477 ++++++-------------- lib/home/view/home_page.dart | 2 +- lib/l10n/app_en.arb | 16 +- lib/l10n/app_localizations.dart | 78 ++++ lib/l10n/app_localizations_en.dart | 40 ++ lib/l10n/app_localizations_zh.dart | 39 ++ lib/l10n/app_zh.arb | 16 +- lib/main.dart | 20 + lib/server/bloc/nearby_devices_cubit.dart | 145 +----- lib/server/bloc/nearby_devices_state.dart | 43 -- lib/server/view/nearby_devices_card.dart | 77 +--- lib/server/view/server_settings_page.dart | 2 +- lib/server/view/server_status_card.dart | 4 +- lib/services/background_service.dart | 58 +++ lib/utils/device_api_cache.dart | 218 ++------- lib/utils/nearby_devices_scanner.dart | 10 - lib/widget/dialog/access_device_dialog.dart | 426 +++++++++++++++++ pubspec.lock | 8 + pubspec.yaml | 3 + 20 files changed, 922 insertions(+), 795 deletions(-) create mode 100644 lib/services/background_service.dart create mode 100644 lib/widget/dialog/access_device_dialog.dart diff --git a/lib/home/cubit/home_cubit.dart b/lib/home/cubit/home_cubit.dart index aa2af4f..064dba8 100755 --- a/lib/home/cubit/home_cubit.dart +++ b/lib/home/cubit/home_cubit.dart @@ -6,8 +6,6 @@ import 'package:hosts/model/simple_host_file.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/util/settings_manager.dart'; import 'package:hosts/util/string_util.dart'; -import 'package:hosts/utils/device_api_cache.dart'; -import 'package:hosts/utils/nearby_devices_scanner.dart'; part 'home_state.dart'; @@ -266,6 +264,10 @@ class HomeCubit extends Cubit { /// 刷新hosts文件列表 /// [context] 可选的context参数,用于system文件的本地化 Future refreshHostFiles([BuildContext? context]) async { + final String? defaultHostsText = context != null + ? gen.AppLocalizations.of(context)!.default_hosts_text + : null; + List tempHostFiles = []; List hostConfigs = await _settingsManager.getList(settingKeyHostConfigs); @@ -276,9 +278,8 @@ class HomeCubit extends Cubit { // 特殊处理system文件的remark if (hostFile.fileName == "system") { - if (context != null) { - hostFile.remark = - gen.AppLocalizations.of(context)!.default_hosts_text; + if (defaultHostsText != null) { + hostFile.remark = defaultHostsText; } else { hostFile.remark = "默认"; // 后备文本 } @@ -294,28 +295,4 @@ class HomeCubit extends Cubit { ); } - void loadRemoteHostFiles(BuildContext context, NearbyDevice device) async { - List tempHostFiles = []; - - final hostConfigs = await DeviceApiCache.getCachedDeviceData(device.ip); - - for (Map config in hostConfigs) { - SimpleHostFile hostFile = SimpleHostFile.fromJson(config); - tempHostFiles.add(hostFile); - - if (hostFile.fileName == "system") { - hostFile.remark = gen.AppLocalizations.of(context)!.default_hosts_text; - } - } - - emit( - HomeInitial( - HomeStateData( - hostFiles: tempHostFiles, - useHostFiles: [], - editMode: EditMode.Table, - ), - ), - ); - } } diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart index 29bcf85..0460340 100644 --- a/lib/home/view/home_drawer.dart +++ b/lib/home/view/home_drawer.dart @@ -4,10 +4,8 @@ import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/simple_host_file.dart'; -import 'package:hosts/server/bloc/nearby_devices_cubit.dart'; import 'package:hosts/server/view/server_settings_page.dart'; import 'package:hosts/util/file_manager.dart'; -import 'package:hosts/utils/nearby_devices_scanner.dart'; import 'package:hosts/widget/dialog/dialog.dart'; import 'package:hosts/widget/dialog/export_hosts_dialog.dart'; import 'package:hosts/widget/dialog/import_hosts_dialog.dart'; @@ -18,8 +16,6 @@ class HomeDrawer extends StatelessWidget { @override Widget build(BuildContext context) { - NearbyDevice? selectedDevice; - return BlocBuilder( builder: (context, state) { return Drawer( @@ -31,172 +27,9 @@ class HomeDrawer extends StatelessWidget { padding: const EdgeInsets.all(16), child: Row( children: [ - BlocBuilder( - builder: (context, state) { - // print(state.devices); - if (state is NearbyDevicesSelectionChanged) { - if (state.selectedDevice != null) { - if (selectedDevice != state.selectedDevice) { - context.read().loadRemoteHostFiles( - context, - state.selectedDevice!, - ); - } - } else { - context.read().loadHostFiles(context); - } - - selectedDevice = state.selectedDevice; - } - - if (state.devices.isEmpty) { - return Text( - AppLocalizations.of(context)!.app_name, - style: Theme.of(context).textTheme.titleLarge, - ); - } - return PopupMenuButton( - onSelected: (value) { - // 只处理设备点击,不添加其他功能 - if (value.startsWith('device_')) { - final ip = value.substring(7); - final device = - state.devices.firstWhere((d) => d.ip == ip); - context - .read() - .toggleDeviceSelection(device); - - return; - } - - context - .read() - .selectDevice(null); - }, - itemBuilder: (BuildContext context) { - final List> items = []; - items.add( - const PopupMenuItem( - value: "local", - child: Text('本地'), - ), - ); - // 只显示设备列表 - for (final device in state.devices) { - items.add( - PopupMenuItem( - value: 'device_${device.ip}', - child: Row( - children: [ - Stack( - children: [ - Icon( - device.hasSharing - ? Icons.share - : Icons.computer, - color: device.isOnline - ? (device.hasSharing - ? Colors.green - : Colors.orange) - : Colors.grey, - size: 20, - ), - if (device.isOnline) - Positioned( - right: 0, - bottom: 0, - child: Container( - width: 6, - height: 6, - decoration: BoxDecoration( - color: Colors.green, - shape: BoxShape.circle, - border: Border.all( - color: Colors.white, - width: 1), - ), - ), - ), - ], - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - device.ip, - style: TextStyle( - fontWeight: FontWeight.w500, - color: device.isOnline - ? null - : Colors.grey, - ), - ), - Text( - device.isOnline - ? (device.hasSharing - ? '可访问' - : '在线') - : '离线', - style: TextStyle( - fontSize: 12, - color: device.isOnline - ? (device.hasSharing - ? Colors.green - : Colors.orange) - : Colors.grey, - ), - ), - ], - ), - ), - if (device.hasSharing && - device.isOnline) - Icon( - state.selectedDevice?.ip == - device.ip - ? Icons.check_circle - : Icons.radio_button_unchecked, - size: 16, - color: state.selectedDevice?.ip == - device.ip - ? Colors.green - : Colors.grey, - ), - ], - ), - ), - ); - } - - return items; - }, - child: Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.app_name, - style: Theme.of(context) - .textTheme - .titleMedium, - ), - Text( - state.selectedDevice?.ip ?? "本地", - style: - Theme.of(context).textTheme.bodySmall, - ) - ], - ), - const SizedBox(width: 4), - const Icon(Icons.arrow_drop_down), - ], - ), - ); - }, + Text( + AppLocalizations.of(context)!.app_name, + style: Theme.of(context).textTheme.titleLarge, ), Spacer(), IconButton( @@ -206,72 +39,7 @@ class HomeDrawer extends StatelessWidget { context.read().addHostFile(remark); }, icon: const Icon(Icons.add)), - PopupMenuButton( - icon: const Icon(Icons.more_vert), - onSelected: (value) async { - switch (value) { - case 1: - // Import functionality - await importHostsDialog( - context, state.data.hostFiles, - onImportSuccess: () { - context - .read() - .refreshHostFiles(context); - }); - break; - case 2: - // Export functionality - await exportHostsDialog( - context, state.data.hostFiles); - break; - case 3: - // Remote sync functionality - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ServerSettingsPage(), - ), - ); - break; - } - }, - itemBuilder: (BuildContext context) { - return [ - PopupMenuItem( - value: 1, - child: Row( - children: [ - const Icon(Icons.file_upload), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.import), - ], - ), - ), - PopupMenuItem( - value: 2, - child: Row( - children: [ - const Icon(Icons.file_download), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)!.export), - ], - ), - ), - PopupMenuItem( - value: 3, - child: Row( - children: [ - const Icon(Icons.cloud_sync), - const SizedBox(width: 8), - Text(AppLocalizations.of(context)! - .remote_sync), - ], - ), - ), - ]; - }, - ) + _buildOptionsMenu(context, state), ], ), ), @@ -341,9 +109,7 @@ class HomeDrawer extends StatelessWidget { return; } - context - .read() - .selectHost(hostFile.fileName); + context.read().selectHost(hostFile.fileName); }, ); }, @@ -357,6 +123,72 @@ class HomeDrawer extends StatelessWidget { ); } + Widget _buildOptionsMenu(BuildContext context, HomeState state) { + return PopupMenuButton( + icon: const Icon(Icons.more_vert), + onSelected: (value) async { + switch (value) { + case 1: + // Import functionality + await importHostsDialog(context, state.data.hostFiles, + onImportSuccess: () { + context.read().refreshHostFiles(context); + }); + break; + case 2: + // Export functionality + await exportHostsDialog(context, state.data.hostFiles); + break; + case 3: + // Remote sync functionality + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ServerSettingsPage(), + ), + ); + + context.read().refreshHostFiles(context); + break; + } + }, + itemBuilder: (BuildContext context) { + return [ + PopupMenuItem( + value: 1, + child: Row( + children: [ + const Icon(Icons.file_upload), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.import), + ], + ), + ), + PopupMenuItem( + value: 2, + child: Row( + children: [ + const Icon(Icons.file_download), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.export), + ], + ), + ), + PopupMenuItem( + value: 3, + child: Row( + children: [ + const Icon(Icons.cloud_sync), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.remote_sync), + ], + ), + ), + ]; + }, + ); + } + Widget buildMoreButton(SimpleHostFile hostFile) { if (hostFile.fileName == "system") { return const SizedBox(); @@ -364,112 +196,75 @@ class HomeDrawer extends StatelessWidget { return BlocBuilder( builder: (context, state) { - final homeCubit = context.read(); - - return PopupMenuButton( - style: OutlinedButton.styleFrom( - minimumSize: Size.zero, - padding: EdgeInsets.zero, - ), - onSelected: (value) async { - switch (value) { - case 1: - String result = - (await hostConfigDialog(context, hostFile.remark) ?? ""); - if (result.isEmpty) return; - homeCubit.updateHostFileRemark(hostFile.fileName, result); - break; - case 2: - deleteMultiple(context, [hostFile.remark], () async { - homeCubit.deleteHostFile(hostFile.fileName); - // 判断是否删除使用的 Host 文件 - // final bool isDeleteUse = list - // .where( - // (host) => state.useHostFiles.contains(host.fileName)) - // .isNotEmpty; - // 判断是否删除选择的 Host 文件 - // final bool isDeleteSelect = list - // .where((host) => state.selectHostFile == host.fileName) - // .isNotEmpty; - // - // if (isDeleteUse) { - // final String path = - // await _fileManager.getHostsFilePath("system"); - // - // if (!await widget - // .onClickUse(File(path).readAsStringSync())) { - // return; - // } - // await _settingsManager.setString( - // settingKeyUseHostFile, "system"); - // useHostFile = "system"; - // selectHostFile = "system"; - // } - // - // if (isDeleteSelect) { - // homeCubit.selectHost("system"); - // } + return _buildHostFileOptionsMenu(context, hostFile); + }, + ); + } - // homeCubit.deleteHostFile(hostFile.fileName); - // - // setState(() { - // hostFiles.removeWhere((hostFile) => list.contains(hostFile)); - // }); - // await _settingsManager.setList(settingKeyHostConfigs, hostFiles); - // widget.onChanged( - // await _fileManager.getHostsFilePath(selectHostFile!), - // selectHostFile!); - // _fileManager - // .deleteFiles(list.map((file) => file.fileName).toList()); - }); - break; - case 3: - final FileManager fileManager = FileManager(); - final bool success = await fileManager.exportMultipleHostFiles( - [hostFile], AppLocalizations.of(context)!.export_data); - if (success) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text(AppLocalizations.of(context)!.export_success)), - ); - } - break; + Widget _buildHostFileOptionsMenu(BuildContext context, SimpleHostFile hostFile) { + final homeCubit = context.read(); + + return PopupMenuButton( + style: OutlinedButton.styleFrom( + minimumSize: Size.zero, + padding: EdgeInsets.zero, + ), + onSelected: (value) async { + switch (value) { + case 1: + String result = (await hostConfigDialog(context, hostFile.remark) ?? ""); + if (result.isEmpty) return; + homeCubit.updateHostFileRemark(hostFile.fileName, result); + break; + case 2: + deleteMultiple(context, [hostFile.remark], () async { + homeCubit.deleteHostFile(hostFile.fileName); + }); + break; + case 3: + final FileManager fileManager = FileManager(); + final bool success = await fileManager.exportMultipleHostFiles( + [hostFile], AppLocalizations.of(context)!.export_data); + if (success) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.export_success)), + ); } + break; + } + }, + itemBuilder: (BuildContext context) { + List> list = [ + { + "icon": Icons.edit, + "text": AppLocalizations.of(context)!.edit, + "value": 1 }, - itemBuilder: (BuildContext context) { - List> list = [ - { - "icon": Icons.edit, - "text": AppLocalizations.of(context)!.edit, - "value": 1 - }, - { - "icon": Icons.delete_outline, - "text": AppLocalizations.of(context)!.remove, - "value": 2 - }, - { - "icon": Icons.file_download, - "text": AppLocalizations.of(context)!.export, - "value": 3 - }, - ]; - - return list.map((item) { - return PopupMenuItem( - value: int.parse(item["value"].toString()), - child: Row( - children: [ - Icon(item["icon"]! as IconData), - const SizedBox(width: 8), - Text(item["text"]!.toString()), - ], - ), - ); - }).toList(); + { + "icon": Icons.file_download, + "text": AppLocalizations.of(context)!.export, + "value": 3 }, - ); + { + "icon": Icons.delete_outline, + "text": AppLocalizations.of(context)!.remove, + "value": 2 + }, + ]; + + return list.map((item) { + return PopupMenuItem( + value: int.parse(item["value"].toString()), + child: Row( + children: [ + Icon(item["icon"]! as IconData), + const SizedBox(width: 8), + Text(item["text"]!.toString()), + ], + ), + ); + }).toList(); }, ); } diff --git a/lib/home/view/home_page.dart b/lib/home/view/home_page.dart index d10c310..5edf66d 100644 --- a/lib/home/view/home_page.dart +++ b/lib/home/view/home_page.dart @@ -21,7 +21,7 @@ class HomePage extends StatelessWidget { providers: [ BlocProvider(create: (context) => HomeCubit()), BlocProvider(create: (context) => HostCubit()), - BlocProvider(create: (context) => NearbyDevicesCubit()..loadCachedDevices()), + BlocProvider(create: (context) => NearbyDevicesCubit()), ], child: const HomeView(), ); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2000527..55b025a 100755 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -130,5 +130,19 @@ "device_reachable": "Device reachable", "visit_device": "Visit device", "access_denied_file_not_allowed": "Access denied: File not allowed", - "select_hosts_to_share": "Please select hosts files to share" + "select_hosts_to_share": "Please select hosts files to share", + "offline": "Offline", + "scan_nearby_devices_failed": "Failed to scan nearby devices", + "import_remote_hosts": "Import remote hosts files", + "refresh": "Refresh", + "getting_remote_hosts": "Getting remote hosts files...", + "connection_failed": "Failed to connect to device", + "retry": "Retry", + "select_all": "Select All", + "no_hosts_files_found": "No available hosts files found", + "device_no_shared_files": "This device may not be sharing any hosts files", + "no_importable_content": "No importable file content found", + "remote_files": " remote files", + "show_qr_code": "Show QR Code", + "port": "Port" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index cd845e8..312f161 100755 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -889,6 +889,84 @@ abstract class AppLocalizations { /// In zh, this message translates to: /// **'请选择要分享的hosts文件'** String get select_hosts_to_share; + + /// No description provided for @offline. + /// + /// In zh, this message translates to: + /// **'离线'** + String get offline; + + /// No description provided for @scan_nearby_devices_failed. + /// + /// In zh, this message translates to: + /// **'扫描附近设备失败'** + String get scan_nearby_devices_failed; + + /// No description provided for @import_remote_hosts. + /// + /// In zh, this message translates to: + /// **'导入远程hosts文件'** + String get import_remote_hosts; + + /// No description provided for @refresh. + /// + /// In zh, this message translates to: + /// **'刷新'** + String get refresh; + + /// No description provided for @getting_remote_hosts. + /// + /// In zh, this message translates to: + /// **'正在获取远程hosts文件...'** + String get getting_remote_hosts; + + /// No description provided for @connection_failed. + /// + /// In zh, this message translates to: + /// **'连接设备失败'** + String get connection_failed; + + /// No description provided for @retry. + /// + /// In zh, this message translates to: + /// **'重试'** + String get retry; + + /// No description provided for @no_hosts_files_found. + /// + /// In zh, this message translates to: + /// **'没有找到可用的hosts文件'** + String get no_hosts_files_found; + + /// No description provided for @device_no_shared_files. + /// + /// In zh, this message translates to: + /// **'该设备可能没有共享任何hosts文件'** + String get device_no_shared_files; + + /// No description provided for @no_importable_content. + /// + /// In zh, this message translates to: + /// **'没有找到可导入的文件内容'** + String get no_importable_content; + + /// No description provided for @remote_files. + /// + /// In zh, this message translates to: + /// **'个远程文件'** + String get remote_files; + + /// No description provided for @show_qr_code. + /// + /// In zh, this message translates to: + /// **'显示二维码'** + String get show_qr_code; + + /// No description provided for @port. + /// + /// In zh, this message translates to: + /// **'端口'** + String get port; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index c3aa5f1..52aa012 100755 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -421,4 +421,44 @@ class AppLocalizationsEn extends AppLocalizations { @override String get select_hosts_to_share => 'Please select hosts files to share'; + + @override + String get offline => 'Offline'; + + @override + String get scan_nearby_devices_failed => 'Failed to scan nearby devices'; + + @override + String get import_remote_hosts => 'Import remote hosts files'; + + @override + String get refresh => 'Refresh'; + + @override + String get getting_remote_hosts => 'Getting remote hosts files...'; + + @override + String get connection_failed => 'Failed to connect to device'; + + @override + String get retry => 'Retry'; + + @override + String get no_hosts_files_found => 'No available hosts files found'; + + @override + String get device_no_shared_files => + 'This device may not be sharing any hosts files'; + + @override + String get no_importable_content => 'No importable file content found'; + + @override + String get remote_files => ' remote files'; + + @override + String get show_qr_code => 'Show QR Code'; + + @override + String get port => 'Port'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index f1005f0..99a12dd 100755 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -410,4 +410,43 @@ class AppLocalizationsZh extends AppLocalizations { @override String get select_hosts_to_share => '请选择要分享的hosts文件'; + + @override + String get offline => '离线'; + + @override + String get scan_nearby_devices_failed => '扫描附近设备失败'; + + @override + String get import_remote_hosts => '导入远程hosts文件'; + + @override + String get refresh => '刷新'; + + @override + String get getting_remote_hosts => '正在获取远程hosts文件...'; + + @override + String get connection_failed => '连接设备失败'; + + @override + String get retry => '重试'; + + @override + String get no_hosts_files_found => '没有找到可用的hosts文件'; + + @override + String get device_no_shared_files => '该设备可能没有共享任何hosts文件'; + + @override + String get no_importable_content => '没有找到可导入的文件内容'; + + @override + String get remote_files => '个远程文件'; + + @override + String get show_qr_code => '显示二维码'; + + @override + String get port => '端口'; } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 8c72454..e23e3e3 100755 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -130,5 +130,19 @@ "device_reachable": "设备可达", "visit_device": "访问设备", "access_denied_file_not_allowed": "访问被拒绝:文件不被允许", - "select_hosts_to_share": "请选择要分享的hosts文件" + "select_hosts_to_share": "请选择要分享的hosts文件", + "offline": "离线", + "scan_nearby_devices_failed": "扫描附近设备失败", + "import_remote_hosts": "导入远程hosts文件", + "refresh": "刷新", + "getting_remote_hosts": "正在获取远程hosts文件...", + "connection_failed": "连接设备失败", + "retry": "重试", + "select_all": "全选", + "no_hosts_files_found": "没有找到可用的hosts文件", + "device_no_shared_files": "该设备可能没有共享任何hosts文件", + "no_importable_content": "没有找到可导入的文件内容", + "remote_files": "个远程文件", + "show_qr_code": "显示二维码", + "port": "端口" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index a9917e9..93298b4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,12 +2,17 @@ import 'dart:io'; import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_background/flutter_background.dart'; import 'package:hosts/app.dart'; import 'package:hosts/host_observer.dart'; void main(List args) async { WidgetsFlutterBinding.ensureInitialized(); Bloc.observer = const HostObserver(); + + // 初始化后台运行配置 + await _initializeBackgroundService(); + final Iterable files = args.where((path) => File(path).existsSync()); if (files.isNotEmpty) { runApp(HostsApp(files.first)); @@ -15,3 +20,18 @@ void main(List args) async { runApp(HostsApp("")); } } + +Future _initializeBackgroundService() async { + if (!Platform.isAndroid) { + return; + } + const androidConfig = FlutterBackgroundAndroidConfig( + notificationTitle: "Hosts 编辑器", + notificationText: "正在后台运行 hosts 服务", + notificationImportance: AndroidNotificationImportance.normal, + notificationIcon: + AndroidResource(name: 'background_icon', defType: 'drawable'), + ); + + await FlutterBackground.initialize(androidConfig: androidConfig); +} diff --git a/lib/server/bloc/nearby_devices_cubit.dart b/lib/server/bloc/nearby_devices_cubit.dart index be0cc7f..612954b 100644 --- a/lib/server/bloc/nearby_devices_cubit.dart +++ b/lib/server/bloc/nearby_devices_cubit.dart @@ -10,39 +10,11 @@ part 'nearby_devices_state.dart'; class NearbyDevicesCubit extends Cubit { NearbyDevicesCubit() : super(const NearbyDevicesInitial(NearbyDevicesStateData())); - /// 加载缓存的设备 - Future loadCachedDevices() async { - try { - emit(NearbyDevicesLoading( - state.data.copyWith(isLoading: true, errorMessage: null), - )); - - final cachedDevices = await NearbyDevicesScanner.getCachedDevices(); - - emit(NearbyDevicesInitial( - state.data.copyWith( - isLoading: false, - devices: cachedDevices, - ), - )); - - // 如果有缓存设备,自动检查在线状态 - if (cachedDevices.isNotEmpty) { - checkDevicesOnlineStatus(); - } - } catch (e) { - emit(NearbyDevicesError( - state.data.copyWith( - isLoading: false, - errorMessage: '加载缓存设备失败: $e', - ), - )); - } - } - /// 扫描附近设备 Future scanNearbyDevices() async { try { + if (isClosed) return; + emit(NearbyDevicesScanning( state.data.copyWith( isScanning: true, @@ -55,78 +27,35 @@ class NearbyDevicesCubit extends Cubit { await NearbyDevicesScanner.scanNearbyDevicesRealTime( onDeviceFound: (device) { // 实时更新设备列表 - onDeviceFound(device); + if (!isClosed) { + onDeviceFound(device); + } }, ); - emit(NearbyDevicesSuccess( - state.data.copyWith( - isScanning: false, - successMessage: '扫描完成,发现${state.devices.length}个设备', - ), - )); - } catch (e) { - emit(NearbyDevicesError( - state.data.copyWith( - isScanning: false, - errorMessage: '扫描附近设备失败: $e', - ), - )); - } - } - - /// 检查设备在线状态 - Future checkDevicesOnlineStatus() async { - if (state.devices.isEmpty) return; - - try { - emit(NearbyDevicesCheckingStatus( - state.data.copyWith(isCheckingStatus: true, errorMessage: null), - )); - - await NearbyDevicesScanner.checkCachedDevicesOnlineStatus(); - - // 重新加载更新后的设备列表 - final updatedDevices = await NearbyDevicesScanner.getCachedDevices(); - - emit(NearbyDevicesInitial( - state.data.copyWith( - isCheckingStatus: false, - devices: updatedDevices, - ), - )); - } catch (e) { - emit(NearbyDevicesError( - state.data.copyWith( - isCheckingStatus: false, - errorMessage: '检查设备在线状态失败: $e', - ), - )); - } - } - - /// 清除设备缓存 - Future clearDeviceCache() async { - try { - await NearbyDevicesScanner.clearCache(); - - emit(NearbyDevicesSuccess( - state.data.copyWith( - devices: [], - successMessage: '设备缓存已清除', - ), - )); + if (!isClosed) { + emit(NearbyDevicesInitial( + state.data.copyWith( + isScanning: false, + ), + )); + } } catch (e) { - emit(NearbyDevicesError( - state.data.copyWith( - errorMessage: '清除设备缓存失败: $e', - ), - )); + if (!isClosed) { + emit(NearbyDevicesError( + state.data.copyWith( + isScanning: false, + errorMessage: 'Failed to scan nearby devices: $e', + ), + )); + } } } /// 设备发现处理 void onDeviceFound(NearbyDevice device) { + if (isClosed) return; + final updatedDevices = List.from(state.devices); // 避免重复添加同一IP的设备 @@ -142,33 +71,6 @@ class NearbyDevicesCubit extends Cubit { )); } - /// 选择设备 - void selectDevice(NearbyDevice? device) { - print("selectDevice = $device"); - emit(NearbyDevicesSelectionChanged( - state.data.copyWith( - selectedDevice: device, - clearSelectedDevice: device == null, - ), - )); - } - - /// 切换设备选择状态 - void toggleDeviceSelection(NearbyDevice device) { - if (state.selectedDevice?.ip == device.ip) { - // 如果已选中,则取消选择 - selectDevice(null); - } else { - // 否则选择该设备 - selectDevice(device); - } - } - - /// 检查设备是否被选中 - bool isDeviceSelected(NearbyDevice device) { - return state.selectedDevice?.ip == device.ip; - } - /// 获取设备状态摘要(供其他组件使用) Map getDevicesSummary() { return { @@ -180,10 +82,11 @@ class NearbyDevicesCubit extends Cubit { /// 清除消息 void clearMessages() { + if (isClosed) return; + emit(NearbyDevicesInitial( state.data.copyWith( errorMessage: null, - successMessage: null, ), )); } diff --git a/lib/server/bloc/nearby_devices_state.dart b/lib/server/bloc/nearby_devices_state.dart index 84e69ff..70c4281 100644 --- a/lib/server/bloc/nearby_devices_state.dart +++ b/lib/server/bloc/nearby_devices_state.dart @@ -6,33 +6,21 @@ class NearbyDevicesStateData { /// 设备列表 final List devices; - /// 当前选中的设备 - final NearbyDevice? selectedDevice; - /// 是否正在扫描 final bool isScanning; - /// 是否正在检查状态 - final bool isCheckingStatus; - /// 是否正在加载 final bool isLoading; /// 错误信息 final String? errorMessage; - - /// 成功信息 - final String? successMessage; /// 构造函数 const NearbyDevicesStateData({ this.devices = const [], - this.selectedDevice, this.isScanning = false, - this.isCheckingStatus = false, this.isLoading = false, this.errorMessage, - this.successMessage, }); /// 获取在线设备数量 @@ -51,22 +39,15 @@ class NearbyDevicesStateData { /// 用于基于当前状态创建新状态 NearbyDevicesStateData copyWith({ List? devices, - NearbyDevice? selectedDevice, - bool clearSelectedDevice = false, bool? isScanning, - bool? isCheckingStatus, bool? isLoading, String? errorMessage, - String? successMessage, }) { return NearbyDevicesStateData( devices: devices ?? this.devices, - selectedDevice: clearSelectedDevice ? null : (selectedDevice ?? this.selectedDevice), isScanning: isScanning ?? this.isScanning, - isCheckingStatus: isCheckingStatus ?? this.isCheckingStatus, isLoading: isLoading ?? this.isLoading, errorMessage: errorMessage, - successMessage: successMessage, ); } } @@ -95,23 +76,14 @@ sealed class NearbyDevicesState { /// 设备列表 List get devices => data.devices; - /// 当前选中的设备 - NearbyDevice? get selectedDevice => data.selectedDevice; - /// 是否正在扫描 bool get isScanning => data.isScanning; - /// 是否正在检查状态 - bool get isCheckingStatus => data.isCheckingStatus; - /// 是否正在加载 bool get isLoading => data.isLoading; /// 错误信息 String? get errorMessage => data.errorMessage; - - /// 成功信息 - String? get successMessage => data.successMessage; } /// 初始状态 @@ -124,11 +96,6 @@ class NearbyDevicesScanning extends NearbyDevicesState { const NearbyDevicesScanning(super.data); } -/// 检查状态中状态 -class NearbyDevicesCheckingStatus extends NearbyDevicesState { - const NearbyDevicesCheckingStatus(super.data); -} - /// 加载中状态 class NearbyDevicesLoading extends NearbyDevicesState { const NearbyDevicesLoading(super.data); @@ -139,17 +106,7 @@ class NearbyDevicesDeviceFound extends NearbyDevicesState { const NearbyDevicesDeviceFound(super.data); } -/// 设备选择变更状态 -class NearbyDevicesSelectionChanged extends NearbyDevicesState { - const NearbyDevicesSelectionChanged(super.data); -} - /// 错误状态 class NearbyDevicesError extends NearbyDevicesState { const NearbyDevicesError(super.data); -} - -/// 成功状态 -class NearbyDevicesSuccess extends NearbyDevicesState { - const NearbyDevicesSuccess(super.data); } \ No newline at end of file diff --git a/lib/server/view/nearby_devices_card.dart b/lib/server/view/nearby_devices_card.dart index 50cb099..644109c 100644 --- a/lib/server/view/nearby_devices_card.dart +++ b/lib/server/view/nearby_devices_card.dart @@ -3,8 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/server/bloc/nearby_devices_cubit.dart'; -import 'package:hosts/utils/datetime_extensions.dart'; import 'package:hosts/utils/nearby_devices_scanner.dart'; +import 'package:hosts/widget/dialog/access_device_dialog.dart'; /// 附近设备卡片组件 class NearbyDevicesCard extends StatelessWidget { @@ -23,16 +23,6 @@ class NearbyDevicesCard extends StatelessWidget { ), ); } - - // 处理成功消息 - if (state.successMessage != null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.successMessage!), - backgroundColor: Theme.of(context).colorScheme.primary, - ), - ); - } }, builder: (context, state) { return Card( @@ -52,32 +42,6 @@ class NearbyDevicesCard extends StatelessWidget { Row( mainAxisSize: MainAxisSize.min, children: [ - IconButton( - icon: state.isCheckingStatus - ? const SizedBox( - width: 16, - height: 16, - child: - CircularProgressIndicator(strokeWidth: 2), - ) - : const Icon(Icons.wifi_find), - onPressed: - state.devices.isEmpty || state.isCheckingStatus - ? null - : () => context - .read() - .checkDevicesOnlineStatus(), - tooltip: '检查设备在线状态', - ), - IconButton( - icon: const Icon(Icons.clear_all), - onPressed: state.devices.isEmpty - ? null - : () => context - .read() - .clearDeviceCache(), - tooltip: '清除设备缓存', - ), IconButton( icon: state.isScanning ? const SizedBox( @@ -113,7 +77,7 @@ class NearbyDevicesCard extends StatelessWidget { Text( state.isScanning ? AppLocalizations.of(context)!.scanning_devices - : '加载中...', + : AppLocalizations.of(context)!.loading, style: const TextStyle(color: Colors.grey), ), ], @@ -126,6 +90,21 @@ class NearbyDevicesCard extends StatelessWidget { /// 构建设备网格 Widget _buildDeviceGrid(BuildContext context, List devices) { + // 对设备进行排序:共享服务开启的设备在前面 + final sortedDevices = List.from(devices) + ..sort((a, b) { + // 首先按是否开启共享服务排序(开启的在前) + if (a.hasSharing && !b.hasSharing) return -1; + if (!a.hasSharing && b.hasSharing) return 1; + + // 然后按在线状态排序(在线的在前) + if (a.isOnline && !b.isOnline) return -1; + if (!a.isOnline && b.isOnline) return 1; + + // 最后按IP地址排序 + return a.ip.compareTo(b.ip); + }); + return LayoutBuilder( builder: (context, constraints) { // 根据屏幕宽度确定网格列数 @@ -149,7 +128,7 @@ class NearbyDevicesCard extends StatelessWidget { crossAxisCount: crossAxisCount, mainAxisSpacing: 12, crossAxisSpacing: 12, - children: devices.map((device) { + children: sortedDevices.map((device) { return StaggeredGridTile.fit( crossAxisCellCount: 1, child: _buildDeviceItem(context, device), @@ -171,7 +150,7 @@ class NearbyDevicesCard extends StatelessWidget { if (!device.isOnline) { statusColor = Colors.red; statusIcon = Icons.offline_bolt; - statusText = '离线'; + statusText = AppLocalizations.of(context)!.offline; } else if (device.hasSharing) { statusColor = Colors.green; statusIcon = Icons.share; @@ -255,7 +234,7 @@ class NearbyDevicesCard extends StatelessWidget { color: Colors.red.withValues(alpha: 0.3)), ), child: Text( - '离线', + AppLocalizations.of(context)!.offline, style: TextStyle( fontSize: 10, color: Colors.red[700], @@ -274,14 +253,6 @@ class NearbyDevicesCard extends StatelessWidget { fontWeight: FontWeight.w500, ), ), - const SizedBox(height: 4), - Text( - '最后见到: ${device.lastSeen.formatLastSeen()}', - style: TextStyle( - fontSize: 12, - color: Colors.grey[600], - ), - ), ], ), ), @@ -289,10 +260,10 @@ class NearbyDevicesCard extends StatelessWidget { IconButton( icon: const Icon(Icons.launch), onPressed: () { - context - .read() - .toggleDeviceSelection(device); - Navigator.pop(context); + accessDeviceDialog( + context, + device, + ); }, tooltip: AppLocalizations.of(context)!.visit_device, ), diff --git a/lib/server/view/server_settings_page.dart b/lib/server/view/server_settings_page.dart index 6ad55cb..4ae904b 100755 --- a/lib/server/view/server_settings_page.dart +++ b/lib/server/view/server_settings_page.dart @@ -22,7 +22,7 @@ class ServerSettingsPage extends StatelessWidget { ..add(LoadNetworkInterfaces()), ), BlocProvider( - create: (context) => NearbyDevicesCubit()..loadCachedDevices(), + create: (context) => NearbyDevicesCubit(), ), ], child: const _ServerSettingsView(), diff --git a/lib/server/view/server_status_card.dart b/lib/server/view/server_status_card.dart index a3a24d4..699fb8e 100644 --- a/lib/server/view/server_status_card.dart +++ b/lib/server/view/server_status_card.dart @@ -110,7 +110,7 @@ class ServerStatusCard extends StatelessWidget { color: primaryColor, ), onPressed: () => _showQrCodeDialog(context, url), - tooltip: '显示二维码', + tooltip: AppLocalizations.of(context)!.show_qr_code, padding: const EdgeInsets.all(4), constraints: const BoxConstraints( minWidth: 28, @@ -252,7 +252,7 @@ class ServerStatusCard extends StatelessWidget { if (isRunning && port != null) ...[ const SizedBox(height: 2), Text( - '端口: $port', + '${AppLocalizations.of(context)!.port}: $port', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context) diff --git a/lib/services/background_service.dart b/lib/services/background_service.dart new file mode 100644 index 0000000..f294ff4 --- /dev/null +++ b/lib/services/background_service.dart @@ -0,0 +1,58 @@ +import 'package:flutter_background/flutter_background.dart'; + +class BackgroundService { + static bool _isRunning = false; + + static bool get isRunning => _isRunning; + + /// 启动后台服务 + static Future startBackgroundService() async { + if (_isRunning) return true; + + try { + final hasPermissions = await FlutterBackground.hasPermissions; + if (!hasPermissions) { + return false; + } + + final success = await FlutterBackground.enableBackgroundExecution(); + if (success) { + _isRunning = true; + } + return success; + } catch (e) { + print('启动后台服务失败: $e'); + return false; + } + } + + /// 停止后台服务 + static Future stopBackgroundService() async { + if (!_isRunning) return true; + + try { + final success = await FlutterBackground.disableBackgroundExecution(); + if (success) { + _isRunning = false; + } + return success; + } catch (e) { + print('停止后台服务失败: $e'); + return false; + } + } + + /// 请求后台权限 + static Future requestPermissions() async { + try { + final hasPermissions = await FlutterBackground.hasPermissions; + if (hasPermissions) return true; + + // 在Android上会自动请求权限 + return await FlutterBackground.hasPermissions; + } catch (e) { + print('请求后台权限失败: $e'); + return false; + } + } +} \ No newline at end of file diff --git a/lib/utils/device_api_cache.dart b/lib/utils/device_api_cache.dart index a3bece7..7823132 100644 --- a/lib/utils/device_api_cache.dart +++ b/lib/utils/device_api_cache.dart @@ -2,39 +2,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:path/path.dart' as path; -import 'package:path_provider/path_provider.dart'; - -/// 设备API缓存工具类 +/// 设备API直接访问工具类 class DeviceApiCache { static const int _defaultPort = 1204; static const Duration _scanTimeout = Duration(seconds: 2); - /// 缓存设备API响应 - static Future cacheDeviceAPIResponses(String ip) async { - try { - // 创建缓存目录 - final cacheDir = await _getCacheDirectoryForDevice(ip); - await cacheDir.create(recursive: true); - - // 缓存hosts文件列表 - await _cacheHostsFilesList(ip, cacheDir); - - print('设备 $ip 的API响应已缓存'); - } catch (e) { - print('缓存设备 $ip 的API响应失败: $e'); - } - } - - /// 获取设备缓存目录 - static Future _getCacheDirectoryForDevice(String ip) async { - final appCacheDir = await getApplicationCacheDirectory(); - return Directory(path.join(appCacheDir.path, ip)); - } - - /// 缓存hosts文件列表 - static Future _cacheHostsFilesList( - String ip, Directory cacheDir) async { + /// 获取设备API响应(直接访问,不缓存) + static Future>> getCachedDeviceData(String ip) async { try { final httpClient = HttpClient(); httpClient.connectionTimeout = _scanTimeout; @@ -45,15 +19,13 @@ class DeviceApiCache { if (response.statusCode == 200) { final responseBody = await response.transform(utf8.decoder).join(); - final hostsListFile = File(path.join(cacheDir.path, 'hosts_list')); - - // 解析响应并只保存fileName和remark字段 + try { final responseData = jsonDecode(responseBody); if (responseData['success'] == true) { final List hostsList = responseData['data']; - // 只保存fileName和remark字段 + // 只返回fileName和remark字段 final List> filteredHostsList = hostsList .map((host) => { 'fileName': host['fileName'], @@ -61,17 +33,8 @@ class DeviceApiCache { }) .toList(); - await hostsListFile.writeAsString(jsonEncode(filteredHostsList)); - print('hosts文件列表已缓存: $ip'); - - // 进一步缓存每个文件的详细信息 - for (final hosts in hostsList) { - final fileName = hosts['fileName']; - if (fileName != null) { - await _cacheHostsFileContent(ip, fileName, cacheDir); - await _cacheHostsFileHistory(ip, fileName, cacheDir); - } - } + httpClient.close(); + return filteredHostsList; } } catch (e) { print('解析hosts文件列表失败 $ip: $e'); @@ -79,71 +42,56 @@ class DeviceApiCache { } httpClient.close(); + return []; } catch (e) { - print('缓存hosts文件列表失败 $ip: $e'); + print('获取设备数据失败 $ip: $e'); + return []; } } - /// 缓存hosts文件内容 - static Future _cacheHostsFileContent( - String ip, String fileName, Directory cacheDir) async { + /// 获取特定hosts文件的内容(直接访问,不缓存) + static Future getCachedHostsFileContent(String ip, String fileName) async { try { final httpClient = HttpClient(); httpClient.connectionTimeout = _scanTimeout; httpClient.idleTimeout = _scanTimeout; - final request = - await httpClient.get(ip, _defaultPort, '/api/hosts/$fileName'); + final request = await httpClient.get(ip, _defaultPort, '/api/hosts/$fileName'); final response = await request.close(); if (response.statusCode == 200) { final responseBody = await response.transform(utf8.decoder).join(); - final fileDir = Directory(path.join(cacheDir.path, fileName)); - await fileDir.create(recursive: true); - - final hostsFile = File(path.join(fileDir.path, 'hosts')); - await hostsFile.writeAsString(responseBody); - print('hosts文件内容已缓存: $ip/$fileName'); + httpClient.close(); + return responseBody; } httpClient.close(); + return null; } catch (e) { - print('缓存hosts文件内容失败 $ip/$fileName: $e'); + print('获取hosts文件内容失败 $ip/$fileName: $e'); + return null; } } - /// 缓存hosts文件历史 - static Future _cacheHostsFileHistory( - String ip, String fileName, Directory cacheDir) async { + /// 获取特定hosts文件的历史记录列表(直接访问,不缓存) + static Future>> getCachedHostsFileHistory(String ip, String fileName) async { try { final httpClient = HttpClient(); httpClient.connectionTimeout = _scanTimeout; httpClient.idleTimeout = _scanTimeout; - final request = await httpClient.get( - ip, _defaultPort, '/api/hosts/$fileName/history'); + final request = await httpClient.get(ip, _defaultPort, '/api/hosts/$fileName/history'); final response = await request.close(); if (response.statusCode == 200) { final responseBody = await response.transform(utf8.decoder).join(); - final historyDir = - Directory(path.join(cacheDir.path, fileName, 'history')); - await historyDir.create(recursive: true); - - print('hosts文件历史已缓存: $ip/$fileName'); - - // 缓存每个历史版本的内容 + try { final responseData = jsonDecode(responseBody); if (responseData['success'] == true) { final List historyList = responseData['data']; - for (final historyItem in historyList) { - final historyId = historyItem['id']; - if (historyId != null) { - await _cacheHostsFileHistoryContent( - ip, fileName, historyId, historyDir); - } - } + httpClient.close(); + return historyList.map((item) => item as Map).toList(); } } catch (e) { print('解析hosts文件历史失败 $ip/$fileName: $e'); @@ -151,124 +99,10 @@ class DeviceApiCache { } httpClient.close(); - } catch (e) { - print('缓存hosts文件历史失败 $ip/$fileName: $e'); - } - } - - /// 缓存hosts文件历史内容 - static Future _cacheHostsFileHistoryContent(String ip, String fileName, - String historyId, Directory historyDir) async { - try { - final httpClient = HttpClient(); - httpClient.connectionTimeout = _scanTimeout; - httpClient.idleTimeout = _scanTimeout; - - final request = await httpClient.get( - ip, _defaultPort, '/api/hosts/$fileName/history/$historyId'); - final response = await request.close(); - - if (response.statusCode == 200) { - final responseBody = await response.transform(utf8.decoder).join(); - final historyContentFile = File(path.join(historyDir.path, historyId)); - await historyContentFile.writeAsString(responseBody); - print('hosts文件历史内容已缓存: $ip/$fileName/$historyId'); - } - - httpClient.close(); - } catch (e) { - print('缓存hosts文件历史内容失败 $ip/$fileName/$historyId: $e'); - } - } - - /// 获取缓存的设备API响应 - static Future>> getCachedDeviceData( - String ip) async { - try { - final cacheDir = await _getCacheDirectoryForDevice(ip); - if (!await cacheDir.exists()) { - return []; - } - - List> result = []; - - // 读取hosts文件列表 - final hostsListFile = File(path.join(cacheDir.path, 'hosts_list')); - if (await hostsListFile.exists()) { - result = (jsonDecode(hostsListFile.readAsStringSync()) as List) - .map((it) => it as Map) - .toList(); - } - - return result; - } catch (e) { - print('读取设备缓存数据失败 $ip: $e'); return []; - } - } - - /// 获取特定hosts文件的缓存内容 - static Future getCachedHostsFileContent( - String ip, String fileName) async { - try { - final cacheDir = await _getCacheDirectoryForDevice(ip); - final hostsFile = File(path.join(cacheDir.path, fileName, 'hosts')); - - if (await hostsFile.exists()) { - return await hostsFile.readAsString(); - } - - return null; } catch (e) { - print('读取hosts文件缓存失败 $ip/$fileName: $e'); - return null; - } - } - - /// 获取特定hosts文件的历史版本内容 - static Future getCachedHostsFileHistoryContent( - String ip, String fileName, String historyId) async { - try { - final cacheDir = await _getCacheDirectoryForDevice(ip); - final historyFile = - File(path.join(cacheDir.path, fileName, 'history', historyId)); - - if (await historyFile.exists()) { - return await historyFile.readAsString(); - } - - return null; - } catch (e) { - print('读取hosts文件历史缓存失败 $ip/$fileName/$historyId: $e'); - return null; - } - } - - /// 清除设备API缓存 - static Future clearDeviceAPICache() async { - try { - final appSupportDir = await getApplicationSupportDirectory(); - final cacheDir = Directory(path.join(appSupportDir.path, 'cache')); - - if (await cacheDir.exists()) { - await cacheDir.delete(recursive: true); - print('设备API缓存已清除'); - } - } catch (e) { - print('清除设备API缓存失败: $e'); - } - } - - /// 清除特定设备的API缓存 - static Future clearDeviceAPIResponseCache(String ip) async { - try { - final cacheDir = await _getCacheDirectoryForDevice(ip); - if (await cacheDir.exists()) { - await cacheDir.delete(recursive: true); - print('设备 $ip 的API缓存已清除'); - } - } catch (e) { - print('清除设备 $ip 的API缓存失败: $e'); + print('获取hosts文件历史失败 $ip/$fileName: $e'); + return []; } } } diff --git a/lib/utils/nearby_devices_scanner.dart b/lib/utils/nearby_devices_scanner.dart index 19458ae..f836b03 100644 --- a/lib/utils/nearby_devices_scanner.dart +++ b/lib/utils/nearby_devices_scanner.dart @@ -105,8 +105,6 @@ class NearbyDevicesScanner { // 等待当前批次扫描完成 await Future.wait(scanFutures); } - - print('实时扫描完成'); } catch (e) { print('实时扫描附近设备失败: $e'); } @@ -171,11 +169,6 @@ class NearbyDevicesScanner { // 连接成功,进一步验证是否是hosts服务器 final bool isHostsServer = await _verifyHostsServer(ip); - // 如果是hosts服务器,缓存API响应 - if (isHostsServer) { - await DeviceApiCache.cacheDeviceAPIResponses(ip); - } - return NearbyDevice( ip: ip, isReachable: true, @@ -393,9 +386,6 @@ class NearbyDevicesScanner { await prefs.remove(_deviceCacheKey); await prefs.remove(_priorityIPsKey); - // 清除API响应缓存 - await DeviceApiCache.clearDeviceAPICache(); - print('设备缓存已清除'); } catch (e) { print('清除设备缓存失败: $e'); diff --git a/lib/widget/dialog/access_device_dialog.dart b/lib/widget/dialog/access_device_dialog.dart new file mode 100644 index 0000000..14246a2 --- /dev/null +++ b/lib/widget/dialog/access_device_dialog.dart @@ -0,0 +1,426 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/util/file_manager.dart'; +import 'package:hosts/util/settings_manager.dart'; +import 'package:hosts/utils/device_api_cache.dart'; +import 'package:hosts/utils/nearby_devices_scanner.dart'; + +Future accessDeviceDialog(BuildContext context, NearbyDevice device) { + return showDialog( + context: context, + builder: (BuildContext context) { + return AccessDeviceDialog(device: device); + }, + ); +} + +class AccessDeviceDialog extends StatefulWidget { + final NearbyDevice device; + + const AccessDeviceDialog({ + super.key, + required this.device, + }); + + @override + State createState() => _AccessDeviceDialogState(); +} + +class _AccessDeviceDialogState extends State { + List availableHosts = []; + List existingHostFiles = []; + late List selectedItems; + bool isAllSelected = false; + bool isLoading = false; + bool hasError = false; + String? errorMessage; + + @override + void initState() { + super.initState(); + selectedItems = []; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _loadExistingHostFiles(); + _loadDeviceHosts(); + } + + Future _loadExistingHostFiles() async { + try { + final String defaultHostsText = AppLocalizations.of(context)!.default_hosts_text; + final SettingsManager settingsManager = SettingsManager(); + final List hostConfigs = + await settingsManager.getList(settingKeyHostConfigs); + + final List hosts = []; + for (final config in hostConfigs) { + final hostFile = SimpleHostFile.fromJson(config); + if (hostFile.fileName == "system") { + hostFile.remark = defaultHostsText; + } + hosts.add(hostFile); + } + + setState(() { + existingHostFiles = hosts; + }); + } catch (e) { + // Ignore errors, use empty list + setState(() { + existingHostFiles = []; + }); + } + } + + Future _loadDeviceHosts() async { + setState(() { + isLoading = true; + hasError = false; + errorMessage = null; + }); + + try { + final String defaultHostsText = AppLocalizations.of(context)!.default_hosts_text; + + // Refresh local file list + await _loadExistingHostFiles(); + + // Get device data directly, no caching + final hostConfigs = await DeviceApiCache.getCachedDeviceData(widget.device.ip); + + final List hosts = []; + for (final config in hostConfigs) { + final hostFile = SimpleHostFile.fromJson(config); + if (hostFile.fileName == "system") { + hostFile.remark = defaultHostsText; + } + hosts.add(hostFile); + } + + setState(() { + availableHosts = hosts; + selectedItems = List.generate(hosts.length, (index) => false); + isLoading = false; + }); + } catch (e) { + setState(() { + isLoading = false; + hasError = true; + errorMessage = e.toString(); + }); + } + } + + void _toggleSelectAll() { + setState(() { + isAllSelected = !isAllSelected; + for (int i = 0; i < selectedItems.length; i++) { + selectedItems[i] = isAllSelected; + } + }); + } + + void _toggleItem(int index) { + setState(() { + selectedItems[index] = !selectedItems[index]; + isAllSelected = selectedItems.every((item) => item); + }); + } + + Future _accessSelected() async { + final List selectedHosts = []; + for (int i = 0; i < availableHosts.length; i++) { + if (selectedItems[i]) { + selectedHosts.add(availableHosts[i]); + } + } + + if (selectedHosts.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalizations.of(context)!.error_null_data)), + ); + return; + } + + // 获取本地化字符串,在关闭对话框之前 + final String importSuccessMessage = + AppLocalizations.of(context)!.import_success; + final String remoteFilesText = + AppLocalizations.of(context)!.remote_files; + final String noImportableContentText = + AppLocalizations.of(context)!.no_importable_content; + final String importFailMessage = + AppLocalizations.of(context)!.error_save_fail; + final ScaffoldMessengerState scaffoldMessenger = + ScaffoldMessenger.of(context); + + Navigator.of(context).pop(); + + try { + final FileManager fileManager = FileManager(); + final SettingsManager settingsManager = SettingsManager(); + final List importedFiles = []; + + for (final hostFile in selectedHosts) { + // 获取远程文件内容 + final String? remoteContent = + await DeviceApiCache.getCachedHostsFileContent( + widget.device.ip, + hostFile.fileName, + ); + + if (remoteContent != null && remoteContent.isNotEmpty) { + // 直接使用原始文件名 + final String fileName = hostFile.fileName; + + // 创建本地文件 + await fileManager.createHosts(fileName); + + // 写入远程内容到本地文件 + final String localFilePath = + await fileManager.getHostsFilePath(fileName); + await File(localFilePath).writeAsString(remoteContent); + + // 创建本地SimpleHostFile对象 + final SimpleHostFile localHostFile = SimpleHostFile( + fileName: fileName, + remark: hostFile.remark, + ); + + importedFiles.add(localHostFile); + } + } + + if (importedFiles.isNotEmpty) { + // 获取当前配置 + List hostConfigs = + await settingsManager.getList(settingKeyHostConfigs); + + // 添加导入的文件到配置中 + for (final importedFile in importedFiles) { + // 检查是否已存在相同fileName的配置 + int existingIndex = hostConfigs.indexWhere( + (config) => config['fileName'] == importedFile.fileName); + + if (existingIndex >= 0) { + // 如果存在,则覆盖 + hostConfigs[existingIndex] = importedFile.toJson(); + } else { + // 如果不存在,则添加 + hostConfigs.add(importedFile.toJson()); + } + } + + // 保存到settingsManager + await settingsManager.setList(settingKeyHostConfigs, hostConfigs); + + scaffoldMessenger.showSnackBar( + SnackBar( + content: + Text('$importSuccessMessage ${importedFiles.length}$remoteFilesText')), + ); + } else { + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(noImportableContentText)), + ); + } + } catch (e) { + scaffoldMessenger.showSnackBar( + SnackBar(content: Text('$importFailMessage: $e')), + ); + } + } + + @override + Widget build(BuildContext context) { + final selectedCount = selectedItems.where((item) => item).length; + + return AlertDialog( + title: Row( + children: [ + Icon(Icons.computer, color: Colors.green), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(AppLocalizations.of(context)!.import_remote_hosts), + Text( + widget.device.ip, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.grey[600], + ), + ), + ], + ), + ), + ], + ), + content: SizedBox( + width: 500, + height: 450, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 设备信息 + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon( + Icons.share, + color: Colors.green, + size: 20, + ), + const SizedBox(width: 8), + Text( + AppLocalizations.of(context)!.sharing_enabled, + style: TextStyle( + color: Colors.green, + fontWeight: FontWeight.w500, + ), + ), + const Spacer(), + IconButton( + icon: Icon(Icons.refresh), + onPressed: isLoading ? null : _loadDeviceHosts, + tooltip: AppLocalizations.of(context)!.refresh, + ), + ], + ), + ), + const SizedBox(height: 16), + + if (isLoading) + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text(AppLocalizations.of(context)!.getting_remote_hosts), + ], + ), + ), + ) + else if (hasError) + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, size: 48, color: Colors.red), + SizedBox(height: 16), + Text(AppLocalizations.of(context)!.connection_failed), + if (errorMessage != null) ...[ + SizedBox(height: 8), + Text( + errorMessage!, + style: + TextStyle(color: Colors.grey[600], fontSize: 12), + textAlign: TextAlign.center, + ), + ], + SizedBox(height: 16), + ElevatedButton.icon( + onPressed: _loadDeviceHosts, + icon: Icon(Icons.refresh), + label: Text(AppLocalizations.of(context)!.retry), + ), + ], + ), + ), + ) + else if (availableHosts.isNotEmpty) ...[ + // 全选/反选按钮和统计 + Row( + children: [ + Checkbox( + value: isAllSelected, + onChanged: (bool? value) => _toggleSelectAll(), + ), + Text(AppLocalizations.of(context)!.select_all), + const Spacer(), + Text('$selectedCount/${availableHosts.length}'), + ], + ), + const Divider(), + // hosts文件列表 + Expanded( + child: ListView.builder( + itemCount: availableHosts.length, + itemBuilder: (context, index) { + final host = availableHosts[index]; + final bool isExisting = existingHostFiles.any( + (existingFile) => + existingFile.fileName == host.fileName); + + return ListTile( + leading: Icon( + Icons.description, + color: Theme.of(context).colorScheme.primary, + ), + trailing: Checkbox( + value: selectedItems[index], + onChanged: (bool? value) => _toggleItem(index), + ), + title: Text(host.remark), + subtitle: isExisting + ? Text( + AppLocalizations.of(context)!.will_overwrite, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 12, + ), + ) + : null, + onTap: () => _toggleItem(index), + ); + }, + ), + ), + ] else ...[ + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.folder_open, size: 48, color: Colors.grey), + SizedBox(height: 16), + Text(AppLocalizations.of(context)!.no_hosts_files_found), + Text( + AppLocalizations.of(context)!.device_no_shared_files, + style: TextStyle(color: Colors.grey[600]), + ), + ], + ), + ), + ), + ], + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(AppLocalizations.of(context)!.cancel), + ), + FilledButton( + onPressed: (selectedCount > 0 && !isLoading) ? _accessSelected : null, + child: Text(AppLocalizations.of(context)!.import), + ), + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 6998560..0d16b4f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -158,6 +158,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_background: + dependency: "direct main" + description: + name: flutter_background + sha256: "8dad66e3102da2b4046cc3adcf70625578809827170bd78e36e5890c074287d2" + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" flutter_bloc: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 05a9865..ea1baf6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,9 @@ dependencies: # URL启动器 url_launcher: ^6.3.1 + + # 后台运行 + flutter_background: ^1.3.0+1 dev_dependencies: flutter_test: From e34b78680135a4ee20aa1ba2b2288844c466b863 Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Sun, 20 Jul 2025 20:30:49 +0800 Subject: [PATCH 30/54] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0host=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=BD=BF=E7=94=A8=E5=8A=9F=E8=83=BD=E5=8F=8A=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=9B=B8=E5=85=B3=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在home_drawer.dart中实现host文件使用功能,替换注释掉的旧代码 - 添加使用失败时的SnackBar错误提示 - 在home_cubit.dart中完整实现useHost方法,包含文件写入、状态更新和错误处理 - 优化删除host文件时的逻辑处理,自动切换到system文件 - 统一代码格式,修复部分代码缩进问题 - 移除无用注释代码,提升代码可读性 --- lib/home/cubit/home_cubit.dart | 55 ++++++++++++++++++++-------------- lib/home/view/home_drawer.dart | 41 ++++++++++++++----------- 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/lib/home/cubit/home_cubit.dart b/lib/home/cubit/home_cubit.dart index 064dba8..3b347f2 100755 --- a/lib/home/cubit/home_cubit.dart +++ b/lib/home/cubit/home_cubit.dart @@ -160,26 +160,13 @@ class HomeCubit extends Cubit { final bool isDeleteSelect = state.data.selectHostFile == fileName; if (isDeleteUse) { - // TODO - // final String path = - // await _fileManager.getHostsFilePath("system"); - // - // if (!await widget - // .onClickUse(File(path).readAsStringSync())) { - // return; - // } - // await _settingsManager.setString( - // settingKeyUseHostFile, "system"); - // useHostFile = "system"; - // selectHostFile = "system"; + if (!await useHost("system")) { + return; + } } if (isDeleteSelect) { - selectHost( - state.data.selectHostFile.isNotEmpty - ? state.data.selectHostFile - : "system", - ); + selectHost("system"); } List updatedHostFiles = state.data.hostFiles @@ -221,8 +208,31 @@ class HomeCubit extends Cubit { /// 使用host文件 /// [fileName] 要使用的文件名 - Future useHost(String fileName) async { - // TODO + Future useHost(String fileName) async { + final hostPath = await _fileManager.getHostsFilePath(fileName); + try { + await _fileManager.writeFileWithAdminPrivileges( + hostPath, FileManager.systemHostFilePath); + + // 更新使用的hosts文件列表 + List updatedUseHostFiles = [fileName]; + + // 保存到设置 + await _settingsManager.setString(settingKeyUseHostFile, fileName); + + // 更新状态 + emit( + HomeInitial( + state.data.copyWith( + useHostFiles: updatedUseHostFiles, + ), + ), + ); + + return true; + } catch (e) { + return false; + } } /// 切换编辑模式 @@ -264,10 +274,10 @@ class HomeCubit extends Cubit { /// 刷新hosts文件列表 /// [context] 可选的context参数,用于system文件的本地化 Future refreshHostFiles([BuildContext? context]) async { - final String? defaultHostsText = context != null - ? gen.AppLocalizations.of(context)!.default_hosts_text + final String? defaultHostsText = context != null + ? gen.AppLocalizations.of(context)!.default_hosts_text : null; - + List tempHostFiles = []; List hostConfigs = await _settingsManager.getList(settingKeyHostConfigs); @@ -294,5 +304,4 @@ class HomeCubit extends Cubit { ), ); } - } diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart index 0460340..67353ea 100644 --- a/lib/home/view/home_drawer.dart +++ b/lib/home/view/home_drawer.dart @@ -61,19 +61,19 @@ class HomeDrawer extends StatelessWidget { .contains(hostFile.fileName) ? null : () async { - // final String path = await _fileManager - // .getHostsFilePath(hostFile.fileName); - // - // if (!await widget - // .onClickUse(File(path).readAsStringSync())) { - // return; - // } - - // setState(() { - // useHostFile = hostFile.fileName; - // }); - // _settingsManager.setString( - // settingKeyUseHostFile, hostFile.fileName); + final result = await context + .read() + .useHost(hostFile.fileName); + if (!result) { + // 使用SnackBar提示错误 + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.error_use_fail), + ), + ); + } + } }, icon: Icon(state.data.useHostFiles .contains(hostFile.fileName) @@ -109,7 +109,9 @@ class HomeDrawer extends StatelessWidget { return; } - context.read().selectHost(hostFile.fileName); + context + .read() + .selectHost(hostFile.fileName); }, ); }, @@ -201,9 +203,10 @@ class HomeDrawer extends StatelessWidget { ); } - Widget _buildHostFileOptionsMenu(BuildContext context, SimpleHostFile hostFile) { + Widget _buildHostFileOptionsMenu( + BuildContext context, SimpleHostFile hostFile) { final homeCubit = context.read(); - + return PopupMenuButton( style: OutlinedButton.styleFrom( minimumSize: Size.zero, @@ -212,7 +215,8 @@ class HomeDrawer extends StatelessWidget { onSelected: (value) async { switch (value) { case 1: - String result = (await hostConfigDialog(context, hostFile.remark) ?? ""); + String result = + (await hostConfigDialog(context, hostFile.remark) ?? ""); if (result.isEmpty) return; homeCubit.updateHostFileRemark(hostFile.fileName, result); break; @@ -228,7 +232,8 @@ class HomeDrawer extends StatelessWidget { if (success) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(AppLocalizations.of(context)!.export_success)), + content: + Text(AppLocalizations.of(context)!.export_success)), ); } break; From 1bfde2db820071e861915cede37d971da9514951 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Mon, 21 Jul 2025 00:53:49 +0800 Subject: [PATCH 31/54] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DHomeDrawer?= =?UTF-8?q?=E4=B8=ADProvider=E6=9F=A5=E6=89=BE=E9=94=99=E8=AF=AF=E5=8F=8A?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=AF=94=E8=BE=83=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复AlertDialog中HomeCubit和HostCubit的Provider查找错误 - 在showDialog前预获取cubit引用,避免在对话框上下文中查找Provider - 添加HostCubit.areFilesEqual方法支持文件比较功能 - 优化对话框中的异步操作和错误处理逻辑 --- lib/home/cubit/host_cubit.dart | 4 ++ lib/home/view/home_drawer.dart | 81 ++++++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/lib/home/cubit/host_cubit.dart b/lib/home/cubit/host_cubit.dart index 95d7cc8..8b9d36c 100644 --- a/lib/home/cubit/host_cubit.dart +++ b/lib/home/cubit/host_cubit.dart @@ -635,4 +635,8 @@ class HostCubit extends Cubit { String toString() { return state.data.fileContent; } + + Future areFilesEqual(String fileId) async { + return await _fileManager.areFilesEqual(fileId); + } } diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart index 67353ea..e59e99c 100644 --- a/lib/home/view/home_drawer.dart +++ b/lib/home/view/home_drawer.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hosts/home/cubit/home_cubit.dart'; @@ -66,13 +68,13 @@ class HomeDrawer extends StatelessWidget { .useHost(hostFile.fileName); if (!result) { // 使用SnackBar提示错误 - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(AppLocalizations.of(context)!.error_use_fail), - ), - ); - } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)! + .error_use_fail), + ), + ); } }, icon: Icon(state.data.useHostFiles @@ -85,10 +87,11 @@ class HomeDrawer extends StatelessWidget { selected: state.data.selectHostFile == hostFile.fileName, trailing: buildMoreButton(hostFile), - onTap: () { + onTap: () async { if (state.data.selectHostFile == hostFile.fileName) { return; } + if (!context.read().state.data.isSave) { ScaffoldMessenger.of(context) .removeCurrentSnackBar(); @@ -112,6 +115,68 @@ class HomeDrawer extends StatelessWidget { context .read() .selectHost(hostFile.fileName); + + if (state.data.useHostFiles + .contains(hostFile.fileName)) { + if (!await context + .read() + .areFilesEqual(hostFile.fileName)) { + final homeCubit = context.read(); + final hostCubit = context.read(); + await showDialog( + context: context, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: Text(AppLocalizations.of(dialogContext)! + .warning), + content: Text( + AppLocalizations.of(dialogContext)! + .warning_different), + actions: [ + TextButton( + onPressed: () async { + final result = await homeCubit.useHost(hostFile.fileName); + if (!result) { + // 使用SnackBar提示错误 + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of( + context)! + .error_use_fail), + ), + ); + return; + } + + Navigator.of(dialogContext).pop(); + }, + child: Text(AppLocalizations.of( + dialogContext)! + .warning_different_covering_system), + ), + TextButton( + onPressed: () async { + // TODO 没有写入到文件页面。 Text模式也没有更新内容。 + hostCubit.fromText( + File(FileManager + .systemHostFilePath) + .readAsStringSync(), + ); + + hostCubit.save(true); + Navigator.of(dialogContext).pop(); + }, + child: Text(AppLocalizations.of( + dialogContext)! + .warning_different_covering_current), + ), + ], + ); + }); + } + } }, ); }, From 5071d41b547ac6393dd8197b52e109362ace7bc2 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Mon, 21 Jul 2025 19:45:39 +0800 Subject: [PATCH 32/54] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E7=BC=96=E8=BE=91=E5=99=A8=E6=BB=9A=E5=8A=A8=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E5=8F=8A=E9=80=89=E6=8B=A9=E8=8C=83=E5=9B=B4=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现文本编辑器与行号的滚动同步功能 - 修复文本选择范围越界问题,防止应用崩溃 - 优化横向滚动支持,提升长文本编辑体验 - 完善行号组件的滚动条样式 --- lib/home/view/host_text.dart | 43 ++++- lib/page/hosts_diff_page.dart | 184 +++++++++++++++++++ lib/widget/host_text_editing_controller.dart | 25 ++- lib/widget/hosts_diff_viewer.dart | 111 +++++++++++ lib/widget/row_line_widget.dart | 75 ++++---- 5 files changed, 395 insertions(+), 43 deletions(-) create mode 100644 lib/page/hosts_diff_page.dart create mode 100644 lib/widget/hosts_diff_viewer.dart diff --git a/lib/home/view/host_text.dart b/lib/home/view/host_text.dart index 06867ab..f3d5762 100644 --- a/lib/home/view/host_text.dart +++ b/lib/home/view/host_text.dart @@ -34,6 +34,7 @@ class _HostTextState extends State { final ScrollController _scrollController = ScrollController(); final ScrollController _textScrollController = ScrollController(); final GlobalKey _textFieldContainerKey = GlobalKey(); + bool _isScrollingSynchronized = false; @override void initState() { @@ -43,12 +44,33 @@ class _HostTextState extends State { ..addListener(() { hostCubit.updateFileContent(textEditingController.text); }); + + // Synchronize scroll controllers + _textScrollController.addListener(() { + if (!_isScrollingSynchronized && _scrollController.hasClients) { + _isScrollingSynchronized = true; + _scrollController.jumpTo(_textScrollController.offset); + _isScrollingSynchronized = false; + } + }); + + _scrollController.addListener(() { + if (!_isScrollingSynchronized && _textScrollController.hasClients) { + _isScrollingSynchronized = true; + _textScrollController.jumpTo(_scrollController.offset); + _isScrollingSynchronized = false; + } + }); + super.initState(); } @override void dispose() { textEditingController.dispose(); + _scrollController.dispose(); + _textScrollController.dispose(); + _focusNode.dispose(); super.dispose(); } @@ -110,12 +132,21 @@ class _HostTextState extends State { hostCubit.onTextSave(); } }, - child: TextField( - controller: textEditingController, - scrollController: _textScrollController, - maxLines: null, - decoration: - const InputDecoration(border: InputBorder.none), + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: IntrinsicWidth( + child: TextField( + controller: textEditingController, + scrollController: _textScrollController, + maxLines: null, + scrollPhysics: const ClampingScrollPhysics(), + decoration: + const InputDecoration(border: InputBorder.none), + ), + ), + ), ), ), ), diff --git a/lib/page/hosts_diff_page.dart b/lib/page/hosts_diff_page.dart new file mode 100644 index 0000000..3904a85 --- /dev/null +++ b/lib/page/hosts_diff_page.dart @@ -0,0 +1,184 @@ +import 'package:flutter/material.dart'; +import 'package:hosts/widget/hosts_diff_viewer.dart'; +import 'package:hosts/l10n/app_localizations.dart'; + +class HostsDiffPage extends StatelessWidget { + final String historyContent; + final String currentContent; + final String historyLabel; + final String currentLabel; + + const HostsDiffPage({ + Key? key, + required this.historyContent, + required this.currentContent, + required this.historyLabel, + required this.currentLabel, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.hosts_diff_title ?? 'Hosts 差异对比'), + actions: [ + IconButton( + icon: Icon(Icons.info_outline), + onPressed: () => _showDiffLegend(context), + ), + ], + ), + body: Padding( + padding: EdgeInsets.all(16), + child: Column( + children: [ + // 统计信息 + _buildDiffStats(historyContent, currentContent, context), + SizedBox(height: 16), + + // 差异显示 + Expanded( + child: HostsDiffViewer( + oldContent: historyContent, + newContent: currentContent, + oldLabel: historyLabel, + newLabel: currentLabel, + ), + ), + ], + ), + ), + ); + } + + Widget _buildDiffStats(String oldContent, String newContent, BuildContext context) { + final oldLines = oldContent.split('\n').length; + final newLines = newContent.split('\n').length; + final diff = newLines - oldLines; + + return Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceVariant, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildStatItem( + context, + AppLocalizations.of(context)!.diff_stats_history ?? '历史版本', + '$oldLines 行', + Icons.history, + ), + _buildStatItem( + context, + AppLocalizations.of(context)!.diff_stats_current ?? '当前版本', + '$newLines 行', + Icons.description, + ), + _buildStatItem( + context, + AppLocalizations.of(context)!.diff_stats_difference ?? '差异', + '${diff > 0 ? '+' : ''}$diff 行', + diff > 0 ? Icons.add : (diff < 0 ? Icons.remove : Icons.check), + color: diff > 0 + ? Theme.of(context).colorScheme.primary + : (diff < 0 ? Theme.of(context).colorScheme.error : null), + ), + ], + ), + ); + } + + Widget _buildStatItem( + BuildContext context, + String label, + String value, + IconData icon, { + Color? color, + }) { + return Column( + children: [ + Icon(icon, size: 20, color: color), + SizedBox(height: 4), + Text( + label, + style: Theme.of(context).textTheme.labelSmall, + ), + Text( + value, + style: Theme.of(context).textTheme.labelMedium?.copyWith( + fontWeight: FontWeight.bold, + color: color, + ), + ), + ], + ); + } + + void _showDiffLegend(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('差异说明'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildLegendItem( + context, + AppLocalizations.of(context)!.diff_legend_added ?? '新增内容', + Theme.of(context).colorScheme.primary, + Theme.of(context).colorScheme.primaryContainer.withOpacity(0.3), + ), + SizedBox(height: 8), + _buildLegendItem( + context, + AppLocalizations.of(context)!.diff_legend_deleted ?? '删除内容', + Theme.of(context).colorScheme.error, + Theme.of(context).colorScheme.errorContainer.withOpacity(0.3), + hasStrikethrough: true, + ), + SizedBox(height: 8), + _buildLegendItem( + context, + AppLocalizations.of(context)!.diff_legend_unchanged ?? '未变更内容', + Theme.of(context).colorScheme.onSurface, + null, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('知道了'), + ), + ], + ), + ); + } + + Widget _buildLegendItem( + BuildContext context, + String text, + Color textColor, + Color? backgroundColor, { + bool hasStrikethrough = false, + }) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + text, + style: TextStyle( + color: textColor, + decoration: hasStrikethrough ? TextDecoration.lineThrough : null, + fontWeight: FontWeight.w500, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widget/host_text_editing_controller.dart b/lib/widget/host_text_editing_controller.dart index 2f52a1a..bda8340 100644 --- a/lib/widget/host_text_editing_controller.dart +++ b/lib/widget/host_text_editing_controller.dart @@ -15,6 +15,21 @@ class HostTextEditingController extends TextEditingController { TextStyle? style, bool? withComposing, }) { + // Ensure selection is within bounds before building text span + final int textLength = text.length; + if (selection.baseOffset > textLength || selection.extentOffset > textLength) { + final TextSelection boundedSelection = selection.copyWith( + baseOffset: min(selection.baseOffset, textLength), + extentOffset: min(selection.extentOffset, textLength), + ); + // Update selection without triggering infinite recursion + WidgetsBinding.instance.addPostFrameCallback((_) { + if (selection != boundedSelection) { + selection = boundedSelection; + } + }); + } + lines.clear(); lines.addAll(text.split("\n")); @@ -177,7 +192,15 @@ class HostTextEditingController extends TextEditingController { set value(TextEditingValue newValue) { lines.clear(); lines.addAll(newValue.text.split("\n")); - super.value = newValue; + + // Ensure selection is within bounds + final int textLength = newValue.text.length; + final TextSelection boundedSelection = newValue.selection.copyWith( + baseOffset: min(newValue.selection.baseOffset, textLength), + extentOffset: min(newValue.selection.extentOffset, textLength), + ); + + super.value = newValue.copyWith(selection: boundedSelection); } int countNewlines(String text) { diff --git a/lib/widget/hosts_diff_viewer.dart b/lib/widget/hosts_diff_viewer.dart new file mode 100644 index 0000000..aea5743 --- /dev/null +++ b/lib/widget/hosts_diff_viewer.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:diff_match_patch/diff_match_patch.dart'; + +class HostsDiffViewer extends StatelessWidget { + final String oldContent; + final String newContent; + final String? oldLabel; + final String? newLabel; + + const HostsDiffViewer({ + Key? key, + required this.oldContent, + required this.newContent, + this.oldLabel, + this.newLabel, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final dmp = DiffMatchPatch(); + final diffs = dmp.diff(oldContent, newContent); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // 标题栏 + if (oldLabel != null || newLabel != null) + Container( + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceVariant, + borderRadius: BorderRadius.vertical(top: Radius.circular(8)), + ), + child: Row( + children: [ + if (oldLabel != null) ...[ + Icon(Icons.remove, color: theme.colorScheme.error, size: 16), + SizedBox(width: 4), + Expanded(child: Text(oldLabel!, style: theme.textTheme.labelMedium)), + ], + if (oldLabel != null && newLabel != null) + Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Icon(Icons.compare_arrows, size: 16), + ), + if (newLabel != null) ...[ + Icon(Icons.add, color: theme.colorScheme.primary, size: 16), + SizedBox(width: 4), + Expanded(child: Text(newLabel!, style: theme.textTheme.labelMedium)), + ], + ], + ), + ), + + // 差异内容 + Expanded( + child: Container( + width: double.infinity, + decoration: BoxDecoration( + border: Border.all(color: theme.colorScheme.outline), + borderRadius: BorderRadius.circular(8), + ), + child: SingleChildScrollView( + padding: EdgeInsets.all(16), + child: RichText( + text: TextSpan( + style: theme.textTheme.bodyMedium?.copyWith( + fontFamily: 'monospace', + height: 1.4, + ), + children: diffs.map((diff) => _buildDiffSpan(diff, theme)).toList(), + ), + ), + ), + ), + ), + ], + ); + } + + TextSpan _buildDiffSpan(Diff diff, ThemeData theme) { + switch (diff.operation) { + case DIFF_INSERT: + return TextSpan( + text: diff.text, + style: TextStyle( + backgroundColor: theme.colorScheme.primaryContainer.withOpacity(0.3), + color: theme.colorScheme.primary, + fontWeight: FontWeight.w500, + ), + ); + case DIFF_DELETE: + return TextSpan( + text: diff.text, + style: TextStyle( + backgroundColor: theme.colorScheme.errorContainer.withOpacity(0.3), + color: theme.colorScheme.error, + decoration: TextDecoration.lineThrough, + fontWeight: FontWeight.w500, + ), + ); + default: + return TextSpan( + text: diff.text, + style: TextStyle(color: theme.colorScheme.onSurface), + ); + } + } +} \ No newline at end of file diff --git a/lib/widget/row_line_widget.dart b/lib/widget/row_line_widget.dart index bf18a54..dcad058 100644 --- a/lib/widget/row_line_widget.dart +++ b/lib/widget/row_line_widget.dart @@ -53,42 +53,45 @@ class RowLineWidget extends StatelessWidget { return Container( width: containerWidth, padding: const EdgeInsets.only(top: 4), - child: SingleChildScrollView( - physics: const NeverScrollableScrollPhysics(), - controller: scrollController, - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.end, - children: List.generate(lines.length, (index) { - final String line = lines[index]; - - final TextPainter textPainter = TextPainter( - text: TextSpan( - text: line, - style: titleMedium, - ), - textDirection: TextDirection.ltr, - )..layout(); - - final double width = textPainter.width + fontSize; - - return buildIndexedLineContainer( - containerWidth, - selectedLine.contains(index), - "${index + 1}", - line, - () { - if (selectedLine.contains(index)) { - textEditingController.updateUseStatus(textSelection); - return; - } - final int length = - lines.sublist(0, index + 1).join("\n").length; - textEditingController.updateUseStatus( - TextSelection(baseOffset: length, extentOffset: length)); - }, - ); - }), + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + child: SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + controller: scrollController, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: List.generate(lines.length, (index) { + final String line = lines[index]; + + final TextPainter textPainter = TextPainter( + text: TextSpan( + text: line, + style: titleMedium, + ), + textDirection: TextDirection.ltr, + )..layout(); + + final double width = textPainter.width + fontSize; + + return buildIndexedLineContainer( + containerWidth, + selectedLine.contains(index), + "${index + 1}", + line, + () { + if (selectedLine.contains(index)) { + textEditingController.updateUseStatus(textSelection); + return; + } + final int length = + lines.sublist(0, index + 1).join("\n").length; + textEditingController.updateUseStatus( + TextSelection(baseOffset: length, extentOffset: length)); + }, + ); + }), + ), ), ), ); From ee502f398b6183059c8cc206e71f61a8c6bd2b0b Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Mon, 21 Jul 2025 23:00:46 +0800 Subject: [PATCH 33/54] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0hosts=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=B7=AE=E5=BC=82=E5=AF=B9=E6=AF=94=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增HostsDiffPage页面显示文件差异对比 - 添加HostsDiffViewer组件提供diff视觉化 - 在历史页面集成差异对比按钮 - 增加diff_match_patch依赖支持文本差异分析 - 完善国际化文本支持差异对比功能 --- lib/home/view/home_app_bar.dart | 5 +- lib/l10n/app_en.arb | 9 ++- lib/l10n/app_localizations.dart | 42 ++++++++++++ lib/l10n/app_localizations_en.dart | 21 ++++++ lib/l10n/app_localizations_zh.dart | 21 ++++++ lib/l10n/app_zh.arb | 9 ++- lib/page/history_page.dart | 104 +++++++++++++++++++++++------ pubspec.lock | 8 +++ pubspec.yaml | 3 + 9 files changed, 196 insertions(+), 26 deletions(-) diff --git a/lib/home/view/home_app_bar.dart b/lib/home/view/home_app_bar.dart index b68a1bb..283eb81 100644 --- a/lib/home/view/home_app_bar.dart +++ b/lib/home/view/home_app_bar.dart @@ -115,6 +115,7 @@ class HomeAppBar extends StatelessWidget { HistoryPage( selectHistory: hostStateData.selectHistory, history: hostStateData.history, + fileId: state.data.fileId, ), ); if (resultHistory == null) { @@ -132,8 +133,8 @@ class HomeAppBar extends StatelessWidget { action: SnackBarAction( label: AppLocalizations.of(context)!.abort, - onPressed: () => - hostCubit.onHistoryChanged(resultHistory), + onPressed: () => hostCubit + .onHistoryChanged(resultHistory), ), )); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 55b025a..b2e0a60 100755 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -144,5 +144,12 @@ "no_importable_content": "No importable file content found", "remote_files": " remote files", "show_qr_code": "Show QR Code", - "port": "Port" + "port": "Port", + "hosts_diff_title": "Hosts Diff Comparison", + "diff_legend_added": "Added Content", + "diff_legend_deleted": "Deleted Content", + "diff_legend_unchanged": "Unchanged Content", + "diff_stats_history": "Historical Version", + "diff_stats_current": "Current Version", + "diff_stats_difference": "Difference" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 312f161..b7b5849 100755 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -967,6 +967,48 @@ abstract class AppLocalizations { /// In zh, this message translates to: /// **'端口'** String get port; + + /// No description provided for @hosts_diff_title. + /// + /// In zh, this message translates to: + /// **'Hosts 差异对比'** + String get hosts_diff_title; + + /// No description provided for @diff_legend_added. + /// + /// In zh, this message translates to: + /// **'新增内容'** + String get diff_legend_added; + + /// No description provided for @diff_legend_deleted. + /// + /// In zh, this message translates to: + /// **'删除内容'** + String get diff_legend_deleted; + + /// No description provided for @diff_legend_unchanged. + /// + /// In zh, this message translates to: + /// **'未变更内容'** + String get diff_legend_unchanged; + + /// No description provided for @diff_stats_history. + /// + /// In zh, this message translates to: + /// **'历史版本'** + String get diff_stats_history; + + /// No description provided for @diff_stats_current. + /// + /// In zh, this message translates to: + /// **'当前版本'** + String get diff_stats_current; + + /// No description provided for @diff_stats_difference. + /// + /// In zh, this message translates to: + /// **'差异'** + String get diff_stats_difference; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 52aa012..7c8c34f 100755 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -461,4 +461,25 @@ class AppLocalizationsEn extends AppLocalizations { @override String get port => 'Port'; + + @override + String get hosts_diff_title => 'Hosts Diff Comparison'; + + @override + String get diff_legend_added => 'Added Content'; + + @override + String get diff_legend_deleted => 'Deleted Content'; + + @override + String get diff_legend_unchanged => 'Unchanged Content'; + + @override + String get diff_stats_history => 'Historical Version'; + + @override + String get diff_stats_current => 'Current Version'; + + @override + String get diff_stats_difference => 'Difference'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 99a12dd..4c6323c 100755 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -449,4 +449,25 @@ class AppLocalizationsZh extends AppLocalizations { @override String get port => '端口'; + + @override + String get hosts_diff_title => 'Hosts 差异对比'; + + @override + String get diff_legend_added => '新增内容'; + + @override + String get diff_legend_deleted => '删除内容'; + + @override + String get diff_legend_unchanged => '未变更内容'; + + @override + String get diff_stats_history => '历史版本'; + + @override + String get diff_stats_current => '当前版本'; + + @override + String get diff_stats_difference => '差异'; } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index e23e3e3..87d1c5f 100755 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -144,5 +144,12 @@ "no_importable_content": "没有找到可导入的文件内容", "remote_files": "个远程文件", "show_qr_code": "显示二维码", - "port": "端口" + "port": "端口", + "hosts_diff_title": "Hosts 差异对比", + "diff_legend_added": "新增内容", + "diff_legend_deleted": "删除内容", + "diff_legend_unchanged": "未变更内容", + "diff_stats_history": "历史版本", + "diff_stats_current": "当前版本", + "diff_stats_difference": "差异" } \ No newline at end of file diff --git a/lib/page/history_page.dart b/lib/page/history_page.dart index 3507656..5970802 100644 --- a/lib/page/history_page.dart +++ b/lib/page/history_page.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/page/hosts_diff_page.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/widget/countdown_timer.dart'; - class HistoryPage extends StatefulWidget { + final String fileId; final SimpleHostFileHistory? selectHistory; final List history; @@ -13,6 +14,7 @@ class HistoryPage extends StatefulWidget { super.key, required this.selectHistory, required this.history, + required this.fileId, }); @override @@ -37,7 +39,7 @@ class _HistoryPageState extends State { @override Widget build(BuildContext context) { return Container( - color:Theme.of(context).colorScheme.surfaceContainer, + color: Theme.of(context).colorScheme.surfaceContainer, height: MediaQuery.of(context).size.height, child: Column( mainAxisSize: MainAxisSize.min, @@ -47,7 +49,7 @@ class _HistoryPageState extends State { width: double.maxFinite, padding: const EdgeInsets.only(left: 16, top: 16, bottom: 8), decoration: BoxDecoration( - color:Theme.of(context).colorScheme.surfaceContainer, + color: Theme.of(context).colorScheme.surfaceContainer, ), child: Text( AppLocalizations.of(context)!.history, @@ -90,26 +92,38 @@ class _HistoryPageState extends State { }, )) : null, - trailing: IconButton( - onPressed: () { - setState(() { - if (deleteSimpleHostFileHistory - .contains(hostFile)) { - deleteSimpleHostFileHistory.remove(hostFile); - } else { - deleteSimpleHostFileHistory.add(hostFile); - } - }); - }, - style: OutlinedButton.styleFrom( - minimumSize: Size.zero, - padding: EdgeInsets.zero, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () { + _showDiffComparison(context, hostFile); + }, + icon: const Icon(Icons.compare_arrows), + tooltip: '查看差异', ), - icon: Icon( - deleteSimpleHostFileHistory.contains(hostFile) - ? Icons.close - : Icons.delete_outline, - )), + IconButton( + onPressed: () { + setState(() { + if (deleteSimpleHostFileHistory + .contains(hostFile)) { + deleteSimpleHostFileHistory.remove(hostFile); + } else { + deleteSimpleHostFileHistory.add(hostFile); + } + }); + }, + style: OutlinedButton.styleFrom( + minimumSize: Size.zero, + padding: EdgeInsets.zero, + ), + icon: Icon( + deleteSimpleHostFileHistory.contains(hostFile) + ? Icons.close + : Icons.delete_outline, + )), + ], + ), selectedTileColor: Theme.of(context).colorScheme.primaryContainer, onTap: () { @@ -125,4 +139,50 @@ class _HistoryPageState extends State { ), ); } + + void _showDiffComparison( + BuildContext context, SimpleHostFileHistory hostFile) async { + try { + final fileManager = FileManager(); + + // 读取历史文件内容 + final historyContent = fileManager.readHistoryFile(hostFile.path); + + // 获取当前文件内容 + final currentContent = await fileManager.readAsString(widget.fileId); + + // 格式化时间标签 + final dateTime = + DateTime.fromMillisecondsSinceEpoch(int.parse(hostFile.fileName)); + final year = dateTime.year.toString(); + final month = dateTime.month.toString().padLeft(2, '0'); + final day = dateTime.day.toString().padLeft(2, '0'); + final hour = dateTime.hour.toString().padLeft(2, '0'); + final minute = dateTime.minute.toString().padLeft(2, '0'); + final second = dateTime.second.toString().padLeft(2, '0'); + + final historyLabel = '历史版本: $year-$month-$day $hour:$minute:$second'; + final currentLabel = '当前版本'; + + // 导航到差异对比页面 + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => HostsDiffPage( + historyContent: historyContent, + currentContent: currentContent, + historyLabel: historyLabel, + currentLabel: currentLabel, + ), + ), + ); + } catch (e) { + // 显示错误信息 + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('无法读取历史文件: $e'), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + } } diff --git a/pubspec.lock b/pubspec.lock index 0d16b4f..3257cae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.11" + diff_match_patch: + dependency: "direct main" + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.dev" + source: hosted + version: "0.4.1" equatable: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index ea1baf6..5e11913 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,9 @@ dependencies: # 后台运行 flutter_background: ^1.3.0+1 + + # 文本差异对比 + diff_match_patch: ^0.4.1 dev_dependencies: flutter_test: From 51924cfca4890f75c9f99398a50383c3eb9acfc2 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Tue, 22 Jul 2025 20:01:59 +0800 Subject: [PATCH 34/54] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E8=AE=BE?= =?UTF-8?q?=E5=A4=87API=E6=96=87=E6=A1=A3=E6=9F=A5=E7=9C=8B=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加ApiDocumentationDialog组件,支持展示设备API端点信息 - 优化AccessDeviceDialog,增加历史记录数量显示和导入功能 - 扩展DeviceApiCache,支持获取hosts文件历史内容 - 更新国际化文件,添加API文档相关翻译 - 支持自适应布局,宽屏显示网格布局 - 支持二维码生成、浏览器打开、URL复制等功能 --- lib/l10n/app_en.arb | 28 +- lib/l10n/app_localizations.dart | 156 ++++ lib/l10n/app_localizations_en.dart | 79 ++ lib/l10n/app_localizations_zh.dart | 78 ++ lib/l10n/app_zh.arb | 28 +- lib/utils/device_api_cache.dart | 24 + lib/widget/dialog/access_device_dialog.dart | 89 ++- .../dialog/api_documentation_dialog.dart | 729 ++++++++++++++++++ 8 files changed, 1202 insertions(+), 9 deletions(-) create mode 100644 lib/widget/dialog/api_documentation_dialog.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b2e0a60..b4ec014 100755 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -151,5 +151,31 @@ "diff_legend_unchanged": "Unchanged Content", "diff_stats_history": "Historical Version", "diff_stats_current": "Current Version", - "diff_stats_difference": "Difference" + "diff_stats_difference": "Difference", + "history_count": "History records", + "getting_device_info": "Getting device information...", + "get_device_info_failed": "Failed to get device information", + "basic_api": "Basic API", + "available_hosts_files_api": "Available Hosts files API:", + "device_no_hosts_files": "This device has no available hosts files", + "history_content": "History content", + "history_count_suffix": " history records", + "get_content_prefix": "Get", + "get_content_suffix": "content", + "scan_qr_code_to_access": "Scan QR code to access", + "open_in_browser": "Open in browser", + "show_qr_code_tooltip": "Show QR code", + "copy_url_tooltip": "Copy URL", + "get_history_content_prefix": "Get history record", + "get_history_content_suffix": "content", + "more_history_records": "... ", + "more_history_records_suffix": " more history records", + "view_diff": "View diff", + "history_version": "History version", + "current_version": "Current version", + "unable_to_read_history_file": "Unable to read history file", + "diff_legend_description": "Difference Legend", + "diff_legend_ok": "Got it", + "current_line": "Current line:", + "total_lines": "Total lines:" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index b7b5849..0b67a24 100755 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1009,6 +1009,162 @@ abstract class AppLocalizations { /// In zh, this message translates to: /// **'差异'** String get diff_stats_difference; + + /// No description provided for @history_count. + /// + /// In zh, this message translates to: + /// **'历史记录'** + String get history_count; + + /// No description provided for @getting_device_info. + /// + /// In zh, this message translates to: + /// **'正在获取设备信息...'** + String get getting_device_info; + + /// No description provided for @get_device_info_failed. + /// + /// In zh, this message translates to: + /// **'获取设备信息失败'** + String get get_device_info_failed; + + /// No description provided for @basic_api. + /// + /// In zh, this message translates to: + /// **'基础 API'** + String get basic_api; + + /// No description provided for @available_hosts_files_api. + /// + /// In zh, this message translates to: + /// **'可用的Hosts文件 API:'** + String get available_hosts_files_api; + + /// No description provided for @device_no_hosts_files. + /// + /// In zh, this message translates to: + /// **'该设备暂无可用的hosts文件'** + String get device_no_hosts_files; + + /// No description provided for @history_content. + /// + /// In zh, this message translates to: + /// **'历史记录内容'** + String get history_content; + + /// No description provided for @history_count_suffix. + /// + /// In zh, this message translates to: + /// **'条历史'** + String get history_count_suffix; + + /// No description provided for @get_content_prefix. + /// + /// In zh, this message translates to: + /// **'获取'** + String get get_content_prefix; + + /// No description provided for @get_content_suffix. + /// + /// In zh, this message translates to: + /// **'的内容'** + String get get_content_suffix; + + /// No description provided for @scan_qr_code_to_access. + /// + /// In zh, this message translates to: + /// **'扫描二维码访问'** + String get scan_qr_code_to_access; + + /// No description provided for @open_in_browser. + /// + /// In zh, this message translates to: + /// **'在浏览器中打开'** + String get open_in_browser; + + /// No description provided for @show_qr_code_tooltip. + /// + /// In zh, this message translates to: + /// **'显示二维码'** + String get show_qr_code_tooltip; + + /// No description provided for @copy_url_tooltip. + /// + /// In zh, this message translates to: + /// **'复制URL'** + String get copy_url_tooltip; + + /// No description provided for @get_history_content_prefix. + /// + /// In zh, this message translates to: + /// **'获取历史记录'** + String get get_history_content_prefix; + + /// No description provided for @get_history_content_suffix. + /// + /// In zh, this message translates to: + /// **'的内容'** + String get get_history_content_suffix; + + /// No description provided for @more_history_records. + /// + /// In zh, this message translates to: + /// **'... 还有'** + String get more_history_records; + + /// No description provided for @more_history_records_suffix. + /// + /// In zh, this message translates to: + /// **'条历史记录'** + String get more_history_records_suffix; + + /// No description provided for @view_diff. + /// + /// In zh, this message translates to: + /// **'查看差异'** + String get view_diff; + + /// No description provided for @history_version. + /// + /// In zh, this message translates to: + /// **'历史版本'** + String get history_version; + + /// No description provided for @current_version. + /// + /// In zh, this message translates to: + /// **'当前版本'** + String get current_version; + + /// No description provided for @unable_to_read_history_file. + /// + /// In zh, this message translates to: + /// **'无法读取历史文件'** + String get unable_to_read_history_file; + + /// No description provided for @diff_legend_description. + /// + /// In zh, this message translates to: + /// **'差异说明'** + String get diff_legend_description; + + /// No description provided for @diff_legend_ok. + /// + /// In zh, this message translates to: + /// **'知道了'** + String get diff_legend_ok; + + /// No description provided for @current_line. + /// + /// In zh, this message translates to: + /// **'当前行:'** + String get current_line; + + /// No description provided for @total_lines. + /// + /// In zh, this message translates to: + /// **'总行数:'** + String get total_lines; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 7c8c34f..6a1043e 100755 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -482,4 +482,83 @@ class AppLocalizationsEn extends AppLocalizations { @override String get diff_stats_difference => 'Difference'; + + @override + String get history_count => 'History records'; + + @override + String get getting_device_info => 'Getting device information...'; + + @override + String get get_device_info_failed => 'Failed to get device information'; + + @override + String get basic_api => 'Basic API'; + + @override + String get available_hosts_files_api => 'Available Hosts files API:'; + + @override + String get device_no_hosts_files => + 'This device has no available hosts files'; + + @override + String get history_content => 'History content'; + + @override + String get history_count_suffix => ' history records'; + + @override + String get get_content_prefix => 'Get'; + + @override + String get get_content_suffix => 'content'; + + @override + String get scan_qr_code_to_access => 'Scan QR code to access'; + + @override + String get open_in_browser => 'Open in browser'; + + @override + String get show_qr_code_tooltip => 'Show QR code'; + + @override + String get copy_url_tooltip => 'Copy URL'; + + @override + String get get_history_content_prefix => 'Get history record'; + + @override + String get get_history_content_suffix => 'content'; + + @override + String get more_history_records => '... '; + + @override + String get more_history_records_suffix => ' more history records'; + + @override + String get view_diff => 'View diff'; + + @override + String get history_version => 'History version'; + + @override + String get current_version => 'Current version'; + + @override + String get unable_to_read_history_file => 'Unable to read history file'; + + @override + String get diff_legend_description => 'Difference Legend'; + + @override + String get diff_legend_ok => 'Got it'; + + @override + String get current_line => 'Current line:'; + + @override + String get total_lines => 'Total lines:'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 4c6323c..5ec95d8 100755 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -470,4 +470,82 @@ class AppLocalizationsZh extends AppLocalizations { @override String get diff_stats_difference => '差异'; + + @override + String get history_count => '历史记录'; + + @override + String get getting_device_info => '正在获取设备信息...'; + + @override + String get get_device_info_failed => '获取设备信息失败'; + + @override + String get basic_api => '基础 API'; + + @override + String get available_hosts_files_api => '可用的Hosts文件 API:'; + + @override + String get device_no_hosts_files => '该设备暂无可用的hosts文件'; + + @override + String get history_content => '历史记录内容'; + + @override + String get history_count_suffix => '条历史'; + + @override + String get get_content_prefix => '获取'; + + @override + String get get_content_suffix => '的内容'; + + @override + String get scan_qr_code_to_access => '扫描二维码访问'; + + @override + String get open_in_browser => '在浏览器中打开'; + + @override + String get show_qr_code_tooltip => '显示二维码'; + + @override + String get copy_url_tooltip => '复制URL'; + + @override + String get get_history_content_prefix => '获取历史记录'; + + @override + String get get_history_content_suffix => '的内容'; + + @override + String get more_history_records => '... 还有'; + + @override + String get more_history_records_suffix => '条历史记录'; + + @override + String get view_diff => '查看差异'; + + @override + String get history_version => '历史版本'; + + @override + String get current_version => '当前版本'; + + @override + String get unable_to_read_history_file => '无法读取历史文件'; + + @override + String get diff_legend_description => '差异说明'; + + @override + String get diff_legend_ok => '知道了'; + + @override + String get current_line => '当前行:'; + + @override + String get total_lines => '总行数:'; } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 87d1c5f..f5850fa 100755 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -151,5 +151,31 @@ "diff_legend_unchanged": "未变更内容", "diff_stats_history": "历史版本", "diff_stats_current": "当前版本", - "diff_stats_difference": "差异" + "diff_stats_difference": "差异", + "history_count": "历史记录", + "getting_device_info": "正在获取设备信息...", + "get_device_info_failed": "获取设备信息失败", + "basic_api": "基础 API", + "available_hosts_files_api": "可用的Hosts文件 API:", + "device_no_hosts_files": "该设备暂无可用的hosts文件", + "history_content": "历史记录内容", + "history_count_suffix": "条历史", + "get_content_prefix": "获取", + "get_content_suffix": "的内容", + "scan_qr_code_to_access": "扫描二维码访问", + "open_in_browser": "在浏览器中打开", + "show_qr_code_tooltip": "显示二维码", + "copy_url_tooltip": "复制URL", + "get_history_content_prefix": "获取历史记录", + "get_history_content_suffix": "的内容", + "more_history_records": "... 还有", + "more_history_records_suffix": "条历史记录", + "view_diff": "查看差异", + "history_version": "历史版本", + "current_version": "当前版本", + "unable_to_read_history_file": "无法读取历史文件", + "diff_legend_description": "差异说明", + "diff_legend_ok": "知道了", + "current_line": "当前行:", + "total_lines": "总行数:" } \ No newline at end of file diff --git a/lib/utils/device_api_cache.dart b/lib/utils/device_api_cache.dart index 7823132..d16773a 100644 --- a/lib/utils/device_api_cache.dart +++ b/lib/utils/device_api_cache.dart @@ -105,4 +105,28 @@ class DeviceApiCache { return []; } } + + /// 获取特定hosts文件的历史内容(直接访问,不缓存) + static Future getCachedHostsFileHistoryContent(String ip, String fileName, String historyFileName) async { + try { + final httpClient = HttpClient(); + httpClient.connectionTimeout = _scanTimeout; + httpClient.idleTimeout = _scanTimeout; + + final request = await httpClient.get(ip, _defaultPort, '/api/hosts/$fileName/history/$historyFileName'); + final response = await request.close(); + + if (response.statusCode == 200) { + final responseBody = await response.transform(utf8.decoder).join(); + httpClient.close(); + return responseBody; + } + + httpClient.close(); + return null; + } catch (e) { + print('获取hosts文件历史内容失败 $ip/$fileName/$historyFileName: $e'); + return null; + } + } } diff --git a/lib/widget/dialog/access_device_dialog.dart b/lib/widget/dialog/access_device_dialog.dart index 14246a2..8ffd33c 100644 --- a/lib/widget/dialog/access_device_dialog.dart +++ b/lib/widget/dialog/access_device_dialog.dart @@ -7,6 +7,8 @@ import 'package:hosts/util/file_manager.dart'; import 'package:hosts/util/settings_manager.dart'; import 'package:hosts/utils/device_api_cache.dart'; import 'package:hosts/utils/nearby_devices_scanner.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; Future accessDeviceDialog(BuildContext context, NearbyDevice device) { return showDialog( @@ -37,6 +39,7 @@ class _AccessDeviceDialogState extends State { bool isLoading = false; bool hasError = false; String? errorMessage; + Map historyCount = {}; @override void initState() { @@ -95,17 +98,31 @@ class _AccessDeviceDialogState extends State { final hostConfigs = await DeviceApiCache.getCachedDeviceData(widget.device.ip); final List hosts = []; + final Map historyCounts = {}; + for (final config in hostConfigs) { final hostFile = SimpleHostFile.fromJson(config); if (hostFile.fileName == "system") { hostFile.remark = defaultHostsText; } hosts.add(hostFile); + + // 获取每个hosts文件的历史记录数量 + try { + final historyList = await DeviceApiCache.getCachedHostsFileHistory( + widget.device.ip, + hostFile.fileName, + ); + historyCounts[hostFile.fileName] = historyList.length; + } catch (e) { + historyCounts[hostFile.fileName] = 0; + } } setState(() { availableHosts = hosts; selectedItems = List.generate(hosts.length, (index) => false); + historyCount = historyCounts; isLoading = false; }); } catch (e) { @@ -187,6 +204,49 @@ class _AccessDeviceDialogState extends State { await fileManager.getHostsFilePath(fileName); await File(localFilePath).writeAsString(remoteContent); + // 导入hosts历史记录 + try { + + // 保存历史文件到本地,使用File直接写入 + final Directory historyDirPath = Directory(p.join( + (await getApplicationSupportDirectory()).path, + fileName, + 'history' + )); + + if (!historyDirPath.existsSync()) { + historyDirPath.createSync(); + } + + final List> historyList = + await DeviceApiCache.getCachedHostsFileHistory( + widget.device.ip, + fileName, + ); + + for (final historyItem in historyList) { + final String historyFileName = historyItem['id'] ?? ''; + if (historyFileName.isNotEmpty) { + // 获取历史文件内容 + final String? historyContent = + await DeviceApiCache.getCachedHostsFileHistoryContent( + widget.device.ip, + fileName, + historyFileName, + ); + + if (historyContent != null && historyContent.isNotEmpty) { + // 直接写入历史文件,文件名保持与远程一致 + final String historyFilePath = p.join(historyDirPath.path, historyFileName); + await File(historyFilePath).writeAsString(historyContent); + } + } + } + } catch (e) { + print('导入hosts历史失败 $fileName: $e'); + // 历史导入失败不影响主文件导入,继续处理 + } + // 创建本地SimpleHostFile对象 final SimpleHostFile localHostFile = SimpleHostFile( fileName: fileName, @@ -376,13 +436,28 @@ class _AccessDeviceDialogState extends State { onChanged: (bool? value) => _toggleItem(index), ), title: Text(host.remark), - subtitle: isExisting - ? Text( - AppLocalizations.of(context)!.will_overwrite, - style: TextStyle( - color: Theme.of(context).colorScheme.error, - fontSize: 12, - ), + subtitle: (historyCount[host.fileName] != null && historyCount[host.fileName]! > 0) || isExisting + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (historyCount[host.fileName] != null && historyCount[host.fileName]! > 0) + Text( + '${AppLocalizations.of(context)!.history_count}: ${historyCount[host.fileName]}', + style: TextStyle( + color: Colors.grey[600], + fontSize: 12, + ), + ), + if (isExisting) + Text( + AppLocalizations.of(context)!.will_overwrite, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 12, + ), + ), + ], ) : null, onTap: () => _toggleItem(index), diff --git a/lib/widget/dialog/api_documentation_dialog.dart b/lib/widget/dialog/api_documentation_dialog.dart new file mode 100644 index 0000000..f5946d6 --- /dev/null +++ b/lib/widget/dialog/api_documentation_dialog.dart @@ -0,0 +1,729 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/utils/device_api_cache.dart'; +import 'package:hosts/widget/dialog/qr_code_dialog.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/// API文档弹窗 +class ApiDocumentationDialog extends StatefulWidget { + final String deviceIp; + final int port; + + const ApiDocumentationDialog({ + super.key, + required this.deviceIp, + this.port = 1204, + }); + + @override + State createState() => _ApiDocumentationDialogState(); +} + +class _ApiDocumentationDialogState extends State { + List> hostsFiles = []; + bool isLoading = true; + bool hasError = false; + String? errorMessage; + bool isBasicEndpointsExpanded = false; + Map hostFileExpansionState = {}; + Map>> hostFileHistories = {}; + + @override + void initState() { + super.initState(); + _loadHostsFiles(); + } + + Future _loadHostsFiles() async { + try { + setState(() { + isLoading = true; + hasError = false; + errorMessage = null; + }); + + final data = await DeviceApiCache.getCachedDeviceData(widget.deviceIp); + + // 为每个hosts文件获取历史记录 + final Map>> histories = {}; + for (final hostFile in data) { + final fileName = hostFile['fileName'] as String?; + if (fileName != null) { + try { + final history = await DeviceApiCache.getCachedHostsFileHistory( + widget.deviceIp, + fileName, + ); + histories[fileName] = history; + } catch (e) { + // 如果获取历史失败,设置为空列表 + histories[fileName] = []; + } + } + } + + setState(() { + hostsFiles = data; + hostFileHistories = histories; + isLoading = false; + }); + } catch (e) { + setState(() { + isLoading = false; + hasError = true; + errorMessage = e.toString(); + }); + } + } + + @override + Widget build(BuildContext context) { + final baseUrl = 'http://${widget.deviceIp}:${widget.port}'; + final screenSize = MediaQuery.of(context).size; + + return AlertDialog( + insetPadding: EdgeInsets.all(16), + title: Row( + children: [ + Icon(Icons.api, color: Theme.of(context).colorScheme.primary), + const SizedBox(width: 8), + Text(AppLocalizations.of(context)!.api_docs), + const Spacer(), + IconButton( + icon: Icon(Icons.refresh), + onPressed: isLoading ? null : _loadHostsFiles, + tooltip: AppLocalizations.of(context)!.refresh, + ), + ], + ), + content: SizedBox( + width: screenSize.width, + height: screenSize.height, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 服务器地址信息 + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon( + Icons.computer, + color: Colors.green, + size: 20, + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.server_address, + style: + Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w500, + ), + ), + Text( + baseUrl, + style: + Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + ], + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.open_in_browser), + onPressed: () => _openInBrowser(baseUrl), + tooltip: AppLocalizations.of(context)!.open_in_browser, + ), + IconButton( + icon: Icon(Icons.qr_code), + onPressed: () => _showQrCode(context, baseUrl), + tooltip: + AppLocalizations.of(context)!.show_qr_code_tooltip, + ), + IconButton( + icon: Icon(Icons.copy), + onPressed: () => _copyToClipboard(context, baseUrl), + tooltip: AppLocalizations.of(context)!.copy_url, + ), + ], + ), + ], + ), + ), + const SizedBox(height: 16), + + // API端点列表 + Text( + '${AppLocalizations.of(context)!.api_endpoints}:', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + + Expanded( + child: isLoading + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text(AppLocalizations.of(context)! + .getting_device_info), + ], + ), + ) + : hasError + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, + size: 48, color: Colors.red), + SizedBox(height: 16), + Text(AppLocalizations.of(context)! + .get_device_info_failed), + if (errorMessage != null) ...[ + SizedBox(height: 8), + Text( + errorMessage!, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + ], + SizedBox(height: 16), + ElevatedButton.icon( + onPressed: _loadHostsFiles, + icon: Icon(Icons.refresh), + label: + Text(AppLocalizations.of(context)!.retry), + ), + ], + ), + ) + : SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 基础API端点 + _buildBasicEndpoints(context, baseUrl), + + const SizedBox(height: 16), + + // Hosts文件相关API + if (hostsFiles.isNotEmpty) ...[ + Text( + AppLocalizations.of(context)! + .available_hosts_files_api, + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + _buildHostsFilesLayout( + context, baseUrl, screenSize), + ] else ...[ + Container( + padding: const EdgeInsets.all(16), + child: Text( + AppLocalizations.of(context)! + .device_no_hosts_files, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + ), + ], + ], + ), + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(AppLocalizations.of(context)!.cancel), + ), + ], + ); + } + + /// 构建hosts文件布局 - 根据屏幕大小决定使用List还是GridView + Widget _buildHostsFilesLayout( + BuildContext context, String baseUrl, Size screenSize) { + // 判断屏幕宽度,决定使用什么布局 + final isWideScreen = screenSize.width > 1200; + + if (isWideScreen && hostsFiles.length >= 2) { + // 宽屏且文件数量多时使用StaggeredGridView + return StaggeredGrid.count( + crossAxisCount: 2, + crossAxisSpacing: 16, + mainAxisSpacing: 8, + children: hostsFiles.map((hostFile) { + return StaggeredGridTile.fit( + crossAxisCellCount: 1, + child: _buildHostFileEndpoints( + context, + baseUrl, + hostFile['fileName'] ?? '', + hostFile['remark'] ?? '', + ), + ); + }).toList(), + ); + } else { + // 窄屏或文件数量少时使用Column + return Column( + children: hostsFiles + .map((hostFile) => _buildHostFileEndpoints( + context, + baseUrl, + hostFile['fileName'] ?? '', + hostFile['remark'] ?? '', + )) + .toList(), + ); + } + } + + /// 构建基础API端点 + Widget _buildBasicEndpoints(BuildContext context, String baseUrl) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all( + color: + Theme.of(context).colorScheme.outline.withValues(alpha: 0.3)), + borderRadius: BorderRadius.circular(8), + color: Theme.of(context).colorScheme.surface, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () { + setState(() { + isBasicEndpointsExpanded = !isBasicEndpointsExpanded; + }); + }, + child: Row( + children: [ + Icon(Icons.api, + size: 20, color: Theme.of(context).colorScheme.primary), + const SizedBox(width: 8), + Expanded( + child: Text( + AppLocalizations.of(context)!.basic_api, + style: Theme.of(context).textTheme.titleSmall, + ), + ), + AnimatedRotation( + turns: isBasicEndpointsExpanded ? 0.5 : 0, + duration: const Duration(milliseconds: 200), + child: Icon( + Icons.expand_more, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + ), + AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + height: isBasicEndpointsExpanded ? null : 0, + child: isBasicEndpointsExpanded + ? Column( + children: [ + const SizedBox(height: 8), + _buildApiEndpoint( + context, + 'GET', + '/', + AppLocalizations.of(context)!.server_status, + baseUrl, + isCompact: true, + ), + _buildApiEndpoint( + context, + 'GET', + '/api/hosts', + AppLocalizations.of(context)!.get_all_hosts_files, + baseUrl, + isCompact: true, + ), + ], + ) + : const SizedBox.shrink(), + ), + ], + ), + ); + } + + /// 构建hosts文件相关的API端点 + Widget _buildHostFileEndpoints( + BuildContext context, + String baseUrl, + String fileName, + String remark, + ) { + final displayName = remark.isNotEmpty ? '$remark ($fileName)' : fileName; + final isExpanded = hostFileExpansionState[fileName] ?? true; + final histories = hostFileHistories[fileName] ?? []; + + return Container( + margin: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all( + color: + Theme.of(context).colorScheme.outline.withValues(alpha: 0.3)), + borderRadius: BorderRadius.circular(8), + color: Theme.of(context).colorScheme.surface, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () { + setState(() { + hostFileExpansionState[fileName] = !isExpanded; + }); + }, + child: Row( + children: [ + Icon(Icons.description, + size: 20, color: Theme.of(context).colorScheme.primary), + const SizedBox(width: 8), + Expanded( + child: Text( + displayName, + style: Theme.of(context).textTheme.titleSmall, + ), + ), + if (histories.isNotEmpty) + Container( + padding: + const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primary + .withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${histories.length}${AppLocalizations.of(context)!.history_count_suffix}', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.w500, + ), + ), + ), + AnimatedRotation( + turns: isExpanded ? 0.5 : 0, + duration: const Duration(milliseconds: 200), + child: Icon( + Icons.expand_more, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + ), + AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + height: isExpanded ? null : 0, + child: isExpanded + ? Column( + children: [ + const SizedBox(height: 8), + + // 获取hosts文件内容 + _buildApiEndpoint( + context, + 'GET', + '/api/hosts/$fileName', + '${AppLocalizations.of(context)!.get_content_prefix} $displayName ${AppLocalizations.of(context)!.get_content_suffix}', + baseUrl, + isCompact: true, + ), + + // 只有当有历史记录时才显示具体的历史记录API端点 + if (histories.isNotEmpty) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(6), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${AppLocalizations.of(context)!.history_content} (${histories.length}${AppLocalizations.of(context)!.history_count_suffix}):', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + fontWeight: FontWeight.w500, + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + const SizedBox(height: 4), + ...histories.take(5).map((history) { + final historyId = + history['fileName'] ?? history['id'] ?? ''; + if (historyId.isNotEmpty) { + return _buildApiEndpoint( + context, + 'GET', + '/api/hosts/$fileName/history/$historyId', + '${AppLocalizations.of(context)!.get_history_content_prefix} $historyId ${AppLocalizations.of(context)!.get_history_content_suffix}', + baseUrl, + isCompact: true, + isHistoryEndpoint: true, + ); + } + return const SizedBox.shrink(); + }), + if (histories.length > 5) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + '${AppLocalizations.of(context)!.more_history_records}${histories.length - 5}${AppLocalizations.of(context)!.more_history_records_suffix}', + style: Theme.of(context) + .textTheme + .labelSmall + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + fontStyle: FontStyle.italic, + ), + ), + ), + ], + ), + ), + ], + ], + ) + : const SizedBox.shrink(), + ), + ], + ), + ); + } + + /// 构建API端点项 + Widget _buildApiEndpoint( + BuildContext context, + String method, + String path, + String description, + String baseUrl, { + bool isCompact = false, + bool isHistoryEndpoint = false, + }) { + Color methodColor; + switch (method) { + case 'GET': + methodColor = Theme.of(context).colorScheme.primary; + break; + case 'POST': + methodColor = Theme.of(context).colorScheme.secondary; + break; + case 'PUT': + methodColor = Theme.of(context).colorScheme.tertiary; + break; + case 'DELETE': + methodColor = Theme.of(context).colorScheme.error; + break; + default: + methodColor = Theme.of(context).colorScheme.outline; + } + + final fullUrl = baseUrl + path; + + return Container( + margin: EdgeInsets.symmetric(vertical: isCompact ? 2 : 4), + padding: EdgeInsets.all(isCompact ? 8 : 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey[300]!.withValues(alpha: 0.5)), + borderRadius: BorderRadius.circular(6), + color: isHistoryEndpoint + ? Colors.blue[25] + : (isCompact ? Colors.grey[50] : null), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: methodColor, + borderRadius: BorderRadius.circular(3), + ), + child: Text( + method, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + path, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + ), + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: + Icon(Icons.open_in_browser, size: isCompact ? 16 : 18), + onPressed: () => _openInBrowser(fullUrl), + tooltip: AppLocalizations.of(context)!.open_in_browser, + constraints: BoxConstraints( + minWidth: isCompact ? 28 : 32, + minHeight: isCompact ? 28 : 32, + ), + padding: EdgeInsets.all(isCompact ? 2 : 4), + ), + IconButton( + icon: Icon(Icons.qr_code, size: isCompact ? 16 : 18), + onPressed: () => _showQrCode(context, fullUrl), + tooltip: AppLocalizations.of(context)!.show_qr_code_tooltip, + constraints: BoxConstraints( + minWidth: isCompact ? 28 : 32, + minHeight: isCompact ? 28 : 32, + ), + padding: EdgeInsets.all(isCompact ? 2 : 4), + ), + IconButton( + icon: Icon(Icons.copy, size: isCompact ? 16 : 18), + onPressed: () => _copyToClipboard(context, fullUrl), + tooltip: AppLocalizations.of(context)!.copy_url_tooltip, + constraints: BoxConstraints( + minWidth: isCompact ? 28 : 32, + minHeight: isCompact ? 28 : 32, + ), + padding: EdgeInsets.all(isCompact ? 2 : 4), + ), + ], + ), + ], + ), + if (!isCompact) ...[ + const SizedBox(height: 4), + Text( + description, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], + const SizedBox(height: 4), + Text( + fullUrl, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ); + } + + /// 在浏览器中打开URL + Future _openInBrowser(String url) async { + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); + } + } + + /// 显示二维码 + void _showQrCode(BuildContext context, String url) { + QrCodeDialog.show( + context, + url: url, + title: AppLocalizations.of(context)!.scan_qr_code_to_access, + ); + } + + /// 复制到剪贴板 + Future _copyToClipboard(BuildContext context, String text) async { + await Clipboard.setData(ClipboardData(text: text)); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.server_url_copied), + duration: Duration(seconds: 2), + ), + ); + } + } +} + +/// 显示API文档对话框的便捷函数 +Future showApiDocumentationDialog( + BuildContext context, + String deviceIp, { + int port = 1204, +}) { + return showDialog( + context: context, + builder: (BuildContext context) { + return ApiDocumentationDialog( + deviceIp: deviceIp, + port: port, + ); + }, + ); +} From fa5e1ece9afbf4c710e64f87efc0b3f3b226c6c1 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Tue, 22 Jul 2025 20:03:51 +0800 Subject: [PATCH 35/54] =?UTF-8?q?feat:=20=E5=9C=A8=E9=99=84=E8=BF=91?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=8D=A1=E7=89=87=E4=B8=AD=E9=9B=86=E6=88=90?= =?UTF-8?q?API=E6=96=87=E6=A1=A3=E6=9F=A5=E7=9C=8B=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为在线且开启共享的设备添加API文档查看按钮 - 使用eye图标提供直观的API文档访问入口 - 导入ApiDocumentationDialog组件引用 - 保持原有设备访问功能不变 --- lib/server/view/nearby_devices_card.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/server/view/nearby_devices_card.dart b/lib/server/view/nearby_devices_card.dart index 644109c..0462585 100644 --- a/lib/server/view/nearby_devices_card.dart +++ b/lib/server/view/nearby_devices_card.dart @@ -5,6 +5,7 @@ import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/server/bloc/nearby_devices_cubit.dart'; import 'package:hosts/utils/nearby_devices_scanner.dart'; import 'package:hosts/widget/dialog/access_device_dialog.dart'; +import 'package:hosts/widget/dialog/api_documentation_dialog.dart'; /// 附近设备卡片组件 class NearbyDevicesCard extends StatelessWidget { @@ -256,6 +257,14 @@ class NearbyDevicesCard extends StatelessWidget { ], ), ), + if (device.hasSharing && device.isOnline) + IconButton( + icon: const Icon(Icons.remove_red_eye), + onPressed: () { + showApiDocumentationDialog(context, device.ip); + }, + tooltip: AppLocalizations.of(context)!.api_docs, + ), if (device.hasSharing && device.isOnline) IconButton( icon: const Icon(Icons.launch), From e1b08fbb771040a4ad737f66413fc0a33cdeba60 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Tue, 22 Jul 2025 20:07:27 +0800 Subject: [PATCH 36/54] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96UI=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E5=92=8C=E4=BF=AE=E5=A4=8D=E5=A4=9A=E9=A1=B9=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化HomeAppBar支持窄屏幕响应式布局,改善移动端体验 - 修复历史记录保存逻辑,使用原始内容而非当前内容 - 增加文本编辑器占位符提示和国际化支持 - 优化文本编辑器焦点处理和交互体验 - 设置默认语言为英文 - 完善历史记录和差异对比功能的国际化 --- lib/app.dart | 1 + lib/home/cubit/host_cubit.dart | 2 +- lib/home/view/home_app_bar.dart | 419 ++++++++++++++++++++++---------- lib/home/view/host_text.dart | 109 +++++---- lib/page/history_page.dart | 50 ++-- lib/page/hosts_diff_page.dart | 24 +- lib/util/file_manager.dart | 7 +- 7 files changed, 400 insertions(+), 212 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 40f3091..bea380f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -16,6 +16,7 @@ class HostsApp extends MaterialApp { HostsApp(this.filePath, {super.key}) : super( onGenerateTitle: (context) => AppLocalizations.of(context)!.app_name, + locale: Locale("en"), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, theme: ThemeData( diff --git a/lib/home/cubit/host_cubit.dart b/lib/home/cubit/host_cubit.dart index 8b9d36c..28b1d9f 100644 --- a/lib/home/cubit/host_cubit.dart +++ b/lib/home/cubit/host_cubit.dart @@ -500,7 +500,7 @@ class HostCubit extends Cubit { File(filePath).writeAsStringSync(content); final List history = []; if (isHistory) { - await _fileManager.saveHistory(fileId, content); + await _fileManager.saveHistory(fileId, state.data.defaultFileContent); history.addAll(await _fileManager.getHistory(fileId)); } diff --git a/lib/home/view/home_app_bar.dart b/lib/home/view/home_app_bar.dart index 283eb81..120ea9b 100644 --- a/lib/home/view/home_app_bar.dart +++ b/lib/home/view/home_app_bar.dart @@ -32,131 +32,15 @@ class HomeAppBar extends StatelessWidget { final homeStateData = state.data; return Column( children: [ - Container( - height: 58, - padding: const EdgeInsets.symmetric(horizontal: 5), - child: Row( - children: [ - Expanded( - child: Row( - children: [ - if (GlobalSettings().isSimple) - IconButton( - onPressed: () async { - FilePickerResult? result = - await FilePicker.platform.pickFiles(); - if (result == null) return; - if (!context.read().state.data.isSave) { - ScaffoldMessenger.of(context) - .removeCurrentSnackBar(); - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)! - .error_not_save), - action: SnackBarAction( - label: AppLocalizations.of(context)!.abort, - onPressed: () => pickFile(context, result), - ), - )); - - return; - } - - pickFile(context, result); - }, - icon: const Icon(Icons.file_open_outlined), - tooltip: AppLocalizations.of(context)!.open_file, - ) - else - IconButton( - onPressed: homeCubit.toggleAdvancedSettingsSwitch, - icon: const Icon(Icons.menu), - tooltip: - AppLocalizations.of(context)!.advanced_settings, - ), - _buildEditModeButton(homeCubit, context), - const SizedBox(width: 10), - if (homeStateData.editMode == EditMode.Table) - Flexible( - child: Container( - constraints: const BoxConstraints( - maxWidth: 430, - minWidth: 100, - ), - child: BlocBuilder( - builder: (context, state) { - return SearchTextField( - text: state.data.searchText, - onChanged: context - .read() - .updateSearchText, - ); - }, - ), - ), - ), - ], - )), - const SizedBox(width: 32), - BlocBuilder( - builder: (context, state) { - final hostStateData = state.data; - final hostCubit = context.read(); - return Row( - children: [ - batchGroupButton(homeCubit), - if (hostStateData.history.isNotEmpty) - IconButton( - onPressed: () async { - SimpleHostFileHistory? resultHistory = - await showModalBottomSheet( - context: context, - builder: (BuildContext context) => - HistoryPage( - selectHistory: hostStateData.selectHistory, - history: hostStateData.history, - fileId: state.data.fileId, - ), - ); - if (resultHistory == null) { - hostCubit.onHistoryChanged(null); - return; - } - - if (!hostStateData.isSave) { - ScaffoldMessenger.of(context) - .removeCurrentSnackBar(); - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)! - .error_not_save), - action: SnackBarAction( - label: - AppLocalizations.of(context)!.abort, - onPressed: () => hostCubit - .onHistoryChanged(resultHistory), - ), - )); - - return; - } - hostCubit.onHistoryChanged(resultHistory); - }, - icon: const Icon(Icons.history), - ), - if (!hostStateData.isSave) - IconButton( - onPressed: context.read().undoHost, - icon: const Icon(Icons.undo), - tooltip: AppLocalizations.of(context)!.reduction, - ), - buildMoreButton(context) - ], - ); - }, - ) - ], - ), + LayoutBuilder( + builder: (context, constraints) { + final isNarrow = constraints.maxWidth < 600; + return Container( + height: isNarrow ? null : 58, + padding: const EdgeInsets.symmetric(horizontal: 5), + child: isNarrow ? _buildNarrowLayout(context, homeCubit, homeStateData) : _buildWideLayout(context, homeCubit, homeStateData), + ); + }, ) ], ); @@ -312,4 +196,289 @@ class HomeAppBar extends StatelessWidget { }).toList(); }); } + + Widget _buildWideLayout(BuildContext context, HomeCubit homeCubit, HomeStateData homeStateData) { + return Row( + children: [ + Expanded( + child: Row( + children: [ + if (GlobalSettings().isSimple) + IconButton( + onPressed: () async { + FilePickerResult? result = await FilePicker.platform.pickFiles(); + if (result == null) return; + if (!context.read().state.data.isSave) { + ScaffoldMessenger.of(context).removeCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.error_not_save), + action: SnackBarAction( + label: AppLocalizations.of(context)!.abort, + onPressed: () => pickFile(context, result), + ), + )); + return; + } + pickFile(context, result); + }, + icon: const Icon(Icons.file_open_outlined), + tooltip: AppLocalizations.of(context)!.open_file, + ) + else + IconButton( + onPressed: homeCubit.toggleAdvancedSettingsSwitch, + icon: const Icon(Icons.menu), + tooltip: AppLocalizations.of(context)!.advanced_settings, + ), + _buildEditModeButton(homeCubit, context), + const SizedBox(width: 10), + if (homeStateData.editMode == EditMode.Table) + Flexible( + child: Container( + constraints: const BoxConstraints( + maxWidth: 430, + minWidth: 100, + ), + child: BlocBuilder( + builder: (context, state) { + return SearchTextField( + text: state.data.searchText, + onChanged: context.read().updateSearchText, + ); + }, + ), + ), + ), + ], + ), + ), + const SizedBox(width: 32), + BlocBuilder( + builder: (context, state) { + final hostStateData = state.data; + final hostCubit = context.read(); + return Row( + children: [ + batchGroupButton(homeCubit), + if (hostStateData.history.isNotEmpty) + IconButton( + onPressed: () async { + SimpleHostFileHistory? resultHistory = await showModalBottomSheet( + context: context, + builder: (BuildContext context) => HistoryPage( + selectHistory: hostStateData.selectHistory, + history: hostStateData.history, + fileId: state.data.fileId, + ), + ); + if (resultHistory == null) { + hostCubit.onHistoryChanged(null); + return; + } + if (!hostStateData.isSave) { + ScaffoldMessenger.of(context).removeCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.error_not_save), + action: SnackBarAction( + label: AppLocalizations.of(context)!.abort, + onPressed: () => hostCubit.onHistoryChanged(resultHistory), + ), + )); + return; + } + hostCubit.onHistoryChanged(resultHistory); + }, + icon: const Icon(Icons.history), + ), + if (!hostStateData.isSave) + IconButton( + onPressed: context.read().undoHost, + icon: const Icon(Icons.undo), + tooltip: AppLocalizations.of(context)!.reduction, + ), + buildMoreButton(context) + ], + ); + }, + ) + ], + ); + } + + Widget _buildNarrowLayout(BuildContext context, HomeCubit homeCubit, HomeStateData homeStateData) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + if (GlobalSettings().isSimple) + IconButton( + onPressed: () async { + FilePickerResult? result = await FilePicker.platform.pickFiles(); + if (result == null) return; + if (!context.read().state.data.isSave) { + ScaffoldMessenger.of(context).removeCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.error_not_save), + action: SnackBarAction( + label: AppLocalizations.of(context)!.abort, + onPressed: () => pickFile(context, result), + ), + )); + return; + } + pickFile(context, result); + }, + icon: const Icon(Icons.file_open_outlined), + tooltip: AppLocalizations.of(context)!.open_file, + ) + else + IconButton( + onPressed: homeCubit.toggleAdvancedSettingsSwitch, + icon: const Icon(Icons.menu), + tooltip: AppLocalizations.of(context)!.advanced_settings, + ), + _buildEditModeButton(homeCubit, context), + const Spacer(), + BlocBuilder( + builder: (context, state) { + final hostStateData = state.data; + final hostCubit = context.read(); + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: _buildNarrowBatchActions(homeCubit), + ), + if (hostStateData.history.isNotEmpty) + IconButton( + onPressed: () async { + SimpleHostFileHistory? resultHistory = await showModalBottomSheet( + context: context, + builder: (BuildContext context) => HistoryPage( + selectHistory: hostStateData.selectHistory, + history: hostStateData.history, + fileId: state.data.fileId, + ), + ); + if (resultHistory == null) { + hostCubit.onHistoryChanged(null); + return; + } + if (!hostStateData.isSave) { + ScaffoldMessenger.of(context).removeCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.error_not_save), + action: SnackBarAction( + label: AppLocalizations.of(context)!.abort, + onPressed: () => hostCubit.onHistoryChanged(resultHistory), + ), + )); + return; + } + hostCubit.onHistoryChanged(resultHistory); + }, + icon: const Icon(Icons.history), + ), + if (!hostStateData.isSave) + IconButton( + onPressed: context.read().undoHost, + icon: const Icon(Icons.undo), + tooltip: AppLocalizations.of(context)!.reduction, + ), + buildMoreButton(context) + ], + ); + }, + ), + ], + ), + ), + if (homeStateData.editMode == EditMode.Table) + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + Expanded( + child: BlocBuilder( + builder: (context, state) { + return SearchTextField( + text: state.data.searchText, + onChanged: context.read().updateSearchText, + ); + }, + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildNarrowBatchActions(HomeCubit homeCubit) { + return BlocBuilder( + builder: (BuildContext context, state) { + final selectHosts = state.data.selectHosts; + final hostCubit = context.read(); + + if (selectHosts.isEmpty || homeCubit.state.data.editMode != EditMode.Table) { + return const SizedBox.shrink(); + } + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Switch( + value: true, + onChanged: (value) { + final Map hostsMap = {}; + for (var host in selectHosts) { + hostsMap[host] = host.withCopy(isUse: true); + } + hostCubit.onToggleUse(hostsMap); + }, + ), + Switch( + value: false, + onChanged: (value) { + final Map hostsMap = {}; + for (var host in selectHosts) { + hostsMap[host] = host.withCopy(isUse: false); + } + hostCubit.onToggleUse(hostsMap); + }, + ), + IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => CopyMultipleDialog(hosts: selectHosts), + ); + }, + tooltip: AppLocalizations.of(context)!.copy_selected, + icon: const Icon(Icons.copy), + ), + IconButton( + onPressed: () { + deleteMultiple( + context, + selectHosts.map((it) => it.host).toList(), + () { + hostCubit.onDelete(selectHosts); + }, + ); + }, + tooltip: AppLocalizations.of(context)!.delete_selected, + icon: const Icon(Icons.delete_outline), + ), + ], + ), + ); + }, + ); + } } diff --git a/lib/home/view/host_text.dart b/lib/home/view/host_text.dart index f3d5762..ea88755 100644 --- a/lib/home/view/host_text.dart +++ b/lib/home/view/host_text.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/widget/host_text_editing_controller.dart'; import 'package:hosts/widget/row_line_widget.dart'; @@ -44,7 +45,7 @@ class _HostTextState extends State { ..addListener(() { hostCubit.updateFileContent(textEditingController.text); }); - + // Synchronize scroll controllers _textScrollController.addListener(() { if (!_isScrollingSynchronized && _scrollController.hasClients) { @@ -53,7 +54,7 @@ class _HostTextState extends State { _isScrollingSynchronized = false; } }); - + _scrollController.addListener(() { if (!_isScrollingSynchronized && _textScrollController.hasClients) { _isScrollingSynchronized = true; @@ -61,7 +62,7 @@ class _HostTextState extends State { _isScrollingSynchronized = false; } }); - + super.initState(); } @@ -96,54 +97,62 @@ class _HostTextState extends State { ), Expanded( key: _textFieldContainerKey, - child: KeyboardListener( - focusNode: _focusNode, - onKeyEvent: (event) { - List logicalKeys = []; - if (Platform.isMacOS) { - logicalKeys = [ - LogicalKeyboardKey.metaLeft, - LogicalKeyboardKey.metaRight - ]; - } else { - logicalKeys = [ - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.controlRight - ]; - } - if (logicalKeys.contains(event.logicalKey)) { - if (isControl) { - isControl = false; + child: GestureDetector( + onTap: () { + _focusNode.requestFocus(); + }, + child: KeyboardListener( + focusNode: _focusNode, + onKeyEvent: (event) { + List logicalKeys = []; + if (Platform.isMacOS) { + logicalKeys = [ + LogicalKeyboardKey.metaLeft, + LogicalKeyboardKey.metaRight + ]; } else { - isControl = true; + logicalKeys = [ + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.controlRight + ]; + } + if (logicalKeys.contains(event.logicalKey)) { + if (isControl) { + isControl = false; + } else { + isControl = true; + } + } + if (event.logicalKey == LogicalKeyboardKey.slash && + isControl && + event is KeyDownEvent) { + textEditingController.updateUseStatus( + textEditingController.selection); } - } - if (event.logicalKey == LogicalKeyboardKey.slash && - isControl && - event is KeyDownEvent) { - textEditingController - .updateUseStatus(textEditingController.selection); - } - if (event.logicalKey == LogicalKeyboardKey.keyS && - isControl && - event is KeyDownEvent && - !state.data.isSave) { - hostCubit.onTextSave(); - } - }, - child: ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: IntrinsicWidth( - child: TextField( - controller: textEditingController, - scrollController: _textScrollController, - maxLines: null, - scrollPhysics: const ClampingScrollPhysics(), - decoration: - const InputDecoration(border: InputBorder.none), + if (event.logicalKey == LogicalKeyboardKey.keyS && + isControl && + event is KeyDownEvent && + !state.data.isSave) { + hostCubit.onTextSave(); + } + }, + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context) + .copyWith(scrollbars: false), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: IntrinsicWidth( + child: TextField( + controller: textEditingController, + scrollController: _textScrollController, + maxLines: null, + scrollPhysics: const ClampingScrollPhysics(), + decoration: InputDecoration( + border: InputBorder.none, + hintText: AppLocalizations.of(context)! + .create_host_template), + ), ), ), ), @@ -158,13 +167,13 @@ class _HostTextState extends State { child: Row( children: [ Text( - "当前行:${textEditingController.countNewlines(textEditingController.text.substring(0, textEditingController.selection.start > 0 ? textEditingController.selection.start : 0)) + 1}", + "${AppLocalizations.of(context)!.current_line}${textEditingController.countNewlines(textEditingController.text.substring(0, textEditingController.selection.start > 0 ? textEditingController.selection.start : 0)) + 1}", ), const SizedBox( width: 8, ), Text( - "总行数:${textEditingController.countNewlines(textEditingController.text) + 1}"), + "${AppLocalizations.of(context)!.total_lines}${textEditingController.countNewlines(textEditingController.text) + 1}"), ], ), ) diff --git a/lib/page/history_page.dart b/lib/page/history_page.dart index 5970802..8ae3804 100644 --- a/lib/page/history_page.dart +++ b/lib/page/history_page.dart @@ -100,28 +100,30 @@ class _HistoryPageState extends State { _showDiffComparison(context, hostFile); }, icon: const Icon(Icons.compare_arrows), - tooltip: '查看差异', + tooltip: AppLocalizations.of(context)!.view_diff, ), IconButton( - onPressed: () { - setState(() { - if (deleteSimpleHostFileHistory - .contains(hostFile)) { - deleteSimpleHostFileHistory.remove(hostFile); - } else { - deleteSimpleHostFileHistory.add(hostFile); - } - }); - }, - style: OutlinedButton.styleFrom( - minimumSize: Size.zero, - padding: EdgeInsets.zero, - ), - icon: Icon( - deleteSimpleHostFileHistory.contains(hostFile) - ? Icons.close - : Icons.delete_outline, - )), + onPressed: () { + setState(() { + if (deleteSimpleHostFileHistory + .contains(hostFile)) { + deleteSimpleHostFileHistory.remove(hostFile); + } else { + deleteSimpleHostFileHistory.add(hostFile); + } + }); + }, + style: OutlinedButton.styleFrom( + minimumSize: Size.zero, + padding: EdgeInsets.zero, + ), + icon: Icon( + deleteSimpleHostFileHistory.contains(hostFile) + ? Icons.close + : Icons.delete_outline, + ), + tooltip: AppLocalizations.of(context)!.delete, + ), ], ), selectedTileColor: @@ -161,8 +163,9 @@ class _HistoryPageState extends State { final minute = dateTime.minute.toString().padLeft(2, '0'); final second = dateTime.second.toString().padLeft(2, '0'); - final historyLabel = '历史版本: $year-$month-$day $hour:$minute:$second'; - final currentLabel = '当前版本'; + final historyLabel = + '${AppLocalizations.of(context)!.history_version}: $year-$month-$day $hour:$minute:$second'; + final currentLabel = AppLocalizations.of(context)!.current_version; // 导航到差异对比页面 Navigator.of(context).push( @@ -179,7 +182,8 @@ class _HistoryPageState extends State { // 显示错误信息 ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('无法读取历史文件: $e'), + content: Text( + '${AppLocalizations.of(context)!.unable_to_read_history_file}: $e'), backgroundColor: Theme.of(context).colorScheme.error, ), ); diff --git a/lib/page/hosts_diff_page.dart b/lib/page/hosts_diff_page.dart index 3904a85..f31049f 100644 --- a/lib/page/hosts_diff_page.dart +++ b/lib/page/hosts_diff_page.dart @@ -20,7 +20,7 @@ class HostsDiffPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(AppLocalizations.of(context)!.hosts_diff_title ?? 'Hosts 差异对比'), + title: Text(AppLocalizations.of(context)!.hosts_diff_title), actions: [ IconButton( icon: Icon(Icons.info_outline), @@ -67,20 +67,20 @@ class HostsDiffPage extends StatelessWidget { children: [ _buildStatItem( context, - AppLocalizations.of(context)!.diff_stats_history ?? '历史版本', - '$oldLines 行', + AppLocalizations.of(context)!.diff_stats_history, + '$oldLines ${AppLocalizations.of(context)!.history_count}', Icons.history, ), _buildStatItem( context, - AppLocalizations.of(context)!.diff_stats_current ?? '当前版本', - '$newLines 行', + AppLocalizations.of(context)!.diff_stats_current, + '$newLines ${AppLocalizations.of(context)!.history_count}', Icons.description, ), _buildStatItem( context, - AppLocalizations.of(context)!.diff_stats_difference ?? '差异', - '${diff > 0 ? '+' : ''}$diff 行', + AppLocalizations.of(context)!.diff_stats_difference, + '${diff > 0 ? '+' : ''}$diff ${AppLocalizations.of(context)!.history_count}', diff > 0 ? Icons.add : (diff < 0 ? Icons.remove : Icons.check), color: diff > 0 ? Theme.of(context).colorScheme.primary @@ -121,20 +121,20 @@ class HostsDiffPage extends StatelessWidget { showDialog( context: context, builder: (context) => AlertDialog( - title: Text('差异说明'), + title: Text(AppLocalizations.of(context)!.diff_legend_description), content: Column( mainAxisSize: MainAxisSize.min, children: [ _buildLegendItem( context, - AppLocalizations.of(context)!.diff_legend_added ?? '新增内容', + AppLocalizations.of(context)!.diff_legend_added, Theme.of(context).colorScheme.primary, Theme.of(context).colorScheme.primaryContainer.withOpacity(0.3), ), SizedBox(height: 8), _buildLegendItem( context, - AppLocalizations.of(context)!.diff_legend_deleted ?? '删除内容', + AppLocalizations.of(context)!.diff_legend_deleted, Theme.of(context).colorScheme.error, Theme.of(context).colorScheme.errorContainer.withOpacity(0.3), hasStrikethrough: true, @@ -142,7 +142,7 @@ class HostsDiffPage extends StatelessWidget { SizedBox(height: 8), _buildLegendItem( context, - AppLocalizations.of(context)!.diff_legend_unchanged ?? '未变更内容', + AppLocalizations.of(context)!.diff_legend_unchanged, Theme.of(context).colorScheme.onSurface, null, ), @@ -151,7 +151,7 @@ class HostsDiffPage extends StatelessWidget { actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), - child: Text('知道了'), + child: Text(AppLocalizations.of(context)!.diff_legend_ok), ), ], ), diff --git a/lib/util/file_manager.dart b/lib/util/file_manager.dart index 0d5e1e8..3ee78c6 100755 --- a/lib/util/file_manager.dart +++ b/lib/util/file_manager.dart @@ -159,7 +159,7 @@ class FileManager { if (!historyDirectory.existsSync()) { return []; } - return historyDirectory + final historyList = historyDirectory .listSync() .map( (item) => SimpleHostFileHistory( @@ -168,6 +168,11 @@ class FileManager { ), ) .toList(); + + // 按fileName倒序排序 + historyList.sort((a, b) => b.fileName.compareTo(a.fileName)); + + return historyList; } Future saveHistory(String fileId, String content) async { From 45732005b03e5f4393d97ba561403489b3a830cb Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Wed, 23 Jul 2025 00:31:08 +0800 Subject: [PATCH 37/54] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=BC=93=E5=AD=98=E5=92=8C=E5=9B=BD=E9=99=85=E5=8C=96?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除设备缓存功能以简化附近设备扫描逻辑 - 修复nearby_devices_scanner中isOnline属性引用错误 - 优化文件差异对比和选择逻辑 - 添加HomeDelete状态类支持删除操作 - 改进文本模式下的文件保存功能 - 移除强制英文locale设置,使用系统默认语言 --- lib/app.dart | 2 +- lib/home/cubit/home_cubit.dart | 7 +- lib/home/cubit/home_state.dart | 5 + lib/home/cubit/host_cubit.dart | 17 ++ lib/home/view/home_drawer.dart | 36 ++-- lib/home/view/home_view.dart | 2 +- lib/home/view/host_table.dart | 2 + lib/server/bloc/nearby_devices_state.dart | 6 +- lib/server/view/nearby_devices_card.dart | 20 +-- lib/utils/nearby_devices_scanner.dart | 205 +--------------------- 10 files changed, 63 insertions(+), 239 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index bea380f..cfb1597 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -16,7 +16,7 @@ class HostsApp extends MaterialApp { HostsApp(this.filePath, {super.key}) : super( onGenerateTitle: (context) => AppLocalizations.of(context)!.app_name, - locale: Locale("en"), + // locale: Locale("en"), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, theme: ThemeData( diff --git a/lib/home/cubit/home_cubit.dart b/lib/home/cubit/home_cubit.dart index 3b347f2..ab09c78 100755 --- a/lib/home/cubit/home_cubit.dart +++ b/lib/home/cubit/home_cubit.dart @@ -165,10 +165,6 @@ class HomeCubit extends Cubit { } } - if (isDeleteSelect) { - selectHost("system"); - } - List updatedHostFiles = state.data.hostFiles .where((file) => file.fileName != fileName) .toList(); @@ -186,9 +182,10 @@ class HomeCubit extends Cubit { // 更新状态 emit( - HomeInitial( + HomeDelete( state.data.copyWith( hostFiles: updatedHostFiles, + selectHostFile: isDeleteSelect ? "system" : null, ), ), ); diff --git a/lib/home/cubit/home_state.dart b/lib/home/cubit/home_state.dart index 08261ba..0875223 100644 --- a/lib/home/cubit/home_state.dart +++ b/lib/home/cubit/home_state.dart @@ -98,3 +98,8 @@ class HomeError extends HomeState { class HomeSelectHostFileChanged extends HomeState { const HomeSelectHostFileChanged(super.data); } + +/// 删除操作状态 +class HomeDelete extends HomeState { + const HomeDelete(super.data); +} diff --git a/lib/home/cubit/host_cubit.dart b/lib/home/cubit/host_cubit.dart index 28b1d9f..fab9237 100644 --- a/lib/home/cubit/host_cubit.dart +++ b/lib/home/cubit/host_cubit.dart @@ -81,6 +81,7 @@ class HostCubit extends Cubit { final content = await _fileManager.readAsString(fileId); final hosts = _fileManager.parseHosts(content.split("\n")); final history = await _fileManager.getHistory(fileId); + emit( HostInitial( HostStateData( @@ -639,4 +640,20 @@ class HostCubit extends Cubit { Future areFilesEqual(String fileId) async { return await _fileManager.areFilesEqual(fileId); } + + Future saveFromText(String text) async { + try { + final filePath = await _fileManager.getHostsFilePath(state.data.fileId); + await _fileManager.saveHistory( + state.data.fileId, + File(filePath).readAsStringSync(), + ); + + File(filePath).writeAsStringSync(text); + + return true; + } catch (e) { + return false; + } + } } diff --git a/lib/home/view/home_drawer.dart b/lib/home/view/home_drawer.dart index e59e99c..90785bd 100644 --- a/lib/home/view/home_drawer.dart +++ b/lib/home/view/home_drawer.dart @@ -35,12 +35,14 @@ class HomeDrawer extends StatelessWidget { ), Spacer(), IconButton( - onPressed: () async { - String? remark = await hostConfigDialog(context); - if (remark == null || remark.isEmpty) return; - context.read().addHostFile(remark); - }, - icon: const Icon(Icons.add)), + onPressed: () async { + String? remark = await hostConfigDialog(context); + if (remark == null || remark.isEmpty) return; + context.read().addHostFile(remark); + }, + icon: const Icon(Icons.add), + tooltip: AppLocalizations.of(context)!.add, + ), _buildOptionsMenu(context, state), ], ), @@ -127,15 +129,17 @@ class HomeDrawer extends StatelessWidget { context: context, builder: (BuildContext dialogContext) { return AlertDialog( - title: Text(AppLocalizations.of(dialogContext)! - .warning), + title: Text( + AppLocalizations.of(dialogContext)! + .warning), content: Text( AppLocalizations.of(dialogContext)! .warning_different), actions: [ TextButton( onPressed: () async { - final result = await homeCubit.useHost(hostFile.fileName); + final result = await homeCubit + .useHost(hostFile.fileName); if (!result) { // 使用SnackBar提示错误 ScaffoldMessenger.of(context) @@ -158,14 +162,12 @@ class HomeDrawer extends StatelessWidget { ), TextButton( onPressed: () async { - // TODO 没有写入到文件页面。 Text模式也没有更新内容。 - hostCubit.fromText( - File(FileManager - .systemHostFilePath) - .readAsStringSync(), - ); - - hostCubit.save(true); + await hostCubit.saveFromText(File( + FileManager + .systemHostFilePath) + .readAsStringSync()); + await homeCubit + .selectHost(hostFile.fileName); Navigator.of(dialogContext).pop(); }, child: Text(AppLocalizations.of( diff --git a/lib/home/view/home_view.dart b/lib/home/view/home_view.dart index 71206df..c64451a 100644 --- a/lib/home/view/home_view.dart +++ b/lib/home/view/home_view.dart @@ -59,7 +59,7 @@ class _HomeViewState extends State { body: SafeArea( child: BlocBuilder( builder: (context, state) { - if (state is HomeSelectHostFileChanged) { + if (state is HomeSelectHostFileChanged || state is HomeDelete) { context.read().updateHost(state.data.selectHostFile); } diff --git a/lib/home/view/host_table.dart b/lib/home/view/host_table.dart index 690a39e..a4b30e4 100644 --- a/lib/home/view/host_table.dart +++ b/lib/home/view/host_table.dart @@ -299,11 +299,13 @@ class HostDataSource extends DataGridSource { IconButton( onPressed: () => onEdit(hosts.indexOf(host), host), icon: const Icon(Icons.edit), + tooltip: AppLocalizations.of(context)!.edit, ), const SizedBox(width: 8), IconButton( onPressed: () => onDelete([host]), icon: const Icon(Icons.delete_outline), + tooltip: AppLocalizations.of(context)!.delete, ), const SizedBox(width: 8), buildMoreButton(context, hosts.indexOf(host), host), diff --git a/lib/server/bloc/nearby_devices_state.dart b/lib/server/bloc/nearby_devices_state.dart index 70c4281..31a6fa1 100644 --- a/lib/server/bloc/nearby_devices_state.dart +++ b/lib/server/bloc/nearby_devices_state.dart @@ -24,16 +24,16 @@ class NearbyDevicesStateData { }); /// 获取在线设备数量 - int get onlineDevicesCount => devices.where((device) => device.isOnline).length; + int get onlineDevicesCount => devices.where((device) => device.isReachable && device.hasSharing).length; /// 获取总设备数量 int get totalDevicesCount => devices.length; /// 获取在线设备列表 - List get onlineDevices => devices.where((device) => device.isOnline).toList(); + List get onlineDevices => devices.where((device) => device.isReachable && device.hasSharing).toList(); /// 获取离线设备列表 - List get offlineDevices => devices.where((device) => !device.isOnline).toList(); + List get offlineDevices => devices.where((device) => !device.isReachable || !device.hasSharing).toList(); /// 复制方法 /// 用于基于当前状态创建新状态 diff --git a/lib/server/view/nearby_devices_card.dart b/lib/server/view/nearby_devices_card.dart index 0462585..9d0e837 100644 --- a/lib/server/view/nearby_devices_card.dart +++ b/lib/server/view/nearby_devices_card.dart @@ -98,9 +98,9 @@ class NearbyDevicesCard extends StatelessWidget { if (a.hasSharing && !b.hasSharing) return -1; if (!a.hasSharing && b.hasSharing) return 1; - // 然后按在线状态排序(在线的在前) - if (a.isOnline && !b.isOnline) return -1; - if (!a.isOnline && b.isOnline) return 1; + // 然后按连接状态排序(可连接的在前) + if (a.isReachable && !b.isReachable) return -1; + if (!a.isReachable && b.isReachable) return 1; // 最后按IP地址排序 return a.ip.compareTo(b.ip); @@ -148,7 +148,7 @@ class NearbyDevicesCard extends StatelessWidget { IconData statusIcon; String statusText; - if (!device.isOnline) { + if (!device.isReachable) { statusColor = Colors.red; statusIcon = Icons.offline_bolt; statusText = AppLocalizations.of(context)!.offline; @@ -189,8 +189,8 @@ class NearbyDevicesCard extends StatelessWidget { color: statusColor, size: 24, ), - // 在线状态指示器 - if (device.isOnline) + // 连接状态指示器 + if (device.isReachable) Positioned( right: 0, bottom: 0, @@ -219,12 +219,12 @@ class NearbyDevicesCard extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, - color: device.isOnline ? null : Colors.grey, + color: device.isReachable ? null : Colors.grey, ), overflow: TextOverflow.ellipsis, ), ), - if (!device.isOnline) + if (!device.isReachable) Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), @@ -257,7 +257,7 @@ class NearbyDevicesCard extends StatelessWidget { ], ), ), - if (device.hasSharing && device.isOnline) + if (device.hasSharing && device.isReachable) IconButton( icon: const Icon(Icons.remove_red_eye), onPressed: () { @@ -265,7 +265,7 @@ class NearbyDevicesCard extends StatelessWidget { }, tooltip: AppLocalizations.of(context)!.api_docs, ), - if (device.hasSharing && device.isOnline) + if (device.hasSharing && device.isReachable) IconButton( icon: const Icon(Icons.launch), onPressed: () { diff --git a/lib/utils/nearby_devices_scanner.dart b/lib/utils/nearby_devices_scanner.dart index f836b03..8ed015b 100644 --- a/lib/utils/nearby_devices_scanner.dart +++ b/lib/utils/nearby_devices_scanner.dart @@ -2,35 +2,18 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:network_info_plus/network_info_plus.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:hosts/utils/device_api_cache.dart'; /// 附近设备扫描器 class NearbyDevicesScanner { static const int _defaultPort = 1204; static const Duration _scanTimeout = Duration(seconds: 2); - // SharedPreferences键名 - static const String _deviceCacheKey = 'nearby_devices_cache'; - static const String _priorityIPsKey = 'priority_ips_cache'; - - // 设备缓存,记录之前扫描到的设备 - static final Map _deviceCache = {}; - - // 设备优先级列表,之前扫描到的设备优先扫描 - static final List _priorityIPs = []; - - // 是否已初始化(加载缓存) - static bool _isInitialized = false; /// 实时扫描附近设备,发现设备时立即回调 static Future scanNearbyDevicesRealTime({ required Function(NearbyDevice) onDeviceFound, }) async { try { - // 确保缓存已初始化 - await _initializeCache(); - print('开始实时扫描附近设备...'); // 获取当前设备的IP地址 @@ -56,17 +39,10 @@ class NearbyDevicesScanner { // 创建所有需要扫描的IP列表 final List ipsToScan = []; - // 首先添加优先级IP(之前扫描到的设备) - for (final ip in _priorityIPs) { - if (ip.startsWith(baseIP) && ip != currentIP) { - ipsToScan.add(ip); - } - } - - // 然后添加其他IP + // 添加所有IP for (int i = 1; i <= 254; i++) { final targetIP = '$baseIP.$i'; - if (targetIP != currentIP && !ipsToScan.contains(targetIP)) { + if (targetIP != currentIP) { ipsToScan.add(targetIP); } } @@ -85,17 +61,6 @@ class NearbyDevicesScanner { for (final future in scanFutures) { future.then((device) async { if (device != null) { - // 更新设备缓存 - _deviceCache[device.ip] = device; - - // 更新优先级列表 - if (!_priorityIPs.contains(device.ip)) { - _priorityIPs.add(device.ip); - } - - // 保存到持久存储 - await _saveCache(); - print('发现设备: ${device.ip}'); onDeviceFound(device); } @@ -233,164 +198,6 @@ class NearbyDevicesScanner { } } - /// 初始化缓存(从持久存储加载) - static Future _initializeCache() async { - if (_isInitialized) return; - - try { - final prefs = await SharedPreferences.getInstance(); - - // 加载设备缓存 - final deviceCacheJson = prefs.getString(_deviceCacheKey); - if (deviceCacheJson != null) { - final Map cacheData = jsonDecode(deviceCacheJson); - _deviceCache.clear(); - cacheData.forEach((ip, deviceData) { - _deviceCache[ip] = NearbyDevice.fromJson(deviceData); - }); - } - - // 加载优先级IP列表 - final priorityIPs = prefs.getStringList(_priorityIPsKey); - if (priorityIPs != null) { - _priorityIPs.clear(); - _priorityIPs.addAll(priorityIPs); - } - - _isInitialized = true; - print('设备缓存初始化完成,加载了${_deviceCache.length}个设备'); - } catch (e) { - print('加载设备缓存失败: $e'); - _isInitialized = true; // 即使失败也标记为已初始化,避免重复尝试 - } - } - - /// 保存缓存到持久存储 - static Future _saveCache() async { - try { - final prefs = await SharedPreferences.getInstance(); - - // 保存设备缓存 - final Map cacheData = {}; - _deviceCache.forEach((ip, device) { - cacheData[ip] = device.toJson(); - }); - await prefs.setString(_deviceCacheKey, jsonEncode(cacheData)); - - // 保存优先级IP列表 - await prefs.setStringList(_priorityIPsKey, _priorityIPs); - - print('设备缓存已保存到存储'); - } catch (e) { - print('保存设备缓存失败: $e'); - } - } - - /// 获取缓存的设备列表 - static Future> getCachedDevices() async { - await _initializeCache(); - return _deviceCache.values.toList(); - } - - /// 检查缓存设备的在线状态 - static Future checkCachedDevicesOnlineStatus() async { - await _initializeCache(); - - if (_deviceCache.isEmpty) return; - - print('开始检查${_deviceCache.length}个缓存设备的在线状态...'); - - final List offlineDevices = []; - final List> checkFutures = []; - - for (final device in _deviceCache.values) { - final future = _checkSingleDeviceOnlineStatus(device).then((isOnline) { - if (isOnline) { - // 设备在线,更新最后见到时间和在线状态 - final updatedDevice = device.copyWith( - lastSeen: DateTime.now(), - isOnline: true, - ); - _deviceCache[device.ip] = updatedDevice; - } else { - // 设备离线,标记为离线 - final updatedDevice = device.copyWith(isOnline: false); - _deviceCache[device.ip] = updatedDevice; - - // 检查是否长时间离线(超过7天),如果是则加入移除列表 - final daysSinceLastSeen = DateTime.now().difference(device.lastSeen).inDays; - if (daysSinceLastSeen > 7) { - offlineDevices.add(device.ip); - } - } - }); - checkFutures.add(future); - } - - // 等待所有检查完成 - await Future.wait(checkFutures); - - // 移除长时间离线的设备 - for (final ip in offlineDevices) { - _deviceCache.remove(ip); - _priorityIPs.remove(ip); - print('移除长时间离线的设备: $ip'); - } - - // 保存更新后的缓存 - await _saveCache(); - - print('设备在线状态检查完成,移除了${offlineDevices.length}个长时间离线的设备'); - } - - /// 检查单个设备的在线状态 - static Future _checkSingleDeviceOnlineStatus(NearbyDevice device) async { - try { - // 使用更短的超时时间进行快速检查 - const quickTimeout = Duration(milliseconds: 500); - final socket = await Socket.connect(device.ip, _defaultPort, timeout: quickTimeout); - await socket.close(); - return true; - } catch (e) { - return false; - } - } - - /// 获取在线的缓存设备列表 - static Future> getOnlineCachedDevices() async { - await _initializeCache(); - return _deviceCache.values.where((device) => device.isOnline).toList(); - } - - /// 获取离线的缓存设备列表 - static Future> getOfflineCachedDevices() async { - await _initializeCache(); - return _deviceCache.values.where((device) => !device.isOnline).toList(); - } - - - - - - - - - - /// 清除设备缓存 - static Future clearCache() async { - _deviceCache.clear(); - _priorityIPs.clear(); - - try { - final prefs = await SharedPreferences.getInstance(); - await prefs.remove(_deviceCacheKey); - await prefs.remove(_priorityIPsKey); - - print('设备缓存已清除'); - } catch (e) { - print('清除设备缓存失败: $e'); - } - } } /// 附近设备信息 @@ -399,14 +206,12 @@ class NearbyDevice { final bool isReachable; final bool hasSharing; final DateTime lastSeen; - final bool isOnline; // 当前是否在线 const NearbyDevice({ required this.ip, required this.isReachable, required this.hasSharing, required this.lastSeen, - this.isOnline = true, // 默认为在线 }); /// 从JSON创建NearbyDevice实例 @@ -416,7 +221,6 @@ class NearbyDevice { isReachable: json['isReachable'] as bool, hasSharing: json['hasSharing'] as bool, lastSeen: DateTime.parse(json['lastSeen'] as String), - isOnline: json['isOnline'] as bool? ?? true, // 兼容旧数据 ); } @@ -427,7 +231,6 @@ class NearbyDevice { 'isReachable': isReachable, 'hasSharing': hasSharing, 'lastSeen': lastSeen.toIso8601String(), - 'isOnline': isOnline, }; } @@ -437,19 +240,17 @@ class NearbyDevice { bool? isReachable, bool? hasSharing, DateTime? lastSeen, - bool? isOnline, }) { return NearbyDevice( ip: ip ?? this.ip, isReachable: isReachable ?? this.isReachable, hasSharing: hasSharing ?? this.hasSharing, lastSeen: lastSeen ?? this.lastSeen, - isOnline: isOnline ?? this.isOnline, ); } @override String toString() { - return 'NearbyDevice(ip: $ip, isReachable: $isReachable, hasSharing: $hasSharing, lastSeen: $lastSeen, isOnline: $isOnline)'; + return 'NearbyDevice(ip: $ip, isReachable: $isReachable, hasSharing: $hasSharing, lastSeen: $lastSeen)'; } } \ No newline at end of file From c348b0b4d26880217ccc2c7f99cd28c2b3f0396d Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Thu, 24 Jul 2025 20:39:43 +0800 Subject: [PATCH 38/54] =?UTF-8?q?=20refactor:=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=BB=93=E6=9E=84=E5=B9=B6=E6=B8=85=E7=90=86?= =?UTF-8?q?=E6=9C=AA=E4=BD=BF=E7=94=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将页面文件迁移到更合理的目录结构 (lib/home/view/) - 删除未使用的文件和组件以减少代码库体积 - 移除 lib/services/background_service.dart - 移除 lib/server/index.dart 和 README.md - 移除多个已废弃的页面和组件文件 - 重命名 utils 目录为 util 保持命名一致性 - 增强用户体验功能 - 添加检查更新和问题反馈菜单项 - 实现主机链接点击打开功能 - 改进服务器自动启动机制 - 完善国际化支持,新增相关本地化字符串 - 修复文本编辑器撤销操作的滚动位置问题 --- lib/app.dart | 39 ++ lib/home/cubit/host_cubit.dart | 6 + lib/{page => home/view}/history_page.dart | 2 +- lib/home/view/home_app_bar.dart | 88 ++- lib/home/view/home_page.dart | 1 - lib/home/view/home_view.dart | 2 +- lib/home/view/host_list.dart | 18 +- lib/{page => home/view}/host_page.dart | 0 lib/home/view/host_table.dart | 36 +- lib/home/view/host_text.dart | 3 + lib/{page => home/view}/hosts_diff_page.dart | 0 lib/home/view/simple_home_view.dart | 2 +- lib/l10n/app_en.arb | 5 +- lib/l10n/app_localizations.dart | 18 + lib/l10n/app_localizations_en.dart | 11 + lib/l10n/app_localizations_zh.dart | 11 + lib/l10n/app_zh.arb | 5 +- lib/main.dart | 8 + lib/page/home_base_page.dart | 510 ------------------ lib/page/home_page.dart | 223 -------- lib/page/simple_home_page.dart | 110 ---- lib/server/README.md | 202 ------- lib/server/bloc/nearby_devices_cubit.dart | 2 +- lib/server/bloc/server_settings_bloc.dart | 37 ++ lib/server/bloc/server_settings_event.dart | 13 +- lib/server/bloc/server_settings_state.dart | 5 + lib/server/hosts_server.dart | 6 +- lib/server/index.dart | 4 - lib/server/view/nearby_devices_card.dart | 2 +- lib/server/view/server_settings_page.dart | 6 + lib/server/view/server_status_card.dart | 168 ++++-- lib/services/background_service.dart | 58 -- lib/{utils => util}/device_api_cache.dart | 0 .../nearby_devices_scanner.dart | 0 lib/util/settings_manager.dart | 2 + lib/utils/datetime_extensions.dart | 20 - lib/widget/app_bar/home_app_bar.dart | 361 ------------- lib/widget/dialog/access_device_dialog.dart | 4 +- .../dialog/api_documentation_dialog.dart | 2 +- lib/widget/host_base_view.dart | 29 - lib/widget/host_list.dart | 241 --------- lib/widget/host_table.dart | 407 -------------- lib/widget/text_field/search_dialog.dart | 167 ------ pubspec.lock | 24 + pubspec.yaml | 3 + 45 files changed, 446 insertions(+), 2415 deletions(-) rename lib/{page => home/view}/history_page.dart (99%) rename lib/{page => home/view}/host_page.dart (100%) rename lib/{page => home/view}/hosts_diff_page.dart (100%) delete mode 100644 lib/page/home_base_page.dart delete mode 100644 lib/page/home_page.dart delete mode 100644 lib/page/simple_home_page.dart delete mode 100755 lib/server/README.md delete mode 100755 lib/server/index.dart delete mode 100644 lib/services/background_service.dart rename lib/{utils => util}/device_api_cache.dart (100%) rename lib/{utils => util}/nearby_devices_scanner.dart (100%) delete mode 100644 lib/utils/datetime_extensions.dart delete mode 100644 lib/widget/app_bar/home_app_bar.dart delete mode 100644 lib/widget/host_base_view.dart delete mode 100644 lib/widget/host_list.dart delete mode 100644 lib/widget/host_table.dart delete mode 100644 lib/widget/text_field/search_dialog.dart diff --git a/lib/app.dart b/lib/app.dart index cfb1597..9f0501d 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -6,6 +6,7 @@ import 'package:hosts/home/view/home_page.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/global_settings.dart'; import 'package:hosts/model/simple_host_file.dart'; +import 'package:hosts/server/server_manager.dart'; import 'package:hosts/theme.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/util/settings_manager.dart'; @@ -55,6 +56,7 @@ Widget _platformSpecificWidget(String filePath) { Future _initializeApp() async { SettingsManager settingsManager = SettingsManager(); FileManager fileManager = FileManager(); + bool firstOpenApp = await settingsManager.getBool(settingKeyFirstOpenApp); if (!firstOpenApp) { const String fileName = "system"; @@ -66,4 +68,41 @@ Future _initializeApp() async { .copy(await fileManager.getHostsFilePath(fileName)); settingsManager.setBool(settingKeyFirstOpenApp, true); } + + // 异步启动服务器,不阻塞应用初始化 + _startServerInBackground(settingsManager); +} + +/// 在后台异步启动服务器,不阻塞应用初始化 +void _startServerInBackground(SettingsManager settingsManager) { + ServerManager serverManager = ServerManager(); + + Future.microtask(() async { + try { + // 检查是否启用了自动启动服务器 + bool isAutoStartEnabled = await settingsManager.getBool(settingKeyAutoStartEnabled); + if (isAutoStartEnabled) { + // 获取保存的hosts文件列表 + List savedHostsList = await settingsManager.getList(settingKeyAutoStartHosts); + + if (savedHostsList.isNotEmpty) { + // 将JSON数据转换为SimpleHostFile对象 + List autoStartHosts = savedHostsList + .map((json) => SimpleHostFile.fromJson(json)) + .toList(); + + // 启动服务器 + await serverManager.startServer( + allowedHostFiles: autoStartHosts.map((host) => host.fileName).toList(), + ); + + print('自动启动服务器成功,共享${autoStartHosts.length}个hosts文件'); + } else { + print('自动启动已启用,但没有找到保存的hosts文件列表'); + } + } + } catch (e) { + print('自动启动服务器失败: $e'); + } + }); } diff --git a/lib/home/cubit/host_cubit.dart b/lib/home/cubit/host_cubit.dart index fab9237..0190680 100644 --- a/lib/home/cubit/host_cubit.dart +++ b/lib/home/cubit/host_cubit.dart @@ -428,10 +428,16 @@ class HostCubit extends Cubit { if (text == state.data.defaultFileContent) { return; } + + print(text); + + print(state.data.defaultFileContent); + emit( HostFileContent( state.data.copyWith( fileContent: text, + // TODO 这里判断不够正确 isSave: text == state.data.defaultFileContent, ), ), diff --git a/lib/page/history_page.dart b/lib/home/view/history_page.dart similarity index 99% rename from lib/page/history_page.dart rename to lib/home/view/history_page.dart index 8ae3804..bb47e77 100644 --- a/lib/page/history_page.dart +++ b/lib/home/view/history_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:hosts/home/view/hosts_diff_page.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/simple_host_file.dart'; -import 'package:hosts/page/hosts_diff_page.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/widget/countdown_timer.dart'; diff --git a/lib/home/view/home_app_bar.dart b/lib/home/view/home_app_bar.dart index 120ea9b..0bc5716 100644 --- a/lib/home/view/home_app_bar.dart +++ b/lib/home/view/home_app_bar.dart @@ -12,10 +12,12 @@ import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/global_settings.dart'; import 'package:hosts/model/host_file.dart'; import 'package:hosts/model/simple_host_file.dart'; -import 'package:hosts/page/history_page.dart'; +import 'package:hosts/home/view/history_page.dart'; import 'package:hosts/widget/dialog/copy_multiple_dialog.dart'; import 'package:hosts/widget/snakbar.dart'; import 'package:hosts/widget/text_field/search_text_field.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; /// 首页应用栏组件 /// @@ -156,30 +158,57 @@ class HomeAppBar extends StatelessWidget { } Widget buildMoreButton(BuildContext context) { - return PopupMenuButton(onSelected: (value) { + return PopupMenuButton(onSelected: (value) async { switch (value) { case 1: - showAboutDialog( - context: context, - applicationVersion: '1.5.0', - applicationIcon: Image.asset( - "assets/icon/logo.png", - width: 50, - height: 50, - ), - children: [ - Text(AppLocalizations.of(context)!.about_description), - const SizedBox(height: 10), - const Text('Developed by Webb.'), - ], - ); + // 检查更新 + await _launchUrl(context, 'https://github.com/webb-l/hosts/releases'); + break; + case 2: + // 反馈问题 + await _launchUrl(context, 'https://github.com/webb-l/hosts/issues'); + break; + case 3: + // 关于 + final packageInfo = await PackageInfo.fromPlatform(); + + if (context.mounted) { + showAboutDialog( + context: context, + applicationVersion: packageInfo.version, + applicationIcon: Image.asset( + "assets/icon/logo.png", + width: 50, + height: 50, + ), + children: [ + Text(AppLocalizations.of(context)!.about_description), + const SizedBox(height: 10), + const Text('Developed by Webb.'), + ], + ); + } break; default: break; } }, itemBuilder: (BuildContext context) { final List> list = [ - {"text": AppLocalizations.of(context)!.about, "value": 1}, + { + "text": AppLocalizations.of(context)!.check_for_updates, + "value": 1, + "icon": Icons.system_update + }, + { + "text": AppLocalizations.of(context)!.report_issue, + "value": 2, + "icon": Icons.bug_report + }, + { + "text": AppLocalizations.of(context)!.about, + "value": 3, + "icon": Icons.info + }, ]; return list.map((item) { @@ -188,7 +217,7 @@ class HomeAppBar extends StatelessWidget { child: Row( children: [ if (item["icon"] != null) Icon(item["icon"]! as IconData), - SizedBox(width: item["icon"] != null ? 8 : 32), + SizedBox(width: item["icon"] != null ? 8 : 0), Text(item["text"]!.toString()), ], ), @@ -197,6 +226,28 @@ class HomeAppBar extends StatelessWidget { }); } + /// 打开URL + Future _launchUrl(BuildContext context, String url) async { + try { + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalizations.of(context)!.unable_to_open(url))), + ); + } + } + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${AppLocalizations.of(context)!.unable_to_open(url)}: $e')), + ); + } + } + } + Widget _buildWideLayout(BuildContext context, HomeCubit homeCubit, HomeStateData homeStateData) { return Row( children: [ @@ -381,6 +432,7 @@ class HomeAppBar extends StatelessWidget { hostCubit.onHistoryChanged(resultHistory); }, icon: const Icon(Icons.history), + tooltip: AppLocalizations.of(context)!.history, ), if (!hostStateData.isSave) IconButton( diff --git a/lib/home/view/home_page.dart b/lib/home/view/home_page.dart index 5edf66d..e190401 100644 --- a/lib/home/view/home_page.dart +++ b/lib/home/view/home_page.dart @@ -5,7 +5,6 @@ import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/home/view/home_view.dart'; import 'package:hosts/home/view/simple_home_view.dart'; import 'package:hosts/server/bloc/nearby_devices_cubit.dart'; -import 'package:hosts/utils/nearby_devices_scanner.dart'; /// 首页页面组件 /// diff --git a/lib/home/view/home_view.dart b/lib/home/view/home_view.dart index c64451a..e1c4782 100644 --- a/lib/home/view/home_view.dart +++ b/lib/home/view/home_view.dart @@ -5,10 +5,10 @@ import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/home/view/home_app_bar.dart'; import 'package:hosts/home/view/home_drawer.dart'; +import 'package:hosts/home/view/host_page.dart'; import 'package:hosts/home/view/host_view.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; -import 'package:hosts/page/host_page.dart'; class HomeView extends StatefulWidget { const HomeView({super.key}); diff --git a/lib/home/view/host_list.dart b/lib/home/view/host_list.dart index 230c900..9b3880a 100644 --- a/lib/home/view/host_list.dart +++ b/lib/home/view/host_list.dart @@ -4,13 +4,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hosts/enums.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/home/view/host_page.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; -import 'package:hosts/page/host_page.dart'; import 'package:hosts/widget/dialog/copy_dialog.dart'; import 'package:hosts/widget/dialog/link_dialog.dart'; import 'package:hosts/widget/dialog/test_dialog.dart'; import 'package:hosts/widget/snakbar.dart'; +import 'package:url_launcher/url_launcher.dart'; /// 主机列表组件 /// @@ -311,8 +312,19 @@ class ListItem extends StatelessWidget { textSpans.add(TextSpan( text: hosts[i], recognizer: TapGestureRecognizer() - ..onTap = () { - // onLaunchUrl(hosts[i]); + ..onTap = () async { + final url = Uri.parse('http://${hosts[i]}'); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.unable_to_open(hosts[i]))), + ); + } + } }, )); diff --git a/lib/page/host_page.dart b/lib/home/view/host_page.dart similarity index 100% rename from lib/page/host_page.dart rename to lib/home/view/host_page.dart diff --git a/lib/home/view/host_table.dart b/lib/home/view/host_table.dart index a4b30e4..61154d7 100644 --- a/lib/home/view/host_table.dart +++ b/lib/home/view/host_table.dart @@ -5,12 +5,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; -import 'package:hosts/page/host_page.dart'; import 'package:hosts/widget/dialog/copy_dialog.dart'; import 'package:hosts/widget/dialog/link_dialog.dart'; import 'package:hosts/widget/dialog/test_dialog.dart'; import 'package:hosts/widget/snakbar.dart'; import 'package:syncfusion_flutter_datagrid/datagrid.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'host_page.dart'; /// 主机表格组件 /// @@ -57,8 +59,17 @@ class HostTable extends StatelessWidget { () => hostCubit.onDelete(hosts)); }, onToggleUse: hostCubit.onToggleUse, - onLaunchUrl: (host) { - print("onLaunchUrl, $host"); + onLaunchUrl: (host) async { + final url = Uri.parse('http://$host'); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalizations.of(context)!.unable_to_open(host))), + ); + } + } }, context: context, ), @@ -199,7 +210,7 @@ class HostDataSource extends DataGridSource { value: MyDataGridCell( value: host.host, child: GestureDetector( - onTap: () => onLaunchUrl(host.host), + onTap: () async => await _launchUrl(host.host, context), child: Container( alignment: Alignment.centerLeft, child: Text.rich( @@ -346,14 +357,27 @@ class HostDataSource extends DataGridSource { // notifyListeners(); } + Future _launchUrl(String host, BuildContext context) async { + final url = Uri.parse('http://$host'); + if (await canLaunchUrl(url)) { + await launchUrl(url); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalizations.of(context)!.unable_to_open(host))), + ); + } + } + } + List _buildTextSpans(List hosts, BuildContext context) { List textSpans = []; for (int i = 0; i < hosts.length; i++) { textSpans.add(TextSpan( text: hosts[i], recognizer: TapGestureRecognizer() - ..onTap = () { - onLaunchUrl(hosts[i]); + ..onTap = () async { + await _launchUrl(hosts[i], context); }, )); if (i < hosts.length - 1) { diff --git a/lib/home/view/host_text.dart b/lib/home/view/host_text.dart index ea88755..867bec4 100644 --- a/lib/home/view/host_text.dart +++ b/lib/home/view/host_text.dart @@ -80,7 +80,10 @@ class _HostTextState extends State { return BlocBuilder( builder: (BuildContext context, state) { if (state is HostUndo || state is HostInitial || state is HostHistory) { + // TODO 会出现撤回情况。 + textEditingController.clear(); textEditingController.text = state.data.fileContent; + _scrollController.jumpTo(0); } final hostCubit = context.read(); return Column( diff --git a/lib/page/hosts_diff_page.dart b/lib/home/view/hosts_diff_page.dart similarity index 100% rename from lib/page/hosts_diff_page.dart rename to lib/home/view/hosts_diff_page.dart diff --git a/lib/home/view/simple_home_view.dart b/lib/home/view/simple_home_view.dart index a2e2935..c7a55f0 100644 --- a/lib/home/view/simple_home_view.dart +++ b/lib/home/view/simple_home_view.dart @@ -8,10 +8,10 @@ import 'package:hosts/enums.dart'; import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/home/view/home_app_bar.dart'; +import 'package:hosts/home/view/host_page.dart'; import 'package:hosts/home/view/host_view.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/host_file.dart'; -import 'package:hosts/page/host_page.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b4ec014..ad7a17a 100755 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -177,5 +177,8 @@ "diff_legend_description": "Difference Legend", "diff_legend_ok": "Got it", "current_line": "Current line:", - "total_lines": "Total lines:" + "total_lines": "Total lines:", + "unable_to_open": "Unable to open {host}", + "check_for_updates": "Check for Updates", + "report_issue": "Report Issue" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 0b67a24..6444005 100755 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1165,6 +1165,24 @@ abstract class AppLocalizations { /// In zh, this message translates to: /// **'总行数:'** String get total_lines; + + /// No description provided for @unable_to_open. + /// + /// In zh, this message translates to: + /// **'无法打开 {host}'** + String unable_to_open(Object host); + + /// No description provided for @check_for_updates. + /// + /// In zh, this message translates to: + /// **'检查更新'** + String get check_for_updates; + + /// No description provided for @report_issue. + /// + /// In zh, this message translates to: + /// **'反馈问题'** + String get report_issue; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 6a1043e..e2e8ec7 100755 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -561,4 +561,15 @@ class AppLocalizationsEn extends AppLocalizations { @override String get total_lines => 'Total lines:'; + + @override + String unable_to_open(Object host) { + return 'Unable to open $host'; + } + + @override + String get check_for_updates => 'Check for Updates'; + + @override + String get report_issue => 'Report Issue'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 5ec95d8..8ab5c8d 100755 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -548,4 +548,15 @@ class AppLocalizationsZh extends AppLocalizations { @override String get total_lines => '总行数:'; + + @override + String unable_to_open(Object host) { + return '无法打开 $host'; + } + + @override + String get check_for_updates => '检查更新'; + + @override + String get report_issue => '反馈问题'; } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index f5850fa..fa06ed5 100755 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -177,5 +177,8 @@ "diff_legend_description": "差异说明", "diff_legend_ok": "知道了", "current_line": "当前行:", - "total_lines": "总行数:" + "total_lines": "总行数:", + "unable_to_open": "无法打开 {host}", + "check_for_updates": "检查更新", + "report_issue": "反馈问题" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 93298b4..aa7fcf5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:bloc/bloc.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter_background/flutter_background.dart'; import 'package:hosts/app.dart'; @@ -22,9 +23,16 @@ void main(List args) async { } Future _initializeBackgroundService() async { + // Web环境不支持后台服务 + if (kIsWeb) { + return; + } + + // 只在Android平台初始化后台服务 if (!Platform.isAndroid) { return; } + const androidConfig = FlutterBackgroundAndroidConfig( notificationTitle: "Hosts 编辑器", notificationText: "正在后台运行 hosts 服务", diff --git a/lib/page/home_base_page.dart b/lib/page/home_base_page.dart deleted file mode 100644 index 4284b33..0000000 --- a/lib/page/home_base_page.dart +++ /dev/null @@ -1,510 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:hosts/l10n/app_localizations.dart'; -import 'package:hosts/enums.dart'; -import 'package:hosts/model/host_file.dart'; -import 'package:hosts/model/simple_host_file.dart'; -import 'package:hosts/page/host_page.dart'; -import 'package:hosts/util/file_manager.dart'; -import 'package:hosts/widget/dialog/link_dialog.dart'; -import 'package:hosts/widget/error/error_empty.dart'; -import 'package:hosts/widget/host_list.dart'; -import 'package:hosts/widget/host_table.dart'; -import 'package:hosts/widget/host_text_editing_controller.dart'; -import 'package:hosts/widget/row_line_widget.dart'; -import 'package:hosts/widget/snakbar.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart'; - -abstract class BaseHomePage extends StatefulWidget { - const BaseHomePage({super.key}); - - @override - BaseHomePageState createState(); -} - -abstract class BaseHomePageState extends State { - // 选中的主机列表 - final List selectHosts = []; - - // 过滤后的主机列表 - final List filterHosts = []; - HostsFile hostsFile = HostsFile("", ""); - EditMode editMode = EditMode.Table; - AdvancedSettingsEnum advancedSettingsEnum = AdvancedSettingsEnum.Close; - String searchText = ""; - Map sortConfig = { - "host": null, - "isUse": null, - "hosts": null, - "description": null, - }; - SimpleHostFileHistory? selectHistory; - HostTextEditingController textEditingController = HostTextEditingController(); - final FocusNode _focusNode = FocusNode(); - bool isControl = false; - - final ScrollController _scrollController = ScrollController(); - final ScrollController _textScrollController = ScrollController(); - - final GlobalKey _textFieldContainerKey = GlobalKey(); - - @override - void initState() { - _textScrollController.addListener(() { - if (_textScrollController.hasClients) { - _scrollController.jumpTo(_textScrollController.offset); - } - }); - super.initState(); - } - - @override - void dispose() { - super.dispose(); - _scrollController.dispose(); - textEditingController.dispose(); - } - - /// 处理打开文件的操作 - /// [content] 是文件的内容 - onOpenFile(String content) { - setState(() { - if (editMode == EditMode.Table) { - hostsFile.formString(content); - hostsFile.defaultContent = content; - hostsFile.isUpdateHost(); - syncFilterHosts(); - } else { - textEditingController.dispose(); - - textEditingController = HostTextEditingController() - ..text = content - ..addListener(() { - setState(() { - hostsFile.isUpdateHostWithText(textEditingController.text); - }); - }); - } - }); - } - - /// 撤销上一次的主机操作 - undoHost() { - setState(() { - hostsFile.undoHost(); - textEditingController.value = - TextEditingValue(text: hostsFile.toString()); - syncFilterHosts(); - }); - } - - /// 处理搜索文本变化 - /// [value] 是新的搜索文本 - onSearchChanged(String value) { - setState(() { - searchText = value; - syncFilterHosts(); - }); - } - - /// 切换高级设置的状态 - /// [value] 是新的高级设置状态 - onSwitchAdvancedSettings(AdvancedSettingsEnum value) { - setState(() { - advancedSettingsEnum = value; - }); - } - - /// 切换编辑模式 - /// [value] 是新的编辑模式 - onSwitchMode(EditMode value) { - setState(() { - if (editMode == EditMode.Text) { - editMode = EditMode.Table; - hostsFile.formString(textEditingController.text); - syncFilterHosts(); - } else { - editMode = EditMode.Text; - textEditingController.value = - TextEditingValue(text: hostsFile.toString()); - } - }); - } - - /// 处理删除操作 - onDeletePressed() { - deleteMultiple( - context, - selectHosts.map((item) => item.host).toList(), - () => setState(() { - hostsFile.deleteMultiple(selectHosts); - syncFilterHosts(); - }), - ); - } - - /// 处理全选状态变化 - /// [value] 是全选的状态 - onCheckedAllChanged(bool? value) { - setState(() { - selectHosts.clear(); - if (value ?? false) { - selectHosts.addAll(hostsFile.hosts); - } - }); - } - - /// 处理排序配置变化 - /// [value] 是新的排序配置 - onSortConfChanged(Map value) { - setState(() { - sortConfig = value; - syncFilterHosts(); - }); - } - - /// 切换主机的使用状态 - /// [value] 是新的使用状态 - onSwitchHosts(bool value) { - setState(() { - for (var host in selectHosts) { - host.isUse = value; - } - hostsFile.updateHostUseState(selectHosts); - syncFilterHosts(); - }); - } - - /// 处理单个主机的选中状态 - /// [index] 是主机的索引 - /// [host] 是被选中的主机 - onChecked(int index, HostsModel host) { - setState(() { - if (selectHosts.contains(host)) { - selectHosts.remove(host); - } else { - selectHosts.add(host); - } - }); - } - - /// 处理主机链接的操作 - /// [index] 是主机的索引 - /// [host] 是被链接的主机 - onLink(int index, HostsModel host) async { - final Map>? result = - await linkDialog(context, hostsFile.hosts, host); - if (result == null) return; - setState(() { - host.config = result; - hostsFile.updateHost(index, host); - }); - } - - /// 处理主机编辑操作 - /// [index] 是主机的索引 - /// [host] 是被编辑的主机 - onEdit(int index, HostsModel host) async { - List? hostsModels = await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => HostPage(hostModel: host), - ), - ); - if (hostsModels == null) return; - setState(() { - hostsFile.updateHost( - index, - hostsFile.hosts[index].withCopy( - host: hostsModels.first.host, - isUse: hostsModels.first.isUse, - description: hostsModels.first.description, - hosts: hostsModels.first.hosts, - hostLine: hostsModels.first.hostLine, - descLine: hostsModels.first.descLine, - )); - syncFilterHosts(); - }); - } - - /// 处理主机删除操作 - /// [hosts] 是要删除的主机列表 - onDelete(List hosts) { - deleteMultiple( - context, - hosts.map((item) => item.host).toList(), - () => setState(() { - hostsFile.deleteMultiple(hosts); - syncFilterHosts(); - }), - ); - } - - /// 切换主机的使用状态 - /// [hosts] 是要切换状态的主机列表 - onToggleUse(List hosts) { - setState(() { - hostsFile.updateHostUseState(hosts); - syncFilterHosts(); - }); - } - - /// 同步变更的 Hosts 文件 - void syncFilterHosts() { - selectHosts.clear(); - filterHosts.clear(); - filterHosts.addAll(hostsFile.filterHosts(searchText, sortConfig)); - } - - /// 构建浮动操作按钮 - /// [context] 是构建按钮的上下文 - FloatingActionButton? buildFloatingActionButton(BuildContext context) { - if (editMode == EditMode.Table) { - return FloatingActionButton( - onPressed: () async { - List? hostsModels = await Navigator.of(context) - .push(MaterialPageRoute(builder: (context) => const HostPage())); - if (hostsModels == null) return; - setState(() { - for (HostsModel hostsModel in hostsModels) { - hostsFile.addHost(hostsModel); - } - syncFilterHosts(); - }); - }, - child: const Icon(Icons.add), - ); - } - return null; - } - - /// 构建主机表格或文本编辑器 - /// [filterHosts] 是过滤后的主机列表 - Widget buildHostTableOrTextEdit(List filterHosts) { - if (editMode == EditMode.Text) { - return Expanded( - child: Column( - children: [ - Expanded( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RowLineWidget( - textEditingController: textEditingController, - context: context, - textFieldContainerKey: _textFieldContainerKey, - scrollController: _scrollController, - ), - Expanded( - key: _textFieldContainerKey, - child: KeyboardListener( - focusNode: _focusNode, - onKeyEvent: (event) { - if ([ - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.controlRight - ].contains(event.logicalKey)) { - if (isControl) { - isControl = false; - } else { - isControl = true; - } - } - if (event.logicalKey == LogicalKeyboardKey.slash && - isControl && - event is KeyDownEvent) { - textEditingController - .updateUseStatus(textEditingController.selection); - } - - if (event.logicalKey == LogicalKeyboardKey.keyS && - isControl && - event is KeyDownEvent && - !hostsFile.isSave) { - onKeySaveChange(); - } - }, - child: TextField( - controller: textEditingController, - scrollController: _textScrollController, - maxLines: double.maxFinite.toInt(), - decoration: - const InputDecoration(border: InputBorder.none), - ), - ), - ), - ], - ), - ), - Container( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), - child: Row( - children: [ - Text( - "当前行:${textEditingController.countNewlines(textEditingController.text.substring(0, textEditingController.selection.start > 0 ? textEditingController.selection.start : 0)) + 1}", - ), - const SizedBox( - width: 8, - ), - Text( - "总行数:${textEditingController.countNewlines(textEditingController.text) + 1}"), - ], - ), - ) - ], - ), - ); - } - - if (filterHosts.isEmpty) { - return Expanded( - child: Container( - alignment: Alignment.center, - width: double.maxFinite, - height: double.maxFinite, - child: const ErrorEmpty(), - ), - ); - } - - if (MediaQuery.of(context).size.width >= 1000) { - return Expanded( - child: HostTable( - hosts: filterHosts, - selectHosts: selectHosts, - isCheckedAll: hostsFile.hosts.length == selectHosts.length, - onCheckedAllChanged: onCheckedAllChanged, - onChecked: onChecked, - onLink: onLink, - onEdit: onEdit, - onDelete: onDelete, - onToggleUse: onToggleUse, - onLaunchUrl: (url) { - // Uncomment and implement the URL launching logic if needed - // if (!await launchUrl(Uri.https(url))) { - // throw Exception('Could not launch $url'); - // } - }, - ), - ); - } else { - return Expanded( - child: HostList( - hosts: filterHosts, - selectHosts: selectHosts, - isCheckedAll: hostsFile.hosts.length == selectHosts.length, - onCheckedAllChanged: onCheckedAllChanged, - onChecked: onChecked, - onLink: onLink, - onEdit: onEdit, - onDelete: onDelete, - onToggleUse: onToggleUse, - onLaunchUrl: (url) { - // Uncomment and implement the URL launching logic if needed - // if (!await launchUrl(Uri.https(url))) { - // throw Exception('Could not launch $url'); - // } - }, - ), - ); - } - } - - Future saveHost(String filePath, String hostContent) async { - if (kIsWeb) { - final String tempContent = hostContent.replaceAll("\"", "\\\""); - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("保存"), - content: SizedBox( - width: MediaQuery.of(context).size.width * 0.5, - child: SelectableText(hostContent), - ), - actions: [ - TextButton( - onPressed: () => writeClipboard( - 'echo "$tempContent" > /etc/hosts', - tempContent, - context, - ), - child: const Text("Linux(echo)")), - TextButton( - onPressed: () { - final String systemHostPath = p.joinAll([ - "C:", - "Windows", - "System32", - "drivers", - "etc", - "hosts" - ]); - final String content = hostContent - .split("\n") - .map((item) => 'echo $item') - .join("\n"); - writeClipboard( - '(\n$content\n) > $systemHostPath', - hostContent, - context, - ); - }, - child: const Text("Windows(echo)")), - TextButton( - onPressed: () => writeClipboard( - 'echo "$tempContent" > /etc/hosts', - tempContent, - context, - ), - child: const Text("MacOS(echo)")), - ], - )); - return true; - } - - final File file = File(filePath); - try { - await file.writeAsString(hostContent); - } catch (e) { - try { - final Directory cacheDirectory = await getApplicationCacheDirectory(); - final File cacheFile = File(p.join(cacheDirectory.path, 'hosts')); - await cacheFile.writeAsString(hostContent); - - await FileManager() - .writeFileWithAdminPrivileges(cacheFile.path, filePath); - } catch (e) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)!.error_save_fail))); - return false; - } - } - - setState(() { - hostsFile.defaultContent = hostContent; - hostsFile.isUpdateHost(); - }); - return true; - } - - void writeClipboard( - String hostContent, String defaultContent, BuildContext context) { - Clipboard.setData(ClipboardData(text: hostContent)).then((_) { - setState(() { - hostsFile.defaultContent = defaultContent; - hostsFile.isUpdateHost(); - Navigator.pop(context); - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(AppLocalizations.of(context)!.copy_to_tip), - ), - ); - }); - } - - void onKeySaveChange(); -} diff --git a/lib/page/home_page.dart b/lib/page/home_page.dart deleted file mode 100644 index 669e344..0000000 --- a/lib/page/home_page.dart +++ /dev/null @@ -1,223 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:hosts/l10n/app_localizations.dart'; -import 'package:hosts/enums.dart'; -import 'package:hosts/model/host_file.dart'; -import 'package:hosts/model/simple_host_file.dart'; -import 'package:hosts/page/home_base_page.dart'; -import 'package:hosts/util/file_manager.dart'; -import 'package:hosts/util/settings_manager.dart'; -import 'package:hosts/widget/app_bar/home_app_bar.dart'; -import 'package:hosts/widget/home_drawer.dart'; -import 'package:hosts/widget/host_text_editing_controller.dart'; - -class HomePage extends BaseHomePage { - const HomePage({super.key}); - - @override - _HomePageState createState() => _HomePageState(); // 返回 _HomePageState -} - -class _HomePageState extends BaseHomePageState { - final GlobalKey _scaffoldKey = GlobalKey(); - final SettingsManager _settingsManager = SettingsManager(); - final FileManager _fileManager = FileManager(); - - @override - void initState() { - textEditingController.addListener(() { - setState(() { - hostsFile.isUpdateHostWithText(textEditingController.text); - }); - }); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - key: _scaffoldKey, - floatingActionButton: buildFloatingActionButton(context), - drawer: MediaQuery.of(context).size.width < 600 - ? buildHomeDrawer(context) - : null, - body: Row( - children: [ - if (advancedSettingsEnum == AdvancedSettingsEnum.Close && - MediaQuery.of(context).size.width > 600) - buildHomeDrawer(context), - Expanded( - child: Column( - children: [ - HomeAppBar( - isSave: hostsFile.isSave, - onOpenFile: onOpenFile, - undoHost: undoHost, - searchText: searchText, - onSearchChanged: onSearchChanged, - advancedSettingsEnum: advancedSettingsEnum, - onSwitchAdvancedSettings: (AdvancedSettingsEnum value) { - setState(() { - advancedSettingsEnum = value; - }); - _scaffoldKey.currentState?.openDrawer(); - }, - editMode: editMode, - onSwitchMode: onSwitchMode, - hosts: selectHosts, - sortConfig: sortConfig, - onDeletePressed: onDeletePressed, - isCheckedAll: hostsFile.hosts.length == selectHosts.length, - onCheckedAllChanged: onCheckedAllChanged, - onSortConfChanged: onSortConfChanged, - selectHistory: selectHistory, - history: hostsFile.history, - onSwitchHosts: onSwitchHosts, - onHistoryChanged: (history) async { - List resultHistory = - await _fileManager.getHistory(hostsFile.fileId); - setState(() { - if (history != null) { - selectHistory = history; - hostsFile.setHistory(history.path).then((value) { - if (editMode != EditMode.Text) return; - updateTextEditingController(); - }); - } - hostsFile.history = resultHistory; - syncFilterHosts(); - }); - }, - ), - if (!hostsFile.isSave) - FutureBuilder( - future: saveTipMessage(context), - builder: (BuildContext context, - AsyncSnapshot snapshot) { - if (snapshot.hasData) { - return snapshot.data!; - } - return const SizedBox(); - }), - buildHostTableOrTextEdit(filterHosts) - ], - ), - ), - ], - ), - ); - } - - HomeDrawer buildHomeDrawer(BuildContext context) { - return HomeDrawer( - isSave: hostsFile.isSave, - onChanged: (String value, String fileId) async { - if (await _settingsManager.getString(settingKeyUseHostFile) == fileId) { - if (!await _fileManager.areFilesEqual(fileId)) { - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text(AppLocalizations.of(context)!.warning), - content: - Text(AppLocalizations.of(context)!.warning_different), - actions: [ - TextButton( - onPressed: () async { - if (await saveHost(FileManager.systemHostFilePath, - hostsFile.toString())) { - Navigator.of(context).pop(); - } - }, - child: Text(AppLocalizations.of(context)! - .warning_different_covering_system), - ), - TextButton( - onPressed: () async { - setState(() { - hostsFile.formString( - File(FileManager.systemHostFilePath) - .readAsStringSync()); - hostsFile.save(true); - Navigator.of(context).pop(); - }); - }, - child: Text(AppLocalizations.of(context)! - .warning_different_covering_current), - ), - ], - ); - }); - } - } - setState(() { - hostsFile = HostsFile(value, fileId); - if (editMode == EditMode.Text) { - updateTextEditingController(); - } else { - syncFilterHosts(); - } - }); - }, - onClickUse: (hostContent) async { - return await saveHost(FileManager.systemHostFilePath, hostContent); - }, - ); - } - - void updateTextEditingController() { - textEditingController.dispose(); - - textEditingController = HostTextEditingController() - ..text = hostsFile.toString() - ..addListener(() { - setState(() { - hostsFile.isUpdateHostWithText(textEditingController.text); - }); - }); - } - - Future saveTipMessage(BuildContext context) async { - final bool isUseFile = hostsFile.fileId == - await _settingsManager.getString(settingKeyUseHostFile); - - final String updateSaveTip = - AppLocalizations.of(context)!.error_not_update_save_tip; - final String updateSavePermissionTip = isUseFile - ? '\n${AppLocalizations.of(context)!.error_not_update_save_permission_tip}' - : ''; - return MaterialBanner( - content: Text("$updateSaveTip$updateSavePermissionTip"), - leading: const Icon(Icons.error_outline), - actions: [ - TextButton( - onPressed: () => onKeySaveChange(true), - child: Text(AppLocalizations.of(context)!.save_create_history), - ), - TextButton( - onPressed: onKeySaveChange, - child: Text(AppLocalizations.of(context)!.save), - ), - ], - ); - } - - @override - void onKeySaveChange([bool isHistory = false]) async { - if (editMode == EditMode.Text) { - hostsFile.formString(textEditingController.text); - } - final bool isUseFile = hostsFile.fileId == - await _settingsManager.getString(settingKeyUseHostFile); - if (isUseFile) { - if (!await saveHost( - FileManager.systemHostFilePath, hostsFile.toString())) { - return; - } - } - setState(() { - hostsFile.save(isHistory); - }); - } -} diff --git a/lib/page/simple_home_page.dart b/lib/page/simple_home_page.dart deleted file mode 100644 index aa67cb6..0000000 --- a/lib/page/simple_home_page.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:flutter/material.dart'; -import 'package:hosts/l10n/app_localizations.dart'; -import 'package:hosts/enums.dart'; -import 'package:hosts/page/home_base_page.dart'; -import 'package:hosts/widget/app_bar/home_app_bar.dart'; - -class SimpleHomePage extends BaseHomePage { - final String filePath; - - const SimpleHomePage({super.key, required this.filePath}); - - @override - _SimpleHomePageState createState() => _SimpleHomePageState(); -} - -class _SimpleHomePageState extends BaseHomePageState { - @override - void initState() { - if (widget.filePath.isNotEmpty) { - final String fileContent = File(widget.filePath).readAsStringSync(); - setState(() { - hostsFile.formString(fileContent); - hostsFile.defaultContent = fileContent; - filterHosts.clear(); - filterHosts.addAll(hostsFile.filterHosts(searchText, sortConfig)); - }); - } - - if (kIsWeb) { - setState(() { - hostsFile.formString(""); - hostsFile.defaultContent = ""; - }); - } - - textEditingController.addListener(() { - setState(() { - hostsFile.isUpdateHostWithText(textEditingController.text); - }); - }); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: buildFloatingActionButton(context), - body: Column( - children: [ - HomeAppBar( - isSave: hostsFile.isSave, - onOpenFile: onOpenFile, - undoHost: undoHost, - searchText: searchText, - onSearchChanged: onSearchChanged, - advancedSettingsEnum: advancedSettingsEnum, - onSwitchAdvancedSettings: onSwitchAdvancedSettings, - editMode: editMode, - onSwitchMode: onSwitchMode, - hosts: selectHosts, - sortConfig: sortConfig, - onDeletePressed: onDeletePressed, - isCheckedAll: hostsFile.hosts.length == selectHosts.length, - onCheckedAllChanged: onCheckedAllChanged, - onSortConfChanged: onSortConfChanged, - selectHistory: selectHistory, - history: hostsFile.history, - onSwitchHosts: onSwitchHosts, - onHistoryChanged: (history) {}, - ), - if (!hostsFile.isSave) - FutureBuilder( - future: saveTipMessage(context), - builder: - (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - return snapshot.data!; - } - return const SizedBox(); - }), - buildHostTableOrTextEdit(filterHosts) - ], - ), - ); - } - - Future saveTipMessage(BuildContext context) async { - return MaterialBanner( - content: Text(AppLocalizations.of(context)!.error_not_update_save_tip), - leading: const Icon(Icons.error_outline), - actions: [ - TextButton( - onPressed: onKeySaveChange, - child: Text(AppLocalizations.of(context)!.save), - ), - ], - ); - } - - @override - void onKeySaveChange() async { - if (editMode == EditMode.Text) { - hostsFile.formString(textEditingController.text); - } - saveHost(widget.filePath, hostsFile.toString()); - } -} diff --git a/lib/server/README.md b/lib/server/README.md deleted file mode 100755 index 34058b5..0000000 --- a/lib/server/README.md +++ /dev/null @@ -1,202 +0,0 @@ -# HTTP服务器模块 - -这个模块为Hosts Editor应用提供了内置的HTTP服务器功能,允许通过RESTful API远程管理hosts文件。 - -## 功能特性 - -- 🚀 内置HTTP服务器,支持RESTful API -- 📝 完整的hosts文件CRUD操作 -- 🔧 灵活的服务器配置管理 -- 🎯 CORS支持,允许跨域访问 -- 📱 集成的Flutter设置页面 -- 🔄 自动启动功能 -- 📋 完整的API文档 - -## 模块结构 - -``` -lib/server/ -├── hosts_server.dart # HTTP服务器核心实现 -├── server_manager.dart # 服务器生命周期管理 -├── server_settings_page.dart # Flutter设置页面 -├── example.dart # 使用示例 -├── index.dart # 模块导出 -└── README.md # 模块文档 -``` - -## 快速开始 - -### 1. 基本使用 - -```dart -import 'package:hosts/server/index.dart'; - -// 创建并启动服务器 -final serverManager = ServerManager(); -await serverManager.initialize(); -await serverManager.startServer(); - -// 服务器现在运行在 http://localhost:1204 -print('服务器URL: ${serverManager.server.serverUrl}'); -``` - -### 2. 自定义配置 - -```dart -// 更新服务器配置 -await serverManager.updateServerConfig( - port: 9090, - host: '0.0.0.0', - autoStart: true, -); - -// 启动服务器 -await serverManager.startServer(); -``` - -### 3. 在Flutter应用中集成 - -```dart -// 在main函数中初始化 -void main() async { - WidgetsFlutterBinding.ensureInitialized(); - - final serverManager = ServerManager(); - await serverManager.initialize(); - - runApp(MyApp()); -} - -// 在设置页面中添加服务器设置 -Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ServerSettingsPage(), - ), -); -``` - -## API接口 - -### 服务器状态 -- `GET /` - 获取服务器状态和API文档 - -### Hosts文件管理 -- `GET /api/hosts` - 获取所有hosts文件列表(JSON格式) -- `GET /api/hosts/{fileId}` - 获取特定hosts文件内容(纯文本格式) - -### 历史记录管理 -- `GET /api/hosts/{fileId}/history` - 获取hosts文件历史记录(JSON格式) -- `GET /api/hosts/{fileId}/history/{historyId}` - 获取特定历史记录内容(纯文本格式) - -## API示例 - -### 获取所有hosts文件 -```bash -curl http://localhost:1204/api/hosts -``` - -响应: -```json -{ - "success": true, - "data": [ - {"fileName": "system", "remark": "系统默认"}, - {"fileName": "custom", "remark": "自定义配置"} - ] -} -``` - -### 获取hosts文件内容(纯文本) -```bash -curl http://localhost:1204/api/hosts/system -``` - -响应(纯文本): -``` -127.0.0.1 localhost -::1 localhost -# 这里是hosts文件的实际内容 -``` - -### 获取hosts文件历史记录(JSON) -```bash -curl http://localhost:1204/api/hosts/system/history -``` - -响应(JSON): -```json -{ - "success": true, - "data": [ - { - "id": "1672531200000", - "fileName": "1672531200000", - "timestamp": "1672531200000", - "createTime": "2023-01-01T00:00:00.000Z" - } - ] -} -``` - -### 获取特定历史记录内容(纯文本) -```bash -curl http://localhost:1204/api/hosts/system/history/1672531200000 -``` - -响应(纯文本): -``` -127.0.0.1 localhost -::1 localhost -# 这里是历史记录的实际内容 -``` - -## 配置选项 - -| 选项 | 类型 | 默认值 | 描述 | -|------|------|--------|------| -| port | int | 1204 | 服务器端口号 | -| host | String | 'localhost' | 服务器绑定地址 | -| autoStart | bool | false | 应用启动时自动启动服务器 | - -## 安全注意事项 - -1. **网络访问**: 默认只绑定到localhost,如需远程访问请谨慎设置host为0.0.0.0 -2. **端口冲突**: 确保选择的端口未被其他应用占用 -3. **文件权限**: 系统hosts文件操作可能需要管理员权限 -4. **CORS策略**: 当前配置允许所有来源的跨域请求,生产环境请根据需要调整 - -## 故障排除 - -### 服务器启动失败 -- 检查端口是否被占用 -- 确认防火墙设置 -- 验证host地址格式 - -### API请求失败 -- 确认服务器正在运行 -- 检查请求URL和参数格式 -- 查看服务器日志输出 - -### 权限错误 -- 系统hosts文件操作可能需要提升权限 -- macOS可能需要授权访问系统文件 - -## 依赖项 - -```yaml -dependencies: - shelf: ^1.4.1 - shelf_router: ^1.1.4 -``` - -## 版本历史 - -- v1.0.0 - 初始版本,基本HTTP服务器功能 -- 支持完整的hosts文件CRUD操作 -- 集成Flutter设置页面 -- 提供自动启动和配置管理功能 - -## 贡献 - -欢迎提交Issue和Pull Request来改进这个模块! \ No newline at end of file diff --git a/lib/server/bloc/nearby_devices_cubit.dart b/lib/server/bloc/nearby_devices_cubit.dart index 612954b..e68b6fe 100644 --- a/lib/server/bloc/nearby_devices_cubit.dart +++ b/lib/server/bloc/nearby_devices_cubit.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; -import 'package:hosts/utils/nearby_devices_scanner.dart'; +import 'package:hosts/util/nearby_devices_scanner.dart'; part 'nearby_devices_state.dart'; diff --git a/lib/server/bloc/server_settings_bloc.dart b/lib/server/bloc/server_settings_bloc.dart index fb9625b..a0f1905 100644 --- a/lib/server/bloc/server_settings_bloc.dart +++ b/lib/server/bloc/server_settings_bloc.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:hosts/server/bloc/server_settings_event.dart'; import 'package:hosts/server/bloc/server_settings_state.dart'; import 'package:hosts/server/server_manager.dart'; +import 'package:hosts/util/settings_manager.dart'; import 'package:network_info_plus/network_info_plus.dart'; /// 服务器设置页面 BLoC @@ -16,9 +17,11 @@ class ServerSettingsBloc extends Bloc on(_onStartServer); on(_onStopServer); on(_onRefreshServerStatus); + on(_onUpdateAutoStartSettings); } final ServerManager _serverManager = ServerManager(); + final SettingsManager _settingsManager = SettingsManager(); /// 加载服务器设置 Future _onLoadServerSettings( @@ -29,11 +32,13 @@ class ServerSettingsBloc extends Bloc emit(state.copyWith(isLoading: true, errorMessage: null)); final status = await _serverManager.getServerStatus(); + final isAutoStartEnabled = await _settingsManager.getBool(settingKeyAutoStartEnabled); emit(state.copyWith( isLoading: false, serverStatus: status, isServerEnabled: status['isEnabled'] ?? false, + isAutoStartEnabled: isAutoStartEnabled, )); } catch (e) { emit(state.copyWith( @@ -163,6 +168,38 @@ class ServerSettingsBloc extends Bloc add(LoadServerSettings()); } + /// 更新自动启动设置 + Future _onUpdateAutoStartSettings( + UpdateAutoStartSettings event, + Emitter emit, + ) async { + try { + emit(state.copyWith(isLoading: true, errorMessage: null)); + + // 保存自动启动开关状态 + await _settingsManager.setBool(settingKeyAutoStartEnabled, event.enabled); + + // 如果启用自动启动且提供了hosts文件列表,则保存 + if (event.enabled && event.selectedHosts != null) { + final hostsList = event.selectedHosts!.map((host) => host.toJson()).toList(); + await _settingsManager.setList(settingKeyAutoStartHosts, hostsList); + } else if (!event.enabled) { + // 如果禁用自动启动,清除保存的hosts文件列表 + await _settingsManager.remove(settingKeyAutoStartHosts); + } + + emit(state.copyWith( + isLoading: false, + isAutoStartEnabled: event.enabled, + )); + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: '更新自动启动设置失败: $e', + )); + } + } + /// 获取所有网络接口 Future>> _getNetworkInterfaces() async { final List> networkList = []; diff --git a/lib/server/bloc/server_settings_event.dart b/lib/server/bloc/server_settings_event.dart index c1049fe..a8da381 100644 --- a/lib/server/bloc/server_settings_event.dart +++ b/lib/server/bloc/server_settings_event.dart @@ -32,4 +32,15 @@ class StartServer extends ServerSettingsEvent { class StopServer extends ServerSettingsEvent {} /// 刷新服务器状态 -class RefreshServerStatus extends ServerSettingsEvent {} \ No newline at end of file +class RefreshServerStatus extends ServerSettingsEvent {} + +/// 更新自动启动设置 +class UpdateAutoStartSettings extends ServerSettingsEvent { + final bool enabled; + final List? selectedHosts; + + const UpdateAutoStartSettings(this.enabled, this.selectedHosts); + + @override + List get props => [enabled, selectedHosts]; +} \ No newline at end of file diff --git a/lib/server/bloc/server_settings_state.dart b/lib/server/bloc/server_settings_state.dart index b0362ab..7f7e9b0 100644 --- a/lib/server/bloc/server_settings_state.dart +++ b/lib/server/bloc/server_settings_state.dart @@ -8,6 +8,7 @@ class ServerSettingsState extends Equatable { this.serverStatus, this.networkInterfaces = const [], this.errorMessage, + this.isAutoStartEnabled = false, }); final bool isLoading; @@ -15,6 +16,7 @@ class ServerSettingsState extends Equatable { final Map? serverStatus; final List> networkInterfaces; final String? errorMessage; + final bool isAutoStartEnabled; ServerSettingsState copyWith({ bool? isLoading, @@ -22,6 +24,7 @@ class ServerSettingsState extends Equatable { Map? serverStatus, List>? networkInterfaces, String? errorMessage, + bool? isAutoStartEnabled, }) { return ServerSettingsState( isLoading: isLoading ?? this.isLoading, @@ -29,6 +32,7 @@ class ServerSettingsState extends Equatable { serverStatus: serverStatus ?? this.serverStatus, networkInterfaces: networkInterfaces ?? this.networkInterfaces, errorMessage: errorMessage, + isAutoStartEnabled: isAutoStartEnabled ?? this.isAutoStartEnabled, ); } @@ -39,5 +43,6 @@ class ServerSettingsState extends Equatable { serverStatus, networkInterfaces, errorMessage, + isAutoStartEnabled, ]; } \ No newline at end of file diff --git a/lib/server/hosts_server.dart b/lib/server/hosts_server.dart index 924dbf6..15c5b76 100755 --- a/lib/server/hosts_server.dart +++ b/lib/server/hosts_server.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:hosts/model/simple_host_file.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/util/settings_manager.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf_router/shelf_router.dart'; @@ -154,9 +155,12 @@ class HostsServer { /// 处理服务器状态请求 Future _handleStatus(Request request) async { + // 获取应用版本信息 + final packageInfo = await PackageInfo.fromPlatform(); + final status = { 'status': 'running', - 'version': '1.0.0', + 'version': packageInfo.version, 'timestamp': DateTime.now().toIso8601String(), 'endpoints': [ 'GET /api/hosts - ${_i18nStrings['get_all_hosts_files'] ?? 'Get all hosts files (JSON)'}', diff --git a/lib/server/index.dart b/lib/server/index.dart deleted file mode 100755 index 37d7aed..0000000 --- a/lib/server/index.dart +++ /dev/null @@ -1,4 +0,0 @@ -// Server module exports -export 'hosts_server.dart'; -export 'server_manager.dart'; -export 'view/server_settings_page.dart'; \ No newline at end of file diff --git a/lib/server/view/nearby_devices_card.dart b/lib/server/view/nearby_devices_card.dart index 9d0e837..86a7ffd 100644 --- a/lib/server/view/nearby_devices_card.dart +++ b/lib/server/view/nearby_devices_card.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/server/bloc/nearby_devices_cubit.dart'; -import 'package:hosts/utils/nearby_devices_scanner.dart'; +import 'package:hosts/util/nearby_devices_scanner.dart'; import 'package:hosts/widget/dialog/access_device_dialog.dart'; import 'package:hosts/widget/dialog/api_documentation_dialog.dart'; diff --git a/lib/server/view/server_settings_page.dart b/lib/server/view/server_settings_page.dart index 4ae904b..c86d62f 100755 --- a/lib/server/view/server_settings_page.dart +++ b/lib/server/view/server_settings_page.dart @@ -138,6 +138,12 @@ class _ServerSettingsView extends StatelessWidget { onStopServer: () { context.read().add(StopServer()); }, + isAutoStartEnabled: state.isAutoStartEnabled, + onAutoStartChanged: (enabled, selectedHosts) { + context.read().add( + UpdateAutoStartSettings(enabled, selectedHosts), + ); + }, ); } diff --git a/lib/server/view/server_status_card.dart b/lib/server/view/server_status_card.dart index 699fb8e..ffc55cf 100644 --- a/lib/server/view/server_status_card.dart +++ b/lib/server/view/server_status_card.dart @@ -4,6 +4,7 @@ import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/simple_host_file.dart'; import 'package:hosts/widget/dialog/qr_code_dialog.dart'; import 'package:hosts/widget/dialog/select_hosts_dialog.dart'; +import 'package:url_launcher/url_launcher.dart'; /// 服务器状态卡片组件 class ServerStatusCard extends StatelessWidget { @@ -11,6 +12,8 @@ class ServerStatusCard extends StatelessWidget { final List> networkInterfaces; final Function(List?) onStartServer; final VoidCallback onStopServer; + final bool isAutoStartEnabled; + final Function(bool, List?) onAutoStartChanged; const ServerStatusCard({ super.key, @@ -18,6 +21,8 @@ class ServerStatusCard extends StatelessWidget { required this.networkInterfaces, required this.onStartServer, required this.onStopServer, + required this.isAutoStartEnabled, + required this.onAutoStartChanged, }); /// 复制URL到剪贴板 @@ -36,6 +41,28 @@ class ServerStatusCard extends StatelessWidget { QrCodeDialog.show(context, url: url); } + /// 打开URL + Future _launchUrl(BuildContext context, String url) async { + try { + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalizations.of(context)!.unable_to_open(url))), + ); + } + } + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${AppLocalizations.of(context)!.unable_to_open(url)}: $e')), + ); + } + } + } + /// 处理服务器切换 Future _handleServerToggle(BuildContext context) async { final isRunning = serverStatus?['isRunning'] ?? false; @@ -52,6 +79,20 @@ class ServerStatusCard extends StatelessWidget { } } + /// 处理自动启动开关切换 + Future _handleAutoStartToggle(BuildContext context, bool value) async { + if (value) { + // 启用自动启动,需要选择hosts文件 + final selectedHosts = await SelectHostsDialog.show(context); + if (selectedHosts != null && selectedHosts.isNotEmpty) { + onAutoStartChanged(true, selectedHosts); + } + } else { + // 禁用自动启动 + onAutoStartChanged(false, null); + } + } + /// 构建地址芯片 Widget _buildAddressChip( BuildContext context, { @@ -118,17 +159,22 @@ class ServerStatusCard extends StatelessWidget { ), ), const SizedBox(width: 8), - // IP地址文本 + // IP地址文本(可点击) Flexible( - child: Text( - displayLabel, - style: TextStyle( - fontSize: 13, - fontFamily: 'monospace', - fontWeight: FontWeight.w600, - color: primaryColor, + child: InkWell( + onTap: () => _launchUrl(context, url), + child: Text( + displayLabel, + style: TextStyle( + fontSize: 13, + fontFamily: 'monospace', + fontWeight: FontWeight.w600, + color: primaryColor, + decoration: TextDecoration.underline, + decorationColor: primaryColor.withValues(alpha: 0.5), + ), + overflow: TextOverflow.ellipsis, ), - overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), @@ -197,39 +243,6 @@ class ServerStatusCard extends StatelessWidget { ), child: Row( children: [ - // 状态指示器 - Container( - width: 12, - height: 12, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: isRunning - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.outline, - boxShadow: isRunning - ? [ - BoxShadow( - color: Theme.of(context) - .colorScheme - .primary - .withValues(alpha: 0.4), - blurRadius: 8, - spreadRadius: 2, - ), - ] - : null, - ), - child: isRunning - ? Container( - margin: const EdgeInsets.all(2), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).colorScheme.onPrimary, - ), - ) - : null, - ), - const SizedBox(width: 12), // 状态文字 Expanded( child: Column( @@ -328,6 +341,71 @@ class ServerStatusCard extends StatelessWidget { ], ), ), + const SizedBox(height: 16), + // 自动启动功能开关 + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: isAutoStartEnabled + ? Theme.of(context) + .colorScheme + .primaryContainer + .withValues(alpha: 0.3) + : Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isAutoStartEnabled + ? Theme.of(context) + .colorScheme + .primary + .withValues(alpha: 0.2) + : Theme.of(context) + .colorScheme + .outline + .withValues(alpha: 0.1), + width: 1, + ), + ), + child: Row( + children: [ + // 状态文字 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.server_auto_start, + style: Theme.of(context).textTheme.titleSmall?.copyWith( + color: isAutoStartEnabled + ? Theme.of(context).colorScheme.primary + : Theme.of(context) + .colorScheme + .onSurfaceVariant, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 2), + Text( + AppLocalizations.of(context)!.server_auto_start_desc, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant + .withValues(alpha: 0.7), + ), + ), + ], + ), + ), + // 开关 + Switch( + value: isAutoStartEnabled, + onChanged: (value) => _handleAutoStartToggle(context, value), + activeColor: Theme.of(context).colorScheme.primary, + ), + ], + ), + ), const SizedBox(height: 12), // 服务器地址信息 Column( @@ -347,7 +425,11 @@ class ServerStatusCard extends StatelessWidget { // 显示所有网络接口的IP地址 ...networkInterfaces.map((interface) { final address = interface['address']!; - final serverUrl = 'http://$address:$port'; + // 检查是否为IPv6地址,如果是则需要用方括号包围 + final formattedAddress = address.contains(':') && !address.startsWith('[') + ? '[$address]' + : address; + final serverUrl = 'http://$formattedAddress:$port'; return _buildAddressChip( context, diff --git a/lib/services/background_service.dart b/lib/services/background_service.dart deleted file mode 100644 index f294ff4..0000000 --- a/lib/services/background_service.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:flutter_background/flutter_background.dart'; - -class BackgroundService { - static bool _isRunning = false; - - static bool get isRunning => _isRunning; - - /// 启动后台服务 - static Future startBackgroundService() async { - if (_isRunning) return true; - - try { - final hasPermissions = await FlutterBackground.hasPermissions; - if (!hasPermissions) { - return false; - } - - final success = await FlutterBackground.enableBackgroundExecution(); - if (success) { - _isRunning = true; - } - return success; - } catch (e) { - print('启动后台服务失败: $e'); - return false; - } - } - - /// 停止后台服务 - static Future stopBackgroundService() async { - if (!_isRunning) return true; - - try { - final success = await FlutterBackground.disableBackgroundExecution(); - if (success) { - _isRunning = false; - } - return success; - } catch (e) { - print('停止后台服务失败: $e'); - return false; - } - } - - /// 请求后台权限 - static Future requestPermissions() async { - try { - final hasPermissions = await FlutterBackground.hasPermissions; - if (hasPermissions) return true; - - // 在Android上会自动请求权限 - return await FlutterBackground.hasPermissions; - } catch (e) { - print('请求后台权限失败: $e'); - return false; - } - } -} \ No newline at end of file diff --git a/lib/utils/device_api_cache.dart b/lib/util/device_api_cache.dart similarity index 100% rename from lib/utils/device_api_cache.dart rename to lib/util/device_api_cache.dart diff --git a/lib/utils/nearby_devices_scanner.dart b/lib/util/nearby_devices_scanner.dart similarity index 100% rename from lib/utils/nearby_devices_scanner.dart rename to lib/util/nearby_devices_scanner.dart diff --git a/lib/util/settings_manager.dart b/lib/util/settings_manager.dart index 9070896..7b4681e 100644 --- a/lib/util/settings_manager.dart +++ b/lib/util/settings_manager.dart @@ -5,6 +5,8 @@ import 'package:shared_preferences/shared_preferences.dart'; const String settingKeyFirstOpenApp = "FirstOpenApp"; const String settingKeyHostConfigs = "HostConfigs"; const String settingKeyUseHostFile = "UseHostFileKey"; +const String settingKeyAutoStartEnabled = "AutoStartEnabled"; +const String settingKeyAutoStartHosts = "AutoStartHosts"; class SettingsManager { static final SettingsManager _instance = SettingsManager._internal(); diff --git a/lib/utils/datetime_extensions.dart b/lib/utils/datetime_extensions.dart deleted file mode 100644 index a1d34ae..0000000 --- a/lib/utils/datetime_extensions.dart +++ /dev/null @@ -1,20 +0,0 @@ -/// DateTime 扩展方法 -extension DateTimeExtensions on DateTime { - /// 格式化最后见到时间 - String formatLastSeen() { - final now = DateTime.now(); - final difference = now.difference(this); - - if (difference.inMinutes < 1) { - return '刚刚'; - } else if (difference.inHours < 1) { - return '${difference.inMinutes}分钟前'; - } else if (difference.inDays < 1) { - return '${difference.inHours}小时前'; - } else if (difference.inDays < 7) { - return '${difference.inDays}天前'; - } else { - return '$month/$day'; - } - } -} \ No newline at end of file diff --git a/lib/widget/app_bar/home_app_bar.dart b/lib/widget/app_bar/home_app_bar.dart deleted file mode 100644 index 7bbab9a..0000000 --- a/lib/widget/app_bar/home_app_bar.dart +++ /dev/null @@ -1,361 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'dart:typed_data'; - -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; -import 'package:hosts/l10n/app_localizations.dart'; -import 'package:hosts/enums.dart'; -import 'package:hosts/model/global_settings.dart'; -import 'package:hosts/model/host_file.dart'; -import 'package:hosts/model/simple_host_file.dart'; -import 'package:hosts/page/history_page.dart'; -import 'package:hosts/widget/dialog/copy_multiple_dialog.dart'; -import 'package:hosts/widget/text_field/search_text_field.dart'; - -class HomeAppBar extends StatelessWidget { - final bool isSave; - final VoidCallback undoHost; - final ValueChanged onOpenFile; - final String searchText; - final ValueChanged onSearchChanged; - final AdvancedSettingsEnum advancedSettingsEnum; - final ValueChanged onSwitchAdvancedSettings; - final EditMode editMode; - final ValueChanged onSwitchMode; - final List hosts; - final Map sortConfig; - final VoidCallback? onDeletePressed; - final bool isCheckedAll; - final ValueChanged onCheckedAllChanged; - final ValueChanged> onSortConfChanged; - final SimpleHostFileHistory? selectHistory; - final List history; - final ValueChanged onSwitchHosts; - final ValueChanged onHistoryChanged; - - const HomeAppBar({ - super.key, - required this.isSave, - required this.onOpenFile, - required this.undoHost, - required this.searchText, - required this.onSearchChanged, - required this.advancedSettingsEnum, - required this.onSwitchAdvancedSettings, - required this.editMode, - required this.onSwitchMode, - required this.hosts, - required this.sortConfig, - required this.onDeletePressed, - required this.isCheckedAll, - required this.onCheckedAllChanged, - required this.onSortConfChanged, - required this.selectHistory, - required this.history, - required this.onSwitchHosts, - required this.onHistoryChanged, - }); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Container( - height: 58, - padding: const EdgeInsets.symmetric(horizontal: 5), - child: Row( - children: [ - Expanded( - child: Row( - children: [ - if (GlobalSettings().isSimple) - IconButton( - onPressed: () async { - FilePickerResult? result = - await FilePicker.platform.pickFiles(); - if (result == null) { - return; - } - if (!isSave) { - ScaffoldMessenger.of(context) - .removeCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - AppLocalizations.of(context)!.error_not_save), - action: SnackBarAction( - label: AppLocalizations.of(context)!.abort, - onPressed: () => pickFile(context, result), - ), - )); - - return; - } - - pickFile(context, result); - }, - icon: const Icon(Icons.file_open_outlined), - tooltip: AppLocalizations.of(context)!.open_file, - ) - else - IconButton( - onPressed: () { - onSwitchAdvancedSettings( - advancedSettingsEnum == AdvancedSettingsEnum.Close - ? AdvancedSettingsEnum.Open - : AdvancedSettingsEnum.Close, - ); - }, - icon: const Icon(Icons.menu), - tooltip: - AppLocalizations.of(context)!.advanced_settings, - ), - _buildEditModeButton(context), - const SizedBox(width: 10), - if (editMode == EditMode.Table) - Flexible( - child: Container( - constraints: const BoxConstraints( - maxWidth: 430, - minWidth: 100, - ), - child: SearchTextField( - text: searchText, - onChanged: onSearchChanged, - ), - ), - ), - ], - ), - ), - const SizedBox(width: 32), - Row( - children: [ - batchGroupButton(context), - if (history.isNotEmpty) - IconButton( - onPressed: () async { - SimpleHostFileHistory? resultHistory = - await showModalBottomSheet( - context: context, - builder: (BuildContext context) => HistoryPage( - selectHistory: selectHistory, history: history), - ); - if (resultHistory == null) { - onHistoryChanged(null); - return; - } - - if (!isSave) { - ScaffoldMessenger.of(context).removeCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - AppLocalizations.of(context)!.error_not_save), - action: SnackBarAction( - label: AppLocalizations.of(context)!.abort, - onPressed: () => onHistoryChanged(resultHistory), - ), - )); - - return; - } - onHistoryChanged(resultHistory); - }, - icon: const Icon(Icons.history), - ), - if (!isSave) - IconButton( - onPressed: undoHost, - icon: const Icon(Icons.undo), - tooltip: AppLocalizations.of(context)!.reduction, - ), - buildMoreButton(context) - ], - ) - ], - ), - ), - if (editMode == EditMode.Table && - MediaQuery.of(context).size.width < 1000) - Table( - columnWidths: const { - 0: FixedColumnWidth(50), - }, - defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: [tableHeader(context)], - ) - ], - ); - } - - IconButton _buildEditModeButton(BuildContext context) { - return IconButton( - onPressed: () { - if (editMode == EditMode.Text) { - onSwitchMode(EditMode.Table); - } else { - onSwitchMode(EditMode.Text); - } - }, - tooltip: editMode == EditMode.Text - ? AppLocalizations.of(context)!.table - : AppLocalizations.of(context)!.text, - icon: Icon( - editMode == EditMode.Text - ? Icons.table_rows_outlined - : Icons.text_snippet_outlined, - ), - ); - } - - TableRow tableHeader(BuildContext context) { - return TableRow( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Checkbox( - value: hosts.isNotEmpty && isCheckedAll, - onChanged: onCheckedAllChanged, - ), - ), - tableHeaderItem("host", AppLocalizations.of(context)!.ip_address), - tableHeaderItem("isUse", AppLocalizations.of(context)!.status), - tableHeaderItem("hosts", AppLocalizations.of(context)!.domain), - tableHeaderItem("description", AppLocalizations.of(context)!.remark), - ], - ); - } - - GestureDetector tableHeaderItem(String columnName, String label) { - return GestureDetector( - onTap: () { - if (sortConfig[columnName] == null) { - sortConfig[columnName] = 1; - } else if (sortConfig[columnName] == 1) { - sortConfig[columnName] = 2; - } else if (sortConfig[columnName] == 2) { - sortConfig[columnName] = null; - } - onSortConfChanged(sortConfig); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Text(label, style: const TextStyle(fontWeight: FontWeight.bold)), - if (sortConfig[columnName] == null) - const SizedBox() - else - Icon( - sortConfig[columnName] == 2 - ? Icons.arrow_upward - : Icons.arrow_downward, - size: 16.0, - ), - ], - ), - ), - ); - } - - Widget batchGroupButton(BuildContext context) { - return Row( - children: [ - if (hosts.isNotEmpty && editMode == EditMode.Table) - Switch( - value: true, - onChanged: (value) => onSwitchHosts(true), - ), - if (hosts.isNotEmpty && editMode == EditMode.Table) - Switch( - value: false, - onChanged: (value) => onSwitchHosts(false), - ), - if (hosts.isNotEmpty && editMode == EditMode.Table) - IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => CopyMultipleDialog(hosts: hosts)); - }, - tooltip: AppLocalizations.of(context)!.copy_selected, - icon: const Icon(Icons.copy)), - if (hosts.isNotEmpty && editMode == EditMode.Table) - IconButton( - onPressed: onDeletePressed, - tooltip: AppLocalizations.of(context)!.delete_selected, - icon: const Icon(Icons.delete_outline)), - ], - ); - } - - void pickFile(BuildContext context, FilePickerResult result) { - if (result.files.first.size > 10 * 1024 * 1024) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)!.error_open_file_size))); - return; - } - - try { - String path = ""; - try { - path = result.files.single.path ?? ""; - } catch (e) { - path = ""; - } - final Uint8List? bytes = result.files.first.bytes; - if (path.isNotEmpty && bytes == null) { - onOpenFile(File(path).readAsStringSync()); - } - - if (path.isEmpty && bytes != null) { - onOpenFile(utf8.decode(bytes)); - } - } catch (e) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)!.error_open_file))); - } - } - - Widget buildMoreButton(BuildContext context) { - return PopupMenuButton(onSelected: (value) { - switch (value) { - case 1: - showAboutDialog( - context: context, - applicationVersion: '1.5.0', - applicationIcon: Image.asset( - "assets/icon/logo.png", - width: 50, - height: 50, - ), - children: [ - Text(AppLocalizations.of(context)!.about_description), - const SizedBox(height: 10), - const Text('Developed by Webb.'), - ], - ); - break; - default: - break; - } - }, itemBuilder: (BuildContext context) { - final List> list = [ - {"text": AppLocalizations.of(context)!.about, "value": 1}, - ]; - - return list.map((item) { - return PopupMenuItem( - value: int.parse(item["value"].toString()), - child: Row( - children: [ - if (item["icon"] != null) Icon(item["icon"]! as IconData), - SizedBox(width: item["icon"] != null ? 8 : 32), - Text(item["text"]!.toString()), - ], - ), - ); - }).toList(); - }); - } -} diff --git a/lib/widget/dialog/access_device_dialog.dart b/lib/widget/dialog/access_device_dialog.dart index 8ffd33c..d9f9abb 100644 --- a/lib/widget/dialog/access_device_dialog.dart +++ b/lib/widget/dialog/access_device_dialog.dart @@ -5,8 +5,8 @@ import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/simple_host_file.dart'; import 'package:hosts/util/file_manager.dart'; import 'package:hosts/util/settings_manager.dart'; -import 'package:hosts/utils/device_api_cache.dart'; -import 'package:hosts/utils/nearby_devices_scanner.dart'; +import 'package:hosts/util/device_api_cache.dart'; +import 'package:hosts/util/nearby_devices_scanner.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; diff --git a/lib/widget/dialog/api_documentation_dialog.dart b/lib/widget/dialog/api_documentation_dialog.dart index f5946d6..5cb3813 100644 --- a/lib/widget/dialog/api_documentation_dialog.dart +++ b/lib/widget/dialog/api_documentation_dialog.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:hosts/l10n/app_localizations.dart'; -import 'package:hosts/utils/device_api_cache.dart'; +import 'package:hosts/util/device_api_cache.dart'; import 'package:hosts/widget/dialog/qr_code_dialog.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/lib/widget/host_base_view.dart b/lib/widget/host_base_view.dart deleted file mode 100644 index d7d3815..0000000 --- a/lib/widget/host_base_view.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hosts/model/host_file.dart'; - -abstract class HostBaseView extends StatelessWidget { - final List hosts; - final List selectHosts; - final bool isCheckedAll; - final ValueChanged onCheckedAllChanged; - final Function(int, HostsModel) onEdit; - final Function(int, HostsModel) onLink; - final Function(int, HostsModel) onChecked; - final Function(List) onDelete; - final Function(List) onToggleUse; - final Function(String) onLaunchUrl; - - const HostBaseView({ - super.key, - required this.hosts, - required this.selectHosts, - required this.isCheckedAll, - required this.onCheckedAllChanged, - required this.onChecked, - required this.onEdit, - required this.onLink, - required this.onDelete, - required this.onToggleUse, - required this.onLaunchUrl, - }); -} diff --git a/lib/widget/host_list.dart b/lib/widget/host_list.dart deleted file mode 100644 index 1511ad2..0000000 --- a/lib/widget/host_list.dart +++ /dev/null @@ -1,241 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:hosts/l10n/app_localizations.dart'; -import 'package:hosts/model/host_file.dart'; -import 'package:hosts/widget/dialog/copy_dialog.dart'; -import 'package:hosts/widget/dialog/test_dialog.dart'; -import 'package:hosts/widget/host_base_view.dart'; - -class HostList extends HostBaseView { - const HostList({ - super.key, - required super.hosts, - required super.selectHosts, - required super.onEdit, - required super.onLink, - required super.onChecked, - required super.onDelete, - required super.onToggleUse, - required super.onLaunchUrl, - required super.isCheckedAll, - required super.onCheckedAllChanged, - }); - - @override - Widget build(BuildContext context) { - return ListView.builder( - itemCount: hosts.length, - itemBuilder: (context, index) { - final HostsModel it = hosts[index]; - return InkWell( - onTap: () => onEdit(index, it), - child: ListItem( - host: it, - onSwitchChanged: (value) { - it.isUse = value; - - final List updateUseHosts = [it]; - void updateHostStates(List hostNames, bool isUse) { - for (var tempHost - in hosts.where((item) => hostNames.contains(item.host))) { - tempHost.isUse = isUse; - updateUseHosts.add(tempHost); - } - } - - // 相同 - if (it.config["same"] != null) { - updateHostStates( - (it.config["same"] as List).cast(), value); - } - // 相反 - if (it.config["contrary"] != null) { - updateHostStates( - (it.config["contrary"] as List).cast(), - !value); - } - - onToggleUse(updateUseHosts); - }, - onCheckChanged: (value) => onChecked(index, it), - trailing: buildMoreButton(context, index, it), - isChecked: selectHosts.contains(it), - ), - ); - }, - ); - } - - Widget buildMoreButton(BuildContext context, int index, HostsModel host) { - return PopupMenuButton( - onSelected: (value) async { - switch (value) { - case 1: - onLink(index, host); - break; - case 2: - testDialog(context, host); - break; - case 3: - copyDialog(context, hosts, index); - break; - case 4: - onDelete([host]); - break; - } - }, - itemBuilder: (BuildContext context) { - List> list = [ - { - "icon": Icons.link, - "text": AppLocalizations.of(context)!.link, - "value": 1 - }, - { - "icon": Icons.sensors, - "text": AppLocalizations.of(context)!.test, - "value": 2 - }, - { - "icon": Icons.copy, - "text": AppLocalizations.of(context)!.copy, - "value": 3 - }, - { - "icon": Icons.delete_outline, - "text": AppLocalizations.of(context)!.delete, - "value": 4 - }, - ]; - - return list - .where((item) => !(item["value"] == 2 && kIsWeb)) - .map((item) { - return PopupMenuItem( - value: int.parse(item["value"].toString()), - child: Row( - children: [ - Icon(item["icon"]! as IconData), - const SizedBox(width: 8), - Text(item["text"]!.toString()), - ], - ), - ); - }).toList(); - }, - ); - } -} - -class ListItem extends StatelessWidget { - final bool isChecked; - final HostsModel host; - final ValueChanged onSwitchChanged; - final ValueChanged onCheckChanged; - final Widget trailing; - - const ListItem( - {super.key, - required this.host, - required this.onSwitchChanged, - required this.onCheckChanged, - required this.isChecked, - required this.trailing}); - - @override - Widget build(BuildContext context) { - bool isLink = false; - if (host.config.isNotEmpty) { - isLink = host.config["same"] != null && host.config["contrary"] != null; - } - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 9), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Checkbox( - value: isChecked, - onChanged: onCheckChanged, - ), - const SizedBox(width: 16), - Switch( - value: host.isUse, - onChanged: onSwitchChanged, - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (host.description.isNotEmpty) - Text( - host.description, - style: Theme.of(context).textTheme.labelSmall, - ), - const SizedBox(height: 4.0), - Text.rich(TextSpan( - children: [ - if (isLink) - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4), - child: Icon( - Icons.link, - color: Theme.of(context).colorScheme.primary, - size: 18, - ), - )), - TextSpan( - text: host.host, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold), - ) - ], - )), - const SizedBox(height: 4.0), - Text.rich(TextSpan( - children: _buildTextSpans(host.hosts, context), - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context) - .colorScheme - .primary - .withOpacity(0.6), - fontWeight: FontWeight.bold))), - ], - ), - ), - const SizedBox(width: 16), - trailing, - ], - ), - ); - } - - List _buildTextSpans(List hosts, BuildContext context) { - List textSpans = []; - - for (int i = 0; i < hosts.length; i++) { - textSpans.add(TextSpan( - text: hosts[i], - recognizer: TapGestureRecognizer() - ..onTap = () { - // onLaunchUrl(hosts[i]); - }, - )); - - if (i < hosts.length - 1) { - textSpans.add(TextSpan( - text: ' - ', - style: TextStyle( - color: Theme.of(context).colorScheme.inverseSurface, - fontWeight: FontWeight.w900))); - } - } - - return textSpans; - } -} diff --git a/lib/widget/host_table.dart b/lib/widget/host_table.dart deleted file mode 100644 index 4e2cea5..0000000 --- a/lib/widget/host_table.dart +++ /dev/null @@ -1,407 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:hosts/l10n/app_localizations.dart'; -import 'package:hosts/model/host_file.dart'; -import 'package:hosts/widget/dialog/copy_dialog.dart'; -import 'package:hosts/widget/dialog/test_dialog.dart'; -import 'package:hosts/widget/host_base_view.dart'; -import 'package:syncfusion_flutter_datagrid/datagrid.dart'; - -class HostTable extends HostBaseView { - const HostTable({ - super.key, - required super.hosts, - required super.selectHosts, - required super.isCheckedAll, - required super.onCheckedAllChanged, - required super.onEdit, - required super.onLink, - required super.onChecked, - required super.onDelete, - required super.onToggleUse, - required super.onLaunchUrl, - }); - - @override - Widget build(BuildContext context) { - return SfDataGrid( - allowSorting: true, - columnWidthMode: ColumnWidthMode.fill, - gridLinesVisibility: GridLinesVisibility.none, - headerGridLinesVisibility: GridLinesVisibility.none, - source: HostDataSource( - hosts: hosts, - selectHosts: selectHosts, - onEdit: onEdit, - onLink: onLink, - onChecked: onChecked, - onDelete: onDelete, - onToggleUse: onToggleUse, - onLaunchUrl: onLaunchUrl, - context: context, - ), - columns: [ - GridColumn( - columnName: 'checkbox', - allowSorting: false, - label: Checkbox( - value: hosts.isNotEmpty && isCheckedAll, - onChanged: onCheckedAllChanged, - ), - width: 50, - ), - GridColumn( - columnName: 'host', - allowSorting: true, - label: Container( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(AppLocalizations.of(context)!.ip_address, - style: const TextStyle(fontWeight: FontWeight.bold)), - ), - ), - ), - GridColumn( - columnName: 'use', - allowSorting: true, - label: Container( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(AppLocalizations.of(context)!.status, - style: const TextStyle(fontWeight: FontWeight.bold)), - ), - ), - ), - GridColumn( - columnName: 'hosts', - allowSorting: true, - label: Container( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(AppLocalizations.of(context)!.domain, - style: const TextStyle(fontWeight: FontWeight.bold)), - ), - ), - ), - GridColumn( - columnName: 'description', - allowSorting: true, - label: Container( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(AppLocalizations.of(context)!.remark, - style: const TextStyle(fontWeight: FontWeight.bold)), - ), - ), - ), - GridColumn( - columnName: 'actions', - allowSorting: false, - label: Container( - alignment: Alignment.center, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(AppLocalizations.of(context)!.action, - style: const TextStyle(fontWeight: FontWeight.bold)), - ), - ), - ), - ], - ); - } -} - -class HostDataSource extends DataGridSource { - HostDataSource({ - required this.hosts, - required this.selectHosts, - required this.onEdit, - required this.onLink, - required this.onChecked, - required this.onDelete, - required this.onToggleUse, - required this.onLaunchUrl, - required this.context, - }); - - final List hosts; - final List selectHosts; - final Function(int, HostsModel) onEdit; - final Function(int, HostsModel) onLink; - final Function(int, HostsModel) onChecked; - final Function(List) onDelete; - final Function(List) onToggleUse; - final Function(String) onLaunchUrl; - final BuildContext context; - - @override - List get rows => hosts.map((host) { - bool isLink = false; - if (host.config.isNotEmpty) { - isLink = - host.config["same"] != null && host.config["contrary"] != null; - } - - return DataGridRow(cells: [ - DataGridCell( - columnName: 'checkbox', - value: Checkbox( - value: selectHosts.contains(host), - onChanged: (bool? newValue) => - onChecked(hosts.indexOf(host), host), - ), - ), - DataGridCell( - columnName: 'host', - value: MyDataGridCell( - value: host.host, - child: GestureDetector( - onTap: () => onLaunchUrl(host.host), - child: Container( - alignment: Alignment.centerLeft, - child: Text.rich( - TextSpan( - children: [ - if (isLink) - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4), - child: Icon( - Icons.link, - color: Theme.of(context).colorScheme.primary, - size: 18, - ), - ), - ), - TextSpan( - text: host.host, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - )), - ), - DataGridCell( - columnName: 'use', - value: MyDataGridCell( - value: host.isUse, - child: Switch( - value: host.isUse, - onChanged: (value) { - host.isUse = value; - final List updateUseHosts = [host]; - void updateHostStates(List hostNames, bool isUse) { - for (var tempHost in hosts - .where((item) => hostNames.contains(item.host))) { - tempHost.isUse = isUse; - updateUseHosts.add(tempHost); - } - } - - if (host.config["same"] != null) { - updateHostStates( - (host.config["same"] as List).cast(), - value); - } - if (host.config["contrary"] != null) { - updateHostStates( - (host.config["contrary"] as List) - .cast(), - !value); - } - onToggleUse(updateUseHosts); - }, - ), - ), - ), - DataGridCell( - columnName: 'hosts', - value: MyDataGridCell( - value: host.hosts, - child: Container( - alignment: Alignment.centerLeft, - child: Text.rich( - TextSpan( - children: _buildTextSpans(host.hosts, context), - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ), - DataGridCell( - columnName: 'description', - value: MyDataGridCell( - value: host.description, - child: Container( - alignment: Alignment.centerLeft, - child: SelectableText(host.description)), - ), - ), - DataGridCell( - columnName: 'actions', - value: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - IconButton( - onPressed: () => onEdit(hosts.indexOf(host), host), - icon: const Icon(Icons.edit), - ), - const SizedBox(width: 8), - IconButton( - onPressed: () => onDelete([host]), - icon: const Icon(Icons.delete_outline), - ), - const SizedBox(width: 8), - buildMoreButton(context, hosts.indexOf(host), host), - ], - ), - ), - ]); - }).toList(); - - @override - Future performSorting(List rows) async { - // sortedColumns 是父类提供的属性,里面包含排序列及排序方向等信息。 - if (sortedColumns.isNotEmpty) { - final sortColumn = sortedColumns.first; - // SortColumnDetails 通常包含 columnName 和 sortDirection - final String columnName = sortColumn.name; - final bool isAscending = - sortColumn.sortDirection == DataGridSortDirection.ascending; - if (!["checkbox", "actions"].contains(columnName)) { - rows.sort((a, b) { - final valueA = a - .getCells() - .firstWhere((cell) => cell.columnName == columnName) - .value - .toString(); - final valueB = b - .getCells() - .firstWhere((cell) => cell.columnName == columnName) - .value - .toString(); - print(valueA); - print(valueB); - - return isAscending - ? valueA.compareTo(valueB) - : valueB.compareTo(valueA); - }); - } - } - // notifyListeners(); - } - - List _buildTextSpans(List hosts, BuildContext context) { - List textSpans = []; - for (int i = 0; i < hosts.length; i++) { - textSpans.add(TextSpan( - text: hosts[i], - recognizer: TapGestureRecognizer() - ..onTap = () { - onLaunchUrl(hosts[i]); - }, - )); - if (i < hosts.length - 1) { - textSpans.add(TextSpan( - text: ' - ', - style: TextStyle( - color: Theme.of(context).colorScheme.inverseSurface, - fontWeight: FontWeight.w900, - ), - )); - } - } - return textSpans; - } - - Widget buildMoreButton(BuildContext context, int index, HostsModel host) { - return PopupMenuButton( - onSelected: (value) async { - switch (value) { - case 1: - onLink(index, host); - break; - case 2: - testDialog(context, host); - break; - case 3: - copyDialog(context, hosts, index); - break; - } - }, - itemBuilder: (BuildContext context) { - List> list = [ - { - "icon": Icons.link, - "text": AppLocalizations.of(context)!.link, - "value": 1 - }, - { - "icon": Icons.sensors, - "text": AppLocalizations.of(context)!.test, - "value": 2 - }, - { - "icon": Icons.copy, - "text": AppLocalizations.of(context)!.copy, - "value": 3 - }, - ]; - return list - .where((item) => !(item["value"] == 2 && kIsWeb)) - .map((item) { - return PopupMenuItem( - value: int.parse(item["value"].toString()), - child: Row( - children: [ - Icon(item["icon"]! as IconData), - const SizedBox(width: 8), - Text(item["text"]!.toString()), - ], - ), - ); - }).toList(); - }, - ); - } - - @override - DataGridRowAdapter? buildRow(DataGridRow row) { - return DataGridRowAdapter( - cells: - row.getCells().map((dataCell) => dataCell.value as Widget).toList(), - ); - } -} - -class MyDataGridCell extends StatelessWidget { - final Widget child; - final dynamic value; - - const MyDataGridCell({super.key, required this.child, required this.value}); - - @override - Widget build(BuildContext context) { - return child; - } - - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return value.toString(); - } -} diff --git a/lib/widget/text_field/search_dialog.dart b/lib/widget/text_field/search_dialog.dart deleted file mode 100644 index eda5145..0000000 --- a/lib/widget/text_field/search_dialog.dart +++ /dev/null @@ -1,167 +0,0 @@ -import 'package:flutter/material.dart'; - -class SearchDialog extends StatefulWidget { - const SearchDialog({super.key}); - - @override - _SearchDialogState createState() => _SearchDialogState(); -} - -class _SearchDialogState extends State { - final TextEditingController _searchTextEditingController = - TextEditingController(); - final TextEditingController _replaceTextEditingController = - TextEditingController(); - bool regexChecked = false; - bool caseSensitiveChecked = false; - bool wholeWordChecked = false; - - @override - void initState() { - _searchTextEditingController.addListener(() {}); - - _replaceTextEditingController.addListener(() {}); - super.initState(); - } - - @override - void dispose() { - super.dispose(); - _searchTextEditingController.dispose(); - _replaceTextEditingController.dispose(); - } - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerLow, - ), - child: Row( - children: [ - Expanded( - child: Column( - children: [ - TextFormField( - controller: _searchTextEditingController, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.search), - suffix: Text( - "1/1", - style: Theme.of(context).textTheme.titleMedium, - ), - label: const Text("查询"), - ), - ), - TextFormField( - controller: _replaceTextEditingController, - decoration: const InputDecoration( - prefixIcon: Icon(Icons.find_replace), - label: Text("替换"), - ), - ), - const SizedBox(height: 8), - Row( - children: [ - InkWell( - borderRadius: BorderRadius.circular(8.0), - onTap: () => setState(() { - regexChecked = !regexChecked; - }), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IgnorePointer( - child: Checkbox( - value: regexChecked, - onChanged: - (bool? value) {}, // Keep Checkbox disabled - ), - ), - const Text("正则表达式"), - const SizedBox(width: 8), - ], - ), - ), - InkWell( - borderRadius: BorderRadius.circular(8.0), - onTap: () => setState(() { - caseSensitiveChecked = !caseSensitiveChecked; - }), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IgnorePointer( - child: Checkbox( - value: caseSensitiveChecked, - onChanged: - (bool? value) {}, // Keep Checkbox disabled - ), - ), - const Text("区分大小写"), - const SizedBox(width: 8), - ], - ), - ), - InkWell( - borderRadius: BorderRadius.circular(8.0), - onTap: () => setState(() { - wholeWordChecked = !wholeWordChecked; - }), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IgnorePointer( - child: Checkbox( - value: wholeWordChecked, - onChanged: - (bool? value) {}, // Keep Checkbox disabled - ), - ), - const Text("只匹配整个单词"), - const SizedBox(width: 8), - ], - ), - ), - ], - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Row( - children: [ - IconButton( - onPressed: () {}, - icon: const Icon(Icons.arrow_upward), - ), - IconButton( - onPressed: () {}, - icon: const Icon(Icons.arrow_downward)), - IconButton( - onPressed: () {}, icon: const Icon(Icons.find_replace)), - IconButton( - onPressed: () {}, icon: const Icon(Icons.settings)), - ], - ), - const Divider(), - Row( - children: [ - TextButton(onPressed: () {}, child: const Text("替换")), - TextButton(onPressed: () {}, child: const Text("全部替换")), - ], - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index 3257cae..28a75aa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -237,6 +237,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" http_methods: dependency: transitive description: @@ -357,6 +365,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" path: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5e11913..fc6a7be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,9 @@ dependencies: # URL启动器 url_launcher: ^6.3.1 + # 应用信息 + package_info_plus: ^8.1.0 + # 后台运行 flutter_background: ^1.3.0+1 From 566eb9cdf64c7079a92b122b42c91d5d1f82e2b1 Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Sat, 26 Jul 2025 15:22:37 +0800 Subject: [PATCH 39/54] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E4=BD=BF=E7=94=A8=E7=9A=84=E6=9C=8D=E5=8A=A1=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E8=84=9A=E6=9C=AC=E5=92=8C=E7=9B=B8=E5=85=B3=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除以下不再需要的文件: - hosts-manager.service - hosts-manager-user.service - install-service.sh - install-user-service.sh 这些文件原本用于安装系统服务和用户服务,但由于项目架构变更,不再需要这些安装脚本和服务定义文件。删除这些文件可以保持代码库的整洁,避免维护过时的代码。 --- hosts-manager-user.service | 13 ------------- hosts-manager.service | 16 ---------------- install-service.sh | 31 ------------------------------ install-user-service.sh | 39 -------------------------------------- 4 files changed, 99 deletions(-) delete mode 100644 hosts-manager-user.service delete mode 100644 hosts-manager.service delete mode 100755 install-service.sh delete mode 100755 install-user-service.sh diff --git a/hosts-manager-user.service b/hosts-manager-user.service deleted file mode 100644 index a3d9ef0..0000000 --- a/hosts-manager-user.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=Hosts Manager Application (User Service) -After=graphical-session.target - -[Service] -Type=simple -ExecStart=/usr/bin/pkexec /opt/hosts-manager/hosts-manager -Restart=on-failure -RestartSec=5 -Environment=DISPLAY=%i - -[Install] -WantedBy=default.target \ No newline at end of file diff --git a/hosts-manager.service b/hosts-manager.service deleted file mode 100644 index 881df2e..0000000 --- a/hosts-manager.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=Hosts Manager Application -After=graphical-session.target - -[Service] -Type=simple -User=root -Group=root -Environment=DISPLAY=:0 -Environment=XAUTHORITY=/home/%i/.Xauthority -ExecStart=/opt/hosts-manager/hosts-manager -Restart=on-failure -RestartSec=5 - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/install-service.sh b/install-service.sh deleted file mode 100755 index 55ebde0..0000000 --- a/install-service.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -# 检查是否以root权限运行 -if [[ $EUID -ne 0 ]]; then - echo "此脚本需要以root权限运行" - echo "请使用: sudo ./install-service.sh" - exit 1 -fi - -# 创建应用目录 -mkdir -p /opt/hosts-manager - -# 复制应用文件 -cp -r build/linux/x64/release/bundle/* /opt/hosts-manager/ - -# 设置权限 -chmod +x /opt/hosts-manager/hosts-manager - -# 复制服务文件 -cp hosts-manager.service /etc/systemd/system/ - -# 重新加载systemd -systemctl daemon-reload - -# 启用服务 -systemctl enable hosts-manager.service - -echo "服务安装完成!" -echo "启动服务: sudo systemctl start hosts-manager" -echo "查看状态: sudo systemctl status hosts-manager" -echo "停止服务: sudo systemctl stop hosts-manager" \ No newline at end of file diff --git a/install-user-service.sh b/install-user-service.sh deleted file mode 100755 index 7fa5d51..0000000 --- a/install-user-service.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# 检查是否以root权限运行安装应用文件 -if [[ $EUID -ne 0 ]]; then - echo "需要root权限安装应用文件" - echo "请使用: sudo ./install-user-service.sh" - exit 1 -fi - -# 创建应用目录 -mkdir -p /opt/hosts-manager - -# 复制应用文件 -cp -r build/linux/x64/release/bundle/* /opt/hosts-manager/ - -# 设置权限 -chmod +x /opt/hosts-manager/hosts-manager - -# 切换到普通用户安装用户服务 -REAL_USER=${SUDO_USER:-$USER} -REAL_HOME=$(eval echo ~$REAL_USER) - -# 创建用户systemd目录 -sudo -u $REAL_USER mkdir -p $REAL_HOME/.config/systemd/user - -# 复制用户服务文件 -sudo -u $REAL_USER cp hosts-manager-user.service $REAL_HOME/.config/systemd/user/hosts-manager.service - -# 重新加载用户systemd -sudo -u $REAL_USER systemctl --user daemon-reload - -# 启用用户服务 -sudo -u $REAL_USER systemctl --user enable hosts-manager.service - -echo "用户服务安装完成!" -echo "启动服务: systemctl --user start hosts-manager" -echo "查看状态: systemctl --user status hosts-manager" -echo "停止服务: systemctl --user stop hosts-manager" -echo "开机自启: loginctl enable-linger $REAL_USER" \ No newline at end of file From 88ee3d68615fbf9eb06730d663d344745b86a015 Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Sat, 26 Jul 2025 15:23:51 +0800 Subject: [PATCH 40/54] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E7=BC=96=E8=BE=91=E6=8E=A7=E5=88=B6=E5=99=A8=E5=9C=A8?= =?UTF-8?q?=E6=92=A4=E5=9B=9E=E6=93=8D=E4=BD=9C=E6=97=B6=E7=9A=84=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复撤回操作时文本编辑控制器未正确处理的问题。在host_cubit.dart中添加撤回可能导致问题的注释说明,在host_text.dart中重构控制器处理逻辑: 1. 销毁旧控制器并创建新控制器以避免状态不一致 2. 重新添加内容变更监听器 3. 使用LayoutBuilder和ConstrainedBox改进文本框布局约束 4. 设置expands: true使文本框填满可用空间 这些修改确保在撤回操作时文本内容能正确显示并保持功能正常。 --- lib/home/cubit/host_cubit.dart | 2 +- lib/home/view/host_text.dart | 62 +++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/lib/home/cubit/host_cubit.dart b/lib/home/cubit/host_cubit.dart index 0190680..ce895cd 100644 --- a/lib/home/cubit/host_cubit.dart +++ b/lib/home/cubit/host_cubit.dart @@ -437,7 +437,7 @@ class HostCubit extends Cubit { HostFileContent( state.data.copyWith( fileContent: text, - // TODO 这里判断不够正确 + // TODO 这里判断不够正确,撤回会导致问题。 isSave: text == state.data.defaultFileContent, ), ), diff --git a/lib/home/view/host_text.dart b/lib/home/view/host_text.dart index 867bec4..99fa761 100644 --- a/lib/home/view/host_text.dart +++ b/lib/home/view/host_text.dart @@ -80,9 +80,19 @@ class _HostTextState extends State { return BlocBuilder( builder: (BuildContext context, state) { if (state is HostUndo || state is HostInitial || state is HostHistory) { - // TODO 会出现撤回情况。 - textEditingController.clear(); + // 销毁旧的控制器 + textEditingController.dispose(); + + // 创建新的控制器 + textEditingController = HostTextEditingController(); textEditingController.text = state.data.fileContent; + + // 重新添加监听器 + final hostCubit = context.read(); + textEditingController.addListener(() { + hostCubit.updateFileContent(textEditingController.text); + }); + _scrollController.jumpTo(0); } final hostCubit = context.read(); @@ -140,24 +150,38 @@ class _HostTextState extends State { hostCubit.onTextSave(); } }, - child: ScrollConfiguration( - behavior: ScrollConfiguration.of(context) - .copyWith(scrollbars: false), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: IntrinsicWidth( - child: TextField( - controller: textEditingController, - scrollController: _textScrollController, - maxLines: null, - scrollPhysics: const ClampingScrollPhysics(), - decoration: InputDecoration( - border: InputBorder.none, - hintText: AppLocalizations.of(context)! - .create_host_template), + child: LayoutBuilder( + builder: (context, constraints) { + return ScrollConfiguration( + behavior: ScrollConfiguration.of(context) + .copyWith(scrollbars: false), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: constraints.maxWidth, + minHeight: constraints.maxHeight, + ), + child: IntrinsicWidth( + child: SizedBox( + height: constraints.maxHeight, + child: TextField( + controller: textEditingController, + scrollController: _textScrollController, + maxLines: null, + expands: true, + scrollPhysics: const ClampingScrollPhysics(), + decoration: InputDecoration( + border: InputBorder.none, + hintText: AppLocalizations.of(context)! + .create_host_template), + ), + ), + ), + ), ), - ), - ), + ); + }, ), ), ), From f37891c91b35dd90a4beed2552b37dca24aac0be Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Sat, 26 Jul 2025 15:24:17 +0800 Subject: [PATCH 41/54] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20hosts.deskto?= =?UTF-8?q?p=20=E6=96=87=E4=BB=B6=E5=85=83=E6=95=B0=E6=8D=AE=E5=92=8C?= =?UTF-8?q?=E6=A1=8C=E9=9D=A2=E6=93=8D=E4=BD=9C=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新了 Linux 桌面入口文件 hosts.desktop,添加了更详细的元数据信息: - 新增 GenericName 和 Keywords 字段 - 更新 Comment 描述 - 调整 Categories 分类 - 添加 MimeType 和版本信息 - 新增两个桌面操作项:简单模式和直接打开 hosts 文件 - 更新图标引用名称 --- linux/hosts.desktop | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/linux/hosts.desktop b/linux/hosts.desktop index b332743..b623358 100644 --- a/linux/hosts.desktop +++ b/linux/hosts.desktop @@ -2,9 +2,25 @@ Version=1.0 Type=Application Name=Hosts Editor -Comment=Linux MacOS Windows Hosts File Editor +GenericName=Hosts File Editor +Comment=A cross-platform hosts file editor built with Flutter +Keywords=hosts;editor;network;system;dns; Exec=hosts %U -Icon=icon.png +Icon=hosts-editor Terminal=false -Categories=Utility;Application; -StartupNotify=true \ No newline at end of file +Categories=System;Network;Utility; +StartupNotify=true +MimeType=text/plain; +X-Desktop-File-Install-Version=0.26 + +[Desktop Action SimpleMode] +Name=Simple Mode +Exec=hosts --mode simple +Icon=hosts-editor + +[Desktop Action OpenHostsFile] +Name=Open Hosts File +Exec=hosts --open /etc/hosts +Icon=hosts-editor + +Actions=SimpleMode;OpenHostsFile; \ No newline at end of file From 16fe830ba5e91d7a48ddc971a8c62c490727728d Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Sat, 26 Jul 2025 15:35:35 +0800 Subject: [PATCH 42/54] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=A4=9A?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E6=89=93=E5=8C=85=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为项目添加各平台打包配置文件,包括: - Linux平台:AppImage、DEB和RPM包的配置文件 - macOS平台:PKG安装包配置文件 - Windows平台:EXE安装程序和MSIX包的配置文件 配置文件包含应用元信息、图标路径、分类、关键词等必要信息,为后续自动化打包流程提供基础配置。各平台配置文件均使用YAML格式,保持一致的显示名称和基本描述信息。 --- README.md | 7 +- linux/packaging/appimage/make_config.yaml | 41 ++++ linux/packaging/deb/make_config.yaml | 32 +++ linux/packaging/rpm/make_config.yaml | 26 +++ macos/packaging/pkg/make_config.yaml | 7 + pubspec.lock | 264 ++++++++++++++++++++++ pubspec.yaml | 1 + windows/packaging/exe/make_config.yaml | 14 ++ windows/packaging/msix/make_config.yaml | 18 ++ 9 files changed, 404 insertions(+), 6 deletions(-) create mode 100644 linux/packaging/appimage/make_config.yaml create mode 100644 linux/packaging/deb/make_config.yaml create mode 100644 linux/packaging/rpm/make_config.yaml create mode 100644 macos/packaging/pkg/make_config.yaml create mode 100644 windows/packaging/exe/make_config.yaml create mode 100644 windows/packaging/msix/make_config.yaml diff --git a/README.md b/README.md index 38b6a06..1f787ae 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,4 @@ hosts /etc/hosts ### 测试是否配置成功 -![6.png](image/6.png) - -1. 构建应用:flutter build linux -2. 安装用户服务:sudo ./install-user-service.sh -3. 启动服务:systemctl --user start hosts-manager -4. 开机自启:loginctl enable-linger $USER \ No newline at end of file +![6.png](image/6.png) \ No newline at end of file diff --git a/linux/packaging/appimage/make_config.yaml b/linux/packaging/appimage/make_config.yaml new file mode 100644 index 0000000..a26d2c9 --- /dev/null +++ b/linux/packaging/appimage/make_config.yaml @@ -0,0 +1,41 @@ +display_name: Hosts Editor + +icon: linux/icon.png + +keywords: + - Hosts + - Editor + - Network + - System + - DNS + +generic_name: Hosts File Editor + +actions: + - name: Open Hosts File + label: open-hosts + arguments: + - --open + - /etc/hosts + - name: Simple Mode + label: simple-mode + arguments: + - --mode + - simple + +categories: + - System + - Network + - Utility + +startup_notify: true + +# 您可以指定要与您的应用捆绑的共享库 +# +# fastforge 会自动检测您的应用所依赖的共享库,但您也可以在此处手动指定它们。 +# +# 以下示例展示了如何将 libcurl 库与您的应用捆绑在一起 +# +# include: +# - libcurl.so.4 +include: [] \ No newline at end of file diff --git a/linux/packaging/deb/make_config.yaml b/linux/packaging/deb/make_config.yaml new file mode 100644 index 0000000..994c258 --- /dev/null +++ b/linux/packaging/deb/make_config.yaml @@ -0,0 +1,32 @@ +display_name: Hosts Editor +package_name: hosts-editor +maintainer: + name: Webb + email: 822028533@qq.com +priority: optional +section: utils +installed_size: 50000 +essential: false +icon: linux/icon.png + +postinstall_scripts: + - echo "Hosts Editor installed successfully" + - echo "You can launch it from the applications menu or run 'hosts-editor' in terminal" +postuninstall_scripts: + - echo "Hosts Editor has been uninstalled" + +keywords: + - Hosts + - Editor + - Network + - System + - DNS + +generic_name: Hosts File Editor + +categories: + - System + - Network + - Utility + +startup_notify: true \ No newline at end of file diff --git a/linux/packaging/rpm/make_config.yaml b/linux/packaging/rpm/make_config.yaml new file mode 100644 index 0000000..38792b0 --- /dev/null +++ b/linux/packaging/rpm/make_config.yaml @@ -0,0 +1,26 @@ +icon: linux/icon.png +summary: A cross-platform hosts file editor built with Flutter +group: Applications/System +vendor: Webb +packager: Webb +packagerEmail: 822028533@qq.com +license: MIT +url: https://github.com/webb-l/hosts + +display_name: Hosts Editor + +keywords: + - Hosts + - Editor + - Network + - System + - DNS + +generic_name: Hosts File Editor + +categories: + - System + - Network + - Utility + +startup_notify: true \ No newline at end of file diff --git a/macos/packaging/pkg/make_config.yaml b/macos/packaging/pkg/make_config.yaml new file mode 100644 index 0000000..52c560e --- /dev/null +++ b/macos/packaging/pkg/make_config.yaml @@ -0,0 +1,7 @@ +install-path: /Applications +# sign-identity: "-" +identifier: top.webb_l.hosts +version: 1.5.0 +title: Hosts Editor +resources-dir: macos/packaging/pkg/Resources +scripts-dir: macos/packaging/pkg/Scripts \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 28a75aa..5bc4649 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _discoveryapis_commons: + dependency: transitive + description: + name: _discoveryapis_commons + sha256: "113c4100b90a5b70a983541782431b82168b3cae166ab130649c36eb3559d498" + url: "https://pub.dev" + source: hosted + version: "1.0.7" ansicolor: dependency: transitive description: @@ -57,6 +65,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + charset: + dependency: transitive + description: + name: charset + sha256: "27802032a581e01ac565904ece8c8962564b1070690794f0072f6865958ce8b9" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -73,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + console: + dependency: transitive + description: + name: console + sha256: e04e7824384c5b39389acdd6dc7d33f3efe6b232f6f16d7626f194f6a01ad69a + url: "https://pub.dev" + source: hosted + version: "4.1.0" cross_file: dependency: transitive description: @@ -121,6 +161,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.1" + dio: + dependency: transitive + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" equatable: dependency: "direct main" description: @@ -137,6 +193,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + fastforge: + dependency: "direct dev" + description: + name: fastforge + sha256: eb8c80b3c071cafa8df48f8fed53381df2bd26587e33ed73cfaf73c5517a3e32 + url: "https://pub.dev" + source: hosted + version: "0.6.2" ffi: dependency: transitive description: @@ -166,6 +230,30 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_app_builder: + dependency: transitive + description: + name: flutter_app_builder + sha256: "75b9be9ba8b67d445a117368f7e603228e8a434136c71a3051307fc727be74f4" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_app_packager: + dependency: transitive + description: + name: flutter_app_packager + sha256: "2061d4ea68caa15b92b42f100e9bfffeef966eec089d04d934bfb0572258f4e2" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_app_publisher: + dependency: transitive + description: + name: flutter_app_publisher + sha256: "16d7e93a6722fafb6ee10b28023d8df92d5c8c8abe66b7bbe741e7fe64952b03" + url: "https://pub.dev" + source: hosted + version: "0.6.2" flutter_background: dependency: "direct main" description: @@ -229,6 +317,46 @@ packages: description: flutter source: sdk version: "0.0.0" + get_it: + dependency: transitive + description: + name: get_it + sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 + url: "https://pub.dev" + source: hosted + version: "7.7.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" + url: "https://pub.dev" + source: hosted + version: "0.3.3+1" + googleapis: + dependency: transitive + description: + name: googleapis + sha256: "864f222aed3f2ff00b816c675edf00a39e2aaf373d728d8abec30b37bee1a81c" + url: "https://pub.dev" + source: hosted + version: "13.2.0" + googleapis_auth: + dependency: transitive + description: + name: googleapis_auth + sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938 + url: "https://pub.dev" + source: hosted + version: "1.6.0" html: dependency: transitive description: @@ -277,6 +405,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -309,6 +453,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + liquid_engine: + dependency: transitive + description: + name: liquid_engine + sha256: "41ae12d5a72451c3efb8d4e7b901cdf0537917597bc7e7376e9b0a237f92df29" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -333,6 +493,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + msix: + dependency: transitive + description: + name: msix + sha256: edde648a8133bf301883c869d19d127049683037c65ff64173ba526ac7a8af2f + url: "https://pub.dev" + source: hosted + version: "3.16.9" + mustache_template: + dependency: transitive + description: + name: mustache_template + sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c + url: "https://pub.dev" + source: hosted + version: "2.0.0" nested: dependency: transitive description: @@ -365,6 +541,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" package_info_plus: dependency: "direct main" description: @@ -381,6 +565,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + parse_app_package: + dependency: transitive + description: + name: parse_app_package + sha256: "8ff00a930da628d5270f58f2befbbf10185ed7959b093195985bf4132e84bc8a" + url: "https://pub.dev" + source: hosted + version: "0.6.0" path: dependency: "direct main" description: @@ -453,6 +645,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.6" + plist_parser: + dependency: transitive + description: + name: plist_parser + sha256: e2a6f9abfa0c45c0253656b7360abb0dfb84af9937bace74605b93d2aad2bf0c + url: "https://pub.dev" + source: hosted + version: "0.0.11" plugin_platform_interface: dependency: transitive description: @@ -469,6 +669,30 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + qiniu_sdk_base: + dependency: transitive + description: + name: qiniu_sdk_base + sha256: "2506c6372512f81cfbddf162ea6da1ad7b1c6521dee1d10e9da6847c92e13349" + url: "https://pub.dev" + source: hosted + version: "0.5.2" qr: dependency: transitive description: @@ -485,6 +709,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" shared_preferences: dependency: "direct main" description: @@ -557,6 +789,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.4" + shell_executor: + dependency: transitive + description: + name: shell_executor + sha256: "6f41c85bffc7e839401bebc8c75cb189896ff038072060d3b0ec538949b615fc" + url: "https://pub.dev" + source: hosted + version: "0.3.0" + shell_uikit: + dependency: transitive + description: + name: shell_uikit + sha256: "8d5bb6bb0b220d47ef11adbe001cb31944b8eb8fe6183687258d36c9320d6c75" + url: "https://pub.dev" + source: hosted + version: "0.3.0" sky_engine: dependency: transitive description: flutter @@ -634,6 +882,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + unified_distributor: + dependency: transitive + description: + name: unified_distributor + sha256: "65732c3c9650de405646df52317d0ed24adc556091333ebfd8684ad72060a46c" + url: "https://pub.dev" + source: hosted + version: "0.2.2" universal_io: dependency: transitive description: @@ -706,6 +962,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.4" + uuid: + dependency: transitive + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fc6a7be..c66cd9d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,6 +64,7 @@ dev_dependencies: flutter_lints: ^6.0.0 flutter_native_splash: ^2.4.1 + fastforge: ^0.6.2 flutter_native_splash: color: "#000000" diff --git a/windows/packaging/exe/make_config.yaml b/windows/packaging/exe/make_config.yaml new file mode 100644 index 0000000..037cc9c --- /dev/null +++ b/windows/packaging/exe/make_config.yaml @@ -0,0 +1,14 @@ +# AppId 的值唯一标识此应用。 +# 不要在其他应用的安装程序中使用相同的 AppId 值。 +app_id: 2E5F8A1B-C4D6-4E3F-8A9B-1C2D3E4F5G6H +publisher: Webb +publisher_url: https://github.com/webb-l/hosts +display_name: Hosts Editor +create_desktop_icon: true +# See: https://jrsoftware.org/ishelp/index.php?topic=setup_defaultdirname +# install_dir_name: "C:\\Program Files\\HostsEditor" +# 这里的路径是相对于项目根目录的路径; 图标格式必须是ico格式,不能是png或其它 +setup_icon_file: windows\runner\resources\app_icon.ico +locales: + - en + - zh \ No newline at end of file diff --git a/windows/packaging/msix/make_config.yaml b/windows/packaging/msix/make_config.yaml new file mode 100644 index 0000000..e5dfeb9 --- /dev/null +++ b/windows/packaging/msix/make_config.yaml @@ -0,0 +1,18 @@ +display_name: Hosts Editor +publisher_display_name: Webb +publisher: CN=Webb +identity_name: top.webb_l.hosts +msix_version: 1.5.0.0 +description: A cross-platform hosts file editor built with Flutter +# logo_path: assets\icon\logo.png +start_menu_icon_path: assets\icon\logo.png +tile_icon_path: assets\icon\logo.png +vs_generated_images_folder_path: assets\icon\ +icons_background_color: transparent +architecture: x64 +certificate_path: +certificate_password: +capabilities: + - internetClient + - privateNetworkClientServer + - documentsLibrary \ No newline at end of file From 0aa0e1dcbfb2d97376cec6d5493773ca5c55fedf Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sat, 26 Jul 2025 21:39:23 +0800 Subject: [PATCH 43/54] =?UTF-8?q?feat:=20=E9=85=8D=E7=BD=AEAndroid?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E5=92=8C=E6=9B=B4=E6=96=B0=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加Android发布签名配置 - 更新Android应用启动图标 - 更新iOS应用图标 - 更新macOS应用图标 - 版本号更新至1.8.0 --- android/app/build.gradle | 19 ++- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 2043 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 1360 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 2732 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 4139 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 5740 bytes ios/Runner.xcodeproj/project.pbxproj | 4 +- .../AppIcon.appiconset/Contents.json | 123 +--------------- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 43553 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 562 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 1045 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 1523 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 838 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 2369 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 1045 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 2081 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 3282 bytes .../AppIcon.appiconset/Icon-App-50x50@1x.png | Bin 0 -> 1244 bytes .../AppIcon.appiconset/Icon-App-50x50@2x.png | Bin 0 -> 2720 bytes .../AppIcon.appiconset/Icon-App-57x57@1x.png | Bin 0 -> 1481 bytes .../AppIcon.appiconset/Icon-App-57x57@2x.png | Bin 0 -> 3080 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 3282 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 5216 bytes .../AppIcon.appiconset/Icon-App-72x72@1x.png | Bin 0 -> 1836 bytes .../AppIcon.appiconset/Icon-App-72x72@2x.png | Bin 0 -> 3979 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 1960 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 4346 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 4767 bytes macos/Flutter/GeneratedPluginRegistrant.swift | 2 + .../AppIcon.appiconset/Contents.json | 132 +++++++++--------- .../AppIcon.appiconset/app_icon_1024.png | Bin 23998 -> 42874 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 4444 -> 3498 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 673 -> 461 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 10118 -> 7726 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 1207 -> 849 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 24003 -> 17894 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 2224 -> 1668 bytes pubspec.yaml | 2 +- 39 files changed, 88 insertions(+), 194 deletions(-) create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png diff --git a/android/app/build.gradle b/android/app/build.gradle index d3f2411..b1fe309 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -5,6 +5,12 @@ plugins { id "dev.flutter.flutter-gradle-plugin" } +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + android { namespace = "top.webb_l.hosts" compileSdk = flutter.compileSdkVersion @@ -21,6 +27,15 @@ android { jvmTarget = JavaVersion.VERSION_1_8 } + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "top.webb_l.hosts" @@ -34,9 +49,7 @@ android { buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.debug + signingConfig = signingConfigs.release } } } diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..c4423857094d68d2e295b6061168464349304e8f 100644 GIT binary patch delta 2040 zcmV$Lx5C6huXe={j&@;j3OyPK>K z&*V2I-)mRXT}We6#iW4APfR*J8(0yP!K9AKH-1&kU0BZK1`Upw>*fod1c97OOdXc|^_h+tC56%C;>ZpAN5%0Uo&-DEeKjBYgJTKL_2rxq?ni(VY~P5-)qt8`0?YE zpPx_B(b2+oA3l80(w_jCAShYZDj^|3N)Dh);l#v5DLFd(6Ceo$#mZWJ{PE_Lw^!V{( z-qw{)+F$)8CnwYH-Mc9_H!Z(~KMUI|Uc8v<>+89n@fO+$ zLK~2z11kN=KF0j{^La5%rEDUCwjri-%2gpqq|?-?Q>m`5&S!D}C4y8;-c1CRmX^}& z*?+UO=%USn5L;~DzTIbq0610|>^?|NP7W7c(B^|sl0AL;w9g8u!s??%L`2Z3Q>Uc& zc6D`8V`C#dc<_L~8;0bPB}-`O(xrTgH$!V26i8SRR9ILjoUvZNeog!K?W3}?GRqkd zvtD_5Igf!1XpMsc3C|%G9v;qDUhDvJvVXI)sjaQewhgYWSh0exUAy)rZc-u$F-LE2 zuTm5mD0qIfyu3VN`mlfhejzyqdU|?j)22;4)j*D>{Kimd%9JUp&HWBff;Mm7Y}@wH zqepc1>{;b!BVnzmsGx1zwy6^YKjbEY%tI_bKAti%GHlycRaHr&Zco4$IVQ+WFn?PR ze2eGoV+MSRQ?$0W^5Le9poWG9I(+!BV@2$MmkJ9Dv%PL62(=v%;7^u zOz++%(cRtM`Yg!U0;3DE+|JknGk<>kc$zU|hHX0x6>ou$piB@H5n=M=$zCgB1(DMQ zj2Sg5DvBmdm|)uuv5YS|C=-N8RRrB3fb4n32a&bIby&AIv+$%zliW*?tUu$r$a(-+ z!JIjB_>ANfv9Yo4BnU61Vi|x+0EjHz8-n2cl?Za8unu5)msy6Vx^UqFty{N_+S}W0+ht{C@luBohba-HY8_~& z;xuJY{UGoWn8i?E#%2R-JQo4w*rui?zTqA9bJU5<*RbfYckkY>f=g`{1n45j$p^vD zACOLtl?16i$5Su(qC) z7|ohBOUo*6e}BJT3sP+-gA*rhg@S?tdiClRpOIu`6FcC33|SC_6A6Sw4F?}&1}cb{ zr7m8)NGDF5;47x8Kz|&EybV7AW3yZ@h{16U(?mC+BmBBghK`O7Wkp0dckUeByLV68 z?(W^Y!s{sfW6d4ODy#wjS}iW3ZjA`LwzifpOPk)c8wBC!&q9K5BP39$uCCVlJbYjX z5^nq!ZRX6G+<&2n_Wb#C>g?>Kw{PEC-P#F)aL~s}ts1!$aDTcW1EPobIH`;p0)fX*|5zD|QDF(Bws zt2vAuz;Zy&pK<2@v`R5>xDa+DVVy+0uVmx^J_p0C)P5#qOv;(8VzQP7Eed;0VkGyv5lG5W4~DPBWYj=|$B_xQfBXyE WmosO!{!6?70000P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..295851a50b0a4a4e2c80119dcd8eb0ea6b8411b1 100644 GIT binary patch delta 1352 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 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..3369c4e2ab2bd8cd5d6fb420ba81deeb51bf3a35 100644 GIT binary patch literal 4139 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 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d6372eebdb28e45604e46eeda8dd24651419bc0..a8ff1eb5065fd6bf09a3f82c98b670020dce5310 100644 GIT binary patch literal 5740 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 PMFv!rG!<*)t-}5XwEVDf literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 84e7b66..bfc2bd9 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -427,7 +427,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -484,7 +484,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..d0d98aa 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index dc9ada4725e9b0ddb1deab583e5b5102493aa332..62f508e17012837ab14d850154e6491532032750 100644 GIT binary patch literal 43553 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 O2&3(0`uVzcA^!^|;6GXb literal 10932 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 797d452e458972bab9d994556c8305db4c827017..b5d85b8ea04bffd447ea7de9c57b6d73c8e676ac 100644 GIT binary patch delta 1035 zcmV+m1oZos1C{fekVx6cMzNU1JF}C|1;Q*4BHsq@zvtF{kLS71 z%$+fBoYix0XYTKwbI&>V+==-3_z3w9iTD=kj|Afg9v>ec9)BJJUfN(V#Ky*khK9<5 z0UH||o0*x}+1dF@1OSK~_V7J>> zS65S0Q+2_xHCR+C`}_Nqm6cAX6V4Zd;o+cDnQU)wkBp4;_xB6H@C?qC4^vW7^78U> zb8~ZYa!yW8dVhO+wadW*DEv|cA1y5{$H&Kd?5U}#zP>&lYyZHAVMG%juCK3y&Aw-6 zXN7Aaa3&`w`SAVyU5^SmI5@bzzBZf9YA_C%#_;p=Gu@MLEvf@~dV2B#hTPrV3BX}peq`)#31FUjDfM9Bk9+>KQJf7FrSAVTeOiWC4boA}*t*RF3f~ntR zELQnBGBUEgy}hli4M`Ce76ve)H8C;KH`rfSQ?Su!TwY!-E-vOXKvq>%(ceme^{xsb zI4UYiMHPyat4m8us@v z*>HY-o<;^%n4X@N0@EBVFE8KN*wE^jOs0#A3l6qeEa~a#Y)H#Klg-UdI+OyxDWcW6 zzrW|PgBt<|%pIW3z?+*JDKN?n{}zTc)8QmEH-EG~AeB|hC}C1y+9hc_CMJ}G%~2;6 z6cnhG(SHREoRE+}!H0*3-1D`ywLLvOmzS4Zp{}k@3QQNJ-+DUOoZIb2dxf+KQCz*a zxL99be|C0;VQXt^O-+s8U>S><&iMHF=;$b$LA^r;BfJsqsBxs`Yha)>XThRKTWrR0) zkwR0*Q^MtP3BkL&yF)`me8_%#b#+x%R)*T%(9nQLMgc&}JU2JDu&{7`elD;W`-L8Q ze>NN*9u~r={82r0@!+%_~t9Wtvj%vK=@Vde~2f>Bj|BFbRD?AKawFxOmCxka% zS9s^(#ev>GU@DRj(d5U&oC$tlRwQ7sclmk$f&6dz3y@CeNc4u#6aWAK07*qoL+>eB z?J{?+FLkYu+4_Uk`r_>LHF~flZm0oBf#vr8%vJ>#p~!KNvqGG3)|f1T_)ydeh8$vDceZ>oNbH^|*hJ*t?Yc*1`WB&W>VYVEzu) zq#7;;VjO)t*nbgf(!`OXJBr45rP>>AQr$6c7slJWvbpNW@KTwna6d?PP>hvXCcp=4 zF;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f?a^uO#lD@ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 6ed2d933e1120817fe9182483a228007b18ab6ae..a956c53deb7c4e5009734048559a9e6326f99d19 100644 GIT binary patch delta 1516 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 Q00000NkvXXt^-0~f+uQ)z5oCK diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index fe730945a01f64a61e2235dbe3f45b08f7729182..ff52e06b8b6e16014927f517c1a7ec4134abc532 100644 GIT binary patch delta 1582 zcmV+}2GRM>1GEf~8Gix*003^;-G2Z81^Y=vK~#7F?V4XmR8JJg*R*oA+EQ642rRLn zOcQ(&VpGgkD@7qt$lSn!#g}yJ#g{T66x0or1)qXg78Qd8i9}g?C|n<;6$>Ah67o;# zgDsVsnt%GT_s9OGnb|wD_vco>=sbCk^XD^X&N*}D-i_JX+JDmheiGEh+}zye=H|8&?AWoxXf&QUaYC=x3mXe?hlhtBJb3W*>C>g9rES9F3s}u& zb4^W+$z+O(iVD>%B<{+}ip^&0=;-)4neOY?ue|}WSS${QBR)Q!+l-G3)=QTzx!vwa z67uEIqeniUZ+~E5V9%aC?0@;Vu+C7qO-8uW@m4J9qB<^yyPr1iN?dMn}KjFWKI1w~KL+vycZ!O>}hhv17;52swK6 z==JN@;bbb4VINacQcj*ciT0;Xox&d&q`tnsv9VDS7k}Oeix>f%B zi%TZ7leR?)VZ?{<+S;0iiO~!3N*Op@Tvo)m+&N@$HS`jS7?-=PLT6ZCU*Fi+h>7`O z{GVi9c7I!ixEleXaKp+BT#>Raiz|$Abb&wMXdsoLtc&uXtgP(ZxpTd&7)f9uvQOdU2ZX4ST>EF9Gf zXL46pS5Z+Bw>$O|M}641y1>?x+X`dLBz!hGkbeunruX#pT)1#S1|TT1xa8w{K!m;2 zaa&qi!e!M-;V&cr1 zGk>$Qv&!R#4$QqQkwzi6jima?G zO0UHkw25-u;EJ1oMCcuvc0`s*z4-mtLf?K!AE6_Q0&LX z#s-xmBO{?MTDUT@N-enW-@iw!2zzB@WPgyj${?AUnPeLTS*Y$)Q&X%`i!%sQm?D(N zD^^6{tBg#_moHyfr4%l80&`zju!3`od{kb{Bqk=3O42dZyZ=KXGcm;%>WvU3R5Mfv z*p^+pc9F`%hYyG9e&E0XRw;!`O)P2My?ghPk1!+*MpZe8eH;he7n{eAA7_9A!(P%6zETrCSC}K97S)~?S%nCcr;i-p<tjbE?wI=kwLo)wQ;^N**^q3@dCw z1-K$Y>mtjl>t}96WfC zYKG(5>-Ac#R-4UMUS6(-i$q9i-oAayUaCe%N7Zrtem~pm&6_vmAgCWpg9gdadf9mE?0!vXn|e98ulTK9O-mA;T3kf z9TgM%I&pgic>u8me}xZd`Gl5 ziGV;=={^- z?sLQGb)?A{hr$_!z8HbH7kH=vM0x-*R~t>;jsO4v^GQTOR7l6|(&r9>FcgO2dg?%> z;=sK?5%;?Pn^T7LL?Y$@5u?06NuIR*0?Yf$Hf5Afk+lM<^ch*jvO$sU*m9J?JI7eI zGFV6+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9R%3*Q+)t%S!MU_`id^@& zY{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&p6kME1_Z%?`+u)^el0!1<0sd p?Eyu!OMLDifi)An*I;?S-wj=m4RYIt!kPd8002ovPDHLkV1gp?^f3Sc diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 321773cd857a8a0f0c9c7d3dc3f5ff4fb298dc10..3cb987f877cb5c9d7413f692b5eb422c09518d08 100644 GIT binary patch literal 2369 zcmV-H3BLA;P)i?%tAu#$83yH%pWR~egrid$QZJR0t>>Z2@=clp#igcs3?4xF|Z#wdsB{=Q3A3XRkSDd7L}lJKuxIS^KQB_P5tsd#!zr zhW!5hJLrFpkbm0%zd(Qm7XmD}5MaTD01GYzSa2c0f(uL*oKEM{r%&74+uy!@`}5~d zeQnZ;kdTm=n3$B5l%%92{Lay!X*5c#*49?L-G24z)t;Un#`DG$QBhGQlgVs0XJ%%O z8#hk}vQ}pK@K|PwAo9pW8a&vP<11J_y7ifLnj2Sb+!^69~yS3gZLS$qlbar-jj#3Zi z!eIf`Axa}!bwor2_GA=osi~>S$;tQ`9UXo0C3kyq3OhgPz3ZXeRHnzUL ze$ARSJcS-O9FDJFzfPV!Sy3uv0R^(CI$XYdd1+}W42_G63l0t*A;lJ@)x&P4{DZb!)_oge(xxrp;pb0(}eOS*t=t5N31wP! z92_L13}1jkRA{2A28m6pLapGiK(98#7ruZ0PDp9ebpeM3dbJr2JTx>!NcGAK1UM|v z^JEx3+gh)zf4PC%t0cH^_F!BPl z5qGKgg@l9zqtS>q1PvHU`+gIjFN_~Qe*OCO zd-v|m$jDG!8#o%8>rbCPi8OFnKnz2#ypIaJ%93=fvFvNFYVaOKLC?c28xuZuc5I&R#!VYON>U%tF_=~DVBI{Sj|7<5DWeb%g5 z8#iv0kEyM#&CkzQ+-u<%xw*Ly9z2*gZyxa!fApqrXKd$wqRzq8;8hnX{HqGgZ|xPJY* zS0&)l;c%GEX3+qhBjU}QH+S#eWhhKaN=i&jlwbE0AS+j{l=pAQohl!9=guAdSr9eC zw{G3qv}qGV;ei7OjvP56zwRj(R;*Ydm-hGf_x1Iu(f{n(GeWAfFR;%DMSX%@r@6%TdP_f+&r43h=XnkybUj7A}>ha;eBRy<0WK7BfP zS97`*85v1P{e=b5jbH)uM*u3GdPB5e!Ge4D?s?T87Z*oJnJkDrU*ysZ*y=AQL^+p+kr0b6TB!0W2VT$AX1x*RGY9mn(M1v9YmeoY$>er}*}1 z*REZ}r(+U0EPz)c3&OV<9}N=;MIz|QlPCA@-_OX%P#l9C+1lEQW&};Nv~hwj1cJq4 z*|KGe4&TJffpjH$F(rjDLnpz4M|Ax7@pI?SiKc|C0V(q6%aLM*GJIiz8zSlO{ivy_>EOYG$BrFiyz(HxVSz3R7%adM)Q1>$3S8O0f4^2D zP<@KH=CDBfJfpd$LrhL9+~N82=O<2_P+71$ozAMNs-B*n!-o$m4iK_H#2!6QhA+?x z6-hsP_H0W_3+9HRkYEr!SZ8NvNl8h4eSK+Zsc4vRsxdJ!B2jyL`|jPlMN*V;B9RCv z`v`@ookiC(Gc#AMT9u!lpOce=ozinWJ2W)Z-QA5OQd3joa5$s`D`G}M78K93UiB99#q+NV#SR{T2ipE0xw4br2|=<_Wb|z`~RBV`-<24{r>;E==`tb{CU#(0alua*7{P! z_>|iF0Z@&o;`@Zw`ed2Hv*!Fwin#$(m7w4Ij@kM+yZ0`*_J0?7s{u=e0YGxN=lnXn z_j;$xb)?A|hr(Z#!1DV3H@o+7qQ_N_ycmMI0acg)Gg|cf|J(EaqTu_A!rvTerUFQQ z05n|zFjFP9FmM0>0mMl}K~z}7?bK^if#bc3@hBPX@I$58-z}(ZZE!t-aOGpjNkbau@>yEzH(5Yj4kZ ziMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_stABAHe$v|ToifVv60B@podBTcIqVcr1w`hG7HeY|fvLid#^Ok4NAXIXSt1 Zxpx7IC@PekH?;r&002ovPDHLkV1gK6YhwTa diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 797d452e458972bab9d994556c8305db4c827017..b5d85b8ea04bffd447ea7de9c57b6d73c8e676ac 100644 GIT binary patch delta 1035 zcmV+m1oZos1C{fekVx6cMzNU1JF}C|1;Q*4BHsq@zvtF{kLS71 z%$+fBoYix0XYTKwbI&>V+==-3_z3w9iTD=kj|Afg9v>ec9)BJJUfN(V#Ky*khK9<5 z0UH||o0*x}+1dF@1OSK~_V7J>> zS65S0Q+2_xHCR+C`}_Nqm6cAX6V4Zd;o+cDnQU)wkBp4;_xB6H@C?qC4^vW7^78U> zb8~ZYa!yW8dVhO+wadW*DEv|cA1y5{$H&Kd?5U}#zP>&lYyZHAVMG%juCK3y&Aw-6 zXN7Aaa3&`w`SAVyU5^SmI5@bzzBZf9YA_C%#_;p=Gu@MLEvf@~dV2B#hTPrV3BX}peq`)#31FUjDfM9Bk9+>KQJf7FrSAVTeOiWC4boA}*t*RF3f~ntR zELQnBGBUEgy}hli4M`Ce76ve)H8C;KH`rfSQ?Su!TwY!-E-vOXKvq>%(ceme^{xsb zI4UYiMHPyat4m8us@v z*>HY-o<;^%n4X@N0@EBVFE8KN*wE^jOs0#A3l6qeEa~a#Y)H#Klg-UdI+OyxDWcW6 zzrW|PgBt<|%pIW3z?+*JDKN?n{}zTc)8QmEH-EG~AeB|hC}C1y+9hc_CMJ}G%~2;6 z6cnhG(SHREoRE+}!H0*3-1D`ywLLvOmzS4Zp{}k@3QQNJ-+DUOoZIb2dxf+KQCz*a zxL99be|C0;VQXt^O-+s8U>S><&iMHF=;$b$LA^r;BfJsqsBxs`Yha)>XThRKTWrR0) zkwR0*Q^MtP3BkL&yF)`me8_%#b#+x%R)*T%(9nQLMgc&}JU2JDu&{7`elD;W`-L8Q ze>NN*9u~r={82r0@!+%_~t9Wtvj%vK=@Vde~2f>Bj|BFbRD?AKawFxOmCxka% zS9s^(#ev>GU@DRj(d5U&oC$tlRwQ7sclmk$f&6dz3y@CeNc4u#6aWAK07*qoL+>eB z?J{?+FLkYu+4_Uk`r_>LHF~flZm0oBf#vr8%vJ>#p~!KNvqGG3)|f1T_)ydeh8$vDceZ>oNbH^|*hJ*t?Yc*1`WB&W>VYVEzu) zq#7;;VjO)t*nbgf(!`OXJBr45rP>>AQr$6c7slJWvbpNW@KTwna6d?PP>hvXCcp=4 zF;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f?a^uO#lD@ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 502f463a9bc882b461c96aadf492d1729e49e725..8b4535feae11f1df2d32f739e5abd9c20617ea6e 100644 GIT binary patch delta 2079 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 KMNUMnLSTaKNi{A2 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 0ec303439225b78712f49115768196d8d76f6790..25417e91aa834622d6b5730e70601fda37bb94f7 100644 GIT binary patch literal 3282 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 G-2VXeL^EUn delta 850 zcmV-Y1Fihh8Qunv8Gi!+000iU#^3+|0OwFlR7L;)|5U~J09TtSw)Xt~|5(QO`~Ck( z!T0|D|3<*~RmJ%E{r+;#`2ba!klFf7!uJMSo%Q?vP{jByxcAZE>;OrUCbaZYjJo^$ z{nGILmD~Da$@upC{`C6(Ey4dPw)Pyc^>5DkHoEo!QcuK-Jwl-l}t(fQKv z{dds$V#@dygS`PvhX6is7Z+@*x-d;$ zb=6f@U3Jw}_s+W3%*+b9H_vS)-R#9?zrXogeLVI2We2RFTTAL}&3C8PS~<5D&v@UI z+`s*$wqQ=yd$laNUY-|ovcS9~n_90tFUdl#qq0tEUXle|k{Op|DHpSrbxEeZ5~$>o%>OSe z^=41qvh3LlC2xXzu+-2eQoqs1^L>7ylB$bCP);(%(xYZL1 cY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f-K+41^@s6 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..44bd318ed7584921afe4cceb4fc2b571df4eb278 GIT binary patch literal 1244 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 0ec303439225b78712f49115768196d8d76f6790..25417e91aa834622d6b5730e70601fda37bb94f7 100644 GIT binary patch literal 3282 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 G-2VXeL^EUn delta 850 zcmV-Y1Fihh8Qunv8Gi!+000iU#^3+|0OwFlR7L;)|5U~J09TtSw)Xt~|5(QO`~Ck( z!T0|D|3<*~RmJ%E{r+;#`2ba!klFf7!uJMSo%Q?vP{jByxcAZE>;OrUCbaZYjJo^$ z{nGILmD~Da$@upC{`C6(Ey4dPw)Pyc^>5DkHoEo!QcuK-Jwl-l}t(fQKv z{dds$V#@dygS`PvhX6is7Z+@*x-d;$ zb=6f@U3Jw}_s+W3%*+b9H_vS)-R#9?zrXogeLVI2We2RFTTAL}&3C8PS~<5D&v@UI z+`s*$wqQ=yd$laNUY-|ovcS9~n_90tFUdl#qq0tEUXle|k{Op|DHpSrbxEeZ5~$>o%>OSe z^=41qvh3LlC2xXzu+-2eQoqs1^L>7ylB$bCP);(%(xYZL1 cY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f-K+41^@s6 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index e9f5fea27c705180eb716271f41b582e76dcbd90..aceff94ce12fcfe428759535ec3a1df3638386e0 100644 GIT binary patch literal 5216 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 O0000^*bF%b(waIBTE1W_@R^z4luBobg#- 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 delta 749 zcmVg;Ps8|O$@u8^{Z_{KM!@$5TAfS6_e#O{MZfpz`2O`0$7~@NRr(1{THzH08y3x{{PYM{eL;T_A9^tcF_4Sxb`8l z_9V3RD6;a(-0A^Pjsi!1?)d#Ap4Tk3^CP0(07;VpJ7@tgQ}z4)*zx@&yZwC9`DV-b z0ZobH_5IB4{KxD3;p_6%|f=bdFhu+F!zMZ2UFj;GUKX7tI;hv3{q~!*pMj75WP_c}> z6)IWvg5_yyg<9Op()eD1hWC19M@?_9_MHec{Z8n3FMs~w_u?Av_yNBmRxVYrpi(M% zFMP21g+hmocQp3ay*Su=qM6He)*HaaTg$E^sym`(t%s3A)x!M+vfjXUBEpK6X9%iU zU!u9jj3(-$dM~sJ%Liy#?|+!6IY#MTau#O6vVj`yh_7%Ni!?!VS+MPTO(_fG+1<#p zqu;A#i+_(N%CmVnYvb>#nA{>Q%3E`Ds7<~jZMywn@h2t>G-LrYy7?Dj{aZqhQd6tzX%(Trn+ z)HNF}%-F{rr=m*0{=a;s#YDL00000NkvXXu0mjf&!U*x diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 8953cba09064923c5daf2d37e7c3c836ccdd794b..b0ac857dba163b177836e5d6a09aab717d7aedd4 100644 GIT binary patch literal 4346 zcmaJ_X*g8<|CXg5vd&m4`;09kQX$4z2QzlE3_`XrN@Zt=WS3>c7+cn45M#@}Mz&+JQ4hz#YCK?(V7BmWJ3fzhR zo&V4R*Yv!4KN=eLPBc>8Ea1Y1ZK%G*#Chl|`=4x_Tw-*Z3Cy4G6y+}@VHo~}AYsI0 ziA3{~q0w#v>{*YsTjaP@buD+n&ue{x4mE1*lo+8=qV^+q)s8!0&eU%E{lJi1o8a6X znm3e2%GuM_`6)r^jcnZfa9|E)*n6|F;{0?ksi%8`ZK{*IBSQXW?45t~AuP17Q7=10 z#)SU&<5Lj+{M$C=N2qM|*oy({Xn!*md|#PdhoWL@-SC$&;;xg#|L1Oe7Km_kO;xZCk{|M08C|OmJ~? zbMx?=AI?1C(_9rKMtIAcSy*_^w|wvI6_=NnS5#EIbqj7vG6V}E!Dogrw8HX<(;aqy{w5fy-TyUzRq*QnjV*xmBlL{P?2_N zA_{fW(#)(jn7TWdF5|JQ_OYoP6B*AEeW=z>n#nPAv=}f88-o=m6 z%gf7ygM$cV&Wy$jHe4 zzE9(33KH^F?G;vAVVbtz28rx`b@jH$@c=txVVv-gzNWX3m%f1kB51z^D5$QI8EmsD6*2BVe8DLF|N))X2(Sf9#K^-)(>*U+FP`RG7FHLrR!QcLaC zMn(&DE-^3&8)hl_uF>&>Vfslm*-KKN+`pzi#mP=e^Wo3N4-tgE!hwvvzEfFjx*$U&hDRH!U@_98;yyaybEIN{+j7+<8gRj38gkn5p2< zMSrF2g4D&B7Oxd1w8*ot5AO03@r<g&cuP-Hbbjs3$$Vl(0%`k$gKZRdsMJ zaMai10s|R8h_lDq`yT$WGR5tDV#-SlM%uLorA@oA8^Fdn?pL!1E_N}DN$KhHHEnYQ zE`MUd@~A}L2;ciP9Iqh8juzE+TpD43;ivW3LFLSzhzw5Ykzf&YBCmY%T6l5PvC9ek}pf!|lKyYKU*1$NM##OvR>W9G$XG96W_U44 z*HWC;RA2?WZstH{HxQ)ClwA9y^bDlp#jfXLwhcnd8I%{l(*4(fm-yeeNYh`5l`5|J zWjvo_k+N>&4!xp^F*d9~?4cO`JiOGQTqQ+UBtpSpy*&?m#p9{^`>@-j<&k_%0Qxl0 z9d4%Lf5J&J?mHlK4&0W5l%GS{-3{997Z(iKAI-S!YM6a@^lv$P(c0?j(HZ5(`r6vS z;2=y=5;c>I7_lUOT!ZFpk>}>-oSdBQnVJ?97Usu<2-shH`0;3G`D0g?UGR<;64RT= zKU3$ya}c6s2O(*BTR=#xHVH2Q{@At!Q;!eU^Fx<Q_{FUrT%Pg$ymnL|n4C^PYn*(Trh>YAE-{CJyWzKmdh=5u2? zpxBm$ldbk#0^w)&;IE(;XVOZhxJfYWc$W+bQIG~K?x9S%c!|`9BPK4F@Eq%m7qDcg zXB^%zcyCC-tMZixLkI)BT+4f9q&z-8{sB&XZ((t9XTs8+MLM3ll>weEZR?oQ?FhZ7 zd)=@o#z@QVd{jrUr@OlZY(Tf#S8GjVvp11BKUaQ%ZciV(6 zFD==H989Rkn(*gC1^bT;b0XWaWXU&|KPAjG`NDd+n!HBRld|Fd(z(7`W*VjgFGRmr zyTrG*w`23~yi8=G_4M>?YHD+(@dj~m)j9WrT$#M}R{M0;Sk5xQ#IN-~o@Y6mIWRs4 z9qhx+>EN~lgEGc$8GfTo&Sn4R=IKg|d#KqV)F&TOA2@NH31L`1|tDn&Il zHF--hvRCH1A=yY#5ejgFAy#TlxTJ`*M#%(kjZBcfx*}Nr& zq6+FK{NYJ-A;rYR005s8_-$-#7Ij3w2%c?!WV5oe0*nb{5_$E{{$(98g0!vVuFZT1 zWsX4LZdBjf-F?vdGCaJ5|GS*2xB|UyUV760_BN*0ZS)52KNc=0sS*S@%YMCNQ~3q} zQCFCua~N+YO!@P@rZ<7tf6(qn2*uXMCM)xqo~|x?x2cTDLsfUyXqh(eBG^8Yxj^XY zGmd5^hlLx=qbLbv%dmQ=EADD%3_Q~>d({r`uc1RP1|;gpYO-TF@tQBp&`!ZhO%?E2zd<_ygiPoA2?+_~(tpHgiVgVb*zHKn7pmtkMF>rLpI_Sz1mB1a^mp zqhu}UQOYGXoq8hlpj-wcBO?n3DbDrBmEa{A_%W5*AG1P3bTjS4E{cKf33)=YA6TP3 z8(bY6tQic}fi$#(o8a*mdbb(Fsld@RhKkS8(lQlpK(K!0zj6h2tfZT}O`*Jd9m*L=0%Bj~bU@>xcclU*jG={veH>^t8+2IHckO52) z_@xjLZa8UkLhop7%v2**nH?PH06n)9Z~GL7~2LJN!PC4bT*0KP4sJP zY*dtyVIvsB0TFKt79oYHF_=S79<>9x+hvg8UiYG0*vITn{mn~ovjk+t5~jO7V8>Bc zci`{Y$)%la^W27)`Y`jkv_0;`=&yJ^XBU^)9~+B&H$m6&3*P_rO9-?}p%T;$4Zqa7 ze@%J)S`K7DcM}wQHvubYMIn%ixh_|!UcP|KA`s*QcmLhqetHA>l?F%-{EX}FY^B;7 z&l{qE{70dNHa9gbEiY3x=is4#gIRfJzJHJP@jgA;3E#LH+wD~h>bJf#Wenx|&!Ci) zj$0}7HjQ3yqwZ_Wt*@^G#(4Y%z9oertC4wLpi0Y$45dlqY1(le6BdNEvOg-`dkk6dj$MNO0zH{~0V6OOUbxY6o+1 zab3Nt2!!{rFdC^myJgATgh!7a(R*KFsSyBlz_6loJ<~Haa~$Jv$rx^K-_FAubELbZyDr5*Q5D(@b1lUA^l- zSLfvBCec4PHkL#rUg0h6xbwXJZ%m7I)QQQ!p--WNp-vLts22u zV%;1Z9O`q|p_YCC&IXXi7wBSweIF81%F4)yZ;%>_5L#$SNl7UIW(f%6ce2CFKCW7^ ze}ScY{h6sov6}!7PqZ?9e#sjmn_rGCm=ddm`LYTAtuSP3| zztNsfkD~ipsy<}H&;_hidMXstDmy6@`uMW+}Ex(`E6Lg zd(3r}CdPzgu0y7@s^t9q&Os9S8wljA3iO9WB0U_{q3`3ty9;Sgtc?>0?^YjvCO+F= z9nFFJKXi4a@F~lVURF_20jPzccu^fgM~?1_l+&l)QdDegXlTgG%PTMc%xL-L2X~lo zWKY*;5mC`+EoI$a6&=UXv0SHh`R7L;)|5U~JDYo_jSDRDC`1<|-SjPDL z{{Q{{{{H{}09Kk-#rR9Y_viNgVafPO!S|ls`uzR=MZfp^{QU=8od8La1X`Tr_Wmff z_5e$ivgQ1@=KMy$_g9a+`TPAle6cOJ_Fc#L7qIpvwDkd1mw$fK`6IOUD75rX!}mad zv(fMTE4=(Nx%L54lL1hVF1YpqNrC`FddBPg#_Ietx%Lrkq5wX00X1L{S%Cm9QY*av z#_Rh5PKy9KYTWbvz3BX9%J>0Hi1+#X{rLA{m%$Kamk?i!03AC38#Yrxs)5QTeTVRiEmz~MKK1WAjCw(c-JK6eox;2O)?`?TG`AHia671e^vgmp!llK zp|=5sVHk#C7=~epA~VAf-~%aPC=%Qw01h8mnSZ|p?tc*y?iZ$PR7_ceEIapF3KB14K0Pog?7wtd+^xgUCa_GVmlD z<^nU>AU_Yn-JU?NFdu|wf^bTCNf-wSBYVZltDdvGBln-YrbeGvJ!|s{#`gjN@yAMb zM6cjFz0eFECCsc|_8hTa3*9-JQGehksdoVP^K4m?&wpA~+|b%{EP5D-+7h)6CE; z*{>BP=GRR3Ea}xyV*bqry{l^J=0#DaC4ej;1qs8_by?H6Tr@7hl>UKNZt)^B&yl;)&oqzLg zcfZxpE?3k%_iTOVywh%`XVN-E#COl+($9{v(pqSQcrz=)>G!!3HeNxbXGM@})1|9g zG4*@(OBaMvY0P0_TfMFPh fVHk#CZX3S=^^2mI>Ux-D00000NkvXXu0mjfX}N?2 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..d4010bacf604c6b06de15513c582cd5fa815480e 100644 GIT binary patch literal 4767 zcmb7Is?m8EL|B?YMklwLwWBvfLNPNgLlK}tfpL%O74fu*}kx@75YmQ)GJ z-}(Ll&x_~9iR;WG-^9ek^z`(?!ot#$ z_i(bHU9Aghd44`MD=RB0Nf!=>>*^LBZO{@(NJ#kl-p9nmjE`%2dS2r*yUU{YEi9O3 z%ZiGA4i68HjM!US$5#A!^R=L$pv4HOU+*ySLs3vr5E~o&w-Y}N=Ire3^z?LYZft4%juMcjEX+e8t1RipY4%ga>(ew!0GSbaVU!cxERS; z;!|BwC{$UyfT-geG0n}*&D(eHa&vP>Mn>4V-_fJ}e%mk9+0UYK3JaCuaI|T2TU)&k z=WA*BjRMEvxv^h`2*U(4nQ3SW)dwsTb#$l$`%Ws{Ba^-&1P-#dc(zEs3d~JSx!hiz zu?;;M5)X7-9n$U|4T@wC>(cTWO@CoV^r%<-^(2d#Znv1atDD<1y{gUO6k$sp zO0tJa8F{B0lliKIgoJc-beo$@8E02lUy9Or%jHPi4rU)wQEhE($b&hn$SU={yjoJd z8MtKy$OV@|!JNFjyaekC=%+k9Rn^tjRXk5%PEM6Z711FfEI%pq#)gNrlC)nk?(OZZ zt*zl5tDCmg){2OUC9#Bj%g@g@t2g{u`#!lV!tVQzqN1Xzs>$wd+^5Q>QZh2e1!up3 z;k(4#Jw3-#MQpc+Q$|Ne%gf7&SedO}9UUDlx1Rj|ohoP^l$EtIU8<{uQjwC9%Jvx; z7=XiwFdiNrc*TCS!d4_PL z(A5p5@LRrF59jZAA~)vWL_l+JaFCF8MhNEk8XH)-ySj>H8u$rzBJpY*LF>WJ&TeXI z%Eb-ESXx@bIgn*#S>ES*azz8H6FJ%d&g3H%ahtxdD#fF?`1let&PqynkOU1MkRjN< z_qSqdn$Jy4CWZ%=jFgv|aXPNd6vvS>XtbH)&&}HU`YF6`0mupg9m=XnWfQ!SFD@>^ zI)QdYofSYsZgX>50cKqE8%iB$68DpHrjG8cz}@PAtSyE*!#R`RMpg5hDEi6Bjm6>O ztYM+8$q3q%S&`t5&L-9qR@CU~%)r3+`@ceX5DXsdVY|aiwYrlUHI7dx^{8Zbjvgpl zUtgcyk&xjzoKt@yDvE4?K^O!r(?)pH%KnlMADW+^f8sbSJKpzwezD zKFHkW;J)&trZWHPYz-E^?G7(gbSVKIL)%A%ISV5Bz8%aI z0E78U1?ov_+^N3ske85XF3kKg_-T4`b0FT{-kvm#l$FQE&JI~xdi`fiMpadHEK};K zP>G3I#&~syc=xMPI&E8ayM@Qml2xvIlK@XaAP`m6p`7|exq8_TnG&Cmf+0TRn6vi# zJBgf^9B9HvU@zuqV@PoXFAB4>L&L{tD~cX-KwmvF711F!{2`6UzQq* zkQ>9nNVdg;tixn3e@yMw*)N<=s`7_6zE{qjkenv2VQG9tN#MiubM+Jh4VTMo)Puu& zdVAS9I9~af=Y8TzLTGYi-q7#`DEz^Yk&$_xA7E1@UF+=TDleS}L zCMQiZUMurS3mrm%?CIIOZABTp(#&FKD*)E$X+2xVC#wjG&lSq;UPfR&GO)5L(9#f7 zCfll#27cH3`g~v_*@srfM^fi;Fk&Czr+5xEiU}@{oW*}lF8mfB!|`b6uUk&Kh-bsi zk~`}s!_F|T`asM0$g2XiG*lfE#{()VDn&)blVfAw+i5iV;TIvGS^~5Jn1Gm*#+^)X z8@&YYr7IIgA~AyX^~}?XmURr~X$zO;!{jAkRU0;ZG7ZVXzO_XoLn9-p{?N)oF5wV0 z@@^8*xhb34%HAvb zRHh^%GMi`x#w%>0slDMe#IfJXu|%jEqewCczj@vL+kql>%x`79;dzjU&Y}Rqi{BLz z#RBp@*Xmh+Da!c#lgZF`z8Vr1FvjXvuEStYN3UK83a?}-TINo<0oRqqWOHk4i_f?% zon@$Be1V=G_bSrF#pO>&hpb-}MVqJl!K`3@^<7q5Nh#z?@6vjzfY3nb>)PA5Z`rhk z)icB&II*wUJ4mA|eVD!!78k1&^!@Dy5Vp6v%5rmYdqDkq7LOwh;3|IOwg}tB)dsi2 zd4T#kG%_QoHqDuuEiJo)NI5k!6N&|IPe#O60)vBz@4aQ(pGltH^+SA>J+YMl6#vlc z_u<>I1kE;GG4-ZOdDh9o#?`eHg{Xe^i`RX4v2!u2JoWtLgiUQ%boFA32UvRo)zQ{s z@zq&xCw;|y!K%gfdMy}IS6vO};`)aPt{D%GMj(%zM~^G*h3};BPP%eG4v_yH4~liJ zhNi2E%<64jo^D*<0g^}YjO(9=+PqhCG~xh1Tu-G3CylDUw)XIvArYMDbsOr|lNEi2 z5Agz>d2VDTG5}Olw8GGCW#;I}Z=kvQBmEQCm$Tl3d(T%*h=nJe60R4`*r3)_X#|eRGY@5pHe|(|Aqiody%Q@wkCfNDa}Am@4H}F3F9YnQ~!V zoBr&K<(4qu)C_?V7)G4-#nD|1V@jHH1_X^+K3CSo@Ziaho*OA}F)^|JE5s;f7KNZ& z$XTd&%(cM9#g)1J`!3<936`&4s@OTe>X9!4EHi{v10q8slvKRErCy!?+sO(pkFoeL zAv`82uZks5Nw!U&rm?#ClCO`9MF=s7d@U1_^KYa~Xu!l)I2 zp&nGayt)bs4u(j&gS4$>cmez_D!xX7g<@x0r0BkX{|-0AN5d>hIWNQ|Bmlp>J(9+0 zz-TcwHAPHJ%)r3lWPeLDo^{;vCu&kd^3luuO>Ru5Y(oy-IuCqH>E+8wGVi|resJB;8888w26kgrdY5g%qJg+v_=V&BR};BPU@*Ay8?RBzb5>T< zsRA`F?v5cqYtZ_Rruh+_JxxwT?^v=wuu_`Lz7amiWx&NI#eKXYb}jCCZf9u7r1LL( za&vPNDD$u0(9nph3W8 zIXOGyaPnE`Mzpuz&lvmid8I9ztX7}E9|`1u{t2Vhl}TusjRo(rD%!4Wrc1N~(+Mfh zw$mKjF4x2L>+G;!;t%Zu_-JToC@SKLeeZw;#snfPcgZr#Txy<}w4-n%ykIbFcLTXM zC;77bnNprCD1L5kZhroDpW830s>CF`ClL+~4${)n%*@ObN8M{_73PZB{45a_#l^9? zQhh8vjN^5F(!}YmCMGN?3|!#%RVbPoia_~BpCypse+rV#cSQPEueY~%#ruM3fAg0z ztWl3%xV+2-O6+S)e0tP;>>sOpr^q`PxjkI85P)KVyH~^d2DgTi#4hC4>Ob;`iGAp+ z?NP7h3Q)0MY7*h)eVsDXbpZoj5b=~sgCRCxE~okP@k=G8EJ+V%JjfmcH6t>9#{{|% z{$Qkn9A;x}y|+JI+J5s}bzn?V`2iVPgg!EkPcrf>N51ahz^+WE+56gUw3@N2v$M&x z%%A0Fb!eCv)dtHU>4hb+mWodfW+|d@K}wehsLd&ELG9q!7>k_Z%1bE+4%V# zJN+LtHa42+CRD9pxt0ry1#J%dIjKrZ7qA+i#9rWq9#$3Rxx8O!27 zbS-acqW=+W-XG=R?rvf^fdp6Gn=D&HhGUMtCdZqnomoO4)<_OT?H==|yoOkJoiHTWR_jlLF{Dw^lcHQ#}3l~RA z#JTDs6L?93V zbh015D^w>XAUFZ?J{{H^%?$^uqk`kizKR^#4HadiOC*r*vN6LdM z*?1n}Kxwsnefdf5aVMf#OM!LlQcr^u&L-e*1w_!L(b1^Ga*ZkMLmw%ESKFf*;fZq9 zWb%_MD-f$6a2S;Dj}vUM{WV%4!AVIpudxaIDbc2GkHBD;-SM1>zX}V!k8EekEv4GYz-lL4 zN>PkCp`{@54E1`uIiXbEScK*Up!oj&IMdD11SGc_%eD%}E&-vQc6J8<4-K#vu{;J+ z9~V~$?=zcx5ozhWleKPn8P+m6XIW@QN(#5tk`5&XjYhwC@xsT)CwFi9**9FP9RP1~ zh8of!v(MTco2()tRQ@WlTScIdGt5kIU|d){n_60Gb=grr%`c`Uk^NV{;`4N%K;QaQLJj?mB;1B5xMK#ZOWXZ-1Z zvH0y_^>@4ayc|m)^{am8ZFJp>)9Nkx8p6~icCtm`P?z8yeUSdb1&GJ-F)=9fezMs5 z-D0d%QzNqFcNRU;pvmKiy2{FdpFcyw!f>YfpFR8D;sy3oF9gg!F%ePRhC7n?dMzgh zAP-LUbim339un|_xqGLGM(lXgWI}%Zy4P*p8A6%D+$)4Q@!-;^LxzK)~b2k71^p($w77b2Q_gOq-~t*tF)cXt8Gi!+002YCyxsr+0P|2xR7L;)|5U~JDYo_jSDRDB`2GI>{Qds= z{r_0T`1}6fwc-8!#-TGX}_?g)CZq4{k!uZ_g@DrQdoW0kI zu+W69&uN^)W`CK&06mMNcYMVF00dG=L_t(|+U?wHQxh>12H+Dm+1+fh+IF>G0SjJM zkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJTkdTm&kdTm&kdTm&kdP`e zsgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>VI$fQI%^ugM`#6By?GeadWcu z0gy9!D`m!H>Bd!JW(@avE8`|5XX(0PN}!8K>`dkavs;rHL+wy96QGNT=S@#7%xtlm zIW!++@*2zm-Py#Zr`DzqsLm!b{iskFNULSqE9A>SqHem>o31A%XL>S_5?=;V_i_y+ z(xxXhnt#r-l1Y8_*h`r?8Tr|)(RAiO)4jQR`13X0mx07C&p@KBP_2s``KEhv^|*8c z$$_T(v6^1Ig=#R}sE{vjA?ErGDZGUsyoJuWdJMc7Nb1^KF)-u<7q zPy$=;)0>vuWuK2hQhswLf!9yg`88u&eBbR8uhod?Nw09AXH}-#qOLLxeT2%C;R)QQ$Za#qp~cM&YVmS4i-*Fpd!cC zBXc?(4wcg>sHmXGd^VdE<5QX{Kyz$;$sCPl(_*-P2Iw?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF34$0Z;QO!J zOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUCUoZo%k(yku QW&i*H07*qoM6N<$f)+T;`v3p{ diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index df9be2c..e3d4070 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import file_picker import network_info_plus +import package_info_plus import path_provider_foundation import shared_preferences_foundation import url_launcher_macos @@ -14,6 +15,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index a2ec33f..96d3fee 100644 --- a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ { - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" + "info": { + "version": 1, + "author": "xcode" }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} + "images": [ + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_16.png", + "scale": "1x" + }, + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "2x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "1x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_64.png", + "scale": "2x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_128.png", + "scale": "1x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "2x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "1x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "2x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "1x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_1024.png", + "scale": "2x" + } + ] +} \ No newline at end of file diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index ad18f3b33881e10a90c041f2dedec36ce7c04690..3c5f1c05e71e0832861e349570b250806b4d312c 100644 GIT binary patch literal 42874 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 <)>8B6r?bR@O52l%u$Q4;s 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 z zkDsKhh<&MY>i`oWWFL@7OWUVQd5nhn~q_-t4lU-X7O&>{q77?8VPpo<`#k?i%UCBLkA7JRCb$XXl?6t{a!u z76NEW?m#vz6Dk%X9-=;@DKh`Z745wjn;eha96c!@?{Gi)?###9-}tkB`Kk^dV8_Yk zm`&4DtVvZo5U-IeJ$7toYRmnF-d!f+H+2Ww2H$(ywNS@Twx0dgO(3IjTtLaA6nOak z;K8lWaj8oqSy}aR-3WHs081?M`fK+Ka5^T9#P;^vNiyyPve!2X5I3zIxmh3`FqjU! zpG6m^PqICNkdl7?z9w+=3gbBSR<=F?=LGDV#V#o@>j&r>R5C7)y>2G~svMePd^O9l zW7PpW88PbU4Y2xuD+r|{$Aprbko27w=ah%^z*9mV5dqt)LSCzQ_wKkIj~-T3kZvFC ze%#i?R?f;WkVU!StQ`v&X9cyz3W5v#a92p4e2q8C4qYnrh9=3XdJ`%HBWNX+Lo`ml zHO!sa9@I=-%&4)8I&L{ge?R+eejFNH;0a-E6OufOMOh5SZvq|S=PV^Z2SY>O16BMR z=!tu81-v?8&<8=jm7XS@xKi9U(>v|}9$_b}AHXml$IwXI4b2~#>0_j9z z7pZIQx3b=|IuKKJh1SfvtS(;`YdpLwgcc#RxAVsHe4-UYVznd?uU&*dJTy3~C*s49v=G zLaG)k^ru&KUmDO%dZN~kVB6C#4CyYP4umZ3<2Ex6%%$M%+b+ou!Fx%Ysi}<)a9~Dp z5poy9kUXRET1JB*4I>>_$NY<(Yj&)N3N=;kjhwQ>y=Nq83GRZOTE8>ofMlh=YIkiNdYlL!~& zTcQSsLNFGO<5HIp##bqgM>>(~WTN(Q|JAv&trTR0_|`)e&$8nuvP(Bk!%2{>L&qYN zZ3Uuu!1AB|rywW5gDE2kJiSh$EJ;Xcme3u#QQc6MqRP7~zqdDXH-aFYfTV2A)XW_M zLDfJKtpLFWN&>u%4Xx{?yc0--1Tk>6;z8E!lpjIMd06}$SQ@o#^P**TYvQ^psa7mi z9(fDN$Y^GY)sdgSZX966ac9j|`c+c>pasUt%IdvVnHTL%=df7oikQOVLSVW|I{Y6$ zf>*R*%#K0~KXuvl(GV|kBYCA}bVw^dN%)O78{iP{&l`{f;O}2j9`y9~^7-*6X4jMQ zb4C~uyxpHaf8K#00J@%g$3M|f+AF*Ae)SWadQo=v0hvLBD7q6wVdjpdWx;5U3}WG|m(GoZqb9#6F?niJ z;@jg-recSZpI&ifCdo+*ove9~wUQFDSdojh?$oCV2Pt;3J_dd?PV- zPQlE?zyx1cCntL=Wuo6Qa?%qse6E}Aiy|_JI;%%Ky3FcKnx9SC(@(Idnd1Ur*h+-$ z&v3HZS*wmVx3_^7gW0WT9UUDhxrmhLkMD?4R5B-VI{}dpeNC_7Xlc{+>jQ>U{2j)N zVWYnDHe!OJW)MUi$|8E#v^)fLnXH!f4k5SPvy3VJX zx0rDp(J8@I_ZIT&L{}MV62I;?iFnaOHt65bDO_WwY=x=J)(jae9&m&rijVKxa z>3pO4qXMz2lLonz#fo3l43!62O5Wh)$yW2M?_l?x;iyydN&1ym&sh%ZFTM6-g8N;;KcMoge1$X?81c?}L%Yood9%}w-09wJb zbvjeFNoN1To(#47>0;rf&robE^dw^^!GYhT6;;=`SIQx3Ky21+wbT$M?9H6zI!(sI7c#dD6_=ywdJ zKitof6Z;b<#qpVU4_?a!uTO^)Y%lrfFOv8$DL=RE06Wc_BIv&I`teh+=+S0W{zVkM zkE12Y{RqMZb`JQgFHNN7V0?{E-k7p1nkyv;lDLfQ$Y_}0h^SSjsF2j3&ZggN<3Kzx zIF==kwxp-7(h(0lo#1)*6;BDW$v*KF#%+<*pNr#sWxfd=u_u^d1+@azQB2~E{L)&| z(W~Katc&(TVSZ`-9NL_L9e9w!Tt@J8M6zd$dOqyef%SaY!7)F8qbE&^|u3 zx!5X+AqrHx}XB#{8HQ~Qy+M^I*kca-aCMdYoIRNC$A zrBkh*Zi259$7wx!yoju<>>*mQv=d&sy7$<~5Ra3-&ZZ4&{U}&YoGF8J_9zqyH;fRc zZ(r4j)~Khwgb`%WcnFTESeLHzQBuqNc7(2<*qIb=qi)DqKb^FwZf|HYzEikm{gWaYX`i~LZ5!LW?S39tdT|@1--3f`^ zm87tHRnJy$Vka*H81j-K>VEWP5(A+sexVqq6TDFM&NK1P2wdVbA7AD>cC81HIKX#E zQjl0OCa@OloHqjDn6Te&=RJpzHFG6O#qH!9*Al|MuMMcbItmKp(N_>W*ZrkyGlq9D zO1SGAV*f1&nfKV$!W-lga&%@(E(X`td5|OHG9n%@Df!3q^VSrjVWL*wm3j7&d&_v>k zI5Lvvm_`e0aPt0>h?F-Tg6(GjmdG%O$)0e}>D}HiW3z!y3MeY2v1NeOeL* zyN`r@z??Di^AQFGeC(PIeHbB*69#L9t!`RWuylti=cSdVvt-*WSqO|@9b-CI1RM?SfGZW zh>55GzsR3Bq)BA9`hyf-#%1R_X%Levh4L2kt+0>?E5ovK0D`dZ%a0I9wyqqieEO8v zL4E0v6PA&fx4_c=cEf~I^w1lX&M4b1B|#co<$urQ?=5?1!vY=6p2B;v^2pVFD8$j> z^_p9V#t55-8 z@kfn#K%zKb2RnaD8-M+c!CfadR{IB3m-SIrbovQc;?fdb)&ma2BFf0Ci$6leRC}99JiMP%i+HZ$0uk@awo}QCpufr(kD9@25-g4^vmyPG8@86IA zoo#q-K38z87qDX}DMvk;6Qrvq@4xksN8iV%MP+>T;2n$5hrV;_gw2V=H+k-NrHdgs zCaJqMqMAF-xxLW{1wt5bH;T-2j31bonG)`kRf-HO_3iZmvT9Xn)iSbSOnul9;ECxa;YI;wyil_2S?CcmmpO5 zA=By@wuu*G?_VnR`tbZ6+3(z&QwIoeu;&MPt&i!L}>x>4iHjUSp0&*O6?&*(S z0#CE;7%5N?9w7-cHieP-w)}) zn`y&#mU1JdP{!N$5HrDbWa|^!AeTg$Y|U$rGS|LSTa2R{gmaaPi?OOH)t;ta>clt zdgj@YS|!Yh8|Y4b2MQqByv9JvTqAIE6;KO;sMlGgg3Hd}2&=Hj4Sc#mc`2;$Tkomw z=ql?sR-h?{e0PW#82M>dL^>!c!bcpCdO&Az6VjOGPe(NG1WHc$THd{@*7$mVn&X@E zNN2))qSZwvFt0HF(|Oe=0O5xKH3;=?lXc4A6H~Pmrr2s2>^CH-twPV9TJa;8c{4^t z%NQ%VpP39go3k5htwA23N@Rpf)S>3efmkBM9;^!2n-S>_;1NN0+s0|1vEOk8JuF_atoUHTEBxEV zg5e+_H&&@X6BGR@ZEY^G8`exOnQHm7ekMo-9OlvNgTB^Cy}_>i0l~0<1UDfKzfL%- z-{c7=DI3PNeXz}LDEbGp86Ku^G8;2VSSC6}MU3pMSNWlrK~XV0CuAaMV?}-h+-O!i zqIGjL>`!mWLt5MkuSMNcry>flq8P#qIs2@+In1bC%oEoMfExi~Ca?J5>*Byn(dEY1 zZ{Enh57H_#HuPM+g70_-RPSD3lk1o;(PfF90EfNAEg{8#ItdG(hpSHVbzO=`p1vht zU**@u^4yG!d#@0&%!U}5{;Jd(#ehkQM!>?nB-ye=!!xJE^7zry^mc#FVZEi1O~xkN&tNOp5Z; zqWI}251my+8^h>Yr2o=AcI`iVnyf3ROEG_!@Zh@tlKO&M2%Uw{c=fN$%Y1!_ttAXK z5NQ5D-&6q`nSiB&j+A`(!!Q<5l=0QqBau}rIs<&`$>UOd!_WIngHnn?`rB1Iku&>nF2S!HrER211 z2ZRl}C|EqXpgl-}{o}~n3+UsWI*73IDi-i5IN>SIfadLPQ}z5$M_zNeXI|sR$bAz; zP~~QwUA4Tu4_e`JYhq-M`U;ABP!_2FF^1tM}yp!P9ANHVlz(6eibZLw~r z5jD1GZvE{~%b`hVhVsyts(1R!b0{6Y|5+=2`*uweF?tubH~d9PE6Frt8b;X_ zf?f85ylIc05lQS#l-F;&WU5@noDTmvD_Rpi+*PTsZ*VC1Et}}~%9*RnWZsNuRWM%S z^T#HL1sD8)Q#b5|46pb0M&7IZVkjb3T@XKh%X`tmpmOL}x<5J{QSLdU>FVyD#fA%}IG$j|czzt?IbU)us&aku zmA~CtLYyYBAY~ih%Y?#PqycQJ2I?GL6B-fS;72kFrHu5kZ}#biuHLObccQ^CxH?*^ zYtg^J=!+%Y5ht=};9+JRfxX=qZbcO!J;hfe+=r?(mJ(vclGmz1%Ha-GkGSVS^uqg! z$L8CgzGz8r|H;#of@MfT8cxk>N#sm@Y_cn~4-Pp9ekp~_AAj8WkNG~zl9}&6gTcR8<p4h1ysK^|-oCwzE_a=`0YrvfCwU&vzgjV_$WJT>!BK!W<4{a} zn$S;R2J`}-nH5AA$+8zdz&VNCH~9B@qtNZu^Qt{t1qV`8WB2jY-sr$ud}~IooGL#F zT_NJ90}ksRW+DV2Xng;(@=`0PN{gO&tU>Atv~v5P4K?iv^l5*I#($xgkNo&0%8*65 z?&}%nNsy!?GTOD(NeL6p0!XjfT!A>^So!Ph?0AV&+y0>B3yzMGS^{*L$UI0fN~+gi zRTraVmvf?^5?w!J{wPLRyC4Hb3)O5~e(7eSp&Wy?ysCG=--OH@;a=v0wya1p58E zgQ9hy9Rk;R(|IaU?+76;t@ixpQY0XVW-CLtVXm>JeP_NHYh$>fs{maz>g=ZL9_{&C zL&LV3ArzMSJ+tK6x8ef-v4d6na%3>bk2;*bbx&Q)fhe3N*M&IRuoo#YGrk2%iIE^; zpYj3G)uP5Z`%1s%Vl+5g(Rz7#^?+W;6ENnqoCmGs&dw{)lzlaL{WI(Lo}Rsas$sOA z4oIR-EcQxFosIl`W$g9IPU#_*Iml@;r+jNCt`X1_KAV81U!=M{1k#y- zx?nbd6t0|*vy%0&W~4zJxJEW6-dAtEd2_C$0SK&Dfx0LE9>X;N6NFtM*D9^1ZdAXA zMp*x6Hm%c~qB>=XV{+Rga?>W;qbB~#tf^}L?2v4Vxm?fWRPlNCOFx9Z=}A(mi(~9< zy8$l5$>0dBcWe-dJ~n6MgHO&&GZ9k|bU4N4qW|W61KQCdyG1VTCpyAE}=T z^kw&7jWLp|jgn|Fe8K>M_@ia%A_WT}Fu{scY>e=_+iws)d$snIN)<;B5mG(!_mxhbwjRTe{Yve3L^rRKRu z^^jijxKTM(9QP~RF>}REN=QfuSWEiQTz^~5D%u%E+w?T$Rft(Yt4v$Q3oNPZYa_V6 znR`J(ulS&*F>t-!0D*zTLMPLg)fp{;Se&7Vxv2R}7rq|~?4LbroUE!yxezO2*^8EV zk}m<+`O~PiT~rCLsmXMI#$2%#RW9@i-5&U(P(G#X}0n31{5CZ945I*>i`zG@l+{qm| zG5heVT=l^1T$EwIWf_bX^$=HkAUD)8DPmvpu#DH+S{nq-l@F8#YcFzitd@u${w>7z z`u?$L>Dc5Yeq7y|*=hyR4r70*GA4)}UIz)B2I5z~o@uq}(sse!0r~pc;pN5g*7tO~ zcvesRs}bZ~`sS^MgCKkEgIv&XjO8fW;|OaWv~H?9)2Psy{OkY$VQ&H8$qmGkRp#m% z=km0t#OU&YeKv1E1JKIYE6l}!Wy|xRE}%^48D8CXA(4iI01VK)!*B&{#$Xb!O|Tp) zw@30q>l}-`e0)MvLfBSO8bvushh78SW|PJ8G}L;alxR)8R1F#JmM^$$cOW09(fO@446FaNYo*TXJwiMLT?TYt#*DcR3f#q7ws~v>+Hw}Xp7AmoM z!wmwSr66AG5{V#>o2N1?AKYIQUV#SaBla~&YSvYZIg7d^VwrIKO2mWw2j*+ z$aOvqJnS85W=KGjSG11&tR}Pvh|Fsu=RsR21l=>>tNm6wA71e@op{kQW70t3_RIxz zinse@XKt0!V@(DNi!@>1ek>nahM8as36Jdhm;`Vu6D66pS2fK^^&sYPEeAIK7WWMW zniG9ecvqP}AyXg1Ww6IRe@w^FQP!J4AJ-4}nFfiA&*tilphWxEOu-62vqwX}hcPd7 zA*KNtrBemb+eWmRh2zJ zmN0`_>gh-4m5WSxx($BjZOh6dFp*MZ^;K3;MN*ptHqB)X2O&o$qnA zRdHO-mI*$a-t`jfXgNc_BtbNjgY4ME?uXHLSdlUlOctk0#RgfZZ2b?ggjZ&-=6C8@ zWwO$GUro_BdL$u1qX2x>pZ5YJb}hBOGQtJf1(`g;0E^@P@VzgRbo0!=JF2wTTW!X( zxKr0vx@KQx;`}TyQp{CF!7NgH{?llv=XC85C7vwR&DJXl)RUK@Jc{yhN%fnQexVuQ z=f%w|mF?9S2-$Ja+>rD>VvXGt5+H0LKiAf(=QoXO|9$`UBAy4WhPbwDlJb7uft@@2 zjHliLsgoO&w#>chW)Zgqg6Xx5A(V0+jS;o#D@_}s&5QJOLlWbBc+Tcd0L2&JZz(!X zmw9vi8>6Vbdyu{WnAiD6)~2+{3Pa)r1W*LMg%l|hV=te{ge1z+P^wBx6i1$>I7UBC zdRK|2Qy_dm(?Jt%;C$Xk9TbmD{e11=Pe@q~F!a8+w~rij0sQH?+u-~*oaPG>t`%7 zJ+lk3ns4lR-eG&0J8#Hzd^PBrd&SheocH@djU0TanR zQqeuB-H^xZ6zP_T3w=|UE7WAL`AW+Z9gTL9x=e2J6cy=98B>Kw=ixWm`_>-4&czM` zx%@2zsdX7bPdpET7l#G$7+F7vSZ3YluRT}9_4Cf3o}$m76m))}ara#TcDTg{X#$jw z_@b01`+&8cwFL&rJ2hm^oSk#}xx>M;uNgIxO%#K+{Xr7()t|ctkO@*T4IvUWI>Yik zlQcCd^&??xhuw%$?Tb`#tq9##Bc`d)&``3uikKF4tu=tt9*Bs7{n-D1Cs<2khq^=# zaMt|>NL45_??_UH0*bV#tYk9^f!HWK>y9)b=z-Haclzgw?={>?X89Kd%%Lag61W}W zABY7npE+|z{000C`ie$(5iVzKI)o%lN6oVlIAV{ZW>-7Z`lg?;v7bU1t zd}M1EB6=3RmD%O}vLtI--iIMP8faglABc_shDeD^v;j73O&T1{JN1>iy!%ibh;4dc zRERyGaE0*8Hfzml2+loxnH4#4QmtuvKW_w2tx->57_CcAYb+-bz;PB0ZZA`g>Jk7$ zF2|@Zgn81FvIz*`A2n2QAI3&KZ$Qf7SS3))AmSDQQu`HcbeTd@#Idu5yY$zB=C22- zkOTaq!}W$NnO}C9Ry!p)nQ_;^v<5c_7RMoQB+X)RtYFQ9b=XW_KbSfE`61g%`ANX` z*))WRv7M_HtawGdpeog_Gy5&TbtNdb8)^^x4E?C$Cr_S`&9Q_}P$YQmQM0el$UNg0 zAV%nebCER)c;GI02kka^hXOm#T`n*4)Llf0Dd{c@QIPzE;Gn?bPY z3iVf26yLFi112e_kMbgA1f>IH)=9#fK?WX?`V1K)+7lVn~bNTYJ%X|PRQbvq*l9!RX;kkt#tz?xdz#4;8xjFU&9tYU{zn z)gQP%;EytQ9vIX+{Mq`*;b zpIa9>)X&BtRu}JFo!}HiVkOWovg3H3MkCkn(%=OuH#$$<(@(0;aLf_MJr5a0D=?7N z&1_&;fSV}7G#ti@_^Y3-#wlg)lIFzJ*WIFVX&le_(7Za0VrZ?J8?Otndlj76x8Qg5 z_Moq*U2X@*`LMNoTMY*(IWBboi&tk3x7@OLH!EA&s^r#V4^5P3CRkKNeB8ca2 zC_7*L77DrcsrRvYAzmD>?F?xV+lC?;?EHNyT_VeYHO<2kQGMi zOIjdek%!dG0wjPp?Az+zZ~X{_7r5>$4LqR-giDRWzQY)lW@=(KVHJ25BMMn9g`OYb zd66R2%7#m{7)#CxL|`aFf&Z}@0ndsyCHmLR)Z56a0_-4vmSdB&EG=HqC@}B-OJSrw zV^(%KpHJQQIwg11115=rVQXpaU=7m|@b`Jp+3=#-^=PDBsE66!ix!?k3PT1Cn%VKV zgXN_`_d-Twe4&Qlx_dDZfqHo~nT@!BzA}#K&JAbDlWO)hj5HM0e9WQ48s&K}AI(lr zm%>oJ7RlG!-PCov<50T=dSER0;e6}MWOOd;taI1LA3q`%Oyeb#D!^rU_ii~VjMALg z7@Q64){hakeNM}W_;Wqph0j>xSDD#+_;CIWT9?l=TH)D1h!kN^1{(A6Z#T; z+TTB=SruGFx#?2AZ8Je#KDC5Hv70AMbESvDOKskMUqzD-U`reTNKiiE#S*Js+HkyU z4q~yGGUHjTJjd`i;oL|rRM`EX{`AN(PF{FKfT3Y=|1seC{%xUDulC`0GIAloHqzGZ zDlVE@^_jxEf$WEpqx(TdN|~U&F;d5Lce<$2UokcA9Q^84Fx3^2QbSK!;i~LAnZXCF zS8~J8j5do01X_BAe4wW;cD*B1)TW@CHAkY)g&@M3>BS@;#2 zB*MCeC3XP(&bHbXs=!36oeQ^^h^6Qfw_}aeni6Vxc+0nO?iOpAYMo4?5r#vy(BYJL zw1@!-L@9N9mil+-4yqjh(3~34ABvw`qN@A|H zrkp7dL9&2UP73BRapBCg<>IpP+x_IkZn7e?AoNUt42e68Mu2J%!9%<5Th-Pd&wfyT z)wQbO#iGwQJfzT(I9L-9i$tTw#z?-;YK6q50m`Mt2`|CnS_YA&_q3GO$&g)#MVSz< z$C_GjNhS?ef8d-F^{4rH?fh6pwOPoD>;F;Q6lQB3CgIl_AV{}EFyx2VQuCsa!F8en zTt{$=D~*`dj|`g0K3mXK^?<(fvVjXbFKl#_ver5OvQI_8^

*ac&nxzaxx<@EDnQ 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 literal 23998 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 Qy?8*dzeIm=HB4~){~Z#)egFUf diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index 62a3c89a8bd7cc6218ee4e51097e8d4759a7da8f..c626d3a3546d2ee45542a141130a2374cc44b4af 100644 GIT binary patch literal 3498 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 PV1VIuW8DgEhmijP+{k3v literal 4444 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 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index c371b3c0792ab35e71f5cb8a6fcdd4f8a0ded3a1..6f87f7ba10c14650d5c002d6fff4cf575c8a8234 100644 GIT binary patch delta 436 zcmV;l0Zaa&1R<>!x!a}=<7J{6g zrGk~eLwm7Ru!xO+AqW<>f>t()g=poeDDF(W%Uw2LNCpP>-rIR*W_MP>KTWZ$UxdF2 zSD(aeg})2etpE!C5FYzw;+gPQf*1hG94nX0NG6l0R;xH3kAM8*C+(4lV)_Pv%PtfO zNF)*{l}ctRP16vM$5AX6zgMAJTVOk#PPksLIG@jwcDY>mJC#Zy7z|=O9%Hpy$pBr~ zZD|N*XuV#KTOw;Vo9J{pGQi{U*eY;3o!ISmNT<`>Vm_Z|psIdemIT=E_h_|RrdFv` z5C{a2$z(8{PJb~N3|NmuA~L|?aB#2PZr9Z407Ib={C+n+3KfYE3~%h@Bw1kgp(Z2$Q& e8Ib;yV9D-}#i4N#mYVVa0000Fe`{LpWHF=P!dGi>c7`yC+;6Kk_=|hg-h2FxFusSO1+5Fy&wLt{+D<+lxF8FFSc-;=h?6{+3_LsIQW&^Q`~&`|pY8pFdRC z%sBsi(yk~AnY6g&mlHQeXw3Ss{EH#)KM(O3JqgY9~Al zwDMan%xE^7cQIGj{k-j6yNiZZj4%M6Sw|)y~py)FG{Qyd{*c_n$&YAdTZ3vxc}ltGka$Es3jRlbR-yP z1g)GBp*Q`}VZK{$&DstthA#`y5E1A6(7GZ-Ylh$QH-GE?Z)M$S)V=GjUewxa zYa4FAbyJ$yaQ=C+-&QYStB75!X5B{*0j-Ijp=}|f>H^Cr>mdKI%cPYCICPR1D^l@ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index 0c5bcc2f2e80c02794f096aa7cf4e9d6c0daebf1..580b78e08f5a45e3d4c6eb11ad4c895a380acb15 100644 GIT binary patch literal 7726 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 literal 10118 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 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 8b447f9a2119a7c5df0aab87772811af980b5332..4ddbb18ddba8b5f3594254f9e1623911aecad7f6 100644 GIT binary patch delta 827 zcmV-B1H}Bd3DE|SBYy)JNklInTD1vrA_u|%%YLi+}*iIL@av^S{N6{)JLxt=E%6HG;oj3P7*CwjhTf74Y zXYT#qbAB^-W<0kYzS!FT187_ym3&q5S;@b>Ge<@8q~t#(|9>R{@I&&T#>NJ0ZEaCqG#YINpvb`kpbM^}qk{%Tdw6)@ zMibTXa_|7CJ%2tv(sr#@(+vFd^dzLyG9iGMmls99nT`eeFaT`h=y=8^RO{N>+Q8{_ zg2Ul}p`jtTy}gAu1l-1$RI5;?fxw$!ozQ4a;3_vTe+SnOj zp#yo_($3{_;D7h~$$*xZm%(T>iUF`QfZI&3 z*Ebyr3$V7fMt*N@ZeV6+28zWZ>HB;>h{a-W2e`YtgKRd-)%EuF!ua?&1u&b94uV}T|nCK~j^!^2_#Y+&5eq5$l)&(F`p z$jAs>U0snr&g#O#0))fij{{)uVLO>jLa9`OzP>&%7z_}P$6<4Glk5mQ6{n`A6weC* zU?8OD9mi@=JTLHX+~e_(Yhz<$5DW&X=JN7V41a(f86Gxx4)6{mR)>qOv$GStUN6~> zE|*KOn9>#-Hz2E0XxbvwR>FZ(coy3nO@??dgM{Wkn-_8Z$6PSmf>IX3_R002ovPDHLk FV1gnpmJk2{ delta 1188 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 QP!~W+LH!<9&f>-Y0ZDIZJOBUy literal 24003 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 diff --git a/pubspec.yaml b/pubspec.yaml index c66cd9d..f0e75b4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: hosts description: "Hosts Editor" publish_to: 'none' # 防止意外发布到pub.dev -version: 1.5.0 +version: 1.8.0 environment: sdk: ^3.5.0 From e8258315126d2a46a3ed1b517c64f4e0567592a0 Mon Sep 17 00:00:00 2001 From: Webb <822028533@qq.com> Date: Sun, 27 Jul 2025 00:44:50 +0800 Subject: [PATCH 44/54] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0macOS=20DMG?= =?UTF-8?q?=E6=89=93=E5=8C=85=E9=85=8D=E7=BD=AE=E5=92=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?PKG=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 DMG 背景图片和 SVG 文件 - 添加 DMG 打包配置文件 - 简化 PKG 配置文件 --- macos/packaging/dmg/background.png | Bin 0 -> 110245 bytes macos/packaging/dmg/background.svg | 65 +++++++++++++++++++++++++++ macos/packaging/dmg/make_config.yaml | 19 ++++++++ macos/packaging/pkg/make_config.yaml | 7 +-- 4 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 macos/packaging/dmg/background.png create mode 100644 macos/packaging/dmg/background.svg create mode 100644 macos/packaging/dmg/make_config.yaml diff --git a/macos/packaging/dmg/background.png b/macos/packaging/dmg/background.png new file mode 100644 index 0000000000000000000000000000000000000000..93243b71b6bf07ae4ca1cacc8a26cfe1b7a48f01 GIT binary patch literal 110245 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

iP#`fJw?x}e3Q<8uN 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 lS8R{dz6;s!&afH*`Jnqk^#~VbG6eh_J#xa~^ + + + + + + + + + + + + + + + + + + + + + + Hosts Editor + + + + + A cross-platform hosts file editor + + + + + Drag Hosts Editor to Applications folder to install + + + + + + + + + + + + + + + Hosts Editor + + + + + + Applications + + + + + 1. Drag the app icon to Applications folder + + + 2. Launch from Applications or Spotlight + + \ No newline at end of file diff --git a/macos/packaging/dmg/make_config.yaml b/macos/packaging/dmg/make_config.yaml new file mode 100644 index 0000000..a9cbc94 --- /dev/null +++ b/macos/packaging/dmg/make_config.yaml @@ -0,0 +1,19 @@ +title: Hosts Editor +# icon: assets/icon/logo.png # 暂时移除图标避免格式问题 +background: background.png +window: + size: + width: 640 + height: 480 + position: + x: 200 + y: 120 +contents: + - x: 160 + y: 240 + type: file + path: hosts.app + - x: 480 + y: 240 + type: link + path: '/Applications' \ No newline at end of file diff --git a/macos/packaging/pkg/make_config.yaml b/macos/packaging/pkg/make_config.yaml index 52c560e..93a3d55 100644 --- a/macos/packaging/pkg/make_config.yaml +++ b/macos/packaging/pkg/make_config.yaml @@ -1,7 +1,2 @@ install-path: /Applications -# sign-identity: "-" -identifier: top.webb_l.hosts -version: 1.5.0 -title: Hosts Editor -resources-dir: macos/packaging/pkg/Resources -scripts-dir: macos/packaging/pkg/Scripts \ No newline at end of file +# sign-identity: "-" \ No newline at end of file From d1d9428ebd21c550a6dd238a3cef544dda4926cf Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sun, 27 Jul 2025 11:48:10 +0800 Subject: [PATCH 45/54] update icon --- .gitignore | 4 +++- windows/runner/resources/app_icon.ico | Bin 33772 -> 1382 bytes 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6ebb677..ff69a8e 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,6 @@ app.*.map.json /android/app/release /android/app/.cxx/ -/macos/DerivedData/ \ No newline at end of file +/macos/DerivedData/ + +/dist \ No newline at end of file diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20caf6370ebb9253ad831cc31de4a9c965f6..107e6d25ab9af5915d4b7a0ab979e372bb1fa2c1 100644 GIT binary patch literal 1382 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 From cc3e447ab48713bfd98ab6cf9579573abf2c45a9 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sun, 27 Jul 2025 14:40:02 +0800 Subject: [PATCH 46/54] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E6=9E=B6=E6=9E=84=E5=B9=B6=E4=BC=98=E5=8C=96=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 用 GlobalSettings().filePath 替代 isSimple 标志位 - 统一 Web 和桌面版本的文件保存逻辑 - 改进文本编辑模式下的 Ctrl+S 保存功能 - 清理调试输出和不必要的注释代码 - 优化应用栏布局和交互逻辑 --- lib/app.dart | 26 ++++---- lib/home/cubit/host_cubit.dart | 48 ++++----------- lib/home/view/home_app_bar.dart | 74 +++++++++++++++-------- lib/home/view/home_page.dart | 6 +- lib/home/view/host_page.dart | 2 +- lib/home/view/host_text.dart | 17 +++--- lib/home/view/simple_home_view.dart | 94 ++++------------------------- lib/model/global_settings.dart | 2 +- test/widget_test.dart | 3 +- 9 files changed, 103 insertions(+), 169 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 9f0501d..ab3fe9c 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,5 @@ import 'dart:io'; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:hosts/home/view/home_page.dart'; import 'package:hosts/l10n/app_localizations.dart'; @@ -36,9 +35,11 @@ class HostsApp extends MaterialApp { } Widget _platformSpecificWidget(String filePath) { - GlobalSettings().isSimple = kIsWeb || filePath.isNotEmpty; - if (GlobalSettings().isSimple) { - return SimpleHomePage(filePath: filePath); + if (filePath.isNotEmpty) { + GlobalSettings().filePath = filePath; + } + if (GlobalSettings().filePath != null) { + return SimpleHomePage(); } else { return FutureBuilder( future: _initializeApp(), @@ -68,7 +69,7 @@ Future _initializeApp() async { .copy(await fileManager.getHostsFilePath(fileName)); settingsManager.setBool(settingKeyFirstOpenApp, true); } - + // 异步启动服务器,不阻塞应用初始化 _startServerInBackground(settingsManager); } @@ -80,22 +81,25 @@ void _startServerInBackground(SettingsManager settingsManager) { Future.microtask(() async { try { // 检查是否启用了自动启动服务器 - bool isAutoStartEnabled = await settingsManager.getBool(settingKeyAutoStartEnabled); + bool isAutoStartEnabled = + await settingsManager.getBool(settingKeyAutoStartEnabled); if (isAutoStartEnabled) { // 获取保存的hosts文件列表 - List savedHostsList = await settingsManager.getList(settingKeyAutoStartHosts); - + List savedHostsList = + await settingsManager.getList(settingKeyAutoStartHosts); + if (savedHostsList.isNotEmpty) { // 将JSON数据转换为SimpleHostFile对象 List autoStartHosts = savedHostsList .map((json) => SimpleHostFile.fromJson(json)) .toList(); - + // 启动服务器 await serverManager.startServer( - allowedHostFiles: autoStartHosts.map((host) => host.fileName).toList(), + allowedHostFiles: + autoStartHosts.map((host) => host.fileName).toList(), ); - + print('自动启动服务器成功,共享${autoStartHosts.length}个hosts文件'); } else { print('自动启动已启用,但没有找到保存的hosts文件列表'); diff --git a/lib/home/cubit/host_cubit.dart b/lib/home/cubit/host_cubit.dart index ce895cd..809e7d9 100644 --- a/lib/home/cubit/host_cubit.dart +++ b/lib/home/cubit/host_cubit.dart @@ -12,6 +12,7 @@ import 'package:flutter/services.dart'; import 'package:hosts/enums.dart'; import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/global_settings.dart'; import 'package:hosts/model/host_file.dart'; import 'package:hosts/model/simple_host_file.dart'; import 'package:hosts/util/file_manager.dart'; @@ -429,15 +430,10 @@ class HostCubit extends Cubit { return; } - print(text); - - print(state.data.defaultFileContent); - emit( HostFileContent( state.data.copyWith( fileContent: text, - // TODO 这里判断不够正确,撤回会导致问题。 isSave: text == state.data.defaultFileContent, ), ), @@ -493,13 +489,17 @@ class HostCubit extends Cubit { save(isHistory); } - // TODO 保存到文件中。 - void onTextSave() { - // state.data.fileContent + void onTextSave(BuildContext context, String content) async { + final result = await saveHost( + context, + GlobalSettings().filePath ?? FileManager.systemHostFilePath, + state.data.fileContent, + ); + if (GlobalSettings().filePath != null && result) { + fromText(content); + } } - void saveToFile() {} - void save([bool isHistory = false]) async { final fileId = state.data.fileId; final String content = toString(); @@ -530,7 +530,7 @@ class HostCubit extends Cubit { await showDialog( context: context, builder: (context) => AlertDialog( - title: const Text("保存"), + title: Text(AppLocalizations.of(context)!.save), content: SizedBox( width: MediaQuery.of(context).size.width * 0.5, child: SelectableText(hostContent), @@ -594,38 +594,12 @@ class HostCubit extends Cubit { } } - // emit( - // HostSave( - // state.data.copyWith( - // defaultHosts: state.data.hosts, - // defaultFileContent: state.data.fileContent, - // isSave: true, - // ), - // ), - // ); - // setState(() { - // hostsFile.defaultContent = hostContent; - // hostsFile.isUpdateHost(); - // }); return true; } void writeClipboard( String hostContent, String defaultContent, BuildContext context) { Clipboard.setData(ClipboardData(text: hostContent)).then((_) { - // emit( - // HostSave( - // state.data.copyWith( - // defaultHosts: state.data.hosts, - // defaultFileContent: state.data.fileContent, - // isSave: true, - // ), - // ), - // ); - // setState(() { - // hostsFile.defaultContent = defaultContent; - // hostsFile.isUpdateHost(); - // }); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar( diff --git a/lib/home/view/home_app_bar.dart b/lib/home/view/home_app_bar.dart index 0bc5716..c68c013 100644 --- a/lib/home/view/home_app_bar.dart +++ b/lib/home/view/home_app_bar.dart @@ -8,11 +8,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hosts/enums.dart'; import 'package:hosts/home/cubit/home_cubit.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; +import 'package:hosts/home/view/history_page.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/model/global_settings.dart'; import 'package:hosts/model/host_file.dart'; import 'package:hosts/model/simple_host_file.dart'; -import 'package:hosts/home/view/history_page.dart'; import 'package:hosts/widget/dialog/copy_multiple_dialog.dart'; import 'package:hosts/widget/snakbar.dart'; import 'package:hosts/widget/text_field/search_text_field.dart'; @@ -40,7 +40,9 @@ class HomeAppBar extends StatelessWidget { return Container( height: isNarrow ? null : 58, padding: const EdgeInsets.symmetric(horizontal: 5), - child: isNarrow ? _buildNarrowLayout(context, homeCubit, homeStateData) : _buildWideLayout(context, homeCubit, homeStateData), + child: isNarrow + ? _buildNarrowLayout(context, homeCubit, homeStateData) + : _buildWideLayout(context, homeCubit, homeStateData), ); }, ) @@ -148,6 +150,8 @@ class HomeAppBar extends StatelessWidget { context.read().fromText(File(path).readAsStringSync()); } + GlobalSettings().filePath = path; + if (bytes != null) { context.read().fromText(utf8.decode(bytes)); } @@ -171,7 +175,7 @@ class HomeAppBar extends StatelessWidget { case 3: // 关于 final packageInfo = await PackageInfo.fromPlatform(); - + if (context.mounted) { showAboutDialog( context: context, @@ -200,7 +204,7 @@ class HomeAppBar extends StatelessWidget { "icon": Icons.system_update }, { - "text": AppLocalizations.of(context)!.report_issue, + "text": AppLocalizations.of(context)!.report_issue, "value": 2, "icon": Icons.bug_report }, @@ -235,34 +239,41 @@ class HomeAppBar extends StatelessWidget { } else { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(AppLocalizations.of(context)!.unable_to_open(url))), + SnackBar( + content: + Text(AppLocalizations.of(context)!.unable_to_open(url))), ); } } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('${AppLocalizations.of(context)!.unable_to_open(url)}: $e')), + SnackBar( + content: Text( + '${AppLocalizations.of(context)!.unable_to_open(url)}: $e')), ); } } } - Widget _buildWideLayout(BuildContext context, HomeCubit homeCubit, HomeStateData homeStateData) { + Widget _buildWideLayout( + BuildContext context, HomeCubit homeCubit, HomeStateData homeStateData) { return Row( children: [ Expanded( child: Row( children: [ - if (GlobalSettings().isSimple) + if (GlobalSettings().filePath != null) IconButton( onPressed: () async { - FilePickerResult? result = await FilePicker.platform.pickFiles(); + FilePickerResult? result = + await FilePicker.platform.pickFiles(); if (result == null) return; if (!context.read().state.data.isSave) { ScaffoldMessenger.of(context).removeCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)!.error_not_save), + content: + Text(AppLocalizations.of(context)!.error_not_save), action: SnackBarAction( label: AppLocalizations.of(context)!.abort, onPressed: () => pickFile(context, result), @@ -314,7 +325,8 @@ class HomeAppBar extends StatelessWidget { if (hostStateData.history.isNotEmpty) IconButton( onPressed: () async { - SimpleHostFileHistory? resultHistory = await showModalBottomSheet( + SimpleHostFileHistory? resultHistory = + await showModalBottomSheet( context: context, builder: (BuildContext context) => HistoryPage( selectHistory: hostStateData.selectHistory, @@ -329,10 +341,12 @@ class HomeAppBar extends StatelessWidget { if (!hostStateData.isSave) { ScaffoldMessenger.of(context).removeCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)!.error_not_save), + content: Text( + AppLocalizations.of(context)!.error_not_save), action: SnackBarAction( label: AppLocalizations.of(context)!.abort, - onPressed: () => hostCubit.onHistoryChanged(resultHistory), + onPressed: () => + hostCubit.onHistoryChanged(resultHistory), ), )); return; @@ -356,22 +370,25 @@ class HomeAppBar extends StatelessWidget { ); } - Widget _buildNarrowLayout(BuildContext context, HomeCubit homeCubit, HomeStateData homeStateData) { + Widget _buildNarrowLayout( + BuildContext context, HomeCubit homeCubit, HomeStateData homeStateData) { return Column( children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ - if (GlobalSettings().isSimple) + if (GlobalSettings().filePath != null) IconButton( onPressed: () async { - FilePickerResult? result = await FilePicker.platform.pickFiles(); + FilePickerResult? result = + await FilePicker.platform.pickFiles(); if (result == null) return; if (!context.read().state.data.isSave) { ScaffoldMessenger.of(context).removeCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)!.error_not_save), + content: + Text(AppLocalizations.of(context)!.error_not_save), action: SnackBarAction( label: AppLocalizations.of(context)!.abort, onPressed: () => pickFile(context, result), @@ -406,7 +423,8 @@ class HomeAppBar extends StatelessWidget { if (hostStateData.history.isNotEmpty) IconButton( onPressed: () async { - SimpleHostFileHistory? resultHistory = await showModalBottomSheet( + SimpleHostFileHistory? resultHistory = + await showModalBottomSheet( context: context, builder: (BuildContext context) => HistoryPage( selectHistory: hostStateData.selectHistory, @@ -419,12 +437,16 @@ class HomeAppBar extends StatelessWidget { return; } if (!hostStateData.isSave) { - ScaffoldMessenger.of(context).removeCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)!.error_not_save), + ScaffoldMessenger.of(context) + .removeCurrentSnackBar(); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)! + .error_not_save), action: SnackBarAction( label: AppLocalizations.of(context)!.abort, - onPressed: () => hostCubit.onHistoryChanged(resultHistory), + onPressed: () => + hostCubit.onHistoryChanged(resultHistory), ), )); return; @@ -475,8 +497,9 @@ class HomeAppBar extends StatelessWidget { builder: (BuildContext context, state) { final selectHosts = state.data.selectHosts; final hostCubit = context.read(); - - if (selectHosts.isEmpty || homeCubit.state.data.editMode != EditMode.Table) { + + if (selectHosts.isEmpty || + homeCubit.state.data.editMode != EditMode.Table) { return const SizedBox.shrink(); } @@ -508,7 +531,8 @@ class HomeAppBar extends StatelessWidget { onPressed: () { showDialog( context: context, - builder: (context) => CopyMultipleDialog(hosts: selectHosts), + builder: (context) => + CopyMultipleDialog(hosts: selectHosts), ); }, tooltip: AppLocalizations.of(context)!.copy_selected, diff --git a/lib/home/view/home_page.dart b/lib/home/view/home_page.dart index e190401..7416ef7 100644 --- a/lib/home/view/home_page.dart +++ b/lib/home/view/home_page.dart @@ -28,9 +28,7 @@ class HomePage extends StatelessWidget { } class SimpleHomePage extends StatelessWidget { - final String filePath; - - const SimpleHomePage({super.key, required this.filePath}); + const SimpleHomePage({super.key}); @override Widget build(BuildContext context) { @@ -39,7 +37,7 @@ class SimpleHomePage extends StatelessWidget { BlocProvider(create: (context) => HomeCubit()), BlocProvider(create: (context) => HostCubit()), ], - child: SimpleHomeView(filePath: filePath), + child: SimpleHomeView(), ); } } diff --git a/lib/home/view/host_page.dart b/lib/home/view/host_page.dart index 7e0caf2..ce7a43b 100644 --- a/lib/home/view/host_page.dart +++ b/lib/home/view/host_page.dart @@ -208,7 +208,7 @@ class _HostPageState extends State { icon: const Icon(Icons.chevron_right), tooltip: AppLocalizations.of(context)!.next, ), - const SizedBox(width: 16), + const SizedBox(width: 8), IconButton( onPressed: hosts.length == 1 ? null diff --git a/lib/home/view/host_text.dart b/lib/home/view/host_text.dart index 99fa761..7984636 100644 --- a/lib/home/view/host_text.dart +++ b/lib/home/view/host_text.dart @@ -82,17 +82,17 @@ class _HostTextState extends State { if (state is HostUndo || state is HostInitial || state is HostHistory) { // 销毁旧的控制器 textEditingController.dispose(); - + // 创建新的控制器 textEditingController = HostTextEditingController(); textEditingController.text = state.data.fileContent; - + // 重新添加监听器 final hostCubit = context.read(); textEditingController.addListener(() { hostCubit.updateFileContent(textEditingController.text); }); - + _scrollController.jumpTo(0); } final hostCubit = context.read(); @@ -147,7 +147,8 @@ class _HostTextState extends State { isControl && event is KeyDownEvent && !state.data.isSave) { - hostCubit.onTextSave(); + hostCubit.onTextSave( + context, textEditingController.text); } }, child: LayoutBuilder( @@ -170,11 +171,13 @@ class _HostTextState extends State { scrollController: _textScrollController, maxLines: null, expands: true, - scrollPhysics: const ClampingScrollPhysics(), + scrollPhysics: + const ClampingScrollPhysics(), decoration: InputDecoration( border: InputBorder.none, - hintText: AppLocalizations.of(context)! - .create_host_template), + hintText: + AppLocalizations.of(context)! + .create_host_template), ), ), ), diff --git a/lib/home/view/simple_home_view.dart b/lib/home/view/simple_home_view.dart index c7a55f0..9d24338 100644 --- a/lib/home/view/simple_home_view.dart +++ b/lib/home/view/simple_home_view.dart @@ -11,20 +11,20 @@ import 'package:hosts/home/view/home_app_bar.dart'; import 'package:hosts/home/view/host_page.dart'; import 'package:hosts/home/view/host_view.dart'; import 'package:hosts/l10n/app_localizations.dart'; +import 'package:hosts/model/global_settings.dart'; import 'package:hosts/model/host_file.dart'; -import 'package:hosts/util/file_manager.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart'; class SimpleHomeView extends StatelessWidget { - final String filePath; - - const SimpleHomeView({super.key, required this.filePath}); + const SimpleHomeView({super.key}); @override Widget build(BuildContext context) { - if (!kIsWeb && filePath.isNotEmpty && File(filePath).existsSync()) { - context.read().fromText(File(filePath).readAsStringSync()); + if (!kIsWeb && + GlobalSettings().filePath != null && + File(GlobalSettings().filePath!).existsSync()) { + context + .read() + .fromText(File(GlobalSettings().filePath!).readAsStringSync()); } return Scaffold( @@ -90,81 +90,11 @@ class SimpleHomeView extends StatelessWidget { Future saveHost(BuildContext context, String hostContent) async { final hostCubit = context.read(); - - if (kIsWeb) { - final String tempContent = hostContent.replaceAll("\"", "\\\""); - await showDialog( - context: context, - builder: (dialogContext) => AlertDialog( - title: const Text("保存"), - content: SizedBox( - width: MediaQuery.of(dialogContext).size.width * 0.5, - child: SelectableText(hostContent), - ), - actions: [ - TextButton( - onPressed: () => writeClipboard( - 'echo "$tempContent" > /etc/hosts', - tempContent, - context, - hostCubit, - ), - child: const Text("Linux(echo)")), - TextButton( - onPressed: () { - final String systemHostPath = p.joinAll([ - "C:", - "Windows", - "System32", - "drivers", - "etc", - "hosts" - ]); - final String content = hostContent - .split("\n") - .map((item) => 'echo $item') - .join("\n"); - writeClipboard( - '(\n$content\n) > $systemHostPath', - hostContent, - context, - hostCubit, - ); - }, - child: const Text("Windows(echo)")), - TextButton( - onPressed: () => writeClipboard( - 'echo "$tempContent" > /etc/hosts', - tempContent, - context, - hostCubit, - ), - child: const Text("MacOS(echo)")), - ], - )); - return true; + final result = await hostCubit.saveHost(context, GlobalSettings().filePath!, hostContent); + if (result) { + hostCubit.fromText(hostContent); } - - final File file = File(filePath); - try { - await file.writeAsString(hostContent); - } catch (e) { - try { - final Directory cacheDirectory = await getApplicationCacheDirectory(); - final File cacheFile = File(p.join(cacheDirectory.path, 'hosts')); - await cacheFile.writeAsString(hostContent); - - await FileManager() - .writeFileWithAdminPrivileges(cacheFile.path, filePath); - } catch (e) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(AppLocalizations.of(context)!.error_save_fail))); - return false; - } - } - - hostCubit.fromText(hostContent); - return true; + return result; } void writeClipboard(String hostContent, String defaultContent, diff --git a/lib/model/global_settings.dart b/lib/model/global_settings.dart index 9e4d516..e551297 100644 --- a/lib/model/global_settings.dart +++ b/lib/model/global_settings.dart @@ -7,5 +7,5 @@ class GlobalSettings { GlobalSettings._internal(); - bool isSimple = false; + String? filePath; } diff --git a/test/widget_test.dart b/test/widget_test.dart index bb9962f..68a3681 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -7,12 +7,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:hosts/app.dart'; import 'package:hosts/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp(fileContent: '',)); + await tester.pumpWidget(HostsApp('',)); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); From 2b3bcc44fbc4bc9b10fbfdefc28db7791dc41cc2 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sun, 27 Jul 2025 15:03:43 +0800 Subject: [PATCH 47/54] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=20flutter?= =?UTF-8?q?=5Fbackground=20=E4=BE=9D=E8=B5=96=E5=B9=B6=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=90=AF=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从 pubspec.yaml 移除 flutter_background 依赖 - 删除 main.dart 中的后台服务初始化逻辑 - 简化应用启动流程,提高启动速度 - 减少应用包体积 --- lib/main.dart | 27 --------------------------- pubspec.lock | 8 -------- pubspec.yaml | 3 --- 3 files changed, 38 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index aa7fcf5..fcc91a9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,7 @@ import 'dart:io'; import 'package:bloc/bloc.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; -import 'package:flutter_background/flutter_background.dart'; import 'package:hosts/app.dart'; import 'package:hosts/host_observer.dart'; @@ -11,9 +9,6 @@ void main(List args) async { WidgetsFlutterBinding.ensureInitialized(); Bloc.observer = const HostObserver(); - // 初始化后台运行配置 - await _initializeBackgroundService(); - final Iterable files = args.where((path) => File(path).existsSync()); if (files.isNotEmpty) { runApp(HostsApp(files.first)); @@ -21,25 +16,3 @@ void main(List args) async { runApp(HostsApp("")); } } - -Future _initializeBackgroundService() async { - // Web环境不支持后台服务 - if (kIsWeb) { - return; - } - - // 只在Android平台初始化后台服务 - if (!Platform.isAndroid) { - return; - } - - const androidConfig = FlutterBackgroundAndroidConfig( - notificationTitle: "Hosts 编辑器", - notificationText: "正在后台运行 hosts 服务", - notificationImportance: AndroidNotificationImportance.normal, - notificationIcon: - AndroidResource(name: 'background_icon', defType: 'drawable'), - ); - - await FlutterBackground.initialize(androidConfig: androidConfig); -} diff --git a/pubspec.lock b/pubspec.lock index 5bc4649..ddf464f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -254,14 +254,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.2" - flutter_background: - dependency: "direct main" - description: - name: flutter_background - sha256: "8dad66e3102da2b4046cc3adcf70625578809827170bd78e36e5890c074287d2" - url: "https://pub.dev" - source: hosted - version: "1.3.0+1" flutter_bloc: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f0e75b4..67dddbc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,9 +52,6 @@ dependencies: # 应用信息 package_info_plus: ^8.1.0 - # 后台运行 - flutter_background: ^1.3.0+1 - # 文本差异对比 diff_match_patch: ^0.4.1 From 6d5e34f0b281c3e529416e370ebb77f8610658f7 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sun, 27 Jul 2025 15:45:35 +0800 Subject: [PATCH 48/54] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=94=AE?= =?UTF-8?q?=E7=9B=98=E5=8F=AF=E8=A7=81=E6=80=A7=E6=A3=80=E6=B5=8B=E5=92=8C?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E5=B8=83=E5=B1=80=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 flutter_keyboard_visibility 依赖用于键盘状态检测 - 在移动平台键盘展开时动态添加 TextField 顶部内边距 - 优化文本编辑器在键盘显示/隐藏时的用户体验 - 使用专门的键盘检测库替代 MediaQuery 方式 --- lib/home/view/home_view.dart | 4 +- lib/home/view/host_text.dart | 254 ++++++++++++++++++----------------- pubspec.lock | 48 +++++++ pubspec.yaml | 3 + 4 files changed, 187 insertions(+), 122 deletions(-) diff --git a/lib/home/view/home_view.dart b/lib/home/view/home_view.dart index e1c4782..5fc5904 100644 --- a/lib/home/view/home_view.dart +++ b/lib/home/view/home_view.dart @@ -29,7 +29,6 @@ class _HomeViewState extends State { @override Widget build(BuildContext context) { - return Scaffold( key: _scaffoldKey, drawer: @@ -68,7 +67,8 @@ class _HomeViewState extends State { } if (state is HomeAdvancedSettings) { - if (state.data.advancedSettingsEnum == AdvancedSettingsEnum.Open) { + if (state.data.advancedSettingsEnum == + AdvancedSettingsEnum.Open) { WidgetsBinding.instance.addPostFrameCallback((_) { _scaffoldKey.currentState?.openDrawer(); }); diff --git a/lib/home/view/host_text.dart b/lib/home/view/host_text.dart index 7984636..70a83c1 100644 --- a/lib/home/view/host_text.dart +++ b/lib/home/view/host_text.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/widget/host_text_editing_controller.dart'; @@ -77,137 +78,150 @@ class _HostTextState extends State { @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (BuildContext context, state) { - if (state is HostUndo || state is HostInitial || state is HostHistory) { - // 销毁旧的控制器 - textEditingController.dispose(); + return KeyboardVisibilityBuilder( + builder: (context, isKeyboardVisible) { + return BlocBuilder( + builder: (BuildContext context, state) { + if (state is HostUndo || state is HostInitial || state is HostHistory) { + // 销毁旧的控制器 + textEditingController.dispose(); - // 创建新的控制器 - textEditingController = HostTextEditingController(); - textEditingController.text = state.data.fileContent; + // 创建新的控制器 + textEditingController = HostTextEditingController(); + textEditingController.text = state.data.fileContent; - // 重新添加监听器 - final hostCubit = context.read(); - textEditingController.addListener(() { - hostCubit.updateFileContent(textEditingController.text); - }); + // 重新添加监听器 + final hostCubit = context.read(); + textEditingController.addListener(() { + hostCubit.updateFileContent(textEditingController.text); + }); - _scrollController.jumpTo(0); - } - final hostCubit = context.read(); - return Column( - children: [ - Expanded( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RowLineWidget( - textEditingController: textEditingController, - context: context, - textFieldContainerKey: _textFieldContainerKey, - scrollController: _scrollController, - ), - Expanded( - key: _textFieldContainerKey, - child: GestureDetector( - onTap: () { - _focusNode.requestFocus(); - }, - child: KeyboardListener( - focusNode: _focusNode, - onKeyEvent: (event) { - List logicalKeys = []; - if (Platform.isMacOS) { - logicalKeys = [ - LogicalKeyboardKey.metaLeft, - LogicalKeyboardKey.metaRight - ]; - } else { - logicalKeys = [ - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.controlRight - ]; - } - if (logicalKeys.contains(event.logicalKey)) { - if (isControl) { - isControl = false; - } else { - isControl = true; - } - } - if (event.logicalKey == LogicalKeyboardKey.slash && - isControl && - event is KeyDownEvent) { - textEditingController.updateUseStatus( - textEditingController.selection); - } + _scrollController.jumpTo(0); + } + final hostCubit = context.read(); + return Column( + children: [ + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric( + vertical: Platform.isIOS || Platform.isAndroid ? 4 : 0, + ), + child: RowLineWidget( + textEditingController: textEditingController, + context: context, + textFieldContainerKey: _textFieldContainerKey, + scrollController: _scrollController, + ), + ), + Expanded( + key: _textFieldContainerKey, + child: GestureDetector( + onTap: () { + _focusNode.requestFocus(); + }, + child: KeyboardListener( + focusNode: _focusNode, + onKeyEvent: (event) { + List logicalKeys = []; + if (Platform.isMacOS) { + logicalKeys = [ + LogicalKeyboardKey.metaLeft, + LogicalKeyboardKey.metaRight + ]; + } else { + logicalKeys = [ + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.controlRight + ]; + } + if (logicalKeys.contains(event.logicalKey)) { + if (isControl) { + isControl = false; + } else { + isControl = true; + } + } + if (event.logicalKey == LogicalKeyboardKey.slash && + isControl && + event is KeyDownEvent) { + textEditingController.updateUseStatus( + textEditingController.selection); + } - if (event.logicalKey == LogicalKeyboardKey.keyS && - isControl && - event is KeyDownEvent && - !state.data.isSave) { - hostCubit.onTextSave( - context, textEditingController.text); - } - }, - child: LayoutBuilder( - builder: (context, constraints) { - return ScrollConfiguration( - behavior: ScrollConfiguration.of(context) - .copyWith(scrollbars: false), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: constraints.maxWidth, - minHeight: constraints.maxHeight, - ), - child: IntrinsicWidth( - child: SizedBox( - height: constraints.maxHeight, - child: TextField( - controller: textEditingController, - scrollController: _textScrollController, - maxLines: null, - expands: true, - scrollPhysics: - const ClampingScrollPhysics(), - decoration: InputDecoration( - border: InputBorder.none, - hintText: - AppLocalizations.of(context)! - .create_host_template), + if (event.logicalKey == LogicalKeyboardKey.keyS && + isControl && + event is KeyDownEvent && + !state.data.isSave) { + hostCubit.onTextSave( + context, textEditingController.text); + } + }, + child: LayoutBuilder( + builder: (context, constraints) { + return ScrollConfiguration( + behavior: ScrollConfiguration.of(context) + .copyWith(scrollbars: false), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: constraints.maxWidth, + minHeight: constraints.maxHeight, + ), + child: IntrinsicWidth( + child: Padding( + padding: EdgeInsets.only( + top: (Platform.isIOS || Platform.isAndroid) && + isKeyboardVisible && !state.data.isSave ? 16 : 0, + ), + child: TextField( + controller: textEditingController, + scrollController: _textScrollController, + maxLines: null, + expands: true, + scrollPadding: EdgeInsets.zero, + scrollPhysics: + const ClampingScrollPhysics(), + decoration: InputDecoration( + border: InputBorder.none, + hintText: + AppLocalizations.of(context)! + .create_host_template), + ), + ), ), ), ), - ), - ), - ); - }, + ); + }, + ), + ), ), ), - ), - ), - ], - ), - ), - Container( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), - child: Row( - children: [ - Text( - "${AppLocalizations.of(context)!.current_line}${textEditingController.countNewlines(textEditingController.text.substring(0, textEditingController.selection.start > 0 ? textEditingController.selection.start : 0)) + 1}", + ], ), - const SizedBox( - width: 8, + ), + Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Row( + children: [ + Text( + "${AppLocalizations.of(context)!.current_line}${textEditingController.countNewlines(textEditingController.text.substring(0, textEditingController.selection.start > 0 ? textEditingController.selection.start : 0)) + 1}", + ), + const SizedBox( + width: 8, + ), + Text( + "${AppLocalizations.of(context)!.total_lines}${textEditingController.countNewlines(textEditingController.text) + 1}"), + ], ), - Text( - "${AppLocalizations.of(context)!.total_lines}${textEditingController.countNewlines(textEditingController.text) + 1}"), - ], - ), - ) - ], + ) + ], + ); + }, ); }, ); diff --git a/pubspec.lock b/pubspec.lock index ddf464f..590f396 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -262,6 +262,54 @@ packages: url: "https://pub.dev" source: hosted version: "9.1.1" + flutter_keyboard_visibility: + dependency: "direct main" + description: + name: flutter_keyboard_visibility + sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_keyboard_visibility_linux: + dependency: transitive + description: + name: flutter_keyboard_visibility_linux + sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_macos: + dependency: transitive + description: + name: flutter_keyboard_visibility_macos + sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_windows: + dependency: transitive + description: + name: flutter_keyboard_visibility_windows + sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_lints: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 67dddbc..34b3809 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,9 @@ dependencies: # 文本差异对比 diff_match_patch: ^0.4.1 + # 键盘可见性检测 + flutter_keyboard_visibility: ^6.0.0 + dev_dependencies: flutter_test: sdk: flutter From bf55c85a653f97ba421a21b10e76a9468a0e20e9 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sun, 27 Jul 2025 16:42:27 +0800 Subject: [PATCH 49/54] =?UTF-8?q?feat:=20=E7=A6=81=E7=94=A8=E8=87=AA?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=92=8C=E5=AE=89=E8=A3=85=E5=90=8E=E5=90=AF?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- windows/packaging/exe/make_config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/windows/packaging/exe/make_config.yaml b/windows/packaging/exe/make_config.yaml index 037cc9c..64355af 100644 --- a/windows/packaging/exe/make_config.yaml +++ b/windows/packaging/exe/make_config.yaml @@ -5,6 +5,10 @@ publisher: Webb publisher_url: https://github.com/webb-l/hosts display_name: Hosts Editor create_desktop_icon: true +# 禁用自启动和安装后启动 +start_menu_folder: false +auto_start: false +run_after_install: false # See: https://jrsoftware.org/ishelp/index.php?topic=setup_defaultdirname # install_dir_name: "C:\\Program Files\\HostsEditor" # 这里的路径是相对于项目根目录的路径; 图标格式必须是ico格式,不能是png或其它 From 41827e78e70d84ae5f854bed9ad363db04da6c03 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sun, 27 Jul 2025 16:48:32 +0800 Subject: [PATCH 50/54] =?UTF-8?q?feat:=20=E7=A6=81=E7=94=A8=E8=87=AA?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=92=8C=E5=AE=89=E8=A3=85=E5=90=8E=E5=90=AF?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- windows/packaging/exe/inno_setup.iss | 48 ++++++++++++++++++++++++++ windows/packaging/exe/make_config.yaml | 5 +-- 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 windows/packaging/exe/inno_setup.iss diff --git a/windows/packaging/exe/inno_setup.iss b/windows/packaging/exe/inno_setup.iss new file mode 100644 index 0000000..1376104 --- /dev/null +++ b/windows/packaging/exe/inno_setup.iss @@ -0,0 +1,48 @@ +[Setup] +AppId={{app_id}} +AppName={{display_name}} +AppVersion={{version}} +AppPublisher={{publisher}} +AppPublisherURL={{publisher_url}} +AppSupportURL={{support_url}} +AppUpdatesURL={{updates_url}} +DefaultDirName={autopf}\{{display_name}} +DefaultGroupName={{display_name}} +AllowNoIcons=yes +LicenseFile={{license_file}} +SetupIconFile={{setup_icon_file}} +Compression=lzma +SolidCompression=yes +WizardStyle=modern +PrivilegesRequired=admin +DisableProgramGroupPage=yes +; 禁用自动启动和安装后运行 +DisableStartupPrompt=yes +DisableFinishedPage=no +DisableReadyPage=no +ShowRunList=no + +[Languages] +{{#each locales}} +Name: "{{this}}"; MessagesFile: "compiler:Default.isl" +{{/each}} + +[Tasks] +{{#if create_desktop_icon}} +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked +{{/if}} + +[Files] +Source: "{{files_path}}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{group}\{{display_name}}"; Filename: "{app}\{{executable_name}}" +{{#if create_desktop_icon}} +Name: "{autodesktop}\{{display_name}}"; Filename: "{app}\{{executable_name}}"; Tasks: desktopicon +{{/if}} + +[Run] +; 注意:这里故意留空,不添加任何运行项目,确保安装后不会自动启动应用 + +[Registry] +; 不添加任何自启动注册表项 \ No newline at end of file diff --git a/windows/packaging/exe/make_config.yaml b/windows/packaging/exe/make_config.yaml index 64355af..706491e 100644 --- a/windows/packaging/exe/make_config.yaml +++ b/windows/packaging/exe/make_config.yaml @@ -5,10 +5,7 @@ publisher: Webb publisher_url: https://github.com/webb-l/hosts display_name: Hosts Editor create_desktop_icon: true -# 禁用自启动和安装后启动 -start_menu_folder: false -auto_start: false -run_after_install: false +script_template: inno_setup.iss # See: https://jrsoftware.org/ishelp/index.php?topic=setup_defaultdirname # install_dir_name: "C:\\Program Files\\HostsEditor" # 这里的路径是相对于项目根目录的路径; 图标格式必须是ico格式,不能是png或其它 From ff3164d6ec7682a516ed5d2cfe7a0163451f1c6e Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sun, 27 Jul 2025 16:54:26 +0800 Subject: [PATCH 51/54] =?UTF-8?q?feat:=20=E7=A6=81=E7=94=A8=E8=87=AA?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=92=8C=E5=AE=89=E8=A3=85=E5=90=8E=E5=90=AF?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- windows/packaging/exe/inno_setup.iss | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/windows/packaging/exe/inno_setup.iss b/windows/packaging/exe/inno_setup.iss index 1376104..e7d8d0d 100644 --- a/windows/packaging/exe/inno_setup.iss +++ b/windows/packaging/exe/inno_setup.iss @@ -23,23 +23,18 @@ DisableReadyPage=no ShowRunList=no [Languages] -{{#each locales}} -Name: "{{this}}"; MessagesFile: "compiler:Default.isl" -{{/each}} +Name: "en"; MessagesFile: "compiler:Default.isl" +Name: "zh"; MessagesFile: "compiler:Languages\ChineseSimplified.isl" [Tasks] -{{#if create_desktop_icon}} Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked -{{/if}} [Files] Source: "{{files_path}}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs [Icons] Name: "{group}\{{display_name}}"; Filename: "{app}\{{executable_name}}" -{{#if create_desktop_icon}} Name: "{autodesktop}\{{display_name}}"; Filename: "{app}\{{executable_name}}"; Tasks: desktopicon -{{/if}} [Run] ; 注意:这里故意留空,不添加任何运行项目,确保安装后不会自动启动应用 From 343f7de8b265d71e5bce79713738d1f076435d04 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sun, 27 Jul 2025 17:12:21 +0800 Subject: [PATCH 52/54] =?UTF-8?q?feat:=20=E7=A6=81=E7=94=A8=E8=87=AA?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=92=8C=E5=AE=89=E8=A3=85=E5=90=8E=E5=90=AF?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- windows/packaging/exe/inno_setup.iss | 43 -------------------------- windows/packaging/exe/make_config.yaml | 1 - 2 files changed, 44 deletions(-) delete mode 100644 windows/packaging/exe/inno_setup.iss diff --git a/windows/packaging/exe/inno_setup.iss b/windows/packaging/exe/inno_setup.iss deleted file mode 100644 index e7d8d0d..0000000 --- a/windows/packaging/exe/inno_setup.iss +++ /dev/null @@ -1,43 +0,0 @@ -[Setup] -AppId={{app_id}} -AppName={{display_name}} -AppVersion={{version}} -AppPublisher={{publisher}} -AppPublisherURL={{publisher_url}} -AppSupportURL={{support_url}} -AppUpdatesURL={{updates_url}} -DefaultDirName={autopf}\{{display_name}} -DefaultGroupName={{display_name}} -AllowNoIcons=yes -LicenseFile={{license_file}} -SetupIconFile={{setup_icon_file}} -Compression=lzma -SolidCompression=yes -WizardStyle=modern -PrivilegesRequired=admin -DisableProgramGroupPage=yes -; 禁用自动启动和安装后运行 -DisableStartupPrompt=yes -DisableFinishedPage=no -DisableReadyPage=no -ShowRunList=no - -[Languages] -Name: "en"; MessagesFile: "compiler:Default.isl" -Name: "zh"; MessagesFile: "compiler:Languages\ChineseSimplified.isl" - -[Tasks] -Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked - -[Files] -Source: "{{files_path}}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs - -[Icons] -Name: "{group}\{{display_name}}"; Filename: "{app}\{{executable_name}}" -Name: "{autodesktop}\{{display_name}}"; Filename: "{app}\{{executable_name}}"; Tasks: desktopicon - -[Run] -; 注意:这里故意留空,不添加任何运行项目,确保安装后不会自动启动应用 - -[Registry] -; 不添加任何自启动注册表项 \ No newline at end of file diff --git a/windows/packaging/exe/make_config.yaml b/windows/packaging/exe/make_config.yaml index 706491e..037cc9c 100644 --- a/windows/packaging/exe/make_config.yaml +++ b/windows/packaging/exe/make_config.yaml @@ -5,7 +5,6 @@ publisher: Webb publisher_url: https://github.com/webb-l/hosts display_name: Hosts Editor create_desktop_icon: true -script_template: inno_setup.iss # See: https://jrsoftware.org/ishelp/index.php?topic=setup_defaultdirname # install_dir_name: "C:\\Program Files\\HostsEditor" # 这里的路径是相对于项目根目录的路径; 图标格式必须是ico格式,不能是png或其它 From f7a65dc2f107d5216597bb4980e730fa6c712cd2 Mon Sep 17 00:00:00 2001 From: webb <822028533@qq.com> Date: Sun, 27 Jul 2025 22:27:10 +0800 Subject: [PATCH 53/54] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E8=B5=84=E6=BA=90=E5=92=8CREADME=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/flutter_web_build.yml | 4 ++-- README.md | 8 ++++++-- image/1.png | Bin 70646 -> 67794 bytes image/2.png | Bin 62509 -> 62318 bytes image/3.png | Bin 81809 -> 60828 bytes image/4.png | Bin 54133 -> 37900 bytes image/7.png | Bin 0 -> 73791 bytes 7 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 image/7.png diff --git a/.github/workflows/flutter_web_build.yml b/.github/workflows/flutter_web_build.yml index 8c64d2d..42cc147 100644 --- a/.github/workflows/flutter_web_build.yml +++ b/.github/workflows/flutter_web_build.yml @@ -13,12 +13,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install Flutter uses: subosito/flutter-action@v2 with: - flutter-version: '3.24.3' + flutter-version: '3.32.8' channel: 'stable' - name: Build web diff --git a/README.md b/README.md index 1f787ae..099328d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Hosts Editor 是一个使用 Flutter 开发的应用程序,旨在简化 Linux ## 特性 -- **跨平台支持**:虽然目前只支持 Linux、Windows、Web 系统,但未来计划扩展到其他操作系统。 +- **跨平台支持**:虽然目前只支持 Linux、Windows、MacOS、Web 系统,但未来计划扩展到其他操作系统。 - **直观的用户界面**:使用 Flutter 构建,提供流畅的用户体验。 - **实时预览**:在编辑 hosts 文件时,实时查看更改效果。 - **安全性**:确保对 hosts 文件的修改是安全的,避免不必要的错误。 @@ -35,4 +35,8 @@ hosts /etc/hosts ### 测试是否配置成功 -![6.png](image/6.png) \ No newline at end of file +![6.png](image/6.png) + +### 导入导出 + +![7.png](image/7.png) \ No newline at end of file diff --git a/image/1.png b/image/1.png index b56dc2b79075dfe2ad35600f31bd873a75d4b9c0..0a15fc67792aeb9a38818e83b269053c2bb5094f 100644 GIT binary patch literal 67794 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�$}@W&{?3|-*J9SQ7SG@!7BQe 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

PMXU9r1b4vmqyE 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 B9o7H< diff --git a/image/4.png b/image/4.png index 22f2129da78196240131dcc95bc1137852812c5b..d04e496b7a8b75137c70314932da5dc746ad1741 100644 GIT binary patch literal 37900 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

vA*m$*(@Ojqi0P8*~$emc74e=9x)kR~maq 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 Date: Thu, 31 Jul 2025 19:00:41 +0800 Subject: [PATCH 54/54] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E5=8C=85=E5=92=8C=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 flutter_keyboard_visibility 替换为 flutter_keyboard_visibility_temp_fork - 简化 ServerManager 中的 null 处理逻辑 - 移除 FileManager 中未使用的 exitCode 变量 - 清理 RowLineWidget 中的无用变量和计算 --- lib/home/view/host_text.dart | 2 +- lib/server/server_manager.dart | 4 ++-- lib/util/file_manager.dart | 2 +- lib/widget/row_line_widget.dart | 6 ------ pubspec.lock | 18 +++++------------- pubspec.yaml | 2 +- 6 files changed, 10 insertions(+), 24 deletions(-) diff --git a/lib/home/view/host_text.dart b/lib/home/view/host_text.dart index 70a83c1..a442d75 100644 --- a/lib/home/view/host_text.dart +++ b/lib/home/view/host_text.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; +import 'package:flutter_keyboard_visibility_temp_fork/flutter_keyboard_visibility_temp_fork.dart'; import 'package:hosts/home/cubit/host_cubit.dart'; import 'package:hosts/l10n/app_localizations.dart'; import 'package:hosts/widget/host_text_editing_controller.dart'; diff --git a/lib/server/server_manager.dart b/lib/server/server_manager.dart index 2a26867..eeefa0d 100755 --- a/lib/server/server_manager.dart +++ b/lib/server/server_manager.dart @@ -27,7 +27,7 @@ class ServerManager { /// 检查服务器是否启用 Future isServerEnabled() async { - return await _settingsManager.getBool(_serverEnabledKey) ?? false; + return await _settingsManager.getBool(_serverEnabledKey); } /// 设置服务器启用状态 @@ -57,7 +57,7 @@ class ServerManager { /// 获取自动启动设置 Future isAutoStartEnabled() async { - return await _settingsManager.getBool(_serverAutoStartKey) ?? false; + return await _settingsManager.getBool(_serverAutoStartKey); } /// 设置自动启动 diff --git a/lib/util/file_manager.dart b/lib/util/file_manager.dart index 3ee78c6..354c9e2 100755 --- a/lib/util/file_manager.dart +++ b/lib/util/file_manager.dart @@ -259,7 +259,7 @@ class FileManager { }); // 等待进程结束 - int exitCode = await process.exitCode; + await process.exitCode; // 检查退出代码,如果非零则抛出异常 if (errorMessage.isNotEmpty) { diff --git a/lib/widget/row_line_widget.dart b/lib/widget/row_line_widget.dart index dcad058..173a09e 100644 --- a/lib/widget/row_line_widget.dart +++ b/lib/widget/row_line_widget.dart @@ -25,12 +25,6 @@ class RowLineWidget extends StatelessWidget { } Widget buildRowLine() { - double textFieldContainerWidth = 0; - final RenderBox? renderBox = - textFieldContainerKey.currentContext?.findRenderObject() as RenderBox?; - if (renderBox != null) { - textFieldContainerWidth = renderBox.size.width; - } final TextStyle? titleMedium = Theme.of(context).textTheme.titleMedium; final TextSelection textSelection = textEditingController.selection; final List lines = textEditingController.text.split('\n'); diff --git a/pubspec.lock b/pubspec.lock index 590f396..9ac63f9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -262,14 +262,6 @@ packages: url: "https://pub.dev" source: hosted version: "9.1.1" - flutter_keyboard_visibility: - dependency: "direct main" - description: - name: flutter_keyboard_visibility - sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" - url: "https://pub.dev" - source: hosted - version: "6.0.0" flutter_keyboard_visibility_linux: dependency: transitive description: @@ -294,14 +286,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - flutter_keyboard_visibility_web: - dependency: transitive + flutter_keyboard_visibility_temp_fork: + dependency: "direct main" description: - name: flutter_keyboard_visibility_web - sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + name: flutter_keyboard_visibility_temp_fork + sha256: e3d02900640fbc1129245540db16944a0898b8be81694f4bf04b6c985bed9048 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "0.1.5" flutter_keyboard_visibility_windows: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 34b3809..1844076 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,7 +56,7 @@ dependencies: diff_match_patch: ^0.4.1 # 键盘可见性检测 - flutter_keyboard_visibility: ^6.0.0 + flutter_keyboard_visibility_temp_fork: ^0.1.5 dev_dependencies: flutter_test:

qUS*(M(9# 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�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 literal 62509 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 diff --git a/image/3.png b/image/3.png index e8edb6512b94f698ca8fa2a23a2f66944c787aad..c674531acee22cfc6c56dbe629169f274e7dab75 100644 GIT binary patch literal 60828 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�mJ-%O^+L*i6>D^}q9i7qk*C5l4!H?Y;aYDr9~c0dG{;1vFG=@r15`OXy5cHK1Jg6^fZL& zM)QceZ1+#b+a+g(u-CfoepRbkSsY+b|0pW@7*lfzB_lpMpvo%z&q+k!+Srcjt!p)OEU48jYHYlq z>s?z3v$ZAoB=+~k3;0RHRx*<$yk4@Unx}QNa`si}FLOZn&CV*6H>-(Er#-oRvgt1_D(bYlMZdVT$n}5^oS~K! zG}P8cZxh$84b;@s^sVUq)Rc$!hCQt!2YZ0!DKJ80=mq|L0^-7)5Qtvh-C=_YUI6so zB4mkA(t%AnvuH&HLod}~os1UwE=I>X9i2%YmUeW=+fC2BoBcWSetv%H>;R`;T~lKe z)0|(JbN9if2wg-3FjULTD36*}o_6N4M$-l+c^RKAaugWYe{r{ zKI`GOpn$;No)!WxhK7Lo)%?Q3yR`5d18QnB4QD&l6C+t6mD7Ix4pyptVj@;iFDlq! zuDX9^e){xD`VE@Mo}KXq(1Rui0suF6XBvDjXJutESJed{>IT-v#KhzVes^yhY6%BC zCp1O<9xM}}d53$#)`}U1waAAqP;G%5pcQ*?Y;0R-KX2P<-45bi+g~CgU=_B+8yq_i zjEFb9{hv6tKTk{L1tg~Y} zVe~+x(FlELu;DYT(P&>YPTpvS5b{Q73!)AB62`$J9gVg5b@x zx9qo{DXON`)}G=k#l<3$BHowXUVEyb>w~XQQc~<_X-R9v7S#GM^>A~Gl9D4L-kbHh zg9uk#QhE0QM{Oq|Ep+ZnnwaVLbb3go*ZtCxpsP~3N3{OeuZL>q7Zy%d(9+lg%Pio; zQr}U!xM=z%IC!+PToX?7R~7{|?+BHv7~0MQxcL3$!=W{+(E!!d)U-SHy4WIY86F