提交 70946588 authored 作者: 王鹏飞's avatar 王鹏飞

feat: update iOS publish script with enhanced options and error handling

- Bump version in pubspec.yaml from 1.0.1+4 to 1.0.1+5. - Refactor ios_publish.sh to improve usability and error handling. - Add options for skipping build, submitting only, and setting a timeout. - Implement checks for required commands and configuration files. - Enhance version and build number extraction from pubspec.yaml. - Improve output messages for better clarity during the publishing process.
上级 5345b482
...@@ -64,11 +64,11 @@ ...@@ -64,11 +64,11 @@
<true/> <true/>
</dict> </dict>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>清控紫荆数智学堂需要访问相机</string> <string>用于拍摄并上传用户头像图片,例如在个人资料页面拍照设置头像。</string>
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>清控紫荆数智学堂需要访问麦克风</string> <string>清控紫荆数智学堂需要访问麦克风</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>清控紫荆数智学堂需要访问照片</string> <string>用于从相册选择并上传图片,例如设置用户头像、在笔记或讨论中添加图片内容。</string>
<key>UIApplicationSceneManifest</key> <key>UIApplicationSceneManifest</key>
<dict> <dict>
<key>UIApplicationSupportsMultipleScenes</key> <key>UIApplicationSupportsMultipleScenes</key>
......
...@@ -58,11 +58,11 @@ class MineController extends GetxController { ...@@ -58,11 +58,11 @@ class MineController extends GetxController {
value: userInfo.integralNums.toString(), value: userInfo.integralNums.toString(),
link: Routes.point, link: Routes.point,
icon: 'assets/images/point.png'), icon: 'assets/images/point.png'),
// ReadModel( ReadModel(
// name: '紫荆币', name: '紫荆币',
// value: userInfo.beanNums.toString(), value: userInfo.beanNums.toString(),
// link: Routes.coin, link: Routes.coin,
// icon: 'assets/images/coin.png'), icon: 'assets/images/coin.png'),
ReadModel( ReadModel(
name: '订单', name: '订单',
value: userInfo.ordersNums.toString(), value: userInfo.ordersNums.toString(),
......
...@@ -68,12 +68,15 @@ abstract class Access { ...@@ -68,12 +68,15 @@ abstract class Access {
} }
if (showExplanation && context != null && purpose.isNotEmpty) { if (showExplanation && context != null && purpose.isNotEmpty) {
final bool? shouldRequest = await _showPermissionExplanationDialog( if (!context.mounted) {
return false;
}
await _showPermissionExplanationDialog(
context: context, context: context,
permissionName: permissionName, permissionName: permissionName,
purpose: purpose, purpose: purpose,
); );
if (shouldRequest != true) { if (!context.mounted) {
return false; return false;
} }
} }
...@@ -96,12 +99,12 @@ abstract class Access { ...@@ -96,12 +99,12 @@ abstract class Access {
/// 显示权限使用目的说明对话框 /// 显示权限使用目的说明对话框
/// 对话框不会自动消失,需要用户主动确认 /// 对话框不会自动消失,需要用户主动确认
static Future<bool?> _showPermissionExplanationDialog({ static Future<void> _showPermissionExplanationDialog({
required BuildContext context, required BuildContext context,
required String permissionName, required String permissionName,
required String purpose, required String purpose,
}) async { }) async {
return await showDialog<bool>( await showDialog<void>(
context: context, context: context,
barrierDismissible: false, // 不允许点击外部关闭 barrierDismissible: false, // 不允许点击外部关闭
builder: (BuildContext dialogContext) { builder: (BuildContext dialogContext) {
...@@ -162,44 +165,21 @@ abstract class Access { ...@@ -162,44 +165,21 @@ abstract class Access {
], ],
), ),
actions: <Widget>[ actions: <Widget>[
Row( SizedBox(
children: [ width: double.infinity,
Expanded( child: TextButton(
child: TextButton( onPressed: () {
onPressed: () { Navigator.of(dialogContext).pop();
Navigator.of(dialogContext).pop(false); },
}, child: Text(
child: Text( '继续',
'取消', style: TextStyle(
style: TextStyle( fontSize: 16.w,
fontSize: 16.w, color: Colours.cAB1941,
color: Colours.c6, fontWeight: Fonts.boldSemi,
fontWeight: Fonts.medium,
),
),
), ),
), ),
Container( ),
width: 1,
height: 40.w,
color: Colours.cLine,
),
Expanded(
child: TextButton(
onPressed: () {
Navigator.of(dialogContext).pop(true);
},
child: Text(
'确定',
style: TextStyle(
fontSize: 16.w,
color: Colours.cAB1941,
fontWeight: Fonts.boldSemi,
),
),
),
),
],
), ),
], ],
), ),
......
差异被折叠。
...@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev ...@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # 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 # 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. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.1+4 version: 1.0.1+5
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'
......
#!/bin/bash #!/usr/bin/env bash
set -e set -euo pipefail
# ============================================= # iOS App Store publish helper.
# 清控紫荆数智学堂 — iOS 发布脚本 #
# 用法: ./scripts/ios_publish.sh [--submit] # Default behavior:
# ============================================= # 1. Validate local release inputs.
# 2. Run flutter pub get and pod install.
# 3. Build an App Store IPA.
# 4. Upload the IPA and wait for App Store Connect processing.
#
# Submit behavior:
# ./scripts/ios_publish.sh --submit
# Uploads, waits, updates App Review notes, then submits for review.
APP_ID="6476735372" APP_ID="6476735372"
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
EXPORT_OPTIONS="$PROJECT_DIR/ios/ExportOptions.plist"
ASC_CONFIG="$PROJECT_DIR/.asc/config.json"
SUBMIT=false
SKIP_BUILD=false
SUBMIT_ONLY=false
TIMEOUT="45m"
# 从 pubspec.yaml 读取版本号 REVIEW_NOTES=$'App Review notes:\n\nIn-App Purchases can be found after logging in:\n我的 -> 紫荆币 -> 充值\n\nThe recharge page displays the In-App Purchase products, including 6元, 98元, 298元, and 598元. Select a product and tap 立即充值 to start the Apple In-App Purchase flow.\n\nThis build also updates the camera/photo permission flow: the pre-permission explanation no longer has a cancel button, and the iOS purpose strings now describe specific usage examples.'
VERSION=$(grep '^version:' "$PROJECT_DIR/pubspec.yaml" | sed 's/version: //' | cut -d'+' -f1)
BUILD=$(grep '^version:' "$PROJECT_DIR/pubspec.yaml" | sed 's/version: //' | cut -d'+' -f2)
# 解析参数 usage() {
SUBMIT=false cat <<EOF
for arg in "$@"; do Usage:
case $arg in ./scripts/ios_publish.sh [--submit] [--skip-build] [--submit-only] [--timeout 45m]
--submit) SUBMIT=true ;;
Options:
--submit Submit the processed build for App Review after upload.
--skip-build Reuse an existing IPA under build/ios/ipa/.
--submit-only Update App Review notes and submit an already uploaded build.
--timeout Upload/build processing timeout passed to asc. Default: 45m.
-h, --help Show this help.
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--submit)
SUBMIT=true
shift
;;
--skip-build)
SKIP_BUILD=true
shift
;;
--submit-only)
SUBMIT=true
SUBMIT_ONLY=true
shift
;;
--timeout)
TIMEOUT="${2:-}"
if [[ -z "$TIMEOUT" ]]; then
echo "Missing value for --timeout" >&2
exit 2
fi
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage
exit 2
;;
esac esac
done done
echo "=========================================" require_cmd() {
echo " App: 清控紫荆数智学堂 ($APP_ID)" if ! command -v "$1" >/dev/null 2>&1; then
echo " 版本: $VERSION (build $BUILD)" echo "Missing required command: $1" >&2
echo "=========================================" exit 1
fi
}
extract_json_id() {
jq -r '
if type == "object" and (.data | type) == "array" then
.data[0].id // empty
elif type == "object" and (.data | type) == "object" then
.data.id // empty
elif type == "object" and (.id | type) == "string" then
.id
else
empty
end
'
}
# 1. 安装依赖
echo "[1/4] 安装 Flutter 依赖..."
cd "$PROJECT_DIR" cd "$PROJECT_DIR"
flutter pub get
# 2. Pod install require_cmd flutter
echo "[2/4] 安装 CocoaPods 依赖..." require_cmd pod
cd "$PROJECT_DIR/ios" require_cmd asc
pod install require_cmd jq
# 3. 构建 IPA(Flutter 内置流程:build → archive → export) if [[ ! -f "$ASC_CONFIG" ]]; then
echo "[3/4] 构建 IPA..." echo "Missing asc config: $ASC_CONFIG" >&2
cd "$PROJECT_DIR" exit 1
fi
# 生成 ExportOptions.plist(如果不存在) if [[ ! -f "$EXPORT_OPTIONS" ]]; then
EXPORT_OPTIONS="$PROJECT_DIR/ios/ExportOptions.plist" echo "Missing export options plist: $EXPORT_OPTIONS" >&2
if [ ! -f "$EXPORT_OPTIONS" ]; then exit 1
cat > "$EXPORT_OPTIONS" << 'XMLEOF' fi
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> VERSION_LINE="$(grep -E '^version:[[:space:]]*[0-9]+\.[0-9]+\.[0-9]+\+[0-9]+[[:space:]]*$' pubspec.yaml || true)"
<plist version="1.0"> if [[ -z "$VERSION_LINE" ]]; then
<dict> echo "pubspec.yaml version must look like: version: 1.0.1+5" >&2
<key>method</key> exit 1
<string>app-store</string>
<key>destination</key>
<string>export</string>
<key>teamID</key>
<string>MYN43C5WGE</string>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<true/>
<key>signingStyle</key>
<string>automatic</string>
<key>generateAppStoreInformation</key>
<true/>
</dict>
</plist>
XMLEOF
echo "已生成 ExportOptions.plist"
fi fi
flutter build ipa --release \ VERSION="${VERSION_LINE#version: }"
--export-options-plist="$EXPORT_OPTIONS" VERSION="${VERSION%%+*}"
BUILD="${VERSION_LINE##*+}"
BUILD="${BUILD//[[:space:]]/}"
IPA_PATH="$PROJECT_DIR/build/ios/ipa/flutter_book.ipa" if [[ -z "$VERSION" || -z "$BUILD" ]]; then
if [ ! -f "$IPA_PATH" ]; then echo "Failed to parse version/build from pubspec.yaml" >&2
echo "错误: IPA 文件未生成: $IPA_PATH"
exit 1 exit 1
fi fi
echo "IPA 生成成功: $IPA_PATH" echo "========================================="
echo " App: 清控紫荆数智学堂 ($APP_ID)"
echo " Version: $VERSION"
echo " Build: $BUILD"
echo " Submit: $SUBMIT"
echo " Submit only: $SUBMIT_ONLY"
echo "========================================="
# 4. 上传到 App Store echo "[0/5] Checking App Store Connect auth..."
echo "[4/4] 上传到 App Store..." asc doctor >/dev/null
if [ "$SUBMIT" = true ]; then
asc publish appstore \ if [[ "$SUBMIT_ONLY" == true ]]; then
--app "$APP_ID" \ echo "[1-4/5] Submit-only mode; skipping build and upload."
--ipa "$IPA_PATH" \ elif [[ "$SKIP_BUILD" != true ]]; then
--version "$VERSION" \ echo "[1/5] Installing Flutter dependencies..."
--submit --confirm flutter pub get
echo "[2/5] Installing CocoaPods dependencies..."
(cd ios && pod install)
echo "[3/5] Building IPA..."
flutter build ipa --release --build-name="$VERSION" --build-number="$BUILD" \
--export-options-plist="$EXPORT_OPTIONS"
else else
echo "[1-3/5] Skipping build; reusing existing IPA."
fi
if [[ "$SUBMIT_ONLY" != true ]]; then
mapfile -t IPA_FILES < <(find "$PROJECT_DIR/build/ios/ipa" -maxdepth 1 -name '*.ipa' -type f -print | sort)
if [[ "${#IPA_FILES[@]}" -ne 1 ]]; then
echo "Expected exactly one IPA under build/ios/ipa, found ${#IPA_FILES[@]}:" >&2
printf ' %s\n' "${IPA_FILES[@]}" >&2
exit 1
fi
IPA_PATH="${IPA_FILES[0]}"
echo "IPA: $IPA_PATH"
echo "[4/5] Uploading IPA and waiting for processing..."
asc builds upload \ asc builds upload \
--app "$APP_ID" \ --app "$APP_ID" \
--ipa "$IPA_PATH" --ipa "$IPA_PATH" \
--version "$VERSION" \
--build-number "$BUILD" \
--wait \
--verify-timeout 5m
fi fi
echo "" if [[ "$SUBMIT" != true ]]; then
echo "=========================================" echo "[5/5] Upload complete. Build was not submitted."
echo " 上传完成!" echo "Submit later with: ./scripts/ios_publish.sh --submit-only"
echo " 版本: $VERSION (build $BUILD)" exit 0
if [ "$SUBMIT" = true ]; then fi
echo " 已自动提交审核"
echo "[5/5] Preparing App Review submission..."
BUILD_JSON="$(asc builds list --app "$APP_ID" --version "$VERSION" --build-number "$BUILD" --platform IOS --processing-state VALID --output json)"
BUILD_ID="$(printf '%s' "$BUILD_JSON" | extract_json_id)"
if [[ -z "$BUILD_ID" ]]; then
echo "Could not resolve processed build id for $VERSION ($BUILD)." >&2
echo "$BUILD_JSON" >&2
exit 1
fi
VERSION_JSON="$(asc versions list --app "$APP_ID" --version "$VERSION" --platform IOS --output json)"
VERSION_ID="$(printf '%s' "$VERSION_JSON" | extract_json_id)"
if [[ -z "$VERSION_ID" ]]; then
echo "Could not resolve App Store version id for $VERSION." >&2
echo "$VERSION_JSON" >&2
exit 1
fi
DETAIL_JSON="$(asc review details-for-version --version-id "$VERSION_ID" --output json || true)"
DETAIL_ID="$(printf '%s' "$DETAIL_JSON" | extract_json_id)"
if [[ -n "$DETAIL_ID" ]]; then
echo "Updating App Review notes..."
asc review details-update --id "$DETAIL_ID" --notes "$REVIEW_NOTES" >/dev/null
else else
echo " 未提交审核,可用以下命令提交:" echo "Could not resolve review detail id; review notes were not updated." >&2
echo " asc review status --app $APP_ID" echo "Do not submit until App Review notes include the IAP path below:" >&2
echo " 或运行: $0 --submit" printf '%s\n' "$REVIEW_NOTES" >&2
exit 1
fi fi
echo "Submitting for App Review..."
asc review submit \
--app "$APP_ID" \
--version "$VERSION" \
--build "$BUILD_ID" \
--confirm
echo "========================================="
echo "Submitted for App Review."
echo "Version: $VERSION ($BUILD)"
echo "=========================================" echo "========================================="
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论