feat: 修改hidialog弹窗位置,增加macos dmg打包配置

This commit is contained in:
speakeloudest 2026-01-12 00:45:52 -08:00
parent 2d1f391932
commit e6ee3d6db8
22 changed files with 393 additions and 157 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

View File

@ -2,6 +2,12 @@ PODS:
- connectivity_plus (0.0.1): - connectivity_plus (0.0.1):
- Flutter - Flutter
- ReachabilitySwift - 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): - device_info_plus (0.0.1):
- Flutter - Flutter
- EasyPermissionX/Camera (0.0.2) - EasyPermissionX/Camera (0.0.2)
@ -35,12 +41,10 @@ PODS:
- Flutter - Flutter
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES: DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - 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`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- EasyPermissionX/Camera - EasyPermissionX/Camera
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
@ -53,10 +57,10 @@ DEPENDENCIES:
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
SPEC REPOS: SPEC REPOS:
https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git: https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git:
- Crisp
- EasyPermissionX - EasyPermissionX
- OrderedSet - OrderedSet
- ReachabilitySwift - ReachabilitySwift
@ -65,6 +69,8 @@ SPEC REPOS:
EXTERNAL SOURCES: EXTERNAL SOURCES:
connectivity_plus: connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios" :path: ".symlinks/plugins/connectivity_plus/ios"
crisp_chat:
:path: ".symlinks/plugins/crisp_chat/ios"
device_info_plus: device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios" :path: ".symlinks/plugins/device_info_plus/ios"
Flutter: Flutter:
@ -87,11 +93,11 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios" :path: ".symlinks/plugins/share_plus/ios"
url_launcher_ios: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
SPEC CHECKSUMS: SPEC CHECKSUMS:
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
Crisp: 6747c96b2b2c2a81babf1eaecd1688a65d98edd4
crisp_chat: 30994104495de23443af8d5b2c041a6df1e8464d
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
EasyPermissionX: ff4c438f6ee80488f873b4cb921e32d982523067 EasyPermissionX: ff4c438f6ee80488f873b4cb921e32d982523067
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
@ -107,7 +113,6 @@ SPEC CHECKSUMS:
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188
PODFILE CHECKSUM: 579a354deb8d6fdc55c12799569018594328642e PODFILE CHECKSUM: 579a354deb8d6fdc55c12799569018594328642e

View File

@ -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_help_entrance.dart';
import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart'; import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart';
import 'package:easy_refresh/easy_refresh.dart'; import 'package:easy_refresh/easy_refresh.dart';
class HINodePageView extends GetView<HINodeListController> { class HINodePageView extends StatefulWidget {
const HINodePageView({super.key}); const HINodePageView({super.key});
@override
State<HINodePageView> createState() => _HINodePageViewState();
}
class _HINodePageViewState extends State<HINodePageView> {
final GlobalKey _modeSwitcherKey = GlobalKey();
HINodeListController get controller => Get.find<HINodeListController>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return HIBaseScaffold( return HIBaseScaffold(
@ -69,6 +78,7 @@ class HINodePageView extends GetView<HINodeListController> {
// 2. SizedBox Expanded // 2. SizedBox Expanded
Expanded( Expanded(
child: Container( child: Container(
key: _modeSwitcherKey,
padding: EdgeInsets.all(4.w), padding: EdgeInsets.all(4.w),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
@ -99,7 +109,19 @@ class HINodePageView extends GetView<HINodeListController> {
SizedBox(width: 12.w), SizedBox(width: 12.w),
GestureDetector( GestureDetector(
onTap: () { 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( HIDialog.show(
targetOffset: targetOffset,
customMessageWidget: Padding( customMessageWidget: Padding(
padding: EdgeInsets.symmetric(horizontal: 0.w, vertical: 0.w), padding: EdgeInsets.symmetric(horizontal: 0.w, vertical: 0.w),
child: Column( child: Column(

View File

@ -46,6 +46,8 @@ class KRHomeView extends StatefulWidget implements HasSwipeConfig {
class _KRHomeViewState extends State<KRHomeView> { class _KRHomeViewState extends State<KRHomeView> {
late KRHomeController controller; late KRHomeController controller;
final GlobalKey _quickConnectTextKey = GlobalKey();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -198,12 +200,15 @@ class _KRHomeViewState extends State<KRHomeView> {
), ),
SizedBox(width: 8), SizedBox(width: 8),
// 2. // 2.
Text( Container(
'闪连', key: _quickConnectTextKey,
style: TextStyle( child: Text(
color: Colors.white, '闪连',
fontSize: 20, style: TextStyle(
fontWeight: FontWeight.w600, color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w600,
),
), ),
), ),
], ],
@ -214,10 +219,26 @@ class _KRHomeViewState extends State<KRHomeView> {
SizedBox(width: 6), // SizedBox(width: 6), //
GestureDetector( GestureDetector(
onTap: () { 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( HIDialog.show(
title: '闪连功能', title: '闪连功能',
message: message:
'开启后,每次打开软件默认自动连接,无需点击连接按钮\n在后台关闭软件后,软件将自动断开', '开启后,每次打开软件默认自动连接,无需点击连接按钮\n在后台关闭软件后,软件将自动断开',
targetOffset: targetOffset,
); );
}, },
child: KrLocalImage( child: KrLocalImage(

View File

@ -32,7 +32,9 @@ class KRInviteView extends GetView<KRInviteController> {
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return SingleChildScrollView( return SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(), physics: isKeyboardVisible
? const ClampingScrollPhysics()
: const NeverScrollableScrollPhysics(),
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight), constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight( child: IntrinsicHeight(
@ -143,6 +145,7 @@ class KRInviteView extends GetView<KRInviteController> {
), ),
), ),
SizedBox(height: 40.w),
const Spacer(), const Spacer(),
// 🟢 // 🟢
@ -166,6 +169,8 @@ class KRInviteView extends GetView<KRInviteController> {
RepaintBoundary( RepaintBoundary(
child: TextField( child: TextField(
controller: controller.otherInviteCodeController, controller: controller.otherInviteCodeController,
textInputAction: TextInputAction.done,
onSubmitted: (_) => controller.kr_handleBindInviteCode(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
decoration: InputDecoration( decoration: InputDecoration(
@ -190,33 +195,36 @@ class KRInviteView extends GetView<KRInviteController> {
), ),
), ),
), ),
SizedBox(height: 10.w), if (!isKeyboardVisible) ...[
SizedBox( SizedBox(height: 10.w),
width: double.infinity, SizedBox(
height: 50.w, width: double.infinity,
child: ElevatedButton( height: 50.w,
style: ElevatedButton.styleFrom( child: ElevatedButton(
backgroundColor: Theme.of(context).primaryColor, style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder( backgroundColor: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(1000.r), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(1000.r),
),
), ),
), onPressed: () =>
onPressed: () => controller.kr_handleBindInviteCode(), controller.kr_handleBindInviteCode(),
child: Text( child: Text(
'保存', '保存',
style: TextStyle( style: TextStyle(
color: Colors.black, color: Colors.black,
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
),
), ),
), ),
), ),
), ],
], ],
); );
}), }),
// //
SizedBox(height: 90.w), SizedBox(height: isKeyboardVisible ? 20.w : 90.w),
], ],
), ),
), ),

View File

@ -124,11 +124,18 @@ class GlobalOverlayService extends GetxService {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return Text( return DefaultTextStyle(
'购买套餐', style: const TextStyle(
style: TextStyle( decoration: TextDecoration.none, //
color: Colors.black, ),
fontSize: 12.sp, child: Text(
'购买套餐',
style: TextStyle(
color: Colors.black,
fontSize: 12.sp,
fontWeight: FontWeight.w600,
decoration: TextDecoration.none, //
),
), ),
); );
}), }),

View File

@ -24,6 +24,9 @@ class HIDialog extends StatefulWidget {
/// loadingautoClose=false /// loadingautoClose=false
final bool? showLoading; final bool? showLoading;
final Offset? targetOffset;
static double get kDialogWidth => 245.w < 280.0 ? 280.0 : 245.w;
const HIDialog({ const HIDialog({
Key? key, Key? key,
@ -38,6 +41,7 @@ class HIDialog extends StatefulWidget {
this.preventBackDismiss = false, this.preventBackDismiss = false,
this.autoClose = true, this.autoClose = true,
this.showLoading, this.showLoading,
this.targetOffset,
}) : super(key: key); }) : super(key: key);
static Future<void> show({ static Future<void> show({
@ -52,6 +56,7 @@ class HIDialog extends StatefulWidget {
bool preventBackDismiss = false, bool preventBackDismiss = false,
bool autoClose = true, bool autoClose = true,
bool? showLoading, bool? showLoading,
Offset? targetOffset,
}) { }) {
return Get.dialog( return Get.dialog(
HIDialog( HIDialog(
@ -66,9 +71,11 @@ class HIDialog extends StatefulWidget {
preventBackDismiss: preventBackDismiss, preventBackDismiss: preventBackDismiss,
autoClose: autoClose, autoClose: autoClose,
showLoading: showLoading, showLoading: showLoading,
targetOffset: targetOffset,
), ),
barrierDismissible: barrierDismissible, barrierDismissible: barrierDismissible,
barrierColor: Colors.transparent, barrierColor: Colors.transparent,
useSafeArea: false,
); );
} }
@ -112,20 +119,127 @@ class _HIDialogState extends State<HIDialog> {
), ),
child: _isLoading child: _isLoading
? SizedBox( ? SizedBox(
height: 20.w, height: 20.w,
width: 20.w, width: 20.w,
child: const CircularProgressIndicator( child: const CircularProgressIndicator(
color: Colors.black, color: Colors.black,
strokeWidth: 2, strokeWidth: 2,
), ),
) )
: Text( : Text(
widget.confirmText ?? (widget.cancelText == null ? '好的' : AppTranslations.kr_dialog.kr_confirm), widget.confirmText ??
style: TextStyle( (widget.cancelText == null ? '好的' : AppTranslations.kr_dialog.kr_confirm),
fontSize: 16.sp, style: TextStyle(
fontWeight: FontWeight.w600, fontSize: 16.sp,
color: Colors.black, fontWeight: FontWeight.w600,
fontFamily: 'AlibabaPuHuiTi-Medium', 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<HIDialog> {
@override @override
Widget build(BuildContext context) { 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( final dialog = Dialog(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.all(20.w), insetPadding: EdgeInsets.all(20.w),
child: Transform.translate( child: Transform.translate(
offset: Offset(0, -30.w), offset: Offset(0, -30.w),
child: ClipRRect( child: _buildDialogContent(),
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',
),
),
),
),
],
),
]
],
),
),
),
),
), ),
); );

View File

@ -572,9 +572,10 @@
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = NJRRF427XB; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN; INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
@ -585,6 +586,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac; PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "HiFastVPN-Mac-Pord";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Profile; name = Profile;
@ -705,9 +707,10 @@
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = NJRRF427XB; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN; INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
@ -718,6 +721,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac; PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "HiFastVPN-Mac-Pord";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 706 B

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 37 KiB

BIN
scripts/assets/dmg_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 KiB

136
scripts/build_dmg.sh Executable file
View File

@ -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 ""