diff --git a/assets/images/logo-1024-1024.png b/assets/images/logo-1024-1024.png new file mode 100644 index 0000000..d9da984 Binary files /dev/null and b/assets/images/logo-1024-1024.png differ diff --git a/assets/images/logo-1024x024.png b/assets/images/logo-1024x024.png deleted file mode 100644 index 3289695..0000000 Binary files a/assets/images/logo-1024x024.png and /dev/null differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 678215d..e9485f1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2,6 +2,12 @@ PODS: - connectivity_plus (0.0.1): - Flutter - ReachabilitySwift + - Crisp (2.8.2): + - Crisp/Crisp (= 2.8.2) + - Crisp/Crisp (2.8.2) + - crisp_chat (2.4.1): + - Crisp (~> 2.8.2) + - Flutter - device_info_plus (0.0.1): - Flutter - EasyPermissionX/Camera (0.0.2) @@ -35,12 +41,10 @@ PODS: - Flutter - url_launcher_ios (0.0.1): - Flutter - - webview_flutter_wkwebview (0.0.1): - - Flutter - - FlutterMacOS DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - crisp_chat (from `.symlinks/plugins/crisp_chat/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - EasyPermissionX/Camera - Flutter (from `Flutter`) @@ -53,10 +57,10 @@ DEPENDENCIES: - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git: + - Crisp - EasyPermissionX - OrderedSet - ReachabilitySwift @@ -65,6 +69,8 @@ SPEC REPOS: EXTERNAL SOURCES: connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" + crisp_chat: + :path: ".symlinks/plugins/crisp_chat/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" Flutter: @@ -87,11 +93,11 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/share_plus/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" - webview_flutter_wkwebview: - :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d + Crisp: 6747c96b2b2c2a81babf1eaecd1688a65d98edd4 + crisp_chat: 30994104495de23443af8d5b2c041a6df1e8464d device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 EasyPermissionX: ff4c438f6ee80488f873b4cb921e32d982523067 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 @@ -107,7 +113,6 @@ SPEC CHECKSUMS: SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188 PODFILE CHECKSUM: 579a354deb8d6fdc55c12799569018594328642e diff --git a/lib/app/modules/hi_node_list/views/hi_page_node_view.dart b/lib/app/modules/hi_node_list/views/hi_page_node_view.dart index e06b0c3..bc6384e 100644 --- a/lib/app/modules/hi_node_list/views/hi_page_node_view.dart +++ b/lib/app/modules/hi_node_list/views/hi_page_node_view.dart @@ -11,9 +11,18 @@ import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart'; import 'package:kaer_with_panels/app/widgets/hi_help_entrance.dart'; import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart'; import 'package:easy_refresh/easy_refresh.dart'; -class HINodePageView extends GetView { +class HINodePageView extends StatefulWidget { const HINodePageView({super.key}); + @override + State createState() => _HINodePageViewState(); +} + +class _HINodePageViewState extends State { + final GlobalKey _modeSwitcherKey = GlobalKey(); + + HINodeListController get controller => Get.find(); + @override Widget build(BuildContext context) { return HIBaseScaffold( @@ -69,6 +78,7 @@ class HINodePageView extends GetView { // 2. 关键:移除多余的 SizedBox,让 Expanded 正确工作 Expanded( child: Container( + key: _modeSwitcherKey, padding: EdgeInsets.all(4.w), decoration: BoxDecoration( border: Border.all( @@ -99,7 +109,19 @@ class HINodePageView extends GetView { SizedBox(width: 12.w), GestureDetector( onTap: () { + Offset? targetOffset; + final renderBox = _modeSwitcherKey.currentContext?.findRenderObject() as RenderBox?; + if (renderBox != null) { + final position = renderBox.localToGlobal(Offset.zero); + final size = renderBox.size; + targetOffset = Offset( + position.dx + (size.width / 2) - (HIDialog.kDialogWidth / 2), + position.dy + size.height + 10.w, + ); + } + HIDialog.show( + targetOffset: targetOffset, customMessageWidget: Padding( padding: EdgeInsets.symmetric(horizontal: 0.w, vertical: 0.w), child: Column( diff --git a/lib/app/modules/kr_home/views/kr_home_view.dart b/lib/app/modules/kr_home/views/kr_home_view.dart index a232e5d..90cb36f 100755 --- a/lib/app/modules/kr_home/views/kr_home_view.dart +++ b/lib/app/modules/kr_home/views/kr_home_view.dart @@ -46,6 +46,8 @@ class KRHomeView extends StatefulWidget implements HasSwipeConfig { class _KRHomeViewState extends State { late KRHomeController controller; + final GlobalKey _quickConnectTextKey = GlobalKey(); + @override void initState() { super.initState(); @@ -198,12 +200,15 @@ class _KRHomeViewState extends State { ), SizedBox(width: 8), // 2. “闪连”标签 - Text( - '闪连', - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w600, + Container( + key: _quickConnectTextKey, + child: Text( + '闪连', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w600, + ), ), ), ], @@ -214,10 +219,26 @@ class _KRHomeViewState extends State { SizedBox(width: 6), // 文字和问号图标之间的间距 GestureDetector( onTap: () { + Offset? targetOffset; + final renderBox = _quickConnectTextKey + .currentContext + ?.findRenderObject() as RenderBox?; + if (renderBox != null) { + final position = + renderBox.localToGlobal(Offset.zero); + targetOffset = Offset( + position.dx - 26, + position.dy + + renderBox.size.height + + 10.w, + ); + } + HIDialog.show( title: '闪连功能', message: '开启后,每次打开软件默认自动连接,无需点击连接按钮\n在后台关闭软件后,软件将自动断开', + targetOffset: targetOffset, ); }, child: KrLocalImage( diff --git a/lib/app/modules/kr_invite/views/kr_invite_view.dart b/lib/app/modules/kr_invite/views/kr_invite_view.dart index 5deb99a..3b604b6 100755 --- a/lib/app/modules/kr_invite/views/kr_invite_view.dart +++ b/lib/app/modules/kr_invite/views/kr_invite_view.dart @@ -32,7 +32,9 @@ class KRInviteView extends GetView { child: LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( - physics: const NeverScrollableScrollPhysics(), + physics: isKeyboardVisible + ? const ClampingScrollPhysics() + : const NeverScrollableScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: IntrinsicHeight( @@ -143,6 +145,7 @@ class KRInviteView extends GetView { ), ), + SizedBox(height: 40.w), const Spacer(), // 🟢 第三部分:接受他人邀请 @@ -166,6 +169,8 @@ class KRInviteView extends GetView { RepaintBoundary( child: TextField( controller: controller.otherInviteCodeController, + textInputAction: TextInputAction.done, + onSubmitted: (_) => controller.kr_handleBindInviteCode(), textAlign: TextAlign.center, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), decoration: InputDecoration( @@ -190,33 +195,36 @@ class KRInviteView extends GetView { ), ), ), - SizedBox(height: 10.w), - SizedBox( - width: double.infinity, - height: 50.w, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(1000.r), + if (!isKeyboardVisible) ...[ + SizedBox(height: 10.w), + SizedBox( + width: double.infinity, + height: 50.w, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(1000.r), + ), ), - ), - onPressed: () => controller.kr_handleBindInviteCode(), - child: Text( - '保存', - style: TextStyle( - color: Colors.black, - fontSize: 16.sp, - fontWeight: FontWeight.bold, + onPressed: () => + controller.kr_handleBindInviteCode(), + child: Text( + '保存', + style: TextStyle( + color: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), ), ), ), - ), + ], ], ); }), // 底部留白,确保键盘弹出后能滚过遮挡区域 - SizedBox(height: 90.w), + SizedBox(height: isKeyboardVisible ? 20.w : 90.w), ], ), ), diff --git a/lib/app/services/global_overlay_service.dart b/lib/app/services/global_overlay_service.dart index 50da1f7..6c9e877 100644 --- a/lib/app/services/global_overlay_service.dart +++ b/lib/app/services/global_overlay_service.dart @@ -124,11 +124,18 @@ class GlobalOverlayService extends GetxService { return const SizedBox.shrink(); } - return Text( - '购买套餐', - style: TextStyle( - color: Colors.black, - fontSize: 12.sp, + return DefaultTextStyle( + style: const TextStyle( + decoration: TextDecoration.none, // 核心:显式声明无装饰 + ), + child: Text( + '购买套餐', + style: TextStyle( + color: Colors.black, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, // 也可以在这里直接写 + ), ), ); }), diff --git a/lib/app/widgets/dialogs/hi_dialog.dart b/lib/app/widgets/dialogs/hi_dialog.dart index 915cc26..13025db 100755 --- a/lib/app/widgets/dialogs/hi_dialog.dart +++ b/lib/app/widgets/dialogs/hi_dialog.dart @@ -24,6 +24,9 @@ class HIDialog extends StatefulWidget { /// 是否显示按钮 loading(autoClose=false 时自动启用) final bool? showLoading; + final Offset? targetOffset; + + static double get kDialogWidth => 245.w < 280.0 ? 280.0 : 245.w; const HIDialog({ Key? key, @@ -38,6 +41,7 @@ class HIDialog extends StatefulWidget { this.preventBackDismiss = false, this.autoClose = true, this.showLoading, + this.targetOffset, }) : super(key: key); static Future show({ @@ -52,6 +56,7 @@ class HIDialog extends StatefulWidget { bool preventBackDismiss = false, bool autoClose = true, bool? showLoading, + Offset? targetOffset, }) { return Get.dialog( HIDialog( @@ -66,9 +71,11 @@ class HIDialog extends StatefulWidget { preventBackDismiss: preventBackDismiss, autoClose: autoClose, showLoading: showLoading, + targetOffset: targetOffset, ), barrierDismissible: barrierDismissible, barrierColor: Colors.transparent, + useSafeArea: false, ); } @@ -112,20 +119,127 @@ class _HIDialogState extends State { ), child: _isLoading ? SizedBox( - height: 20.w, - width: 20.w, - child: const CircularProgressIndicator( - color: Colors.black, - strokeWidth: 2, - ), - ) + height: 20.w, + width: 20.w, + child: const CircularProgressIndicator( + color: Colors.black, + strokeWidth: 2, + ), + ) : Text( - widget.confirmText ?? (widget.cancelText == null ? '好的' : AppTranslations.kr_dialog.kr_confirm), - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: Colors.black, - fontFamily: 'AlibabaPuHuiTi-Medium', + widget.confirmText ?? + (widget.cancelText == null ? '好的' : AppTranslations.kr_dialog.kr_confirm), + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Colors.black, + fontFamily: 'AlibabaPuHuiTi-Medium', + ), + ), + ); + } + + Widget _buildDialogContent() { + return ClipRRect( + borderRadius: BorderRadius.circular(34.r), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 26, sigmaY: 26), // 毛玻璃模糊 + child: Container( + width: 245.w, + padding: EdgeInsets.fromLTRB(30.w, 16.w, 30.w, 16.w), + decoration: BoxDecoration( + color: const Color(0xFFDEDEDE), // 半透明底色 + borderRadius: BorderRadius.circular(34.r), + border: Border.all( + color: Colors.white.withOpacity(0.2), // 高光边框 + width: 1.5, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.title != null) ...[ + Align( + alignment: Alignment.centerLeft, + child: Text( + widget.title!, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + color: Colors.black, + height: 1.3, + ), + ), + ), + SizedBox(height: 4.w), + ], + if (widget.message != null || widget.customMessageWidget != null) ...[ + Container( + constraints: BoxConstraints(maxHeight: 200.h), + child: SingleChildScrollView( + child: widget.customMessageWidget ?? + Text( + widget.message!, + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 14.sp, + color: Colors.black, + height: 1.4, + fontFamily: 'AlibabaPuHuiTi-Medium', + ), + ), + ), + ), + ], + if (widget.confirmText != null || + widget.cancelText != null || + (widget.confirmText == null && widget.cancelText == null)) ...[ + SizedBox(height: 12.w), + if (widget.confirmText == null && widget.cancelText == null) + Center( + child: SizedBox( + width: 85.w, + child: _buildConfirmButton(), + ), + ) + else + Row( + children: [ + if (widget.confirmText != null) Expanded(child: _buildConfirmButton()), + if (widget.cancelText != null && widget.confirmText != null) + SizedBox(width: 12.w), + if (widget.cancelText != null) + Expanded( + child: TextButton( + onPressed: () { + Get.back(); + widget.onCancel?.call(); + }, + style: TextButton.styleFrom( + backgroundColor: const Color(0xFFFF00B7), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(23.r), + ), + minimumSize: Size(85.w, 40.w), + padding: EdgeInsets.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: Text( + widget.cancelText!, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Colors.black, + fontFamily: 'AlibabaPuHuiTi-Medium', + ), + ), + ), + ), + ], + ), + ] + ], + ), ), ), ); @@ -133,112 +247,31 @@ class _HIDialogState extends State { @override Widget build(BuildContext context) { + if (widget.targetOffset != null) { + return Stack( + fit: StackFit.expand, + children: [ + Positioned( + left: widget.targetOffset!.dx, + top: widget.targetOffset!.dy, + child: Material( + type: MaterialType.transparency, + child: ConstrainedBox( + constraints: const BoxConstraints(minWidth: 280.0), + child: _buildDialogContent(), + ), + ), + ), + ], + ); + } + final dialog = Dialog( backgroundColor: Colors.transparent, insetPadding: EdgeInsets.all(20.w), child: Transform.translate( offset: Offset(0, -30.w), - child: ClipRRect( - borderRadius: BorderRadius.circular(34.r), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 26, sigmaY: 26), // 毛玻璃模糊 - child: Container( - width: 245.w, - padding: EdgeInsets.fromLTRB(30.w, 16.w, 30.w, 16.w), - decoration: BoxDecoration( - color: const Color(0xFFDEDEDE), // 半透明底色 - borderRadius: BorderRadius.circular(34.r), - border: Border.all( - color: Colors.white.withOpacity(0.2), // 高光边框 - width: 1.5, - ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.title != null) ...[ - Align( - alignment: Alignment.centerLeft, - child: Text( - widget.title!, - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w600, - color: Colors.black, - height: 1.3, - ), - ), - ), - SizedBox(height: 4.w), - ], - if (widget.message != null || widget.customMessageWidget != null) ...[ - Container( - constraints: BoxConstraints(maxHeight: 200.h), - child: SingleChildScrollView( - child: widget.customMessageWidget ?? - Text( - widget.message!, - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 14.sp, - color: Colors.black, - height: 1.4, - fontFamily: 'AlibabaPuHuiTi-Medium', - ), - ), - ), - ), - ], - if (widget.confirmText != null || widget.cancelText != null || (widget.confirmText == null && widget.cancelText == null)) ...[ - SizedBox(height: 12.w), - if (widget.confirmText == null && widget.cancelText == null) - Center( - child: SizedBox( - width: 85.w, - child: _buildConfirmButton(), - ), - ) - else - Row( - children: [ - if (widget.confirmText != null) Expanded(child: _buildConfirmButton()), - if (widget.cancelText != null && widget.confirmText != null) - SizedBox(width: 12.w), - if (widget.cancelText != null) - Expanded( - child: TextButton( - onPressed: () { - Get.back(); - widget.onCancel?.call(); - }, - style: TextButton.styleFrom( - backgroundColor: const Color(0xFFFF00B7), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(23.r), - ), - minimumSize: Size(85.w, 40.w), - padding: EdgeInsets.zero, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - child: Text( - widget.cancelText!, - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: Colors.black, - fontFamily: 'AlibabaPuHuiTi-Medium', - ), - ), - ), - ), - ], - ), - ] - ], - ), - ), - ), - ), + child: _buildDialogContent(), ), ); diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 3886f99..eece735 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -572,9 +572,10 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = NJRRF427XB; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN; @@ -585,6 +586,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "HiFastVPN-Mac-Pord"; SWIFT_VERSION = 5.0; }; name = Profile; @@ -705,9 +707,10 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = NJRRF427XB; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN; @@ -718,6 +721,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "HiFastVPN-Mac-Pord"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128.png index 3ba048d..838be55 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png index ede8a13..99c06a0 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16.png index 569fa5a..49561ca 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png index f18608a..6ebbe81 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256.png index ede8a13..99c06a0 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png index 533dbcd..768fe68 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32.png index f18608a..6ebbe81 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png index 582a58f..cbe8f22 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512.png index 533dbcd..768fe68 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png index 34e7b11..07c223a 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png differ diff --git a/scripts/assets/dmg_bg.png b/scripts/assets/dmg_bg.png new file mode 100644 index 0000000..2b75c96 Binary files /dev/null and b/scripts/assets/dmg_bg.png differ diff --git a/scripts/build_dmg.sh b/scripts/build_dmg.sh new file mode 100755 index 0000000..2ef20e9 --- /dev/null +++ b/scripts/build_dmg.sh @@ -0,0 +1,136 @@ +#!/bin/bash +set -e + +# ╔═══════════════════════════════════════════════════════════════════════════╗ +# ║ HiFastVPN macOS DMG 打包脚本 ║ +# ║ ║ +# ║ 使用方法: ║ +# ║ 1. 确保已经运行 sign_and_notarize.sh 生成签名的 .app ║ +# ║ 2. chmod +x build_dmg.sh ║ +# ║ 3. ./build_dmg.sh ║ +# ╚═══════════════════════════════════════════════════════════════════════════╝ + +# ======================== 路径配置 ======================== +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +APP_PATH="${PROJECT_DIR}/build/macos/Build/Products/Release/HiFastVPN.app" +BACKGROUND_IMAGE="${SCRIPT_DIR}/assets/dmg_bg.png" + +# ======================== 提取版本号 ======================== +echo "╔═══════════════════════════════════════════════════════════════════╗" +echo "║ HiFastVPN macOS DMG 打包脚本 ║" +echo "╚═══════════════════════════════════════════════════════════════════╝" +echo "" + +PUBSPEC_PATH="${PROJECT_DIR}/pubspec.yaml" +if [ ! -f "${PUBSPEC_PATH}" ]; then + echo "❌ 错误: 找不到 pubspec.yaml" + exit 1 +fi + +VERSION=$(grep "^version:" "${PUBSPEC_PATH}" | sed 's/version: *//' | tr -d ' ') +if [ -z "${VERSION}" ]; then + echo "❌ 错误: 无法提取版本号" + exit 1 +fi + +echo "📦 版本号: ${VERSION}" +echo "📁 项目目录: ${PROJECT_DIR}" +echo "📦 应用路径: ${APP_PATH}" +echo "🖼️ 背景图: ${BACKGROUND_IMAGE}" +echo "" + +# ======================== 检查依赖 ======================== +if ! command -v create-dmg >/dev/null 2>&1; then + echo "❌ 错误: create-dmg 未安装(brew install create-dmg)" + exit 1 +fi + +if [ ! -d "${APP_PATH}" ]; then + echo "❌ 错误: HiFastVPN.app 不存在" + exit 1 +fi + +if [ ! -f "${BACKGROUND_IMAGE}" ]; then + echo "❌ 错误: 背景图不存在" + exit 1 +fi + +# ======================== 校验背景图尺寸 ======================== +BG_INFO=$(sips -g pixelWidth -g pixelHeight "${BACKGROUND_IMAGE}") +BG_W=$(echo "$BG_INFO" | awk '/pixelWidth/ {print $2}') +BG_H=$(echo "$BG_INFO" | awk '/pixelHeight/ {print $2}') + +if [ "$BG_W" != "1200" ] || [ "$BG_H" != "800" ]; then + echo "❌ 背景图必须是 1200x800,当前是 ${BG_W}x${BG_H}" + exit 1 +fi + +# ======================== 输出目录 ======================== +OUTPUT_DIR="${PROJECT_DIR}/dist/${VERSION}" +mkdir -p "${OUTPUT_DIR}" + +DMG_NAME="HiFastVPN-${VERSION}.dmg" +DMG_PATH="${OUTPUT_DIR}/${DMG_NAME}" + +echo "📂 输出目录: ${OUTPUT_DIR}" +echo "💿 DMG 文件: ${DMG_NAME}" +echo "" + +rm -f "${DMG_PATH}" + +# ======================== 创建 DMG ======================== +echo "💿 开始创建 DMG..." +echo "" + +TEMP_DIR=$(mktemp -d) +cp -R "${APP_PATH}" "${TEMP_DIR}/" + +# ======================== Finder 坐标(左上原点,中心点对齐) ======================== +# 背景:1200 × 800 +# HiFastVPN.app 中心:左 228, 上 418, 尺寸 168×168 +APP_CENTER_X=236 +APP_CENTER_Y=432 +APP_ICON_SIZE=152 +APP_X=$((APP_CENTER_X + APP_ICON_SIZE / 2)) # 左上角坐标 +APP_Y=$((APP_CENTER_Y + APP_ICON_SIZE / 2)) + +# Applications 图标中心:左 742, 上 372, 尺寸 259×259 +DROP_CENTER_X=742 +DROP_CENTER_Y=372 +DROP_ICON_SIZE=259 +DROP_X=$((DROP_CENTER_X + DROP_ICON_SIZE / 2)) +DROP_Y=$((DROP_CENTER_Y + DROP_ICON_SIZE / 2)) + +# ======================== 打包 ======================== +create-dmg \ + --volname "HiFastVPN Installation" \ + --background "${BACKGROUND_IMAGE}" \ + --window-size 1200 800 \ + --icon-size "${APP_ICON_SIZE}" \ + --icon "HiFastVPN.app" "${APP_X}" "${APP_Y}" \ + --app-drop-link "${DROP_X}" "${DROP_Y}" \ + --hide-extension "HiFastVPN.app" \ + --no-internet-enable \ + "${DMG_PATH}" \ + "${TEMP_DIR}" + +# 清理临时目录 +rm -rf "${TEMP_DIR}" + +# ======================== 完成 ======================== +echo "" +echo "╔═══════════════════════════════════════════════════════════════════╗" +echo "║ 🎉 DMG 创建完成! ║" +echo "╚═══════════════════════════════════════════════════════════════════╝" +echo "" +echo "输出文件:" +echo " 💿 ${DMG_PATH}" +echo "" +echo "📝 注意事项:" +echo " • DMG 已保存到 dist/${VERSION}/ 目录" +echo " • 图标和 Applications 已按设计稿中心点对齐" +echo " • 此 DMG 包含已签名和公证的 .app" +echo " • 可以直接分发给用户使用" +echo "" diff --git a/sign_and_notarize.sh b/scripts/sign_and_notarize.sh similarity index 100% rename from sign_and_notarize.sh rename to scripts/sign_and_notarize.sh