diff --git a/app/ios/ExportOptions.plist b/app/ios/ExportOptions.plist index 6f7de16..73f108b 100644 --- a/app/ios/ExportOptions.plist +++ b/app/ios/ExportOptions.plist @@ -6,13 +6,14 @@ `xcodebuild -exportArchive` 가 이 파일을 읽는다. 없으면 대화형으로 물어보므로 자동 배포가 불가능하다 — 계정이 열린 뒤에 만들면 그날 하루를 여기서 쓴다. + teamID 는 발주사 Fancake Inc. 개발자 팀이다. 앱(kr.co.fanuzu.app)이 그 팀 소속이라 + 다른 팀으로 서명하면 업로드가 엉뚱한 App Store Connect 로 간다. --> method app-store-connect - teamID V28G543VK5 diff --git a/app/ios/Flutter/Debug.xcconfig b/app/ios/Flutter/Debug.xcconfig index 093bdc1..c858fd2 100644 --- a/app/ios/Flutter/Debug.xcconfig +++ b/app/ios/Flutter/Debug.xcconfig @@ -1,6 +1,7 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" -// Google 로그인 콜백 스킴. tool/ios/inject_url_scheme.py 가 만든다. -// `#include?` 는 파일이 없어도 빌드를 멈추지 않는다 — 그때는 Google 로그인만 동작하지 않는다. +// Google 로그인 콜백 스킴. tools/ios/inject_url_scheme.py 가 만든다. +// `#include?` 는 파일이 없어도 빌드를 멈추지 않는다. 설정 파일 투입 전에도 +// 빌드가 되어야 하기 때문이다 — 그때는 Google 로그인만 동작하지 않는다. #include? "../config/Google.xcconfig" diff --git a/app/ios/Flutter/Release.xcconfig b/app/ios/Flutter/Release.xcconfig index 02fef1a..f1c1184 100644 --- a/app/ios/Flutter/Release.xcconfig +++ b/app/ios/Flutter/Release.xcconfig @@ -1,6 +1,7 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" -// Google 로그인 콜백 스킴. tool/ios/inject_url_scheme.py 가 만든다. -// `#include?` 는 파일이 없어도 빌드를 멈추지 않는다 — 그때는 Google 로그인만 동작하지 않는다. +// Google 로그인 콜백 스킴. tools/ios/inject_url_scheme.py 가 만든다. +// `#include?` 는 파일이 없어도 빌드를 멈추지 않는다. 설정 파일 투입 전에도 +// 빌드가 되어야 하기 때문이다 — 그때는 Google 로그인만 동작하지 않는다. #include? "../config/Google.xcconfig" diff --git a/app/ios/Runner.xcodeproj/project.pbxproj b/app/ios/Runner.xcodeproj/project.pbxproj index a848bd9..6db6495 100644 --- a/app/ios/Runner.xcodeproj/project.pbxproj +++ b/app/ios/Runner.xcodeproj/project.pbxproj @@ -65,8 +65,8 @@ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 4F2B0E1A2C3D4E5F60718293 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4F2B0E1A2C3D4E5F60718293 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; A68C3B80D45981965B89CA40 /* 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 = ""; }; AB2C33122C7A95220B242430 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CBE157220A7C382F845775F0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; diff --git a/app/ios/Runner/Info.plist b/app/ios/Runner/Info.plist index 8798140..09b578d 100644 --- a/app/ios/Runner/Info.plist +++ b/app/ios/Runner/Info.plist @@ -26,6 +26,269 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + + + + ITSAppUsesNonExemptEncryption + + + + NSPhotoLibraryUsageDescription + 프로필 사진을 고르기 위해 사진에 접근합니다. + NSCameraUsageDescription + 프로필 사진을 찍고 방문 인증 사진을 남기기 위해 카메라를 사용합니다. + NSLocationWhenInUseUsageDescription + 지정한 장소에 방문하셨는지 확인해 퀘스트 보상을 드리기 위해 위치를 확인합니다. + NSMotionUsageDescription + 걸음 수를 세어 만보기 퀘스트를 확인하기 위해 동작 데이터를 읽습니다. + NSHealthShareUsageDescription + 걸음 수를 읽어 만보기 퀘스트 달성을 확인합니다. 다른 건강 정보는 읽지 않습니다. + + GADApplicationIdentifier + ca-app-pub-3940256099942544~1458002511 + + SKAdNetworkItems + + + SKAdNetworkIdentifier + cstr6suwn9.skadnetwork + + + SKAdNetworkIdentifier + 4fzdc2evr5.skadnetwork + + + SKAdNetworkIdentifier + 2fnua5tdw4.skadnetwork + + + SKAdNetworkIdentifier + ydx93a7ass.skadnetwork + + + SKAdNetworkIdentifier + p78axxw29g.skadnetwork + + + SKAdNetworkIdentifier + v72qych5uu.skadnetwork + + + SKAdNetworkIdentifier + ludvb6z3bs.skadnetwork + + + SKAdNetworkIdentifier + cp8zw746q7.skadnetwork + + + SKAdNetworkIdentifier + 3sh42y64q3.skadnetwork + + + SKAdNetworkIdentifier + c6k4g5qg8m.skadnetwork + + + SKAdNetworkIdentifier + s39g8k73mm.skadnetwork + + + SKAdNetworkIdentifier + wg4vff78zm.skadnetwork + + + SKAdNetworkIdentifier + 3qy4746246.skadnetwork + + + SKAdNetworkIdentifier + f38h382jlk.skadnetwork + + + SKAdNetworkIdentifier + hs6bdukanm.skadnetwork + + + SKAdNetworkIdentifier + mlmmfzh3r3.skadnetwork + + + SKAdNetworkIdentifier + v4nxqhlyqp.skadnetwork + + + SKAdNetworkIdentifier + wzmmz9fp6w.skadnetwork + + + SKAdNetworkIdentifier + su67r6k2v3.skadnetwork + + + SKAdNetworkIdentifier + yclnxrl5pm.skadnetwork + + + SKAdNetworkIdentifier + t38b2kh725.skadnetwork + + + SKAdNetworkIdentifier + 7ug5zh24hu.skadnetwork + + + SKAdNetworkIdentifier + gta9lk7p23.skadnetwork + + + SKAdNetworkIdentifier + vutu7akeur.skadnetwork + + + SKAdNetworkIdentifier + y5ghdn5j9k.skadnetwork + + + SKAdNetworkIdentifier + v9wttpbfk9.skadnetwork + + + SKAdNetworkIdentifier + n38lu8286q.skadnetwork + + + SKAdNetworkIdentifier + 47vhws6wlr.skadnetwork + + + SKAdNetworkIdentifier + kbd757ywx3.skadnetwork + + + SKAdNetworkIdentifier + 9t245vhmpl.skadnetwork + + + SKAdNetworkIdentifier + a2p9lx4jpn.skadnetwork + + + SKAdNetworkIdentifier + 22mmun2rn5.skadnetwork + + + SKAdNetworkIdentifier + 44jx6755aq.skadnetwork + + + SKAdNetworkIdentifier + k674qkevps.skadnetwork + + + SKAdNetworkIdentifier + 4468km3ulz.skadnetwork + + + SKAdNetworkIdentifier + 2u9pt9hc89.skadnetwork + + + SKAdNetworkIdentifier + 8s468mfl3y.skadnetwork + + + SKAdNetworkIdentifier + klf5c3l5u5.skadnetwork + + + SKAdNetworkIdentifier + ppxm28t8ap.skadnetwork + + + SKAdNetworkIdentifier + kbmxgpxpgc.skadnetwork + + + SKAdNetworkIdentifier + uw77j35x4d.skadnetwork + + + SKAdNetworkIdentifier + 578prtvx9j.skadnetwork + + + SKAdNetworkIdentifier + 4dzt52r2t5.skadnetwork + + + SKAdNetworkIdentifier + tl55sbb4fm.skadnetwork + + + SKAdNetworkIdentifier + c3frkrj4fj.skadnetwork + + + SKAdNetworkIdentifier + e5fvkxwrpn.skadnetwork + + + SKAdNetworkIdentifier + 8c4e2ghe7u.skadnetwork + + + SKAdNetworkIdentifier + 3rd42ekr43.skadnetwork + + + SKAdNetworkIdentifier + 97r2b46745.skadnetwork + + + SKAdNetworkIdentifier + 3qcr597p9d.skadnetwork + + + NSUserTrackingUsageDescription + 관심에 맞는 광고를 보여 드리기 위해 사용합니다. 허용하지 않으셔도 앱을 그대로 쓰실 수 있습니다. + + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google-sign-in + CFBundleURLSchemes + + $(GOOGLE_REVERSED_CLIENT_ID) + + + + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -66,29 +329,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - - ITSAppUsesNonExemptEncryption - - - - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - google-sign-in - CFBundleURLSchemes - - $(GOOGLE_REVERSED_CLIENT_ID) - - - diff --git a/app/ios/Runner/Runner.entitlements b/app/ios/Runner/Runner.entitlements index 53eb7ec..647988c 100644 --- a/app/ios/Runner/Runner.entitlements +++ b/app/ios/Runner/Runner.entitlements @@ -3,8 +3,12 @@ com.apple.developer.applesignin @@ -12,8 +16,8 @@ aps-environment development diff --git a/app/tool/ios/inject_url_scheme.py b/app/tools/ios/inject_url_scheme.py similarity index 90% rename from app/tool/ios/inject_url_scheme.py rename to app/tools/ios/inject_url_scheme.py index 5d56d67..8f94419 100755 --- a/app/tool/ios/inject_url_scheme.py +++ b/app/tools/ios/inject_url_scheme.py @@ -8,7 +8,7 @@ Info.plist 에 손으로 옮기면 오타 한 글자에 로그인이 **조용히 설정 파일이 투입될 때 한 번 돌리면 `ios/config/Google.xcconfig` 가 만들어지고, Info.plist 의 `$(GOOGLE_REVERSED_CLIENT_ID)` 가 그 값으로 치환된다. -실행: python3 tool/ios/inject_url_scheme.py +실행: python3 tools/ios/inject_url_scheme.py """ from __future__ import annotations @@ -25,7 +25,7 @@ def main() -> int: if not SOURCE.exists(): print( f"설정 파일이 아직 없습니다: {SOURCE.relative_to(ROOT)}\n" - "Firebase 콘솔에서 iOS 앱 설정 파일을 받아 그 자리에 두십시오.", + "Firebase 프로젝트를 연 뒤 firebase/setup-project.sh 를 돌리십시오.", file=sys.stderr, ) return 1 @@ -45,7 +45,7 @@ def main() -> int: OUT.parent.mkdir(parents=True, exist_ok=True) OUT.write_text( - "// 자동 생성 — tool/ios/inject_url_scheme.py\n" + "// 자동 생성 — tools/ios/inject_url_scheme.py\n" "// GoogleService-Info.plist 가 바뀌면 다시 돌리십시오.\n" f"GOOGLE_REVERSED_CLIENT_ID = {reversed_id}\n", encoding="utf-8", diff --git a/app/tools/ios/upload_testflight.sh b/app/tools/ios/upload_testflight.sh new file mode 100755 index 0000000..f75fb66 --- /dev/null +++ b/app/tools/ios/upload_testflight.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# iOS 아카이브 → TestFlight 업로드. +# +# **계정이 열린 날 이 스크립트 하나로 끝나게 하는 것이 목적**이다. 별첨 2 가 +# 3주차부터 매주 TestFlight 제출을 요구했고 그것을 지키지 못했으므로(미이행 F-2), +# 계정이 열리면 지체 없이 재개되어야 한다. +# +# 사전 조건 +# 1. ios/Runner/GoogleService-Info.plist 배치 · tools/ios/inject_url_scheme.py 실행 +# 2. Xcode 정식판을 `xcode-select` 로 지정 (베타 SDK 빌드는 업로드가 거부된다) +# 3. App Store Connect API 키 (환경변수 3종) +# +# 실행: +# export ASC_KEY_ID=... ASC_ISSUER_ID=... ASC_KEY_PATH=~/private_keys/AuthKey_XXX.p8 +# bash tools/ios/upload_testflight.sh +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +APP="$ROOT" +ARCHIVE="$APP/build/ios/Runner.xcarchive" +EXPORT_DIR="$APP/build/ios/ipa" +OPTIONS="$APP/ios/ExportOptions.plist" + +if grep -q REPLACE_WITH_TEAM_ID "$OPTIONS"; then + echo "ExportOptions.plist 의 teamID 를 먼저 채우십시오." >&2 + exit 1 +fi +for var in ASC_KEY_ID ASC_ISSUER_ID ASC_KEY_PATH; do + if [ -z "${!var:-}" ]; then + echo "환경변수 $var 이 없습니다." >&2 + exit 1 + fi +done + +echo "▸ 아카이브" +cd "$APP" +flutter build ipa --release --export-options-plist="$OPTIONS" + +# flutter build ipa 가 아카이브와 내보내기를 함께 한다. 실패했을 때만 손으로 나눈다. +IPA="$(find "$EXPORT_DIR" -name '*.ipa' -maxdepth 1 | head -1)" +if [ -z "$IPA" ]; then + echo "IPA 를 찾지 못했습니다. 아카이브만 따로 시도합니다." >&2 + xcodebuild -workspace ios/Runner.xcworkspace -scheme Runner \ + -configuration Release -archivePath "$ARCHIVE" archive + xcodebuild -exportArchive -archivePath "$ARCHIVE" \ + -exportOptionsPlist "$OPTIONS" -exportPath "$EXPORT_DIR" + IPA="$(find "$EXPORT_DIR" -name '*.ipa' -maxdepth 1 | head -1)" +fi + +echo "▸ 검증" +xcrun altool --validate-app -f "$IPA" -t ios \ + --apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID" + +echo "▸ 업로드" +xcrun altool --upload-app -f "$IPA" -t ios \ + --apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID" + +cat <<'DONE' + +업로드했습니다. 남은 것 둘. + + 1. App Store Connect 에서 처리가 끝나면(보통 10~30분) TestFlight 탭에 뜹니다. + 2. 발주사 담당자 이메일을 외부 테스터로 초대하십시오. + +수출 규정 응답은 Info.plist 의 ITSAppUsesNonExemptEncryption 이 대신합니다. +DONE