diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index d94a247..016ad70 100755
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -37,6 +37,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 53ae014..aed9fe8 100755
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -58,6 +58,11 @@
需要相册权限以支持图片上传功能
SERVICE_IDENTIFIER
$(SERVICE_IDENTIFIER)
+ LSApplicationQueriesSchemes
+
+ twitter
+ x-scheme-handler
+
UIApplicationSupportsIndirectInputEvents
UILaunchStoryboardName
diff --git a/lib/app/modules/hi_anti_lost/bindings/hi_anti_lost_binding.dart b/lib/app/modules/hi_anti_lost/bindings/hi_anti_lost_binding.dart
new file mode 100644
index 0000000..b286329
--- /dev/null
+++ b/lib/app/modules/hi_anti_lost/bindings/hi_anti_lost_binding.dart
@@ -0,0 +1,11 @@
+import 'package:get/get.dart';
+import '../controllers/hi_anti_lost_controller.dart';
+
+class HIAntiLostBinding extends Bindings {
+ @override
+ void dependencies() {
+ Get.lazyPut(
+ () => HIAntiLostController(),
+ );
+ }
+}
diff --git a/lib/app/modules/hi_anti_lost/controllers/hi_anti_lost_controller.dart b/lib/app/modules/hi_anti_lost/controllers/hi_anti_lost_controller.dart
new file mode 100644
index 0000000..9ba4b63
--- /dev/null
+++ b/lib/app/modules/hi_anti_lost/controllers/hi_anti_lost_controller.dart
@@ -0,0 +1,85 @@
+import 'dart:typed_data';
+import 'dart:ui' as ui;
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:get/get.dart';
+import 'package:gal/gal.dart';
+import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+class HIAntiLostController extends GetxController {
+ final GlobalKey repaintKey = GlobalKey();
+
+ Future saveImage() async {
+ KRCommonUtil.kr_showLoading(message: "正在保存...");
+ try {
+ // Check permission
+ final hasAccess = await Gal.hasAccess();
+ if (!hasAccess) {
+ await Gal.requestAccess();
+ }
+
+ // Capture image
+ RenderRepaintBoundary? boundary =
+ repaintKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
+
+ if (boundary == null) {
+ throw Exception("Cannot find boundary");
+ }
+
+ // Check if the boundary needs layout (sometimes Offstage needs a frame)
+ if (boundary.debugNeedsPaint) {
+ await Future.delayed(const Duration(milliseconds: 20));
+ boundary = repaintKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
+ }
+
+ ui.Image image = await boundary!.toImage(pixelRatio: 3.0);
+ ByteData? byteData =
+ await image.toByteData(format: ui.ImageByteFormat.png);
+
+ if (byteData != null) {
+ final Uint8List pngBytes = byteData.buffer.asUint8List();
+ await Gal.putImageBytes(pngBytes);
+ KRCommonUtil.kr_showToast("保存成功");
+ } else {
+ KRCommonUtil.kr_showToast("生成图片失败");
+ }
+ } catch (e) {
+ debugPrint("Save image error: $e");
+ if (e is GalException) {
+ KRCommonUtil.kr_showToast("保存失败: No Permission");
+ } else {
+ KRCommonUtil.kr_showToast("保存失败");
+ }
+ } finally {
+ KRCommonUtil.kr_hideLoading();
+ }
+ }
+
+ void openTwitter() async {
+ // Try Deep Link first
+ final Uri appUrl = Uri.parse('twitter://user?screen_name=hifasttech');
+ final Uri webUrl = Uri.parse('https://x.com/hifasttech');
+
+ try {
+ if (await canLaunchUrl(appUrl)) {
+ await launchUrl(appUrl);
+ } else {
+ // Fallback to web
+ if (!await launchUrl(webUrl, mode: LaunchMode.externalApplication)) {
+ KRCommonUtil.kr_showToast("无法打开链接");
+ }
+ }
+ } catch (e) {
+ debugPrint("Open Twitter error: $e");
+ // Fallback to web if anything goes wrong
+ try {
+ if (!await launchUrl(webUrl, mode: LaunchMode.externalApplication)) {
+ KRCommonUtil.kr_showToast("无法打开链接");
+ }
+ } catch (e) {
+ KRCommonUtil.kr_showToast("无法打开链接");
+ }
+ }
+ }
+}
diff --git a/lib/app/modules/hi_anti_lost/views/hi_anti_lost_view.dart b/lib/app/modules/hi_anti_lost/views/hi_anti_lost_view.dart
new file mode 100644
index 0000000..ad8c498
--- /dev/null
+++ b/lib/app/modules/hi_anti_lost/views/hi_anti_lost_view.dart
@@ -0,0 +1,152 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart';
+import 'package:qr_flutter/qr_flutter.dart';
+import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
+import 'package:kaer_with_panels/app/modules/hi_anti_lost/controllers/hi_anti_lost_controller.dart';
+import 'package:kaer_with_panels/app/modules/hi_anti_lost/widgets/hi_anti_lost_share_card.dart';
+
+class HIAntiLostView extends GetView {
+ const HIAntiLostView({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return HIBaseScaffold(
+ showBack: true,
+ title: null,
+ topContentAreaHeight: 0,
+ backgroundColor: Colors.black, // Dark background
+ child: Stack(
+ fit: StackFit.expand,
+ children: [
+ // 1. Visible Content
+ Column(
+ children: [
+ SizedBox(height: 60.h),
+ // Card Container
+ Container(
+ width: double.infinity,
+ margin: EdgeInsets.symmetric(horizontal: 40.w),
+ padding: EdgeInsets.symmetric(vertical: 16.w, horizontal: 0.w),
+ decoration: BoxDecoration(
+ color: Colors.black,
+ borderRadius: BorderRadius.circular(25.r),
+ border: Border.all(
+ color: Theme.of(context).primaryColor, width: 4),
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ '保存二维码,防止失联',
+ style: TextStyle(
+ color: Theme.of(context).primaryColor,
+ fontSize: 24.sp,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ SizedBox(height: 25.h),
+ Text(
+ '建议保存Hi快VPN防丢二维码到相册\n永久保障您的互联网自由',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Theme.of(context).primaryColor,
+ fontSize: 14.sp,
+ height: 1.5,
+ ),
+ ),
+ SizedBox(height: 10.h),
+ // QR Code
+ LayoutBuilder(builder: (context, constraints) {
+ // Use a reasonable size for the QR code
+ return Container(
+ width: 124.w,
+ height: 124.w,
+ child: QrImageView(
+ data:
+ 'https://github.com/hi-vpn/hi-client', // Replace with real URL
+ version: QrVersions.auto,
+ backgroundColor: Colors.transparent,
+ eyeStyle: QrEyeStyle(
+ eyeShape: QrEyeShape.square,
+ color: Theme.of(context).primaryColor,
+ ),
+ dataModuleStyle: QrDataModuleStyle(
+ dataModuleShape: QrDataModuleShape.square,
+ color: Theme.of(context).primaryColor,
+ ),
+ // center image
+ embeddedImage:
+ const AssetImage('assets/images/kr-logo.png'),
+ embeddedImageStyle: QrEmbeddedImageStyle(
+ size: Size(36.w, 36.w),
+ ),
+ ),
+ );
+ }),
+ ],
+ ),
+ ),
+
+ SizedBox(height: 30.h),
+
+ // Save Button
+ GestureDetector(
+ onTap: controller.saveImage,
+ child: Container(
+ width: double.infinity,
+ margin: EdgeInsets.symmetric(horizontal: 40.w),
+ height: 44.h,
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ color: Theme.of(context).primaryColor,
+ borderRadius: BorderRadius.circular(22.h),
+ ),
+ child: Text(
+ '保存到本地',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 16.sp,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ ),
+
+ SizedBox(height: 60.h),
+ ],
+ ),
+
+ // 2. Bottom Button (Follow us on X)
+ Positioned(
+ bottom: 40.h,
+ left: 0,
+ right: 0,
+ child: Center(
+ child: GestureDetector(
+ onTap: controller.openTwitter,
+ child: KrLocalImage(
+ imageName: 'followX',
+ width: 140,
+ height: 30,
+ imageType: ImageType
+ .png, // Assuming it is png as user said followX.png
+ ),
+ ),
+ ),
+ ),
+
+ // 3. Invisible Share Card for Generation
+ Positioned(
+ left: 10000,
+ child: RepaintBoundary(
+ key: controller.repaintKey,
+ child: const HIAntiLostShareCard(),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/app/modules/hi_anti_lost/widgets/hi_anti_lost_share_card.dart b/lib/app/modules/hi_anti_lost/widgets/hi_anti_lost_share_card.dart
new file mode 100644
index 0000000..e8bfa17
--- /dev/null
+++ b/lib/app/modules/hi_anti_lost/widgets/hi_anti_lost_share_card.dart
@@ -0,0 +1,89 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:qr_flutter/qr_flutter.dart';
+import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
+
+class HIAntiLostShareCard extends StatelessWidget {
+ const HIAntiLostShareCard({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ // Fixed width for the generated image, independent of screen size
+ final double cardWidth = 375.0;
+ final double cardHeight = 600.0; // Approx height
+
+ return Container(
+ width: cardWidth,
+ // minimal height or let it expand
+ padding: EdgeInsets.all(20),
+ color: Colors.black, // Dark background
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SizedBox(height: 40),
+ // Title
+ Text(
+ '保存二维码,防止失联',
+ style: TextStyle(
+ color: const Color(0xFFCCFF00),
+ fontSize: 24,
+ fontWeight: FontWeight.bold,
+ fontFamily: 'AlibabaPuHuiTi-Medium',
+ ),
+ ),
+ SizedBox(height: 20),
+ // Description
+ Text(
+ '建议保存Hi快VPN防丢二维码到相册\n永久保障您的互联网自由',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: const Color(0xFFCCFF00),
+ fontSize: 14,
+ height: 1.5,
+ fontFamily: 'AlibabaPuHuiTi-Regular',
+ ),
+ ),
+ SizedBox(height: 40),
+ // QR Code Card
+ Container(
+ padding: EdgeInsets.all(20),
+ decoration: BoxDecoration(
+ color: Colors.black,
+ border: Border.all(color: const Color(0xFFCCFF00), width: 3),
+ borderRadius: BorderRadius.circular(20),
+ ),
+ child: QrImageView(
+ data: 'https://github.com/hi-vpn/hi-client', // Replace with actual URL
+ version: QrVersions.auto,
+ size: 200,
+ backgroundColor: Colors.transparent,
+ eyeStyle: const QrEyeStyle(
+ eyeShape: QrEyeShape.square,
+ color: Color(0xFFCCFF00),
+ ),
+ dataModuleStyle: const QrDataModuleStyle(
+ dataModuleShape: QrDataModuleShape.square,
+ color: Color(0xFFCCFF00),
+ ),
+ embeddedImage: const AssetImage('assets/images/kr-logo.png'),
+ embeddedImageStyle: const QrEmbeddedImageStyle(
+ size: Size(50, 50),
+ ),
+ ),
+ ),
+ SizedBox(height: 40),
+ // Footer branding
+ Text(
+ 'HiVPN',
+ style: TextStyle(
+ color: Colors.white.withOpacity(0.5),
+ fontSize: 12,
+ letterSpacing: 2,
+ ),
+ ),
+ SizedBox(height: 20),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/app/modules/hi_menu/views/hi_menu_view.dart b/lib/app/modules/hi_menu/views/hi_menu_view.dart
index 341695a..cb4b01c 100755
--- a/lib/app/modules/hi_menu/views/hi_menu_view.dart
+++ b/lib/app/modules/hi_menu/views/hi_menu_view.dart
@@ -51,6 +51,11 @@ class HIMenuView extends GetView implements HasSwipeConfig {
title: '邀请好友',
route: Routes.KR_INVITE,
),
+ // const MenuItem(
+ // iconName: 'icon-6',
+ // title: '软件防丢',
+ // route: Routes.HI_ANTI_LOST,
+ // ),
MenuItem(
iconName: 'icon-5',
title: '在线客服',
diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart
index 9235877..d57a324 100755
--- a/lib/app/routes/app_pages.dart
+++ b/lib/app/routes/app_pages.dart
@@ -7,6 +7,8 @@ import 'package:kaer_with_panels/app/modules/hi_node_list/bindings/hi_node_list_
import 'package:kaer_with_panels/app/modules/hi_node_list/views/hi_page_node_view.dart';
import 'package:kaer_with_panels/app/modules/hi_user_info/bindings/hi_user_info_binding.dart';
import 'package:kaer_with_panels/app/modules/hi_user_info/views/hi_user_info_view.dart';
+import 'package:kaer_with_panels/app/modules/hi_anti_lost/bindings/hi_anti_lost_binding.dart';
+import 'package:kaer_with_panels/app/modules/hi_anti_lost/views/hi_anti_lost_view.dart';
import '../modules/kr_crisp_chat/bindings/kr_crisp_binding.dart';
import '../modules/kr_crisp_chat/views/kr_crisp_view.dart';
@@ -130,5 +132,11 @@ class AppPages {
popGesture: false,
arguments: {'showSubscriptionButton': true}, // 显示购买按钮
),
+ GetPage(
+ name: _Paths.HI_ANTI_LOST,
+ page: () => SwipeWrapper.detect(() => const HIAntiLostView()),
+ binding: HIAntiLostBinding(),
+ popGesture: false,
+ ),
];
}
diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart
index 51610d2..60534f3 100755
--- a/lib/app/routes/app_routes.dart
+++ b/lib/app/routes/app_routes.dart
@@ -24,6 +24,7 @@ abstract class Routes {
static const HI_NODE_LIST = _Paths.HI_NODE_LIST;
static const HI_HELP = _Paths.HI_HELP;
static const HI_USER_INFO = _Paths.HI_USER_INFO;
+ static const HI_ANTI_LOST = _Paths.HI_ANTI_LOST;
}
abstract class _Paths {
@@ -48,4 +49,5 @@ abstract class _Paths {
static const HI_NODE_LIST = '/hi_node_list';
static const HI_HELP = '/hi_help';
static const HI_USER_INFO = '/hi-user-info';
+ static const HI_ANTI_LOST = '/hi-anti-lost';
}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index a408754..8bc0808 100755
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -9,6 +9,7 @@ import connectivity_plus
import device_info_plus
import flutter_inappwebview_macos
import flutter_udid
+import gal
import in_app_purchase_storekit
import package_info_plus
import path_provider_foundation
@@ -23,6 +24,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin"))
+ GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
InAppPurchasePlugin.register(with: registry.registrar(forPlugin: "InAppPurchasePlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
diff --git a/pubspec.lock b/pubspec.lock
index bdf974f..1ac3a60 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -680,6 +680,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.0"
+ gal:
+ dependency: "direct main"
+ description:
+ name: gal
+ sha256: "969598f986789127fd407a750413249e1352116d4c2be66e81837ffeeaafdfee"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
get:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 23b36fa..c00d81a 100755
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -112,6 +112,7 @@ dependencies:
in_app_purchase: ^3.2.3
in_app_purchase_storekit: ^0.4.2
intl: ^0.20.2
+ gal: ^2.3.2
dev_dependencies:
flutter_test:
sdk: flutter
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index e8fc12e..9e6f5c2 100755
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -24,6 +25,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
FlutterUdidPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterUdidPluginCApi"));
+ GalPluginCApiRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("GalPluginCApi"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
ProtocolHandlerWindowsPluginCApiRegisterWithRegistrar(
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 2363a7a..fb054fa 100755
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus
flutter_inappwebview_windows
flutter_udid
+ gal
permission_handler_windows
protocol_handler_windows
screen_retriever_windows