diff --git a/.gitignore b/.gitignore
index dd9339a..d8bb030 100644
--- a/.gitignore
+++ b/.gitignore
@@ -303,4 +303,12 @@ app.*.map.json
# FVM Version Cache
.fvm/
+# Fastlane
+ios/fastlane/report.xml
+ios/fastlane/README.md
+ios/fastlane/screenshots/
+ios/fastlane/test_output/
+*.ipa
+*.dSYM.zip
+
/secrets/*
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 842362b..e2e4957 100644
--- a/Makefile
+++ b/Makefile
@@ -286,6 +286,76 @@ build-prod-ios: _ensure-env-prod
$(FLUTTER) build ios --release --dart-define=FLAVOR=prod
@echo "✅ 빌드 완료!"
+# ============================================
+# 🍎 iOS 배포 (Fastlane)
+# ============================================
+
+# iOS 배포 도구 설치 (최초 1회)
+setup-ios-deploy:
+ @echo "======iOS 배포 도구 설치 중...======"
+ @which bundle > /dev/null || (echo "❌ Bundler가 없습니다. gem install bundler 실행하세요." && exit 1)
+ cd ios && bundle install
+ @echo "✅ Fastlane 설치 완료!"
+
+# .env.prod에서 ASC 인증 환경변수 검증 헬퍼
+_load-asc-env:
+ @if [ ! -f ".env.prod" ]; then \
+ echo "❌ .env.prod 파일이 없습니다. make prepare-env-prod 를 먼저 실행하세요."; \
+ exit 1; \
+ fi
+ @ASC_KEY_ID=$$(grep '^ASC_KEY_ID=' .env.prod | cut -d '=' -f2); \
+ ASC_ISSUER_ID=$$(grep '^ASC_ISSUER_ID=' .env.prod | cut -d '=' -f2); \
+ ASC_KEY_BASE64=$$(grep '^ASC_KEY_BASE64=' .env.prod | cut -d '=' -f2); \
+ if [ -z "$$ASC_KEY_ID" ] || [ -z "$$ASC_ISSUER_ID" ] || [ -z "$$ASC_KEY_BASE64" ]; then \
+ echo "❌ .env.prod에 App Store Connect API 키 정보가 누락되었습니다."; \
+ echo " 필요한 값: ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_BASE64"; \
+ echo ""; \
+ echo " [설정 방법]"; \
+ echo " 1. App Store Connect → 사용자 및 액세스 → 통합 → API 키 생성"; \
+ echo " 2. .p8 파일을 base64 인코딩: base64 -i AuthKey_XXXX.p8"; \
+ echo " 3. .env.prod에 추가:"; \
+ echo " ASC_KEY_ID=키ID"; \
+ echo " ASC_ISSUER_ID=발급자ID"; \
+ echo " ASC_KEY_BASE64=base64인코딩값"; \
+ exit 1; \
+ fi
+
+# IPA 빌드 (내부 헬퍼 - Fastfile에서 호출)
+_build-ios-ipa: _ensure-env-prod
+ @echo "======iOS IPA 빌드 중...======"
+ $(FLUTTER) build ipa --release --dart-define=FLAVOR=prod --export-options-plist=ios/ExportOptions.plist 2>/dev/null || \
+ $(FLUTTER) build ipa --release --dart-define=FLAVOR=prod
+ @echo "✅ IPA 빌드 완료!"
+
+# TestFlight 배포
+deploy-ios-testflight: _ensure-env-prod _load-asc-env
+ @echo "======iOS TestFlight 배포 중...======"
+ @# IPA 빌드
+ $(MAKE) _build-ios-ipa
+ @# Fastlane 실행 (환경변수 전달: base64 디코딩하여 키 내용 전달)
+ @export ASC_KEY_ID=$$(grep '^ASC_KEY_ID=' .env.prod | cut -d '=' -f2); \
+ export ASC_ISSUER_ID=$$(grep '^ASC_ISSUER_ID=' .env.prod | cut -d '=' -f2); \
+ export ASC_KEY_CONTENT=$$(grep '^ASC_KEY_BASE64=' .env.prod | cut -d '=' -f2 | base64 --decode); \
+ export IPA_PATH=$$(ls build/ios/ipa/*.ipa 2>/dev/null | head -1); \
+ cd ios && bundle exec fastlane beta
+ @echo "✅ TestFlight 배포 완료!"
+
+# TestFlight 배포 (별칭)
+deploy-ios: deploy-ios-testflight
+
+# App Store 제출
+deploy-ios-appstore: _ensure-env-prod _load-asc-env
+ @echo "======iOS App Store 제출 중...======"
+ @# IPA 빌드
+ $(MAKE) _build-ios-ipa
+ @# Fastlane 실행 (환경변수 전달: base64 디코딩하여 키 내용 전달)
+ @export ASC_KEY_ID=$$(grep '^ASC_KEY_ID=' .env.prod | cut -d '=' -f2); \
+ export ASC_ISSUER_ID=$$(grep '^ASC_ISSUER_ID=' .env.prod | cut -d '=' -f2); \
+ export ASC_KEY_CONTENT=$$(grep '^ASC_KEY_BASE64=' .env.prod | cut -d '=' -f2 | base64 --decode); \
+ export IPA_PATH=$$(ls build/ios/ipa/*.ipa 2>/dev/null | head -1); \
+ cd ios && bundle exec fastlane release
+ @echo "✅ App Store 제출 완료!"
+
# ============================================
# 📋 도움말
# ============================================
@@ -330,10 +400,23 @@ help:
@echo " make build-dev-ios - 개발 iOS 빌드"
@echo " make build-prod-ios - 프로덕션 iOS 빌드"
@echo ""
+ @echo "🍎 iOS 배포 (Fastlane):"
+ @echo " make setup-ios-deploy - Fastlane 설치 (최초 1회)"
+ @echo " make deploy-ios - TestFlight 배포 (별칭)"
+ @echo " make deploy-ios-testflight - TestFlight 배포"
+ @echo " make deploy-ios-appstore - App Store 제출"
+ @echo ""
+ @echo " [사전 준비]"
+ @echo " 1. App Store Connect → 사용자 및 액세스 → 통합 → API 키 생성"
+ @echo " 2. .p8 파일을 base64 인코딩: base64 -i AuthKey_XXXX.p8"
+ @echo " 3. .env.prod에 ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_BASE64 추가"
+ @echo ""
.PHONY: setup setup-signing fresh clean-ios clean-ios-quick clean-android codegen codegen-watch \
version bump-build bump-patch bump-minor bump-major \
decrypt-env-dev decrypt-env-prod prepare-env-dev prepare-env-prod \
build-env-dev build-env-prod _ensure-env-dev _ensure-env-prod run run-dev run-local run-prod \
build-dev-android build-prod-android build-prod-android-aab \
- build-dev-ios build-prod-ios help
\ No newline at end of file
+ build-dev-ios build-prod-ios \
+ setup-ios-deploy _load-asc-env _build-ios-ipa \
+ deploy-ios deploy-ios-testflight deploy-ios-appstore help
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 1276eb7..7912fd6 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -11,8 +11,11 @@
-
-
+
+
+
+
+
diff --git a/ios/Gemfile b/ios/Gemfile
new file mode 100644
index 0000000..731e487
--- /dev/null
+++ b/ios/Gemfile
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+
+gem "fastlane", "~> 2.225"
diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock
new file mode 100644
index 0000000..9506c60
--- /dev/null
+++ b/ios/Gemfile.lock
@@ -0,0 +1,332 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.8)
+ abbrev (0.1.2)
+ addressable (2.8.8)
+ public_suffix (>= 2.0.2, < 8.0)
+ artifactory (3.0.17)
+ atomos (0.1.3)
+ aws-eventstream (1.4.0)
+ aws-partitions (1.1213.0)
+ aws-sdk-core (3.242.0)
+ aws-eventstream (~> 1, >= 1.3.0)
+ aws-partitions (~> 1, >= 1.992.0)
+ aws-sigv4 (~> 1.9)
+ base64
+ bigdecimal
+ jmespath (~> 1, >= 1.6.1)
+ logger
+ aws-sdk-kms (1.121.0)
+ aws-sdk-core (~> 3, >= 3.241.4)
+ aws-sigv4 (~> 1.5)
+ aws-sdk-s3 (1.213.0)
+ aws-sdk-core (~> 3, >= 3.241.4)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.5)
+ aws-sigv4 (1.12.1)
+ aws-eventstream (~> 1, >= 1.0.2)
+ babosa (1.0.4)
+ base64 (0.2.0)
+ benchmark (0.5.0)
+ bigdecimal (4.0.1)
+ claide (1.1.0)
+ colored (1.2)
+ colored2 (3.1.2)
+ commander (4.6.0)
+ highline (~> 2.0.0)
+ csv (3.3.5)
+ declarative (0.0.20)
+ digest-crc (0.7.0)
+ rake (>= 12.0.0, < 14.0.0)
+ domain_name (0.6.20240107)
+ dotenv (2.8.1)
+ emoji_regex (3.2.3)
+ excon (0.112.0)
+ faraday (1.8.0)
+ faraday-em_http (~> 1.0)
+ faraday-em_synchrony (~> 1.0)
+ faraday-excon (~> 1.1)
+ faraday-httpclient (~> 1.0.1)
+ faraday-net_http (~> 1.0)
+ faraday-net_http_persistent (~> 1.1)
+ faraday-patron (~> 1.0)
+ faraday-rack (~> 1.0)
+ multipart-post (>= 1.2, < 3)
+ ruby2_keywords (>= 0.0.4)
+ faraday-cookie_jar (0.0.8)
+ faraday (>= 0.8.0)
+ http-cookie (>= 1.0.0)
+ faraday-em_http (1.0.0)
+ faraday-em_synchrony (1.0.1)
+ faraday-excon (1.1.0)
+ faraday-httpclient (1.0.1)
+ faraday-net_http (1.0.2)
+ faraday-net_http_persistent (1.2.0)
+ faraday-patron (1.0.0)
+ faraday-rack (1.0.0)
+ faraday_middleware (1.2.1)
+ faraday (~> 1.0)
+ fastimage (2.4.0)
+ fastlane (2.232.0)
+ CFPropertyList (>= 2.3, < 4.0.0)
+ abbrev (~> 0.1.2)
+ addressable (>= 2.8, < 3.0.0)
+ artifactory (~> 3.0)
+ aws-sdk-s3 (~> 1.197)
+ babosa (>= 1.0.3, < 2.0.0)
+ base64 (~> 0.2.0)
+ benchmark (>= 0.1.0)
+ bundler (>= 1.17.3, < 5.0.0)
+ colored (~> 1.2)
+ commander (~> 4.6)
+ csv (~> 3.3)
+ dotenv (>= 2.1.1, < 3.0.0)
+ emoji_regex (>= 0.1, < 4.0)
+ excon (>= 0.71.0, < 1.0.0)
+ faraday (~> 1.0)
+ faraday-cookie_jar (~> 0.0.6)
+ faraday_middleware (~> 1.0)
+ fastimage (>= 2.1.0, < 3.0.0)
+ fastlane-sirp (>= 1.0.0)
+ gh_inspector (>= 1.1.2, < 2.0.0)
+ google-apis-androidpublisher_v3 (~> 0.3)
+ google-apis-playcustomapp_v1 (~> 0.1)
+ google-cloud-env (>= 1.6.0, <= 2.1.1)
+ google-cloud-storage (~> 1.31)
+ highline (~> 2.0)
+ http-cookie (~> 1.0.5)
+ json (< 3.0.0)
+ jwt (>= 2.1.0, < 3)
+ logger (>= 1.6, < 2.0)
+ mini_magick (>= 4.9.4, < 5.0.0)
+ multipart-post (>= 2.0.0, < 3.0.0)
+ mutex_m (~> 0.3.0)
+ naturally (~> 2.2)
+ nkf (~> 0.2.0)
+ optparse (>= 0.1.1, < 1.0.0)
+ ostruct (>= 0.1.0)
+ plist (>= 3.1.0, < 4.0.0)
+ rubyzip (>= 2.0.0, < 3.0.0)
+ security (= 0.1.5)
+ simctl (~> 1.6.3)
+ terminal-notifier (>= 2.0.0, < 3.0.0)
+ terminal-table (~> 3)
+ tty-screen (>= 0.6.3, < 1.0.0)
+ tty-spinner (>= 0.8.0, < 1.0.0)
+ word_wrap (~> 1.0.0)
+ xcodeproj (>= 1.13.0, < 2.0.0)
+ xcpretty (~> 0.4.1)
+ xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
+ fastlane-sirp (1.0.0)
+ sysrandom (~> 1.0)
+ gh_inspector (1.1.3)
+ google-apis-androidpublisher_v3 (0.96.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-apis-core (0.18.0)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (~> 1.9)
+ httpclient (>= 2.8.3, < 3.a)
+ mini_mime (~> 1.0)
+ mutex_m
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.a)
+ google-apis-iamcredentials_v1 (0.26.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-apis-playcustomapp_v1 (0.17.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-apis-storage_v1 (0.60.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-cloud-core (1.8.0)
+ google-cloud-env (>= 1.0, < 3.a)
+ google-cloud-errors (~> 1.0)
+ google-cloud-env (2.1.1)
+ faraday (>= 1.0, < 3.a)
+ google-cloud-errors (1.5.0)
+ google-cloud-storage (1.58.0)
+ addressable (~> 2.8)
+ digest-crc (~> 0.4)
+ google-apis-core (>= 0.18, < 2)
+ google-apis-iamcredentials_v1 (~> 0.18)
+ google-apis-storage_v1 (>= 0.42)
+ google-cloud-core (~> 1.6)
+ googleauth (~> 1.9)
+ mini_mime (~> 1.0)
+ googleauth (1.11.2)
+ faraday (>= 1.0, < 3.a)
+ google-cloud-env (~> 2.1)
+ jwt (>= 1.4, < 3.0)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (>= 0.16, < 2.a)
+ highline (2.0.3)
+ http-cookie (1.0.8)
+ domain_name (~> 0.5)
+ httpclient (2.9.0)
+ mutex_m
+ jmespath (1.6.2)
+ json (2.18.1)
+ jwt (2.10.2)
+ base64
+ logger (1.7.0)
+ mini_magick (4.13.2)
+ mini_mime (1.1.5)
+ multi_json (1.19.1)
+ multipart-post (2.4.1)
+ mutex_m (0.3.0)
+ nanaimo (0.4.0)
+ naturally (2.3.0)
+ nkf (0.2.0)
+ optparse (0.8.1)
+ os (1.1.4)
+ ostruct (0.6.3)
+ plist (3.7.2)
+ public_suffix (7.0.2)
+ rake (13.3.1)
+ representable (3.2.0)
+ declarative (< 0.1.0)
+ trailblazer-option (>= 0.1.1, < 0.2.0)
+ uber (< 0.2.0)
+ retriable (3.1.2)
+ rexml (3.4.4)
+ rouge (3.28.0)
+ ruby2_keywords (0.0.5)
+ rubyzip (2.4.1)
+ security (0.1.5)
+ signet (0.21.0)
+ addressable (~> 2.8)
+ faraday (>= 0.17.5, < 3.a)
+ jwt (>= 1.5, < 4.0)
+ multi_json (~> 1.10)
+ simctl (1.6.10)
+ CFPropertyList
+ naturally
+ sysrandom (1.0.5)
+ terminal-notifier (2.0.0)
+ terminal-table (3.0.2)
+ unicode-display_width (>= 1.1.1, < 3)
+ trailblazer-option (0.1.2)
+ tty-cursor (0.7.1)
+ tty-screen (0.8.2)
+ tty-spinner (0.9.3)
+ tty-cursor (~> 0.7)
+ uber (0.1.0)
+ unicode-display_width (2.6.0)
+ word_wrap (1.0.0)
+ xcodeproj (1.27.0)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.4.0)
+ rexml (>= 3.3.6, < 4.0)
+ xcpretty (0.4.1)
+ rouge (~> 3.28.0)
+ xcpretty-travis-formatter (1.0.1)
+ xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+ arm64-darwin-25
+ ruby
+
+DEPENDENCIES
+ fastlane (~> 2.225)
+
+CHECKSUMS
+ CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261
+ abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242
+ addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057
+ artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263
+ atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f
+ aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b
+ aws-partitions (1.1213.0) sha256=5ec132d91d44ef2702125b8f71f0e4fc2cd7de040e02c5d0aefb87219fd2e05e
+ aws-sdk-core (3.242.0) sha256=c17b3003acc78d80c1a8437b285a1cfc5e4d7749ce7821cf3071e847535a29a0
+ aws-sdk-kms (1.121.0) sha256=d563c1cfb4b5754efbc671216c8eca875338748adad0f42518c28dfa0a2d01e0
+ aws-sdk-s3 (1.213.0) sha256=af596ccf544582406db610e95cc9099276eaf03142f57a2f30f76940e598e50d
+ aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
+ babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99
+ base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507
+ benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
+ bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
+ claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e
+ colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c
+ colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a
+ commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9
+ csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
+ declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9
+ digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07
+ domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933
+ dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8
+ emoji_regex (3.2.3) sha256=ecd8be856b7691406c6bf3bb3a5e55d6ed683ffab98b4aa531bb90e1ddcc564b
+ excon (0.112.0) sha256=daf9ac3a4c2fc9aa48383a33da77ecb44fa395111e973084d5c52f6f214ae0f0
+ faraday (1.8.0) sha256=d1fb776cf25973b7f52a82b625bb0a009fe30ad6021ef838fb9109bf1ea6d029
+ faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb
+ faraday-em_http (1.0.0) sha256=7a3d4c7079789121054f57e08cd4ef7e40ad1549b63101f38c7093a9d6c59689
+ faraday-em_synchrony (1.0.1) sha256=bf3ce45dcf543088d319ab051f80985ea6d294930635b7a0b966563179f81750
+ faraday-excon (1.1.0) sha256=b055c842376734d7f74350fe8611542ae2000c5387348d9ba9708109d6e40940
+ faraday-httpclient (1.0.1) sha256=4c8ff1f0973ff835be8d043ef16aaf54f47f25b7578f6d916deee8399a04d33b
+ faraday-net_http (1.0.2) sha256=63992efea42c925a20818cf3c0830947948541fdcf345842755510d266e4c682
+ faraday-net_http_persistent (1.2.0) sha256=0b0cbc8f03dab943c3e1cc58d8b7beb142d9df068b39c718cd83e39260348335
+ faraday-patron (1.0.0) sha256=dc2cd7b340bb3cc8e36bcb9e6e7eff43d134b6d526d5f3429c7a7680ddd38fa7
+ faraday-rack (1.0.0) sha256=ef60ec969a2bb95b8dbf24400155aee64a00fc8ba6c6a4d3968562bcc92328c0
+ faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9
+ fastimage (2.4.0) sha256=5fce375e27d3bdbb46c18dbca6ba9af29d3304801ae1eb995771c4796c5ac7e8
+ fastlane (2.232.0) sha256=441c9481115c4cdb60354b95426964358c32e1e1ee339a03ced05915e30c6222
+ fastlane-sirp (1.0.0) sha256=66478f25bcd039ec02ccf65625373fca29646fa73d655eb533c915f106c5e641
+ gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939
+ google-apis-androidpublisher_v3 (0.96.0) sha256=9e27b03295fdd2c4a67b5e4d11f891492c89f73beff4a3f9323419165a56d01c
+ google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee
+ google-apis-iamcredentials_v1 (0.26.0) sha256=3ff70a10a1d6cddf2554e95b7c5df2c26afdeaeb64100048a355194da19e48a3
+ google-apis-playcustomapp_v1 (0.17.0) sha256=d5bc90b705f3f862bab4998086449b0abe704ee1685a84821daa90ca7fa95a78
+ google-apis-storage_v1 (0.60.0) sha256=ad2511b7128344248c2a16adb56ad4b1b48f37d53925455b7b77acccfe75367a
+ google-cloud-core (1.8.0) sha256=e572edcbf189cfcab16590628a516cec3f4f63454b730e59f0b36575120281cf
+ google-cloud-env (2.1.1) sha256=cf4bb8c7d517ee1ea692baedf06e0b56ce68007549d8d5a66481aa9f97f46999
+ google-cloud-errors (1.5.0) sha256=b56be28b8c10628125214dde571b925cfcebdbc58619e598250c37a2114f7b4b
+ google-cloud-storage (1.58.0) sha256=1bedc07a9c75af169e1ede1dd306b9f941f9ffa9e7095d0364c0803c468fdffd
+ googleauth (1.11.2) sha256=7e6bacaeed7aea3dd66dcea985266839816af6633e9f5983c3c2e0e40a44731e
+ highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479
+ http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6
+ httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8
+ jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1
+ json (2.18.1) sha256=fe112755501b8d0466b5ada6cf50c8c3f41e897fa128ac5d263ec09eedc9f986
+ jwt (2.10.2) sha256=31e1ee46f7359883d5e622446969fe9c118c3da87a0b1dca765ce269c3a0c4f4
+ logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
+ mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9
+ mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
+ multi_json (1.19.1) sha256=7aefeff8f2c854bf739931a238e4aea64592845e0c0395c8a7d2eea7fdd631b7
+ multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
+ mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751
+ nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723
+ naturally (2.3.0) sha256=459923cf76c2e6613048301742363200c3c7e4904c324097d54a67401e179e01
+ nkf (0.2.0) sha256=fbc151bda025451f627fafdfcb3f4f13d0b22ae11f58c6d3a2939c76c5f5f126
+ optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a
+ os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f
+ ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
+ plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42
+ public_suffix (7.0.2) sha256=9114090c8e4e7135c1fd0e7acfea33afaab38101884320c65aaa0ffb8e26a857
+ rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
+ representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace
+ retriable (3.1.2) sha256=0a5a5d0ca4ba61a76fb31a17ab8f7f80281beb040c329d34dfc137a1398688e0
+ rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
+ rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20
+ ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef
+ rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615
+ security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7
+ signet (0.21.0) sha256=d617e9fbf24928280d39dcfefba9a0372d1c38187ffffd0a9283957a10a8cd5b
+ simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b
+ sysrandom (1.0.5) sha256=5ac1ac3c2ec64ef76ac91018059f541b7e8f437fbda1ccddb4f2c56a9ccf1e75
+ terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea
+ terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91
+ trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3
+ tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48
+ tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50
+ tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542
+ uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc
+ unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a
+ word_wrap (1.0.0) sha256=f556d4224c812e371000f12a6ee8102e0daa724a314c3f246afaad76d82accc7
+ xcodeproj (1.27.0) sha256=8cc7a73b4505c227deab044dce118ede787041c702bc47636856a2e566f854d3
+ xcpretty (0.4.1) sha256=b14c50e721f6589ee3d6f5353e2c2cfcd8541fa1ea16d6c602807dd7327f3892
+ xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93
+
+BUNDLED WITH
+ 4.0.3
diff --git a/ios/fastlane/Appfile b/ios/fastlane/Appfile
new file mode 100644
index 0000000..fec79ed
--- /dev/null
+++ b/ios/fastlane/Appfile
@@ -0,0 +1,5 @@
+app_identifier("com.bottlenote.official.app")
+apple_id(ENV["APPLE_ID"]) # App Store Connect 이메일 (환경변수)
+team_id("3T9984GSWD")
+
+itc_team_id(ENV["ITC_TEAM_ID"]) # App Store Connect Team ID (환경변수, 선택)
diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile
new file mode 100644
index 0000000..d41f9ed
--- /dev/null
+++ b/ios/fastlane/Fastfile
@@ -0,0 +1,71 @@
+default_platform(:ios)
+
+# App Store Connect API Key 로드
+def load_asc_api_key
+ # .env.prod에서 값 읽기 (Makefile에서 환경변수로 전달)
+ key_id = ENV["ASC_KEY_ID"]
+ issuer_id = ENV["ASC_ISSUER_ID"]
+ key_content = ENV["ASC_KEY_CONTENT"]
+
+ unless key_id && issuer_id && key_content
+ UI.user_error!(
+ "ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_CONTENT 환경변수가 필요합니다.\n" \
+ ".env.prod에 다음을 추가하세요:\n" \
+ " ASC_KEY_ID=your_key_id\n" \
+ " ASC_ISSUER_ID=your_issuer_id\n" \
+ " ASC_KEY_BASE64=base64_encoded_p8_content"
+ )
+ end
+
+ app_store_connect_api_key(
+ key_id: key_id,
+ issuer_id: issuer_id,
+ key_content: key_content,
+ in_house: false
+ )
+end
+
+platform :ios do
+ desc "TestFlight에 배포"
+ lane :beta do
+ api_key = load_asc_api_key
+
+ # Flutter IPA 빌드 (Makefile에서 이미 빌드된 경우 스킵 가능)
+ ipa_path = ENV["IPA_PATH"] || "../build/ios/ipa/app.ipa"
+
+ unless File.exist?(ipa_path)
+ UI.message("IPA 빌드 중...")
+ sh("cd ../.. && make _build-ios-ipa")
+ ipa_path = Dir["../../build/ios/ipa/*.ipa"].first
+ end
+
+ upload_to_testflight(
+ api_key: api_key,
+ ipa: ipa_path,
+ skip_waiting_for_build_processing: true
+ )
+ end
+
+ desc "App Store에 제출"
+ lane :release do
+ api_key = load_asc_api_key
+
+ ipa_path = ENV["IPA_PATH"] || "../build/ios/ipa/app.ipa"
+
+ unless File.exist?(ipa_path)
+ UI.message("IPA 빌드 중...")
+ sh("cd ../.. && make _build-ios-ipa")
+ ipa_path = Dir["../../build/ios/ipa/*.ipa"].first
+ end
+
+ upload_to_app_store(
+ api_key: api_key,
+ ipa: ipa_path,
+ submit_for_review: false,
+ automatic_release: false,
+ force: true,
+ skip_metadata: true,
+ skip_screenshots: true
+ )
+ end
+end
diff --git a/lib/bridge/handlers/media_bridge_handler.dart b/lib/bridge/handlers/media_bridge_handler.dart
index 8b70fdd..793d943 100644
--- a/lib/bridge/handlers/media_bridge_handler.dart
+++ b/lib/bridge/handlers/media_bridge_handler.dart
@@ -8,18 +8,6 @@ import 'package:permission_handler/permission_handler.dart';
/// 이미지 선택 및 카메라 관련 브릿지 핸들러
mixin MediaBridgeHandler on BridgeHandlerBase {
Future pickImgFromAlbum() async {
- final status = await Permission.photos.status;
-
- if (status.isPermanentlyDenied) {
- logger.d('앨범 권한 영구 거부됨. 설정으로 안내');
- showPermissionDialog(
- title: '앨범 접근 권한 안내',
- content: '사진을 첨부하려면 접근 권한이 필요해요.\n설정에서 허용해 주시겠어요?',
- onConfirm: openAppSettings,
- );
- return;
- }
-
try {
onShowLoading?.call('이미지 처리 중...');
final ImagePicker picker = ImagePicker();
@@ -91,18 +79,6 @@ mixin MediaBridgeHandler on BridgeHandlerBase {
Future pickMultipleImgsFromAlbum() async {
const int maxImages = 5;
- final status = await Permission.photos.status;
-
- if (status.isPermanentlyDenied) {
- logger.d('앨범 권한 영구 거부됨. 설정으로 안내');
- showPermissionDialog(
- title: '앨범 접근 권한 안내',
- content: '사진을 첨부하려면 접근 권한이 필요해요.\n설정에서 허용해 주시겠어요?',
- onConfirm: openAppSettings,
- );
- return;
- }
-
try {
onShowLoading?.call('이미지 선택 중...');
final ImagePicker picker = ImagePicker();