From a332c039baa45db4b381e0eb9eb896c3725cc55f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?= <116792524+mooninbeom@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:20:27 +0900 Subject: [PATCH 01/54] =?UTF-8?q?=EB=A6=AC=EB=93=9C=EB=AF=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 297 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..d4af0da --- /dev/null +++ b/README.md @@ -0,0 +1,297 @@ +# ๐Ÿƒ๐Ÿ‘Ÿ Run Mile / ๋Ÿฐ ๋งˆ์ผ +๋Ÿฌ๋„ˆ๋“ค์—๊ฒŒ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€ ๋Ÿฌ๋‹ํ™”๐Ÿ‘Ÿ + +์•„์ดํฐ์— ๋“ฑ๋ก๋˜์–ด์žˆ๋Š” ์šด๋™ ๊ธฐ๋ก์œผ๋กœ ์† ์‰ฝ๊ฒŒ ๋Ÿฌ๋‹ํ™” ๋งˆ์ผ๋ฆฌ์ง€๋ฅผ ์ถ”์ ํ•ด์ค๋‹ˆ๋‹ค!๐Ÿ”ฅ๐Ÿ”ฅ + +|์ƒํƒœ|์•ฑ์Šคํ† ์–ด ๋ฐฐํฌ ์™„๋ฃŒ ๋ฐ ์œ ์ง€๋ณด์ˆ˜ ์ง„ํ–‰ ์ค‘(v1.0.0)| +|:--|:--| +|๊ธฐ์ˆ  ์Šคํƒ|SwiftUI, HealthKit, Realm, UserNotifications, Vision(์ด๋ฏธ์ง€ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ)| +|์•ฑ์Šคํ† ์–ด|[Run Mile](https://apps.apple.com/kr/app/run-mile/id6747099791)| +|์ด๋ฉ”์ผ ๋ฌธ์˜|dlsqja567@naver.com| + +### ์‹ ๋ฐœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ 3 Stpes +1. ์‹ ๋ฐœ์„ ๋“ฑ๋กํ•˜๊ณ  +2. ์šด๋™์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”! +3. ์ž๋™ ๋“ฑ๋ก์„ ํ†ตํ•ด ๋”์šฑ ํŽธ๋ฆฌํ•˜๊ฒŒ! + +![Group 1](https://github.com/user-attachments/assets/2f46e30f-c99c-4f34-be48-795c76cc6c74) + +
+ + +## ๊ฐœ๋ฐœ ์ผ์ง€ +|์ด๋ฆ„|๋งํฌ| +|:--|:--| +|**0. ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ Run Mile ์•ฑ ๊ฐœ๋ฐœ๊ธฐ**|https://velog.io/@mooninbeom/0.-์‚ฌ์ด๋“œ-ํ”„๋กœ์ ํŠธ-Run-Mile-์•ฑ-๊ฐœ๋ฐœ๊ธฐ| +|**1. HealthKit ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ(with Continuation)**|https://velog.io/@mooninbeom/1.-HealthKit-๋ฐ์ดํ„ฐ-์‚ฌ์šฉwith-Continuation| +|**2. ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ HealthKit ํ™œ์šฉํ•˜๊ธฐ**|https://velog.io/@mooninbeom/2.-๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ-HealthKit-ํ™œ์šฉํ•˜๊ธฐ-o2p1gg9l| + +
+ + + +## ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ ๋‚ด๊ฐ€ ๋ฐฐ์šด ๊ฒƒ + +### ํด๋ฆฐ ์•„ํ‚คํ…์ณ๋ฅผ ๋‚ด ์ž…๋ง›์— ๋งž๊ฒŒ ์‚ฌ์šฉ + + + +### HealthKit ํ™œ์šฉ +์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์˜ ํ•ต์‹ฌ์€ ๊ธฐ๊ธฐ ๋‚ด์— ์žˆ๋Š” ์šด๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค. +๋•Œ๋ฌธ์— **HealthKit**์„ ํ™œ์šฉํ•ด ์›ํ•˜๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ๋“ค์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. + +**1๏ธโƒฃ ๊ถŒํ•œ ์„ค์ •** + +๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฑด๊ฐ• ๋ฐ์ดํ„ฐ๋Š” Privacy ์˜์—ญ์ด๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ ํ—ˆ์šฉ์ด ํ•„์š”ํ•˜๊ณ  ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ์˜ ์ข…๋ฅ˜ ์ค‘ ํ•„์š”ํ•œ ์˜์—ญ๋งŒ ์ถ”๊ฐ€ํ•ด ์š”์ฒญ์„ ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. +๋˜ํ•œ ์ฝ๊ธฐ(Read), ์“ฐ๊ธฐ(Share) ์˜์—ญ์ด ๋‚˜๋ˆ„์–ด์ ธ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์•ฑ์—์„œ ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๊ณ ๋ คํ•ด ํ•„์š”ํ•œ ํƒ€์ž…๋งŒ ์ถ”๊ฐ€ํ•ด ์š”์ฒญํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +Run Mile์—์„œ๋Š” ์“ฐ๊ธฐ๋Š” ํ˜„์žฌ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์šด๋™ ํƒ€์ž…์— ์žˆ์–ด์„œ ์ฝ๊ธฐ ๋ถ€๋ถ„๋งŒ ๊ถŒํ•œ์„ ์š”์ฒญํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +Run Mile์—์„œ๋Š” 2๊ฐœ์˜ ์Šคํ…์œผ๋กœ ๊ถŒํ•œ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. + +
+ +**1. ๊ถŒํ•œ ์š”์ฒญ ํ™•์ธ** + +๊ถŒํ•œ ์š”์ฒญ์ด ์ง„ํ–‰๋œ ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•˜๊ณ  ์š”์ฒญ์ด ๋˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ ์š”์ฒญ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. + +๊ถŒํ•œ ์š”์ฒญ์˜ ๊ฒฝ์šฐ ํ—ˆ์šฉ/ํ—ˆ์šฉ์•ˆํ•จ ์—ฌ๋ถ€์— ์ƒ๊ด€์—†์ด ํ•œ ๋ฒˆ ์š”์ฒญ์ด ์ง„ํ–‰๋˜๊ณ  ๋‚˜๋ฉด ๋‹ค์‹œ ์•ฑ์—์„œ๋Š” ์š”์ฒญ์„ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ž˜์„œ ๋ถˆํ•„์š”ํ•œ ์š”์ฒญ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์š”์ฒญ ์ „ ์š”์ฒญ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + +๋งŒ์•ฝ ์ด๋ฏธ ์š”์ฒญ๋œ ์ƒํƒœ์ด๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์„ค์ •์—์„œ ๋ฐ”๊พธ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๋ฐฉํ–ฅ์œผ๋กœ ์œ ๋„ํ•˜๋Š” UX๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +```swift +/// in HealthDataUseCase.swift + +/// Health ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ ๊ถŒํ•œ ์š”์ฒญ์ด ์ด๋ฃจ์–ด์กŒ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. +private func checkAuthorizationStatus() async throws -> Bool { + return try await withCheckedThrowingContinuation { continuation in + store.getRequestStatusForAuthorization( + toShare: Set(), + read: Set([.workoutType()]) + ) { status, error in + if let _ = error { + continuation.resume(throwing: HealthError.unknownError) + } + + switch status { + case .shouldRequest: + continuation.resume(returning: true) + default: + continuation.resume(returning: false) + } + } + } +} +``` + +
+ +**2. ๊ถŒํ•œ ์š”์ฒญ** + +๊ถŒํ•œ ์š”์ฒญ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. + +์š”์ฒญ์„ ์ง„ํ–‰ํ•˜๊ธฐ ์ „ Health ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ๊ธฐ๊ธฐ์ธ์ง€ ์—ฌ๋ถ€๋ฅผ ํŒ๋ณ„ํ•ฉ๋‹ˆ๋‹ค. + +```swift +/// in HealthDataUseCase.swift + +/// Health ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ ๊ถŒํ•œ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. +private func requestAuthorization() async throws { + if HKHealthStore.isHealthDataAvailable() { + try await store.requestAuthorization( + toShare: Set(), + read: Set([.workoutType()]) + ) + } else { + throw HealthError.notAvailableDevice + } +} +``` + +**3. info.plist ์ˆ˜์ •** + +์š”์ฒญ ์‹œ ๋‚˜์˜ค๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. + +ํ•ด๋‹น ๊ณผ์ •์€ ํ•„์ˆ˜์ ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +๋งŒ์•ฝ ๋ˆ„๋ฝ์ด ๋˜์–ด์žˆ๊ฑฐ๋‚˜ ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ์ž์„ธํ•œ ์ด์œ ๋ฅผ ์„œ์ˆ ํ•ด๋†“์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ถœ์‹œ ์‹ฌ์‚ฌ ์‹œ ๋ฆฌ์  ์‚ฌ์œ ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์‹ค์ œ๋กœ ๊ฑด๊ฐ• ๋ฐ์ดํ„ฐ ์ชฝ์€ ์•„๋‹ˆ์ง€๋งŒ ์นด๋ฉ”๋ผ ์‚ฌ์šฉ ๊ด€๋ จ ๋ฉ”์‹œ์ง€์— `์‚ฌ์ง„์„ ์ฐ๊ธฐ ์œ„ํ•ด ์นด๋ฉ”๋ผ ํ—ˆ์šฉ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.`๋ผ๊ณ  ์ ์–ด๋†“์œผ๋‹ˆ ๊ตฌ์ฒด์ ์ด์ง€ ์•Š๋‹ค๊ณ  ๋ฆฌ์ ์„ ๋‹นํ–ˆ์Šต๋‹ˆ๋‹ค. + +๋˜ํ•œ ์•ฑ์„ ์˜ฌ๋ฆฌ๋Š” ๊ณผ์ •์—์„œ ์ €ํฌ ์•ฑ์€ ์ฝ๊ธฐ ๋ถ€๋ถ„๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ์— ํ•ด๋‹น info ๋งŒ ์—…๋ฐ์ดํŠธ ํ–ˆ์ง€๋งŒ ์“ฐ๊ธฐ ๋ถ€๋ถ„์ด ๋ˆ„๋ฝ๋˜์–ด ์žˆ๋‹ค๊ณ  ์—…๋กœ๋“œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. + +๊ฒฐ๊ณผ์ ์œผ๋กœ info์— Privacy์™€ ๊ด€๋ จ๋œ ํ•ด๋‹น ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•  ๋•Œ ๊ฐ€๋Šฅํ•œ ๊ตฌ์ฒด์ ์ธ ์ž‘์„ฑ์ด ํ•„์š”ํ•˜๊ณ  Health์˜ ๊ฒฝ์šฐ Update, Share ๋‘˜ ๋‹ค ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. + +แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2025-06-16 แ„‹แ…ฉแ„’แ…ฎ 4 12 16 + + + + +

+ +**2๏ธโƒฃ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ** + +`HKSampleQuery`๋ฅผ ํ†ตํ•ด ์ผ๋ฐ˜์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ์ฟผ๋ฆฌ๋ฌธ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•ด๋‹น ์ฟผ๋ฆฌ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋ง ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +Run Mile ์—์„œ๋Š” ์šด๋™ ๋ฐ์ดํ„ฐ๋งŒ ํ•„์š”ํ•˜๋ฏ€๋กœ workoutType์œผ๋กœ ์ œํ•œํ•˜๊ณ  ๋‚ ์งœ ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌ์„ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค. + +์ฟผ๋ฆฌ ์‚ฌ์šฉ์‹œ ์œ ์˜ํ•  ์ ์€ ๋ฐ˜ํ™˜ ํƒ€์ž…์œผ๋กœ ๋‚˜์˜ค๋Š” `HKSample`์€ ๋‹ค์–‘ํ•œ ๊ฑด๊ฐ• ๋ฐ์ดํ„ฐ๋“ค์˜ ์ถ”์ƒํ™” ํƒ€์ž…์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ณ€ํ™˜ํ•˜์ง€ ์•Š์œผ๋ฉด ์ •๋ณด ์ ‘๊ทผ์ด ์ œํ•œ ๋ฉ๋‹ˆ๋‹ค.(์‹œ๊ฐ„ ๊ด€๋ จ ์ •๋ณด๋งŒ ์ œ๊ณต) + +๋•Œ๋ฌธ์— ์ œ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํŠน์ • ํƒ€์ž…์— ๋งž์ถ”์–ด ํƒ€์ž… ์บ์ŠคํŒ…์„ ๊ผญ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค!(ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์šด๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๋“ค๊ณ  ์˜ค๋ฏ€๋กœ ๊ฑฐ๊ธฐ์— ๋งž๋Š” `HKWorkout` ํƒ€์ž… ์บ์ŠคํŒ… ์ง„ํ–‰) + +```swift +/// in WorkoutDataRepositoryImpl.swift + +private let store = HKHealthStore() + +public func fetchWorkoutData() async throws -> [RunningData] { + let predicate = HKQuery.predicateForWorkouts(with: .running) + let descriptor = [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)] + + let result: [HKWorkout] = try await store.fetchData( + sampleType: .workoutType(), + predicate: predicate, + limit: HKObjectQueryNoLimit, + sortDescriptors: descriptor + ) + + return convertToRunningData(result) +} +``` + +๊ตฌํ˜„๋ถ€์—์„œ ๊ฐ„๋‹จํ•˜๊ณ  ๋ฒ”์šฉ์„ฑ ์žˆ๋Š” ์‚ฌ์šฉ์„ ์œ„ํ•ด ์ œ๋„ค๋ฆญ ๋ฉ”์†Œ๋“œ ์‚ฌ์šฉ +```swift +// in HealthKit+.swift + +public func fetchData( + sampleType: HKSampleType, + predicate: NSPredicate? = nil, + limit: Int, + sortDescriptors: [NSSortDescriptor]? = nil +) async throws -> [T] { + let predicate = HKQuery.predicateForWorkouts(with: .running) + + let data = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[HKSample], any Error>) in + let query = HKSampleQuery( + sampleType: sampleType, + predicate: predicate, + limit: limit, + sortDescriptors: sortDescriptors) { query, samples, error in + if let _ = error { + continuation.resume(with: .failure(HealthError.failedToLoadWorkoutData)) + } + + guard let samples = samples else { + continuation.resume(with: .failure(HealthError.failedToLoadWorkoutData)) + return + } + + continuation.resume(with: .success(samples)) + } + self.execute(query) + } + + guard let result = data as? [T] else { throw HealthError.failedToLoadWorkoutData } + + return result +} +``` + +
+ +**3๏ธโƒฃ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์—…๋ฐ์ดํŠธ ์ ์šฉ** + +๋Œ€ํ‘œ์ ์ธ ์šด๋™ ๊ธฐ๋ก ์•ฑ `Strava`์˜ ๊ฒฝ์šฐ ์ƒˆ๋กœ์šด ์šด๋™ ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ๊ธฐ๋ฉด ์•ฑ์œผ๋กœ ์ž๋™ ์—…๋ฐ์ดํŠธ๋ฅผ ์‹œ์ผœ์ค๋‹ˆ๋‹ค. + +์ด ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ๊ถ๊ธˆ์ฆ๊ณผ ํ˜ธ๊ธฐ์‹ฌ์ด ์ƒ๊ฒจ ๊ณต๋ถ€ ํ›„ ํ”„๋กœ์ ํŠธ์— ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค. + +`HKHealthStore`์˜ `enableBackgroundDelivery()`๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ํŠน์ • ํƒ€์ž…์˜ ๊ฑด๊ฐ• ๋ฐ์ดํ„ฐ๊ฐ€ ์—…๋ฐ์ดํŠธ ๋˜์—ˆ์„ ๋•Œ๋ฅผ ํŠธ๋ฆฌ๊ฑฐ๋กœ ์•ฑ์„ ๊นจ์›Œ Background Task ๋ฅผ ์ง„ํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์œ ์˜ํ•  ์ ์€ ํ•ด๋‹น ๊ธฐ๋Šฅ์€ ํŠน์ • ๋ฐ์ดํ„ฐ์˜ ์—…๋ฐ์ดํŠธ ์—ฌ๋ถ€๋งŒ ์•Œ๋ ค์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์—…๋ฐ์ดํŠธ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Background Task ์•ˆ์—์„œ ์ƒˆ๋กœ์šด Sample Query๋กœ ๋ฐ์ดํ„ฐ๋ฅผ fetchํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +๋˜ํ•œ ์•ฑ์ด ์™„์ „ ์ข…๋ฃŒ(suspended)๋˜๋ฉด background ๋“ฑ๋ก๋„ ๊ฐ™์ด ์ข…๋ฃŒ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์šด๋™์ด ์–ธ์ œ ์ง„ํ–‰๋ ์ง€ ์•Œ ์ˆ˜ ์—†๋Š” ํŠน์„ฑ ์ƒ ํ•ญ์ƒ ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ์•ฑ ์‹คํ–‰ ์‹œ(AppDelegate)์— ๋“ฑ๋ก๋˜๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. + +Run Mile์—์„œ ํ•ด๋‹น ๊ธฐ๋Šฅ์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•œ feature๋Š” + +์ƒˆ๋กœ์šด ์šด๋™(๋Ÿฌ๋‹)์ด ์ถ”๊ฐ€๋˜์—ˆ์„ ๋•Œ +* ์ž๋™ ๋“ฑ๋ก ๊ธฐ๋Šฅ์ด ์ผœ์ ธ ์žˆ์„ ๊ฒฝ์šฐ -> ์‹ ๋ฐœ์— ์šด๋™ ์ถ”๊ฐ€ ํ›„, ์ถ”๊ฐ€ ์™„๋ฃŒ noti ์ƒ์„ฑ +* ์ž๋™ ๋“ฑ๋ก ๊ธฐ๋Šฅ x -> ์šด๋™ ์™„๋ฃŒ noti ์ƒ์„ฑ(์•ฑ์œผ๋กœ ์œ ๋„ํ•˜๊ธฐ ์œ„ํ•จ) + +์ž…๋‹ˆ๋‹ค. + +```swift +/// in AppDelegate.swift + +public static func setHealthBackgroundTask() async { + let store = HKHealthStore() + + do { + try await store.enableBackgroundDelivery(for: .workoutType(), frequency: .immediate) + let query = HKObserverQuery( + sampleType: .workoutType(), + predicate: nil + ) { query, completionHandler, error in + if let error = error { + print(error) + return + } + + let sort = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) + let sampleQuery = HKSampleQuery(queryDescriptors: [.init(sampleType: .workoutType(), predicate: nil)], limit: 1, sortDescriptors: [sort]) { _, samples, error in + + ... + + guard let workout = samples?.first as? HKWorkout else { + return + } + + guard case .running = workout.workoutActivityType else { + return + } + + let workoutId = workout.uuid.uuidString + let currentId = UserDefaults.standard.recentWorkoutID + + if !UserDefaults.standard.isFirstLaunch { + UserDefaults.standard.recentWorkoutID = workoutId + } else { + if workoutId != currentId { + let distance = workout.getKilometerDistance() + if !UserDefaults.standard.selectedShoesID.isEmpty { + + UNUserNotificationCenter.requestNotification( + title: String(format: "%.2fkm ๋Ÿฌ๋‹ ์™„๋ฃŒ ๐Ÿ”ฅ๐Ÿ”ฅ", distance!), + body: distance == nil + ? "์‹ ๋ฐœ์— ์ž๋™ ๋“ฑ๋ก์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!" + : String(format: "์‹ ๋ฐœ์— ์ž๋™ ๋“ฑ๋ก์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋Ÿฌ๋‹ ํ›„ ์ŠคํŠธ๋ ˆ์นญ ๊ผญ ์žŠ์ง€ ๋งˆ์„ธ์š”!", distance!) + ) + + autoRegisterShoes(workout: workout) + } else { + UNUserNotificationCenter.requestNotification( + title: String(format: "%.2fkm ๋Ÿฌ๋‹ ์™„๋ฃŒ ๐Ÿ”ฅ๐Ÿ”ฅ", distance!), + body: distance == nil + ? "์‹ ๋ฐœ ๋งˆ์ผ๋ฆฌ์ง€๋ฅผ ๋“ฑ๋กํ•  ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋“ฑ๋กํ•˜๋Ÿฌ ๊ฐ€๋ณผ๊นŒ์š”?" + : String(format: "%.2fkm, ์žŠ์ง€ ๋ง๊ณ  ๋งˆ์ผ๋ฆฌ์ง€๋ฅผ ๋“ฑ๋กํ•˜๋Ÿฌ ์˜ค์„ธ์š”!", distance!) + ) + } + UserDefaults.standard.recentWorkoutID = workoutId + } + } + } + + store.execute(sampleQuery) + + completionHandler() + } + + store.execute(query) + } catch { + print(error) + } +} +``` + From 351232d8274eddc1ea7266725ee8f0a28c870b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?= <116792524+mooninbeom@users.noreply.github.com> Date: Mon, 16 Jun 2025 18:11:53 +0900 Subject: [PATCH 02/54] =?UTF-8?q?[Fix]=20README=20=EB=82=B4=EC=9A=A9=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d4af0da..3dae04c 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,66 @@ ## ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ ๋‚ด๊ฐ€ ๋ฐฐ์šด ๊ฒƒ -### ํด๋ฆฐ ์•„ํ‚คํ…์ณ๋ฅผ ๋‚ด ์ž…๋ง›์— ๋งž๊ฒŒ ์‚ฌ์šฉ +### ํด๋ฆฐ ์•„ํ‚คํ…์ณ ์ ์šฉ +๊ธฐ์กด์—๋Š” State์™€ Binding์„ ์‚ฌ์šฉํ•ด SwiftUI์—์„œ ์ œ๊ณตํ•˜๋Š” ํ”„๋กœํผํ‹ฐ ๋ž˜ํผ๋ฅผ ํ™œ์šฉํ•ด ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ `@FetchRequest`๋ฅผ ํ™œ์šฉํ•ด View์—์„œ ๋ฐ”๋กœ ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์™€ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ทฐ๋“ค์ด ์—ฌ๋Ÿฌ๊ฐœ ์ƒ๊ธฐ๋ฉด์„œ ์ค‘๋ณต๋œ ์ฝ”๋“œ๋“ค์ด ๋Š˜์–ด๋‚ฌ๊ณ , ๋ทฐ ํ•˜๋‚˜์— ๋‹ค์–‘ํ•œ ๋ชฉ์ ์˜ ์ฝ”๋“œ๋“ค์ด ์ƒ๊ธฐ๋‹ค๋ณด๋‹ˆ ๋ทฐ ํ•˜๋‚˜๊ฐ€ ๋„ˆ๋ฌด ์ปค์ ธ๋ฒ„๋ฆฌ๋Š” ์ƒํ™ฉ์ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค. + +์ด๋Ÿฌ๋‹ค๋ณด๋‹ˆ ์–ด๋А ํ•œ ๋ถ€๋ถ„์—์„œ ๋ฌธ์ œ๊ฐ€ ์ผ์–ด๋‚˜๋ฉด ๊ทธ ์ฝ”๋“œ๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•ด ๋‚œํ•ดํ•œ View์˜ ์ฝ”๋“œ ์†์—์„œ ํ•„์š”ํ•œ ๋ถ€๋ถ„์„ ์ฐพ๊ธฐ๊ฐ€ ์–ด๋ ค์›Œ์กŒ๊ณ  ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ๋‚ฎ์•„์กŒ์Šต๋‹ˆ๋‹ค. + +๊ฐ์ž์˜ ๊ด€์‹ฌ์‚ฌ์— ๋งž๊ฒŒ ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•ด์•ผ ํ•  ํ•„์š”์„ฑ์„ ๋А๋ผ๊ณ  ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ ์ด๋Ÿฐ ์•„ํ‚คํ…์ณ์˜ ๋Œ€ํ‘œ๊ฒฉ์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋Š” **ํด๋ฆฐ ์•„ํ‚คํ…์ณ**๋ฅผ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. + +ํด๋ฆฐ ์•„ํ‚คํ…์ณ์—์„œ ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์— ์ œ์ผ ์ค‘์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•œ ๋ถ€๋ถ„์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. +* ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ถ€๋ถ„(Repository)์™€ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„(UseCase)์˜ ๋ถ„๋ฆฌ +* ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ UseCase๋ฅผ ํ†ตํ•ด ์ฃผ์ž… +* View์˜ ๋ชฉ์ ์„ฑ(Presentation, ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๋งŒ ๋„ฃ๊ธฐ)์„ ํ™•์‹คํžˆ ํ•˜๊ธฐ ์œ„ํ•ด View์™€ ViewModel๋กœ ๋ถ„๋ฆฌ + +
+ +**๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ถ€๋ถ„(Repository)์™€ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„(UseCase)์˜ ๋ถ„๋ฆฌ** + +๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ๋ฅผ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ถ€๋ถ„์„ **Repository ํŒจํ„ด**์„ ์‚ฌ์šฉํ•ด ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. + +์ถ”ํ›„ ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ์„ ์ฆ์ง„์‹œํ‚ค๊ณ  UseCase๊ฐ€ Repository๋ฅผ ์˜์กดํ•˜๋Š” ํ˜•ํƒœ๋ฅผ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ํ”„๋กœํ† ์ฝœ์„ ํ™œ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. + +๋˜ํ•œ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด Swift Concurrency์—์„œ ์ง€์›ํ•ด๋Š” `actor`๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. + +์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉ๋œ Repository๋Š” ์ด 2๊ฐœ ์ž…๋‹ˆ๋‹ค. +* WorkoutDataRepository(HealthKit์œผ๋กœ ์šด๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ ˆํฌ์ง€ํ† ๋ฆฌ) +* ShoesDataRepository(Realm์œผ๋กœ ๊ด€๋ฆฌ๋˜๋Š” ์‹ ๋ฐœ ๋ฐ์ดํ„ฐ๋ฅผ CRUDํ•˜๋Š” ๋ ˆํฌ์ง€ํ† ๋ฆฌ) + +
+ +**ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ UseCase๋ฅผ ํ†ตํ•ด ์ฃผ์ž…** + +์œ„์™€ ๋น„์Šทํ•˜๊ฒŒ ์ถ”ํ›„ ์œ ๋‹› ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•  ๊ฒฝ์šฐ ํ”„๋กœ์ ํŠธ์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ํ…Œ์ŠคํŠธ ์šฉ์˜์„ฑ์„ ์ฆ์ง„์‹œํ‚ค๊ธฐ ์œ„ํ•ด UseCase๋กœ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. + +๋น„์Šทํ•œ ๊ธฐ๋Šฅ์„ ํ•„์š”๋กœ ํ•˜๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋ทฐ์—์„œ ํ•˜๋‚˜์˜ UseCase๋ฅผ ์žฌํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ๊ณผ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์•„์ง ๊ฐœ์ธ์ ์ธ ์ƒ๊ฐ์€ ๋ณผ๋ฅจ์ด ํฌ์ง€ ์•Š์€ ํ”„๋กœ์ ํŠธ๋ผ์„œ UseCase๋ผ๋Š” ํ•œ๋‹จ๊ณ„ ๋” ๊ฑฐ์ณ ๋กœ์ง์„ ์‹คํ–‰ ์‹œํ‚ค๋Š” ๊ฒƒ์ด ์˜คํžˆ๋ ค ๋ถˆํ•„์š”ํ•œ ๊ณผ์ •์ด๋ผ๊ณ  ๋А๊ผˆ์Šต๋‹ˆ๋‹ค. + +ํ”„๋กœ์ ํŠธ์˜ ๊ทœ๋ชจ์— ๋”ฐ๋ผ ๋ถ€๋ถ„์ ์ธ ์ ์šฉ์ด ํ•„์š”ํ•ด ๋ณด์˜€์Šต๋‹ˆ๋‹ค. + +
+ +**View์˜ ๋ชฉ์ ์„ฑ(Presentation, ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๋งŒ ๋„ฃ๊ธฐ)์„ ํ™•์‹คํžˆ ํ•˜๊ธฐ ์œ„ํ•ด View์™€ ViewModel๋กœ ๋ถ„๋ฆฌ** + +View๋Š” ๋ง **๊ทธ๋Œ€๋กœ ๋ณด์—ฌ์ง€๋Š” ๊ฒƒ** ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ณด์—ฌ์ง€๋Š”๋Œ€ ํ•„์š”ํ•œ ํ”„๋กœํผํ‹ฐ๋ฅผ ์ œ์™ธํ•œ ์ƒํƒœ๊ด€๋ฆฌ ๋ณ€์ˆ˜๋“ค๊ณผ ๋ฉ”์†Œ๋“œ๋ฅผ ๋ชจ๋‘ ViewModel๋กœ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. + +๋˜ํ•œ View์˜ action์œผ๋กœ ์‹คํ–‰๋˜๋Š” ๋ฉ”์†Œ๋“œ์˜ ๋„ค์ด๋ฐ์„ `000ButtonTapped`์™€ ๊ฐ™์ด ์ž‘์„ฑํ•˜์—ฌ View์—์„œ ํŠน์ • ์•ก์…˜์ด ์–ด๋–ค ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋Š”์ง€๋ฅผ ์ตœ๋Œ€ํ•œ ์ˆจ๊ฒผ์Šต๋‹ˆ๋‹ค. + +์ด๋ฅผ ํ†ตํ•ด View์˜ ๋ชฉ์ ์„ฑ์ธ **๋ณด์—ฌ์ง€๋Š” ๊ฒƒ**์„ ์ตœ๋Œ€ํ•œ ์ง€ํ‚ฌ ์ˆ˜ ์žˆ์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์œ„์™€ ๋น„์Šทํ•˜๊ฒŒ ๋ณผ๋ฅจ์ด ์ž‘์€ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์†Œ์œ„ ๋งํ•˜๋Š” ์ด๋Ÿฐ MVVM ํŒจํ„ด์ด SwiftUI ํ”„๋กœ์ ํŠธ๋ผ๋ฉด ํ•„์š”ํ•œ ๊ฐ€? ์˜๋ฌธ์ ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์˜คํžˆ๋ ค ์˜ค๋ฒ„์—”์ง€๋‹ˆ์–ด๋ง ์ด๋ผ๋Š” ๋А๋‚Œ์ด ๋“ค์—ˆ์ฃ . + +SwiftUI๊ฐ€ ์ง€์›ํ•˜๋Š” ์ƒํƒœ๊ด€๋ฆฌ ๊ธฐ๋Šฅ๋“ค์„ ๋ณด๋ฉด MVVM ํŒจํ„ด ๋ณด๋‹จ View ํ•˜๋‚˜์—์„œ ๋ชจ๋“  ๊ฒƒ์„ ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑ์„ ํ•ด ๋†“์€๋Œ€๋Š” ์ด์œ ๊ฐ€ ์žˆ์„ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. + +์ œ๊ฐ€ ๋А๋‚€ ์ ์€ ๋‘˜ ๋‹ค ์žฅ๋‹จ์ด ์žˆ๋Š” ๋งŒํผ ๊ฐ์ž์˜ ์‚ฌ์šฉ์„ฑ์„ ์ดํ•ดํ•˜๊ณ  ์ƒํ™ฉ์— ๋งž๊ฒŒ ๋””์ž์ธ ํŒจํ„ด์„ ์ฑ„ํƒํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์„ ๊ธฐ๋ฅด๋Š” ๊ฒƒ์ด ์ข‹์•„๋ณด์˜€์Šต๋‹ˆ๋‹ค. + +

### HealthKit ํ™œ์šฉ ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์˜ ํ•ต์‹ฌ์€ ๊ธฐ๊ธฐ ๋‚ด์— ์žˆ๋Š” ์šด๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค. From 7c7e59cc0bfbfae1d62fe2106507a1ad7265b261 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Mon, 16 Jun 2025 21:47:44 +0900 Subject: [PATCH 03/54] =?UTF-8?q?[#33]=20README=20=EB=A0=88=ED=8D=BC?= =?UTF-8?q?=EB=9F=B0=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj index 0d85d63..2b1714c 100644 --- a/Run Mile.xcodeproj/project.pbxproj +++ b/Run Mile.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 2C079FBF2DB2505700B1EE0D /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 2C079FBE2DB2505700B1EE0D /* RealmSwift */; }; 2C079FC02DB2507700B1EE0D /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 2C079FBE2DB2505700B1EE0D /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 2CE46E212DAE78D7008C6FBC /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 2CE46E202DAE78D7008C6FBC /* .gitignore */; }; - 2CF57F5C2DF9483100F59463 /* PrivacyPolicy.md in Resources */ = {isa = PBXBuildFile; fileRef = 2CF57F5B2DF9483100F59463 /* PrivacyPolicy.md */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -47,6 +46,7 @@ /* Begin PBXFileReference section */ 2C079A562DAF9F7F00B1EE0D /* Run_MileApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Run_MileApp.swift; sourceTree = ""; }; + 2C7633922E0049A30005D988 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 2CE46DF32DAE7780008C6FBC /* Run Mile.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Run Mile.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2CE46E002DAE7782008C6FBC /* Run MileTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Run MileTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 2CE46E0A2DAE7782008C6FBC /* Run MileUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Run MileUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -101,6 +101,7 @@ 2CE46DEA2DAE7780008C6FBC = { isa = PBXGroup; children = ( + 2C7633922E0049A30005D988 /* README.md */, 2CF57F5B2DF9483100F59463 /* PrivacyPolicy.md */, 2C079A562DAF9F7F00B1EE0D /* Run_MileApp.swift */, 2CE46E202DAE78D7008C6FBC /* .gitignore */, @@ -246,7 +247,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 2CF57F5C2DF9483100F59463 /* PrivacyPolicy.md in Resources */, 2CE46E212DAE78D7008C6FBC /* .gitignore in Resources */, ); runOnlyForDeploymentPostprocessing = 0; From 07a473db058d7133090490c03e87d688a576b871 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Mon, 16 Jun 2025 22:21:13 +0900 Subject: [PATCH 04/54] [#33] fastlane init --- Gemfile | 3 + Gemfile.lock | 227 +++++++++++++++++++++++++++++ Run Mile.xcodeproj/project.pbxproj | 10 ++ fastlane/Appfile | 8 + fastlane/Fastfile | 25 ++++ 5 files changed, 273 insertions(+) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 fastlane/Appfile create mode 100644 fastlane/Fastfile diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7a118b4 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..bb63134 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,227 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1116.0) + aws-sdk-core (3.225.2) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.105.0) + aws-sdk-core (~> 3, >= 3.225.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.189.1) + aws-sdk-core (~> 3, >= 3.225.0) + 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.3.0) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + 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.10.4) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.1.0) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.4.0) + fastlane (2.228.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored (~> 1.2) + commander (~> 4.6) + 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.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (>= 0.1.1, < 1.0.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.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.8.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.5.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + 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.12.2) + jwt (2.10.1) + base64 + logger (1.7.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.15.0) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + naturally (2.3.0) + nkf (0.2.0) + optparse (0.6.0) + os (1.1.4) + plist (3.7.2) + public_suffix (6.0.2) + rake (13.3.0) + 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.1) + rouge (3.28.0) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + security (0.1.5) + signet (0.20.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.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-24 + ruby + +DEPENDENCIES + fastlane + +BUNDLED WITH + 2.6.9 diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj index 2b1714c..af30b97 100644 --- a/Run Mile.xcodeproj/project.pbxproj +++ b/Run Mile.xcodeproj/project.pbxproj @@ -47,6 +47,8 @@ /* Begin PBXFileReference section */ 2C079A562DAF9F7F00B1EE0D /* Run_MileApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Run_MileApp.swift; sourceTree = ""; }; 2C7633922E0049A30005D988 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 2C7635B02E0051AD0005D988 /* Gemfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile; sourceTree = ""; }; + 2C7635B12E0051AD0005D988 /* Gemfile.lock */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile.lock; sourceTree = ""; }; 2CE46DF32DAE7780008C6FBC /* Run Mile.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Run Mile.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2CE46E002DAE7782008C6FBC /* Run MileTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Run MileTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 2CE46E0A2DAE7782008C6FBC /* Run MileUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Run MileUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -55,6 +57,11 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ + 2C7635AF2E0051AD0005D988 /* fastlane */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = fastlane; + sourceTree = ""; + }; 2CE46DF52DAE7780008C6FBC /* Run Mile */ = { isa = PBXFileSystemSynchronizedRootGroup; path = "Run Mile"; @@ -101,6 +108,9 @@ 2CE46DEA2DAE7780008C6FBC = { isa = PBXGroup; children = ( + 2C7635AF2E0051AD0005D988 /* fastlane */, + 2C7635B02E0051AD0005D988 /* Gemfile */, + 2C7635B12E0051AD0005D988 /* Gemfile.lock */, 2C7633922E0049A30005D988 /* README.md */, 2CF57F5B2DF9483100F59463 /* PrivacyPolicy.md */, 2C079A562DAF9F7F00B1EE0D /* Run_MileApp.swift */, diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 0000000..4ace4da --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,8 @@ +app_identifier("com.mooni.Run-Mile") # The bundle identifier of your app +apple_id("dlsqja567@naver.com") # Your Apple Developer Portal username + +itc_team_id("126391461") # App Store Connect Team ID +team_id("VF8R3A969C") # Developer Portal Team ID + +# For more information about the Appfile, see: +# https://docs.fastlane.tools/advanced/#appfile diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..3e1cf10 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,25 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:ios) + +platform :ios do + desc "Push a new beta build to TestFlight" + lane :beta do + increment_build_number(xcodeproj: "Run Mile.xcodeproj") + build_app(scheme: "Run Mile") + upload_to_testflight + end +end From 3ca3fd68d3488724ad4c759d2da8ca59e259a8a4 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 18 Jun 2025 13:04:28 +0900 Subject: [PATCH 05/54] =?UTF-8?q?[#33]=20Fastlane=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile.xcodeproj/project.pbxproj | 38 ++++++++++------- fastlane/Fastfile | 65 +++++++++++++++++++++++++++++- fastlane/Matchfile | 13 ++++++ 3 files changed, 100 insertions(+), 16 deletions(-) create mode 100644 fastlane/Matchfile diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj index af30b97..872e465 100644 --- a/Run Mile.xcodeproj/project.pbxproj +++ b/Run Mile.xcodeproj/project.pbxproj @@ -443,9 +443,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 202506113; - DEVELOPMENT_TEAM = VF8R3A969C; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 202506182; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = VF8R3A969C; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Run-Mile-Info.plist"; @@ -457,7 +460,7 @@ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UIStatusBarStyle = ""; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDarkContent; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UIUserInterfaceStyle = Dark; @@ -466,9 +469,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "com.mooni.Run-Mile AppStore"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -485,9 +490,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 202506113; - DEVELOPMENT_TEAM = VF8R3A969C; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 202506182; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = VF8R3A969C; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Run-Mile-Info.plist"; @@ -499,7 +507,7 @@ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UIStatusBarStyle = ""; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDarkContent; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UIUserInterfaceStyle = Dark; @@ -508,9 +516,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "com.mooni.Run-Mile AppStore"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -526,7 +536,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 202506182; DEVELOPMENT_TEAM = VF8R3A969C; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.4; @@ -545,7 +555,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 202506182; DEVELOPMENT_TEAM = VF8R3A969C; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.4; @@ -563,7 +573,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 202506182; DEVELOPMENT_TEAM = VF8R3A969C; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; @@ -580,7 +590,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 202506182; DEVELOPMENT_TEAM = VF8R3A969C; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 3e1cf10..c4fdd8c 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -18,8 +18,69 @@ default_platform(:ios) platform :ios do desc "Push a new beta build to TestFlight" lane :beta do - increment_build_number(xcodeproj: "Run Mile.xcodeproj") - build_app(scheme: "Run Mile") + + today = Time.now.strftime("%Y%m%d") + + current_build_number = "#{latest_testflight_build_number}" + + if current_build_number.length == 9 + current_date = current_build_number[0,8] # ์•ž 8์ž๋ฆฌ๊ฐ€ ๋‚ ์งœ + current_count = current_build_number[8].to_i # ๋งˆ์ง€๋ง‰ 1์ž๋ฆฌ๊ฐ€ ์ˆซ์ž + else + # ๋นŒ๋“œ ๋„˜๋ฒ„๊ฐ€ ์—†๊ฑฐ๋‚˜ ํ˜•์‹์ด ๋‹ค๋ฅด๋ฉด ์ดˆ๊ธฐํ™” + current_date = "" + current_count = 0 + end + + if current_date == today + next_count = current_count + 1 + else + next_count = 1 + end + + new_build_number = "#{today}#{next_count}" + + increment_build_number( + xcodeproj: "Run Mile.xcodeproj", + build_number: new_build_number + ) + + # notification( + # subtitle: "Build Number: #{new_build_number}", + # message: "ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ ์ค€๋น„" + # ) + + app_store_connect_api_key( + key_id: "#{ENV[APP_STORE_CONNECT_KEY_ID]}", + issuer_id: "#{ENV[APP_STORE_CONNECT_ISSUER_ID]}", + key_content: "#{ENV[APP_STORE_CONNECT_KEY]}", + is_key_content_base64: true + ) + + match(type: "appstore") + + build_app( + scheme: "Run Mile", + xcodebuild_formatter: "xcpretty", + export_options: { + provisioningProfiles: { + "com.mooni.Run-Mile" => "com.mooni.Run-Mile AppStore" + } + } + ) + upload_to_testflight + + # notification( + # subtitle: "Testflight ๋ฐฐํฌ ์„ฑ๊ณต!", + # message: "Build Number : #{new_build_number}, ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค!" + # ) end + + # error do |lane, exception, options| + # notification( + # subtitle: "Testflight ๋ฐฐํฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", + # message: "#{exception}" + # ) + # end end diff --git a/fastlane/Matchfile b/fastlane/Matchfile new file mode 100644 index 0000000..4b50f33 --- /dev/null +++ b/fastlane/Matchfile @@ -0,0 +1,13 @@ +git_url("https://github.com/mooninbeom/fastlane-match") + +storage_mode("git") + +type("appstore") # The default type, can be: appstore, adhoc, enterprise or development + +app_identifier("com.mooni.Run-Mile") +username("dlsqja567@naver.com") # Your Apple Developer Portal username + +# For all available options run `fastlane match --help` +# Remove the # in the beginning of the line to enable the other options + +# The docs are available on https://docs.fastlane.tools/actions/match From 28942033dab9c74be14d7b1fc344d5c0e712955e Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 18 Jun 2025 13:16:49 +0900 Subject: [PATCH 06/54] =?UTF-8?q?[#33]=20=EC=9D=B8=EC=A6=9D=EC=84=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20README=20fastlane=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- Run Mile.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3dae04c..f58cffc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ |์ƒํƒœ|์•ฑ์Šคํ† ์–ด ๋ฐฐํฌ ์™„๋ฃŒ ๋ฐ ์œ ์ง€๋ณด์ˆ˜ ์ง„ํ–‰ ์ค‘(v1.0.0)| |:--|:--| -|๊ธฐ์ˆ  ์Šคํƒ|SwiftUI, HealthKit, Realm, UserNotifications, Vision(์ด๋ฏธ์ง€ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ)| +|๊ธฐ์ˆ  ์Šคํƒ|SwiftUI, HealthKit, Realm, Fastlane, UserNotifications, Vision(์ด๋ฏธ์ง€ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ)| |์•ฑ์Šคํ† ์–ด|[Run Mile](https://apps.apple.com/kr/app/run-mile/id6747099791)| |์ด๋ฉ”์ผ ๋ฌธ์˜|dlsqja567@naver.com| @@ -25,6 +25,7 @@ |**0. ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ Run Mile ์•ฑ ๊ฐœ๋ฐœ๊ธฐ**|https://velog.io/@mooninbeom/0.-์‚ฌ์ด๋“œ-ํ”„๋กœ์ ํŠธ-Run-Mile-์•ฑ-๊ฐœ๋ฐœ๊ธฐ| |**1. HealthKit ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ(with Continuation)**|https://velog.io/@mooninbeom/1.-HealthKit-๋ฐ์ดํ„ฐ-์‚ฌ์šฉwith-Continuation| |**2. ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ HealthKit ํ™œ์šฉํ•˜๊ธฐ**|https://velog.io/@mooninbeom/2.-๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ-HealthKit-ํ™œ์šฉํ•˜๊ธฐ-o2p1gg9l| +|**3. Fastlane์œผ๋กœ Testflight ๋ฐฐํฌ ์ž๋™ํ™”**|https://velog.io/@mooninbeom/3.-Fastlane์œผ๋กœ-Testflight-๋ฐฐํฌ-์ž๋™ํ™”|
diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj index 872e465..ef15089 100644 --- a/Run Mile.xcodeproj/project.pbxproj +++ b/Run Mile.xcodeproj/project.pbxproj @@ -444,7 +444,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 202506182; DEVELOPMENT_TEAM = ""; @@ -473,7 +473,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "com.mooni.Run-Mile AppStore"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -491,7 +491,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 202506182; DEVELOPMENT_TEAM = ""; @@ -520,7 +520,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "com.mooni.Run-Mile AppStore"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; From 61ed2116a91678d8801adfddc22aa3701f83278e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?= <116792524+mooninbeom@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:06:58 +0900 Subject: [PATCH 07/54] =?UTF-8?q?[#33]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20Workflow=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/testflight.yml diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml new file mode 100644 index 0000000..5892970 --- /dev/null +++ b/.github/workflows/testflight.yml @@ -0,0 +1,50 @@ +name: ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ ๐Ÿš€ + +on: + push: + branches: + - main + # ์•ก์…˜ ํ…Œ์ŠคํŠธ์šฉ + pull_request: + branches: + - develop + +jobs: + build-upload-testflight: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up RUBY + uses: ruby/setup-ruby@v1 + with: + ruby-version: '2.7' + + - name: Install Bundler + run: gem install bundler + + - name: Install Fastlane + run: brew install fastlane + + - name: Check Fastlane + run: fastlane --version + + - name: Install Dependencies + run: bundle install + + - name: Upload to Testflight โœˆ๏ธ + env: + APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY}} + APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} + APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + run: bundle exec fastlane beta + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: ipa-and-dsym + path: | + ./Run\ Mile.ipa + ./Run\ Mile.app.dSYM.zip From c4aacb083e9e770d4a987b548cdf791e25b55b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?= <116792524+mooninbeom@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:09:36 +0900 Subject: [PATCH 08/54] =?UTF-8?q?[#33]=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๋ฃจ๋น„ ๋ฒ„์ „ ์žฌ์กฐ์ •(2.7 -> 3.1) --- .github/workflows/testflight.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index 5892970..a3657a9 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -19,7 +19,7 @@ jobs: - name: Set up RUBY uses: ruby/setup-ruby@v1 with: - ruby-version: '2.7' + ruby-version: '3.1' - name: Install Bundler run: gem install bundler From 3e39cad1bd8bbc7f21bec6ece273e9451bdc0d31 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 18 Jun 2025 14:14:26 +0900 Subject: [PATCH 09/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(App?= =?UTF-8?q?store=20connect=20api=20key=20=EC=9C=84=EC=B9=98=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c4fdd8c..cca80eb 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -19,6 +19,13 @@ platform :ios do desc "Push a new beta build to TestFlight" lane :beta do + app_store_connect_api_key( + key_id: "#{ENV[APP_STORE_CONNECT_KEY_ID]}", + issuer_id: "#{ENV[APP_STORE_CONNECT_ISSUER_ID]}", + key_content: "#{ENV[APP_STORE_CONNECT_KEY]}", + is_key_content_base64: true + ) + today = Time.now.strftime("%Y%m%d") current_build_number = "#{latest_testflight_build_number}" @@ -50,13 +57,6 @@ platform :ios do # message: "ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ ์ค€๋น„" # ) - app_store_connect_api_key( - key_id: "#{ENV[APP_STORE_CONNECT_KEY_ID]}", - issuer_id: "#{ENV[APP_STORE_CONNECT_ISSUER_ID]}", - key_content: "#{ENV[APP_STORE_CONNECT_KEY]}", - is_key_content_base64: true - ) - match(type: "appstore") build_app( From 8c5c65b6ac817389371c5bdc3e7fd0de5be9a8ca Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 18 Jun 2025 14:20:49 +0900 Subject: [PATCH 10/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20=EB=B3=80=EC=88=98=20=EC=8C=8D=EB=94=B0?= =?UTF-8?q?=EC=98=B4=ED=91=9C=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index cca80eb..2e701c6 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -20,9 +20,9 @@ platform :ios do lane :beta do app_store_connect_api_key( - key_id: "#{ENV[APP_STORE_CONNECT_KEY_ID]}", - issuer_id: "#{ENV[APP_STORE_CONNECT_ISSUER_ID]}", - key_content: "#{ENV[APP_STORE_CONNECT_KEY]}", + key_id: "#{ENV["APP_STORE_CONNECT_KEY_ID"]}", + issuer_id: "#{ENV["APP_STORE_CONNECT_ISSUER_ID"]}", + key_content: "#{ENV["APP_STORE_CONNECT_KEY"]}", is_key_content_base64: true ) From 75f1d22d0c2b613b6cbeb3ae9e87dedd9b39d1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?= <116792524+mooninbeom@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:56:05 +0900 Subject: [PATCH 11/54] =?UTF-8?q?[#33]=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ssh ์—ฐ๊ฒฐ ์ถ”๊ฐ€ --- .github/workflows/testflight.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index a3657a9..5429083 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -16,6 +16,12 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Set up SSH + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.SSH_PRIVATE_KEY }} + known_hosts: ${{ secrets.KNOWN_HOSTS }} + - name: Set up RUBY uses: ruby/setup-ruby@v1 with: From ab3d922969756ab51037c9d84f05929c619e635f Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 18 Jun 2025 15:08:58 +0900 Subject: [PATCH 12/54] =?UTF-8?q?[#33]=20Matchfile=20=EC=88=98=EC=A0=95(gi?= =?UTF-8?q?thub=20url=20HTTPS=20->=20SSH)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Matchfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Matchfile b/fastlane/Matchfile index 4b50f33..3da1c80 100644 --- a/fastlane/Matchfile +++ b/fastlane/Matchfile @@ -1,4 +1,4 @@ -git_url("https://github.com/mooninbeom/fastlane-match") +git_url("git@github.com:mooninbeom/fastlane-match.git") storage_mode("git") From 116632d1efbe178d1c1484a46ea6e4380a182e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?= <116792524+mooninbeom@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:20:38 +0900 Subject: [PATCH 13/54] =?UTF-8?q?[#33]=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index 5429083..0e3d6e5 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -11,7 +11,7 @@ on: jobs: build-upload-testflight: - runs-on: macos-latest + runs-on: macos-15 steps: - uses: actions/checkout@v4 From 8ef3adf3db3b235a5a07d859fc7139226e04f267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?= <116792524+mooninbeom@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:27:28 +0900 Subject: [PATCH 14/54] =?UTF-8?q?[#33]=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index 0e3d6e5..c559960 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -26,6 +26,14 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: '3.1' + + - name: Set up Xcode + uses: maxim-lobanov/setup-xcode@v1.6.0 + with: + xcode-version: '16.2' + + - name: Check Xcode version + run: xcodebuild -version - name: Install Bundler run: gem install bundler From 4f2a99d32f902c32314c7925c8652e25609dca30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?= <116792524+mooninbeom@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:38:10 +0900 Subject: [PATCH 15/54] =?UTF-8?q?[#33]=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index c559960..302512e 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -27,10 +27,13 @@ jobs: with: ruby-version: '3.1' - - name: Set up Xcode - uses: maxim-lobanov/setup-xcode@v1.6.0 - with: - xcode-version: '16.2' + # - name: Set up Xcode + # uses: maxim-lobanov/setup-xcode@v1.6.0 + # with: + # xcode-version: '16.2' + + - name: Select Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer - name: Check Xcode version run: xcodebuild -version From d16b348c4073f853cb2b840ceeab1298271af6d9 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 18 Jun 2025 17:21:57 +0900 Subject: [PATCH 16/54] =?UTF-8?q?[#33]=20=EC=9D=B8=EC=A6=9D=EC=84=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj index ef15089..acf749b 100644 --- a/Run Mile.xcodeproj/project.pbxproj +++ b/Run Mile.xcodeproj/project.pbxproj @@ -444,7 +444,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 202506182; DEVELOPMENT_TEAM = ""; @@ -473,7 +473,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -491,7 +491,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 202506182; DEVELOPMENT_TEAM = ""; @@ -520,7 +520,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; From 50bcb9383040f9d51eb55f962758f7796c568aa6 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 18 Jun 2025 19:51:18 +0900 Subject: [PATCH 17/54] =?UTF-8?q?[#33]=20Fastfile=20Slack=20=EB=85=B8?= =?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile/Helper/HealthKit+.swift | 2 +- fastlane/Fastfile | 28 ++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Run Mile/Helper/HealthKit+.swift b/Run Mile/Helper/HealthKit+.swift index 227bc6b..3d423f3 100644 --- a/Run Mile/Helper/HealthKit+.swift +++ b/Run Mile/Helper/HealthKit+.swift @@ -8,7 +8,7 @@ import HealthKit -extension HKHealthStore { +extension HKHealthStore: Sendable { public func fetchData( sampleType: HKSampleType, predicate: NSPredicate? = nil, diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 2e701c6..29c5cf1 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -57,8 +57,18 @@ platform :ios do # message: "ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ ์ค€๋น„" # ) + slack( + message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค€๋น„ ์‹œ์ž‘", + slack_url: "https://hooks.slack.com/services/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" + ) + match(type: "appstore") + slack( + message: "Build Number: #{new_build_number} ์ธ์ฆ์„œ ์ค€๋น„ ์™„๋ฃŒ", + slack_url: "https://hooks.slack.com/services/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" + ) + build_app( scheme: "Run Mile", xcodebuild_formatter: "xcpretty", @@ -69,18 +79,32 @@ platform :ios do } ) + slack( + message: "Build Number: #{new_build_number} ์•„์นด์ด๋ธŒ ์™„๋ฃŒ", + slack_url: "https://hooks.slack.com/services/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" + ) + upload_to_testflight # notification( # subtitle: "Testflight ๋ฐฐํฌ ์„ฑ๊ณต!", # message: "Build Number : #{new_build_number}, ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค!" # ) + + slack( + message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์„ฑ๊ณต!", + slack_url: "https://hooks.slack.com/services/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" + ) end - # error do |lane, exception, options| + error do |lane, exception, options| # notification( # subtitle: "Testflight ๋ฐฐํฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", # message: "#{exception}" # ) - # end + slack( + message: "Testflight ๋ฐฐํฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. #{exception}", + slack_url: "https://hooks.slack.com/services/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" + ) + end end From f617c0ac8c1f8939062d2cc4b5105f7b79497a48 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 18 Jun 2025 19:59:18 +0900 Subject: [PATCH 18/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 29c5cf1..4d1ff6f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -59,14 +59,14 @@ platform :ios do slack( message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค€๋น„ ์‹œ์ž‘", - slack_url: "https://hooks.slack.com/services/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" + slack_url: "https://hooks.slack.com/services/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" ) match(type: "appstore") slack( message: "Build Number: #{new_build_number} ์ธ์ฆ์„œ ์ค€๋น„ ์™„๋ฃŒ", - slack_url: "https://hooks.slack.com/services/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" + slack_url: "https://hooks.slack.com/services/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" ) build_app( @@ -81,7 +81,7 @@ platform :ios do slack( message: "Build Number: #{new_build_number} ์•„์นด์ด๋ธŒ ์™„๋ฃŒ", - slack_url: "https://hooks.slack.com/services/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" + slack_url: "https://hooks.slack.com/services/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" ) upload_to_testflight @@ -93,7 +93,7 @@ platform :ios do slack( message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์„ฑ๊ณต!", - slack_url: "https://hooks.slack.com/services/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" + slack_url: "https://hooks.slack.com/services/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" ) end @@ -104,7 +104,7 @@ platform :ios do # ) slack( message: "Testflight ๋ฐฐํฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. #{exception}", - slack_url: "https://hooks.slack.com/services/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" + slack_url: "https://hooks.slack.com/services/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" ) end end From 107fee5e4b3c5a4d713e8b5b3f5150a11898449d Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sat, 21 Jun 2025 22:37:05 +0900 Subject: [PATCH 19/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(sla?= =?UTF-8?q?ck=20url=20=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 1 + fastlane/Fastfile | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index 302512e..dec17ac 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -56,6 +56,7 @@ jobs: APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + SLACK_URL: ${{ secrets.SLACK_URL }} run: bundle exec fastlane beta - name: Upload Artifacts diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 4d1ff6f..f198155 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -59,14 +59,16 @@ platform :ios do slack( message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค€๋น„ ์‹œ์ž‘", - slack_url: "https://hooks.slack.com/services/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" + channel: "#run_mile", + slack_url: "#{ENV["SLACK_URL"]}" ) match(type: "appstore") slack( message: "Build Number: #{new_build_number} ์ธ์ฆ์„œ ์ค€๋น„ ์™„๋ฃŒ", - slack_url: "https://hooks.slack.com/services/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" + channel: "#run_mile", + slack_url: "#{ENV["SLACK_URL"]}" ) build_app( @@ -93,7 +95,8 @@ platform :ios do slack( message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์„ฑ๊ณต!", - slack_url: "https://hooks.slack.com/services/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" + channel: "#run_mile", + slack_url: "#{ENV["SLACK_URL"]}" ) end @@ -104,7 +107,9 @@ platform :ios do # ) slack( message: "Testflight ๋ฐฐํฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. #{exception}", - slack_url: "https://hooks.slack.com/services/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" + channel: "#run_mile", + success: false, + slack_url: "#{ENV["SLACK_URL"]}" ) end end From 1ef58b605e5c16180fa4c7218caeb02b2e633852 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sat, 21 Jun 2025 22:59:14 +0900 Subject: [PATCH 20/54] =?UTF-8?q?[#33]=20Realm=20Dependency=20rule=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(master=20->=20=ED=8A=B9=EC=A0=95=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj index acf749b..351b8ad 100644 --- a/Run Mile.xcodeproj/project.pbxproj +++ b/Run Mile.xcodeproj/project.pbxproj @@ -649,8 +649,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/realm/realm-swift.git"; requirement = { - branch = master; - kind = branch; + kind = exactVersion; + version = 10.54.5; }; }; /* End XCRemoteSwiftPackageReference section */ From c43db55352562005936961a37ae5fb135ac6fe8c Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Mon, 23 Jun 2025 14:39:29 +0900 Subject: [PATCH 21/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=86=8C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20build=5Fapp=20=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile.xcodeproj/project.pbxproj | 12 +++--- fastlane/Fastfile | 65 +++++++++++++++++++++--------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj index 351b8ad..4664ded 100644 --- a/Run Mile.xcodeproj/project.pbxproj +++ b/Run Mile.xcodeproj/project.pbxproj @@ -446,7 +446,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 202506182; + CURRENT_PROJECT_VERSION = 202506231; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = VF8R3A969C; ENABLE_PREVIEWS = YES; @@ -493,7 +493,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 202506182; + CURRENT_PROJECT_VERSION = 202506231; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = VF8R3A969C; ENABLE_PREVIEWS = YES; @@ -536,7 +536,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 202506182; + CURRENT_PROJECT_VERSION = 202506231; DEVELOPMENT_TEAM = VF8R3A969C; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.4; @@ -555,7 +555,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 202506182; + CURRENT_PROJECT_VERSION = 202506231; DEVELOPMENT_TEAM = VF8R3A969C; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.4; @@ -573,7 +573,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 202506182; + CURRENT_PROJECT_VERSION = 202506231; DEVELOPMENT_TEAM = VF8R3A969C; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; @@ -590,7 +590,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 202506182; + CURRENT_PROJECT_VERSION = 202506231; DEVELOPMENT_TEAM = VF8R3A969C; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f198155..08607c3 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -16,6 +16,50 @@ default_platform(:ios) platform :ios do + lane :local do + app_store_connect_api_key( + key_id: "", + issuer_id: "", + key_content: "" + ) + + today = Time.now.strftime("%Y%m%d") + + current_build_number = "#{latest_testflight_build_number}" + + if current_build_number.length == 9 + current_date = current_build_number[0,8] # ์•ž 8์ž๋ฆฌ๊ฐ€ ๋‚ ์งœ + current_count = current_build_number[8].to_i # ๋งˆ์ง€๋ง‰ 1์ž๋ฆฌ๊ฐ€ ์ˆซ์ž + else + # ๋นŒ๋“œ ๋„˜๋ฒ„๊ฐ€ ์—†๊ฑฐ๋‚˜ ํ˜•์‹์ด ๋‹ค๋ฅด๋ฉด ์ดˆ๊ธฐํ™” + current_date = "" + current_count = 0 + end + + if current_date == today + next_count = current_count + 1 + else + next_count = 1 + end + + new_build_number = "#{today}#{next_count}" + + increment_build_number( + xcodeproj: "Run Mile.xcodeproj", + build_number: new_build_number + ) + + match(type: "appstore") + + build_app( + scheme: "Run Mile", + xcodebuild_formatter: "xcpretty" + ) + + upload_to_testflight + end + + desc "Push a new beta build to TestFlight" lane :beta do @@ -52,11 +96,6 @@ platform :ios do build_number: new_build_number ) - # notification( - # subtitle: "Build Number: #{new_build_number}", - # message: "ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ ์ค€๋น„" - # ) - slack( message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค€๋น„ ์‹œ์ž‘", channel: "#run_mile", @@ -73,12 +112,7 @@ platform :ios do build_app( scheme: "Run Mile", - xcodebuild_formatter: "xcpretty", - export_options: { - provisioningProfiles: { - "com.mooni.Run-Mile" => "com.mooni.Run-Mile AppStore" - } - } + xcodebuild_formatter: "xcpretty" ) slack( @@ -88,11 +122,6 @@ platform :ios do upload_to_testflight - # notification( - # subtitle: "Testflight ๋ฐฐํฌ ์„ฑ๊ณต!", - # message: "Build Number : #{new_build_number}, ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค!" - # ) - slack( message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์„ฑ๊ณต!", channel: "#run_mile", @@ -101,10 +130,6 @@ platform :ios do end error do |lane, exception, options| - # notification( - # subtitle: "Testflight ๋ฐฐํฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", - # message: "#{exception}" - # ) slack( message: "Testflight ๋ฐฐํฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. #{exception}", channel: "#run_mile", From 1f0bd2414fd20e07e85057a538606e873503f636 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Mon, 23 Jun 2025 14:41:23 +0900 Subject: [PATCH 22/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(Sla?= =?UTF-8?q?ck=20url)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 08607c3..d19a94f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -117,7 +117,8 @@ platform :ios do slack( message: "Build Number: #{new_build_number} ์•„์นด์ด๋ธŒ ์™„๋ฃŒ", - slack_url: "https://hooks.slack.com/services/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" + channel: "#run_mile", + slack_url: "#{ENV["SLACK_URL"]}" ) upload_to_testflight From 48999f4462bff0f332b02417aa9b48dd5fa7c673 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Mon, 23 Jun 2025 14:50:31 +0900 Subject: [PATCH 23/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(set?= =?UTF-8?q?up=5Fci=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index d19a94f..0ad93ff 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -102,6 +102,8 @@ platform :ios do slack_url: "#{ENV["SLACK_URL"]}" ) + setup_ci + match(type: "appstore") slack( From 1366903ecba549014747590b919ed43ed9b35ea7 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Mon, 23 Jun 2025 15:01:41 +0900 Subject: [PATCH 24/54] =?UTF-8?q?[#33]=20Fastfile=20=EC=88=98=EC=A0=95(lan?= =?UTF-8?q?e=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 2 +- Run Mile.xcodeproj/project.pbxproj | 4 ++-- fastlane/Fastfile | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index dec17ac..f12fbe0 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -57,7 +57,7 @@ jobs: APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} SLACK_URL: ${{ secrets.SLACK_URL }} - run: bundle exec fastlane beta + run: bundle exec fastlane testflight - name: Upload Artifacts uses: actions/upload-artifact@v4 diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj index 4664ded..9745616 100644 --- a/Run Mile.xcodeproj/project.pbxproj +++ b/Run Mile.xcodeproj/project.pbxproj @@ -649,8 +649,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/realm/realm-swift.git"; requirement = { - kind = exactVersion; - version = 10.54.5; + kind = upToNextMajorVersion; + minimumVersion = 10.54.5; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 0ad93ff..da94af5 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -61,7 +61,7 @@ platform :ios do desc "Push a new beta build to TestFlight" - lane :beta do + lane :testflight do app_store_connect_api_key( key_id: "#{ENV["APP_STORE_CONNECT_KEY_ID"]}", From 7692c77f9d07bb2f4ea08731e59af2e8c633e939 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Mon, 23 Jun 2025 15:12:27 +0900 Subject: [PATCH 25/54] =?UTF-8?q?[#33]=20workflow=20=EC=88=98=EC=A0=95(?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=ED=8A=B8=EB=A6=AC?= =?UTF-8?q?=EA=B1=B0=20=EC=A0=9C=EA=B1=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index f12fbe0..dca5719 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -4,10 +4,6 @@ on: push: branches: - main - # ์•ก์…˜ ํ…Œ์ŠคํŠธ์šฉ - pull_request: - branches: - - develop jobs: build-upload-testflight: From af0020f100f4dceaf30c517ae37daed2b05a61be Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Tue, 24 Jun 2025 21:41:14 +0900 Subject: [PATCH 26/54] =?UTF-8?q?[#35]=20noti=20=EC=95=A1=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcdebugger/Breakpoints_v2.xcbkptlist | 6 +++ .../Health/WorkoutDataRepositoryImpl.swift | 30 +------------- Run Mile/Helper/Delegate/AppDelegate.swift | 29 +++++++++++++- Run Mile/Helper/HealthKit+.swift | 10 ++++- .../Helper/UNUserNotificationCenter+.swift | 39 ++++++++++++++++++- 5 files changed, 81 insertions(+), 33 deletions(-) create mode 100644 Run Mile.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist diff --git a/Run Mile.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist b/Run Mile.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..a375fdc --- /dev/null +++ b/Run Mile.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift index 5abc71c..09f9942 100644 --- a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift +++ b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift @@ -23,34 +23,6 @@ actor WorkoutDataRepositoryImpl: WorkoutDataRepository { sortDescriptors: descriptor ) - return convertToRunningData(result) - } -} - - -extension WorkoutDataRepositoryImpl { - /// HKWorkout ์„ RunningData ์—”ํ‹ฐํ‹ฐ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. - private func convertToRunningData(_ workouts: [HKWorkout]) -> [RunningData] { - var resultArray = [RunningData]() - workouts.forEach { - if let statistics = $0.statistics(for: HKQuantityType(.distanceWalkingRunning)), - let sumDistance = statistics.sumQuantity()?.doubleValue(for: .meter()) { - let localStartDate = Calendar.current.date( - byAdding: .second, - value: TimeZone.current.secondsFromGMT(), - to: $0.startDate - ) - - let result = RunningData( - id: $0.uuid, - distance: sumDistance, - date: localStartDate - ) - - resultArray.append(result) - } - } - - return resultArray + return result.map { $0.toEntity } } } diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift index e6806e7..e475a79 100644 --- a/Run Mile/Helper/Delegate/AppDelegate.swift +++ b/Run Mile/Helper/Delegate/AppDelegate.swift @@ -98,6 +98,7 @@ extension AppDelegate { if !UserDefaults.standard.selectedShoesID.isEmpty { UNUserNotificationCenter.requestNotification( + category: .autoRegister(workout.toEntity), title: String(format: "%.2fkm ๋Ÿฌ๋‹ ์™„๋ฃŒ ๐Ÿ”ฅ๐Ÿ”ฅ", distance!), body: distance == nil ? "์‹ ๋ฐœ์— ์ž๋™ ๋“ฑ๋ก์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!" @@ -107,6 +108,7 @@ extension AppDelegate { autoRegisterShoes(workout: workout) } else { UNUserNotificationCenter.requestNotification( + category: .manualRegister(workout.toEntity), title: String(format: "%.2fkm ๋Ÿฌ๋‹ ์™„๋ฃŒ ๐Ÿ”ฅ๐Ÿ”ฅ", distance!), body: distance == nil ? "์‹ ๋ฐœ ๋งˆ์ผ๋ฆฌ์ง€๋ฅผ ๋“ฑ๋กํ•  ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋“ฑ๋กํ•˜๋Ÿฌ ๊ฐ€๋ณผ๊นŒ์š”?" @@ -137,7 +139,7 @@ extension AppDelegate { let shoesID = UUID(uuidString: UserDefaults.standard.selectedShoesID)! let shoes = try await shoesDataRepository.fetchSingleShoes(id: shoesID) var workouts = shoes.workouts - workouts.append(workout.toEntity()) + workouts.append(workout.toEntity) let newShoes = Shoes( id: shoes.id, @@ -152,6 +154,7 @@ extension AppDelegate { try await shoesDataRepository.updateShoes(shoes: newShoes) } catch { UNUserNotificationCenter.requestNotification( + category: .manualRegister(workout.toEntity), title: "๋งˆ์ผ๋ฆฌ์ง€ ์ž๋™ ๋“ฑ๋ก์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", body: "์•ฑ์—์„œ ์ˆ˜๋™์œผ๋กœ ๋“ฑ๋ก ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค." ) @@ -167,6 +170,30 @@ extension AppDelegate: UNUserNotificationCenterDelegate { didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void ) { + let userInfo = response.notification.request.content.userInfo + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + + + if let category = userInfo["category"] as? String, + category == "ManualRegister", + let uuidString = userInfo["id"] as? String, + let uuid = UUID(uuidString: uuidString), + let dateString = userInfo["date"] as? String, + let date = dateFormatter.date(from: dateString), + let distanceString = userInfo["distance"] as? String, + let distance = Double(distanceString) + { + let runningData = RunningData( + id: uuid, + distance: distance, + date: date + ) + + NavigationCoordinator.shared.push(.chooseShoes([runningData], {})) + } + completionHandler() } diff --git a/Run Mile/Helper/HealthKit+.swift b/Run Mile/Helper/HealthKit+.swift index 3d423f3..c8221c1 100644 --- a/Run Mile/Helper/HealthKit+.swift +++ b/Run Mile/Helper/HealthKit+.swift @@ -52,14 +52,20 @@ extension HKWorkout { return nil } - public func toEntity() -> RunningData { + public var toEntity: RunningData { let statistics = self.statistics(for: HKQuantityType(.distanceWalkingRunning))! let sumDistance = statistics.sumQuantity()! + let localStartDate = Calendar.current.date( + byAdding: .second, + value: TimeZone.current.secondsFromGMT(), + to: self.startDate + ) + return .init( id: self.uuid, distance: sumDistance.doubleValue(for: .meter()), - date: self.startDate + date: localStartDate ) } } diff --git a/Run Mile/Helper/UNUserNotificationCenter+.swift b/Run Mile/Helper/UNUserNotificationCenter+.swift index 4f6c22c..221365f 100644 --- a/Run Mile/Helper/UNUserNotificationCenter+.swift +++ b/Run Mile/Helper/UNUserNotificationCenter+.swift @@ -9,7 +9,12 @@ import UserNotifications extension UNUserNotificationCenter { - static func requestNotification(id: String = UUID().uuidString, title: String, body: String) { + static func requestNotification( + category: NotificationCategory = .none, + id: String = UUID().uuidString, + title: String, + body: String + ) { let center = self.current() let content = UNMutableNotificationContent() @@ -17,8 +22,40 @@ extension UNUserNotificationCenter { content.body = body content.sound = .default + switch category { + case let .manualRegister(runningData): + let formatter = DateFormatter() + + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + content.userInfo = [ + "id": runningData.id.uuidString, + "date": formatter.string(from: runningData.date ?? .now), + "distance": "\(runningData.distance)", + "category": category.rawValue + ] + case .autoRegister, .none: + break + } + let request = UNNotificationRequest(identifier: id, content: content, trigger: nil) center.add(request) } + + public enum NotificationCategory: Hashable { + case autoRegister(RunningData) + case manualRegister(RunningData) + case none + + public var rawValue: String { + switch self { + case .autoRegister: + "AutoRegister" + case .manualRegister: + "ManualRegister" + case .none: + "None" + } + } + } } From 133069fb9d0143a0e6196161a41c26e40c9bdf1e Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Tue, 24 Jun 2025 22:24:24 +0900 Subject: [PATCH 27/54] =?UTF-8?q?[#35]=20Refreshable=20=EC=95=A1=EC=85=98?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile.xcodeproj/project.pbxproj | 8 ++++---- Run Mile/Helper/HealthKit+.swift | 13 +++++++------ Run Mile/Presentation/1.Shoes/ShoesListView.swift | 3 +++ .../Presentation/2.Workout/WorkoutListView.swift | 5 +++++ 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj index 9745616..b09dae7 100644 --- a/Run Mile.xcodeproj/project.pbxproj +++ b/Run Mile.xcodeproj/project.pbxproj @@ -444,7 +444,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 202506231; DEVELOPMENT_TEAM = ""; @@ -473,7 +473,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -491,7 +491,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 202506231; DEVELOPMENT_TEAM = ""; @@ -520,7 +520,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; diff --git a/Run Mile/Helper/HealthKit+.swift b/Run Mile/Helper/HealthKit+.swift index c8221c1..e2f6584 100644 --- a/Run Mile/Helper/HealthKit+.swift +++ b/Run Mile/Helper/HealthKit+.swift @@ -56,16 +56,17 @@ extension HKWorkout { let statistics = self.statistics(for: HKQuantityType(.distanceWalkingRunning))! let sumDistance = statistics.sumQuantity()! - let localStartDate = Calendar.current.date( - byAdding: .second, - value: TimeZone.current.secondsFromGMT(), - to: self.startDate - ) + // ์‹ค๊ธฐ๊ธฐ ๊ฒ€์ฆ ํ•„์š” +// let localStartDate = Calendar.current.date( +// byAdding: .second, +// value: TimeZone.current.secondsFromGMT(), +// to: self.startDate +// ) return .init( id: self.uuid, distance: sumDistance.doubleValue(for: .meter()), - date: localStartDate + date: self.startDate ) } } diff --git a/Run Mile/Presentation/1.Shoes/ShoesListView.swift b/Run Mile/Presentation/1.Shoes/ShoesListView.swift index 79f6135..f9e49f7 100644 --- a/Run Mile/Presentation/1.Shoes/ShoesListView.swift +++ b/Run Mile/Presentation/1.Shoes/ShoesListView.swift @@ -67,6 +67,9 @@ private struct CurrentShoesListView: View { } .padding(.horizontal, 20) } + .refreshable { + viewModel.onAppear() + } } } diff --git a/Run Mile/Presentation/2.Workout/WorkoutListView.swift b/Run Mile/Presentation/2.Workout/WorkoutListView.swift index 90bcca8..98ff086 100644 --- a/Run Mile/Presentation/2.Workout/WorkoutListView.swift +++ b/Run Mile/Presentation/2.Workout/WorkoutListView.swift @@ -110,6 +110,11 @@ private struct WorkoutScrollView: View { } .padding(.horizontal, 20) } + .refreshable { + Task { + await viewModel.onAppear() + } + } } } From 7efd73d9c3649d5f72d34ccaba2ec2c89eb8d957 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 25 Jun 2025 12:15:46 +0900 Subject: [PATCH 28/54] =?UTF-8?q?[#35]=20UserNotifications=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift | 3 +-- Run Mile/Helper/Delegate/AppDelegate.swift | 7 +++---- Run Mile/Helper/UNUserNotificationCenter+.swift | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift b/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift index d2efe16..3b2658d 100644 --- a/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift +++ b/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift @@ -7,7 +7,6 @@ import Foundation import RealmSwift -import UserNotifications actor ShoesDataRepositoryImpl: ShoesDataRepository { @@ -104,7 +103,7 @@ actor ShoesDataRepositoryImpl: ShoesDataRepository { dto.workouts = list if !shoes.isGradutate, shoes.isOverGoal { - UNUserNotificationCenter.requestNotification( + UserNotificationsManager.requestNotification( title: "\(shoes.nickname)์˜ ๋ชฉํ‘œ ๋งˆ์ผ๋ฆฌ์ง€๋ฅผ ๋‹ฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค!", body: "์ถ•ํ•˜๋“œ๋ฆฝ๋‹ˆ๋‹ค! ์ด์ œ ๋ช…์˜ˆ์˜ ์ „๋‹น์œผ๋กœ ๊ฐˆ ์ผ๋งŒ ๋‚จ์•˜์Šต๋‹ˆ๋‹ค. ๊ฐ€๋ณด์‹ค๊นŒ์š”?" ) diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift index e475a79..322faba 100644 --- a/Run Mile/Helper/Delegate/AppDelegate.swift +++ b/Run Mile/Helper/Delegate/AppDelegate.swift @@ -8,7 +8,6 @@ import UIKit import HealthKit import SwiftUI -import UserNotifications final class AppDelegate: NSObject, UIApplicationDelegate { @@ -97,7 +96,7 @@ extension AppDelegate { let distance = workout.getKilometerDistance() if !UserDefaults.standard.selectedShoesID.isEmpty { - UNUserNotificationCenter.requestNotification( + UserNotificationsManager.requestNotification( category: .autoRegister(workout.toEntity), title: String(format: "%.2fkm ๋Ÿฌ๋‹ ์™„๋ฃŒ ๐Ÿ”ฅ๐Ÿ”ฅ", distance!), body: distance == nil @@ -107,7 +106,7 @@ extension AppDelegate { autoRegisterShoes(workout: workout) } else { - UNUserNotificationCenter.requestNotification( + UserNotificationsManager.requestNotification( category: .manualRegister(workout.toEntity), title: String(format: "%.2fkm ๋Ÿฌ๋‹ ์™„๋ฃŒ ๐Ÿ”ฅ๐Ÿ”ฅ", distance!), body: distance == nil @@ -153,7 +152,7 @@ extension AppDelegate { try await shoesDataRepository.updateShoes(shoes: newShoes) } catch { - UNUserNotificationCenter.requestNotification( + UserNotificationsManager.requestNotification( category: .manualRegister(workout.toEntity), title: "๋งˆ์ผ๋ฆฌ์ง€ ์ž๋™ ๋“ฑ๋ก์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", body: "์•ฑ์—์„œ ์ˆ˜๋™์œผ๋กœ ๋“ฑ๋ก ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค." diff --git a/Run Mile/Helper/UNUserNotificationCenter+.swift b/Run Mile/Helper/UNUserNotificationCenter+.swift index 221365f..0368cec 100644 --- a/Run Mile/Helper/UNUserNotificationCenter+.swift +++ b/Run Mile/Helper/UNUserNotificationCenter+.swift @@ -8,14 +8,14 @@ import UserNotifications -extension UNUserNotificationCenter { +public enum UserNotificationsManager { static func requestNotification( category: NotificationCategory = .none, id: String = UUID().uuidString, title: String, body: String ) { - let center = self.current() + let center = UNUserNotificationCenter.current() let content = UNMutableNotificationContent() content.title = title From 757edb5d42bd96154af7657a1ca90413138a6e69 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 25 Jun 2025 12:23:05 +0900 Subject: [PATCH 29/54] =?UTF-8?q?[#35]=20RunningData=20->=20Workout=20?= =?UTF-8?q?=EB=A6=AC=EB=84=A4=EC=9D=B4=EB=B0=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift | 11 ++++++++++- Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift | 8 ++++---- Run Mile/Domain/Entities/RunningData.swift | 2 +- Run Mile/Domain/Entities/Shoes.swift | 4 ++-- .../Repositories/WorkoutDataRepository.swift | 8 +++++++- Run Mile/Domain/UseCases/ChooseShoesUseCase.swift | 4 ++-- Run Mile/Domain/UseCases/HealthDataUseCase.swift | 4 ++-- Run Mile/Helper/Delegate/AppDelegate.swift | 2 +- Run Mile/Helper/HealthKit+.swift | 2 +- Run Mile/Helper/UNUserNotificationCenter+.swift | 4 ++-- .../Presentation/0.Main/NavigationCoordinator.swift | 2 +- .../1-2.ShoesDetail/ShoesDetailViewModel.swift | 2 +- .../2.Workout/2-1.ChooseShoes/ChooseShoesView.swift | 2 +- .../2-1.ChooseShoes/ChooseShoesViewModel.swift | 4 ++-- .../Presentation/2.Workout/WorkoutListViewModel.swift | 10 +++++----- Run Mile/Presentation/Components/WorkoutCell.swift | 2 +- 16 files changed, 43 insertions(+), 28 deletions(-) diff --git a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift index 09f9942..4d43648 100644 --- a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift +++ b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift @@ -12,7 +12,7 @@ import HealthKit actor WorkoutDataRepositoryImpl: WorkoutDataRepository { private let store = HKHealthStore() - public func fetchWorkoutData() async throws -> [RunningData] { + public func fetchWorkoutData() async throws -> [Workout] { let predicate = HKQuery.predicateForWorkouts(with: .running) let descriptor = [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)] @@ -25,4 +25,13 @@ actor WorkoutDataRepositoryImpl: WorkoutDataRepository { return result.map { $0.toEntity } } + + + public func saveWorkoutData(workouts: [Workout]) async throws { + + } + + public func deleteWorkoutDate(workouts: [Workout]) async throws { + + } } diff --git a/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift b/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift index 3b2658d..12f16a9 100644 --- a/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift +++ b/Run Mile/Data/Shoes/ShoesDataRepositoryImpl.swift @@ -37,10 +37,10 @@ actor ShoesDataRepositoryImpl: ShoesDataRepository { let fetchedResult = realm.object(ofType: ShoesDTO.self, forPrimaryKey: id) if let result = fetchedResult { - var workouts = [RunningData]() + var workouts = [Workout]() result.workouts.forEach { workout in workouts.append( - RunningData( + Workout( id: workout.id, distance: workout.distance, date: workout.date @@ -134,10 +134,10 @@ extension ShoesDataRepositoryImpl { private func toEntities(_ dto: Results) -> [Shoes] { var resultArray: [Shoes] = [] dto.forEach { - var workouts = [RunningData]() + var workouts = [Workout]() $0.workouts.forEach { workout in workouts.append( - RunningData( + Workout( id: workout.id, distance: workout.distance, date: workout.date diff --git a/Run Mile/Domain/Entities/RunningData.swift b/Run Mile/Domain/Entities/RunningData.swift index a844c2b..5ddc1d3 100644 --- a/Run Mile/Domain/Entities/RunningData.swift +++ b/Run Mile/Domain/Entities/RunningData.swift @@ -7,7 +7,7 @@ import Foundation -public struct RunningData: Sendable, Identifiable, Hashable { +public struct Workout: Sendable, Identifiable, Hashable { public let id: UUID public let distance: Double public let date: Date? diff --git a/Run Mile/Domain/Entities/Shoes.swift b/Run Mile/Domain/Entities/Shoes.swift index 77b2209..e7aacd2 100644 --- a/Run Mile/Domain/Entities/Shoes.swift +++ b/Run Mile/Domain/Entities/Shoes.swift @@ -16,7 +16,7 @@ struct Shoes: Sendable, Identifiable, Hashable { let goalMileage: Double // ๋ชฉํ‘œ ๋งˆ์ผ๋ฆฌ์ง€ let currentMileage: Double // ์ดˆ๊ธฐ ๋งˆ์ผ๋ฆฌ์ง€ let isCurrentShoes: Bool // ํ˜„์žฌ ์ž๋™๋“ฑ๋ก์ด ์„ ํƒ๋œ ์‹ ๋ฐœ์ธ์ง€ ์—ฌ๋ถ€ - var workouts: [RunningData] // ์‹ ๋ฐœ์— ๋“ฑ๋ก๋œ ์šด๋™๊ธฐ๋ก + var workouts: [Workout] // ์‹ ๋ฐœ์— ๋“ฑ๋ก๋œ ์šด๋™๊ธฐ๋ก let isGradutate: Bool // ์‹ ๋ฐœ ์กธ์—… ์—ฌ๋ถ€ init(id: UUID, @@ -25,7 +25,7 @@ struct Shoes: Sendable, Identifiable, Hashable { nickname: String, goalMileage: Double, currentMileage: Double, - workouts: [RunningData], + workouts: [Workout], isGraduate: Bool = false ) { self.id = id diff --git a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift index a062250..2530da1 100644 --- a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift +++ b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift @@ -11,5 +11,11 @@ import HealthKit protocol WorkoutDataRepository: Sendable { /// ์šด๋™(๋Ÿฌ๋‹) ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. - func fetchWorkoutData() async throws -> [RunningData] + func fetchWorkoutData() async throws -> [Workout] + + /// ์šด๋™(๋Ÿฌ๋‹) ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. (๋“ฑ๋ก๋œ ์šด๋™ ํ•„ํ„ฐ๋ฆด ์šฉ) + func saveWorkoutData(workouts: [Workout]) async throws + + /// ์šด๋™(๋Ÿฌ๋‹) ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. (๋“ฑ๋ก๋œ ์šด๋™ ํ•„ํ„ฐ๋ฆด ์šฉ) + func deleteWorkoutDate(workouts: [Workout]) async throws } diff --git a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift index 8bf6e76..1b8447f 100644 --- a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift +++ b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift @@ -10,7 +10,7 @@ import Foundation protocol ChooseShoesUseCase: Sendable { func fetchShoesList() async throws -> [Shoes] - func registerWorkouts(shoes: Shoes, workouts: [RunningData]) async throws + func registerWorkouts(shoes: Shoes, workouts: [Workout]) async throws } @@ -26,7 +26,7 @@ final class DefaultChooseShoesUseCase: ChooseShoesUseCase { try await repository.fetchCurrentShoes() } - public func registerWorkouts(shoes: Shoes, workouts: [RunningData]) async throws { + public func registerWorkouts(shoes: Shoes, workouts: [Workout]) async throws { var newWorkouts = shoes.workouts newWorkouts.append(contentsOf: workouts) diff --git a/Run Mile/Domain/UseCases/HealthDataUseCase.swift b/Run Mile/Domain/UseCases/HealthDataUseCase.swift index 3fd5c35..6f475f6 100644 --- a/Run Mile/Domain/UseCases/HealthDataUseCase.swift +++ b/Run Mile/Domain/UseCases/HealthDataUseCase.swift @@ -14,7 +14,7 @@ protocol HealthDataUseCase { @discardableResult func checkHealthAuthorization() async throws -> Bool - func fetchWorkoutData() async throws -> [RunningData] + func fetchWorkoutData() async throws -> [Workout] } @@ -43,7 +43,7 @@ final class DefaultHealthDataUseCase: HealthDataUseCase { return true } - public func fetchWorkoutData() async throws -> [RunningData] { + public func fetchWorkoutData() async throws -> [Workout] { let fetchedResult = try await workoutDataRepository.fetchWorkoutData() let shoes = try await shoesDataRepository.fetchAllShoes() diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift index 322faba..d33f0fe 100644 --- a/Run Mile/Helper/Delegate/AppDelegate.swift +++ b/Run Mile/Helper/Delegate/AppDelegate.swift @@ -184,7 +184,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { let distanceString = userInfo["distance"] as? String, let distance = Double(distanceString) { - let runningData = RunningData( + let runningData = Workout( id: uuid, distance: distance, date: date diff --git a/Run Mile/Helper/HealthKit+.swift b/Run Mile/Helper/HealthKit+.swift index e2f6584..3f13bbe 100644 --- a/Run Mile/Helper/HealthKit+.swift +++ b/Run Mile/Helper/HealthKit+.swift @@ -52,7 +52,7 @@ extension HKWorkout { return nil } - public var toEntity: RunningData { + public var toEntity: Workout { let statistics = self.statistics(for: HKQuantityType(.distanceWalkingRunning))! let sumDistance = statistics.sumQuantity()! diff --git a/Run Mile/Helper/UNUserNotificationCenter+.swift b/Run Mile/Helper/UNUserNotificationCenter+.swift index 0368cec..34e0c89 100644 --- a/Run Mile/Helper/UNUserNotificationCenter+.swift +++ b/Run Mile/Helper/UNUserNotificationCenter+.swift @@ -43,8 +43,8 @@ public enum UserNotificationsManager { } public enum NotificationCategory: Hashable { - case autoRegister(RunningData) - case manualRegister(RunningData) + case autoRegister(Workout) + case manualRegister(Workout) case none public var rawValue: String { diff --git a/Run Mile/Presentation/0.Main/NavigationCoordinator.swift b/Run Mile/Presentation/0.Main/NavigationCoordinator.swift index 5bbac0c..7190fe7 100644 --- a/Run Mile/Presentation/0.Main/NavigationCoordinator.swift +++ b/Run Mile/Presentation/0.Main/NavigationCoordinator.swift @@ -156,7 +156,7 @@ extension NavigationCoordinator { var id: Self { self } case addShoes(() -> Void) - case chooseShoes([RunningData], () -> Void) + case chooseShoes([Workout], () -> Void) case automaticRegister } } diff --git a/Run Mile/Presentation/1.Shoes/1-2.ShoesDetail/ShoesDetailViewModel.swift b/Run Mile/Presentation/1.Shoes/1-2.ShoesDetail/ShoesDetailViewModel.swift index 1981a12..ff579e4 100644 --- a/Run Mile/Presentation/1.Shoes/1-2.ShoesDetail/ShoesDetailViewModel.swift +++ b/Run Mile/Presentation/1.Shoes/1-2.ShoesDetail/ShoesDetailViewModel.swift @@ -99,7 +99,7 @@ extension ShoesDetailViewModel { } @MainActor - public func workoutCellTapped(_ workout: RunningData) { + public func workoutCellTapped(_ workout: Workout) { switch self.viewStatus { case .workouts: if selectedWorkouts.contains(workout.id) { diff --git a/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesView.swift b/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesView.swift index bae151c..f73e717 100644 --- a/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesView.swift +++ b/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesView.swift @@ -14,7 +14,7 @@ struct ChooseShoesView: View { let dismiss: () -> Void init( - workouts: [RunningData], + workouts: [Workout], dismiss: @escaping () -> Void ) { self.viewModel = .init( diff --git a/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesViewModel.swift b/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesViewModel.swift index a261099..d7c6ad1 100644 --- a/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesViewModel.swift +++ b/Run Mile/Presentation/2.Workout/2-1.ChooseShoes/ChooseShoesViewModel.swift @@ -10,13 +10,13 @@ import Foundation @Observable final class ChooseShoesViewModel { private let useCase: ChooseShoesUseCase - private let workouts: [RunningData] + private let workouts: [Workout] public var shoes: [Shoes] = [] init( useCase: ChooseShoesUseCase, - workouts: [RunningData] + workouts: [Workout] ) { self.useCase = useCase self.workouts = workouts diff --git a/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift b/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift index 923ac95..2738ff8 100644 --- a/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift +++ b/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift @@ -13,7 +13,7 @@ final class WorkoutListViewModel { private let useCase: HealthDataUseCase public var dateHeaders: [String] = [] - public var workouts: [[RunningData]] = [] + public var workouts: [[Workout]] = [] public var viewStatus: ViewStatus = .none public var selectedWorkout: Set = [] @@ -76,7 +76,7 @@ extension WorkoutListViewModel { } @MainActor - public func workoutCellTapped(workout: RunningData) { + public func workoutCellTapped(workout: Workout) { if case .selection = self.viewStatus { let id = workout.id @@ -149,18 +149,18 @@ extension WorkoutListViewModel { self.viewStatus = workouts.isEmpty ? .empty : .none } - public func isSelectedWorkout(_ workout: RunningData) -> Bool { + public func isSelectedWorkout(_ workout: Workout) -> Bool { self.selectedWorkout.contains(workout.id) } } extension WorkoutListViewModel { - private func classifyWorkoutsByDate(workouts: [RunningData]) { + private func classifyWorkoutsByDate(workouts: [Workout]) { self.workouts.removeAll() self.dateHeaders.removeAll() - var resultWorkouts = [RunningData]() + var resultWorkouts = [Workout]() for workout in workouts { if dateHeaders.isEmpty { diff --git a/Run Mile/Presentation/Components/WorkoutCell.swift b/Run Mile/Presentation/Components/WorkoutCell.swift index 7c499cd..d8c897e 100644 --- a/Run Mile/Presentation/Components/WorkoutCell.swift +++ b/Run Mile/Presentation/Components/WorkoutCell.swift @@ -9,7 +9,7 @@ import SwiftUI struct WorkoutCell: View { - let workout: RunningData + let workout: Workout let action: () -> Void From c81106ea1d7b90fc310fe255e7032391cd1c2ce3 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 25 Jun 2025 13:42:25 +0900 Subject: [PATCH 30/54] =?UTF-8?q?[#35]=20WorkoutDTO=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=97=AD=EA=B4=80=EA=B3=84=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=20Realm=20DB=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile/Data/DTO/WorkoutDTO.swift | 2 + .../Health/WorkoutDataRepositoryImpl.swift | 5 +++ .../Repositories/WorkoutDataRepository.swift | 3 ++ Run Mile/Helper/Delegate/AppDelegate.swift | 45 +++++++++++++++++++ 4 files changed, 55 insertions(+) diff --git a/Run Mile/Data/DTO/WorkoutDTO.swift b/Run Mile/Data/DTO/WorkoutDTO.swift index 8e7ea3e..9bd3d94 100644 --- a/Run Mile/Data/DTO/WorkoutDTO.swift +++ b/Run Mile/Data/DTO/WorkoutDTO.swift @@ -13,4 +13,6 @@ final class WorkoutDTO: Object { @Persisted(primaryKey: true) public var id: UUID @Persisted public var date: Date? @Persisted public var distance: Double + /// ์—ญ๊ด€๊ณ„ + @Persisted(originProperty: "workouts") public var shoes: LinkingObjects } diff --git a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift index 4d43648..a69a0c2 100644 --- a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift +++ b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift @@ -5,6 +5,7 @@ // Created by ๋ฌธ์ธ๋ฒ” on 4/15/25. // +import RealmSwift import Foundation import HealthKit @@ -26,6 +27,10 @@ actor WorkoutDataRepositoryImpl: WorkoutDataRepository { return result.map { $0.toEntity } } + public func fetchSavedWorkoutData() async throws -> [Workout] { + return [] + } + public func saveWorkoutData(workouts: [Workout]) async throws { diff --git a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift index 2530da1..c0ddb0f 100644 --- a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift +++ b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift @@ -13,6 +13,9 @@ protocol WorkoutDataRepository: Sendable { /// ์šด๋™(๋Ÿฌ๋‹) ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. func fetchWorkoutData() async throws -> [Workout] + /// ์ €์žฅ๋œ ์šด๋™(๋Ÿฌ๋‹) ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. + func fetchSavedWorkoutData() async throws -> [Workout] + /// ์šด๋™(๋Ÿฌ๋‹) ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. (๋“ฑ๋ก๋œ ์šด๋™ ํ•„ํ„ฐ๋ฆด ์šฉ) func saveWorkoutData(workouts: [Workout]) async throws diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift index d33f0fe..c2c1c4d 100644 --- a/Run Mile/Helper/Delegate/AppDelegate.swift +++ b/Run Mile/Helper/Delegate/AppDelegate.swift @@ -8,6 +8,7 @@ import UIKit import HealthKit import SwiftUI +import RealmSwift final class AppDelegate: NSObject, UIApplicationDelegate { @@ -19,6 +20,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate { Task { await self.userNotificationAuthorize() await AppDelegate.setHealthBackgroundTask() + self.realmMigration() } return true @@ -160,6 +162,49 @@ extension AppDelegate { } } } + + private func realmMigration() { + let config = Realm.Configuration( + schemaVersion: 1, + migrationBlock: { migration, oldSchemaVersion in + if oldSchemaVersion < 1 { + + } + } + ) + + Realm.Configuration.defaultConfiguration = config + + #if DEBUG + print(Realm.Configuration.defaultConfiguration.fileURL ?? "์•Œ ์ˆ˜ ์—†์Œ") + #endif + } + + private func workoutDataMigration() { + let workoutRepository = WorkoutDataRepositoryImpl() + let shoesRepository = ShoesDataRepositoryImpl() + + Task { + do { + let workouts = try await workoutRepository.fetchSavedWorkoutData() + let shoes = try await shoesRepository.fetchAllShoes() + + if !shoes.isEmpty, workouts.isEmpty { + var workouts = [Workout]() + + shoes.forEach { + $0.workouts.forEach { + workouts.append($0) + } + } + + try await workoutRepository.saveWorkoutData(workouts: workouts) + } + } catch { + // TODO: Error ์ฒ˜๋ฆฌ + } + } + } } extension AppDelegate: UNUserNotificationCenterDelegate { From 433703c92a4ef19fc76a2a4a2389b8dbaae25786 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Wed, 25 Jun 2025 22:50:15 +0900 Subject: [PATCH 31/54] =?UTF-8?q?[#35]=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8,=20=EC=A4=91=EB=B3=B5=20=EC=9A=B4=EB=8F=99=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Health/WorkoutDataRepositoryImpl.swift | 28 +++++--- .../Repositories/WorkoutDataRepository.swift | 11 ++-- .../Domain/UseCases/ChooseShoesUseCase.swift | 65 +++++++++++++++---- .../Domain/UseCases/HealthDataUseCase.swift | 9 +-- Run Mile/Helper/Delegate/AppDelegate.swift | 28 +------- 5 files changed, 79 insertions(+), 62 deletions(-) diff --git a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift index a69a0c2..d384e59 100644 --- a/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift +++ b/Run Mile/Data/Health/WorkoutDataRepositoryImpl.swift @@ -13,7 +13,7 @@ import HealthKit actor WorkoutDataRepositoryImpl: WorkoutDataRepository { private let store = HKHealthStore() - public func fetchWorkoutData() async throws -> [Workout] { + public func fetchAllWorkoutData() async throws -> [Workout] { let predicate = HKQuery.predicateForWorkouts(with: .running) let descriptor = [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)] @@ -27,16 +27,28 @@ actor WorkoutDataRepositoryImpl: WorkoutDataRepository { return result.map { $0.toEntity } } - public func fetchSavedWorkoutData() async throws -> [Workout] { - return [] - } - - - public func saveWorkoutData(workouts: [Workout]) async throws { + public func fetchUnsavedWorkoutData() async throws -> [Workout] { + let savedWorkouts = try await fetchSavedWorkoutData() + let entireWorkouts = try await fetchAllWorkoutData() + let result = entireWorkouts.filter { first in + !savedWorkouts.contains(where: { $0.id == first.id }) + } + + return result } - public func deleteWorkoutDate(workouts: [Workout]) async throws { + public func fetchSavedWorkoutData() async throws -> [Workout] { + let realm = try await Realm.open() + let fetchedResult = realm.objects(WorkoutDTO.self) + var result = [Workout]() + + fetchedResult.forEach { + result.append( + .init(id: $0.id, distance: $0.distance, date: $0.date) + ) + } + return result } } diff --git a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift index c0ddb0f..785820c 100644 --- a/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift +++ b/Run Mile/Domain/Interfaces/Repositories/WorkoutDataRepository.swift @@ -11,14 +11,11 @@ import HealthKit protocol WorkoutDataRepository: Sendable { /// ์šด๋™(๋Ÿฌ๋‹) ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. - func fetchWorkoutData() async throws -> [Workout] + func fetchAllWorkoutData() async throws -> [Workout] + + /// + func fetchUnsavedWorkoutData() async throws -> [Workout] /// ์ €์žฅ๋œ ์šด๋™(๋Ÿฌ๋‹) ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. func fetchSavedWorkoutData() async throws -> [Workout] - - /// ์šด๋™(๋Ÿฌ๋‹) ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. (๋“ฑ๋ก๋œ ์šด๋™ ํ•„ํ„ฐ๋ฆด ์šฉ) - func saveWorkoutData(workouts: [Workout]) async throws - - /// ์šด๋™(๋Ÿฌ๋‹) ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. (๋“ฑ๋ก๋œ ์šด๋™ ํ•„ํ„ฐ๋ฆด ์šฉ) - func deleteWorkoutDate(workouts: [Workout]) async throws } diff --git a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift index 1b8447f..b5e1f98 100644 --- a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift +++ b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift @@ -28,19 +28,60 @@ final class DefaultChooseShoesUseCase: ChooseShoesUseCase { public func registerWorkouts(shoes: Shoes, workouts: [Workout]) async throws { var newWorkouts = shoes.workouts - newWorkouts.append(contentsOf: workouts) - let newShoes = Shoes( - id: shoes.id, - image: shoes.image, - shoesName: shoes.shoesName, - nickname: shoes.nickname, - goalMileage: shoes.goalMileage, - currentMileage: shoes.currentMileage, - workouts: newWorkouts - ) - - try await repository.updateShoes(shoes: newShoes) + if newWorkouts.contains(workouts) { + if workouts.count == 1 { + await NavigationCoordinator.shared.push( + .init( + title: "์ค‘๋ณต๋œ ์šด๋™ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.", + message: "๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”!", + firstButton: .cancel(title: "ํ™•์ธ", action: {}), + secondButton: nil + ) + ) + } else { + workouts.forEach { + if !newWorkouts.contains($0) { + newWorkouts.append($0) + } + } + + let newShoes = Shoes( + id: shoes.id, + image: shoes.image, + shoesName: shoes.shoesName, + nickname: shoes.nickname, + goalMileage: shoes.goalMileage, + currentMileage: shoes.currentMileage, + workouts: newWorkouts + ) + + try await repository.updateShoes(shoes: newShoes) + + await NavigationCoordinator.shared.push( + .init( + title: "์ค‘๋ณต๋œ ์šด๋™ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.", + message: "ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ์šด๋™ ๋ฐ์ดํ„ฐ๋งŒ ์ €์žฅ ์™„๋ฃŒ ํ–ˆ์Šต๋‹ˆ๋‹ค.", + firstButton: .cancel(title: "ํ™•์ธ", action: {}), + secondButton: nil + ) + ) + } + } else { + newWorkouts.append(contentsOf: workouts) + + let newShoes = Shoes( + id: shoes.id, + image: shoes.image, + shoesName: shoes.shoesName, + nickname: shoes.nickname, + goalMileage: shoes.goalMileage, + currentMileage: shoes.currentMileage, + workouts: newWorkouts + ) + + try await repository.updateShoes(shoes: newShoes) + } } diff --git a/Run Mile/Domain/UseCases/HealthDataUseCase.swift b/Run Mile/Domain/UseCases/HealthDataUseCase.swift index 6f475f6..a50e6a9 100644 --- a/Run Mile/Domain/UseCases/HealthDataUseCase.swift +++ b/Run Mile/Domain/UseCases/HealthDataUseCase.swift @@ -44,14 +44,7 @@ final class DefaultHealthDataUseCase: HealthDataUseCase { } public func fetchWorkoutData() async throws -> [Workout] { - let fetchedResult = try await workoutDataRepository.fetchWorkoutData() - let shoes = try await shoesDataRepository.fetchAllShoes() - - let registeredId = Set(shoes.flatMap { $0.workouts.map { $0.id } }) - - return fetchedResult.filter ({ workout in - !registeredId.contains(workout.id) - }) + try await workoutDataRepository.fetchUnsavedWorkoutData() } } diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift index c2c1c4d..40c4b52 100644 --- a/Run Mile/Helper/Delegate/AppDelegate.swift +++ b/Run Mile/Helper/Delegate/AppDelegate.swift @@ -16,11 +16,11 @@ final class AppDelegate: NSObject, UIApplicationDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil ) -> Bool { + self.realmMigration() Task { await self.userNotificationAuthorize() await AppDelegate.setHealthBackgroundTask() - self.realmMigration() } return true @@ -179,32 +179,6 @@ extension AppDelegate { print(Realm.Configuration.defaultConfiguration.fileURL ?? "์•Œ ์ˆ˜ ์—†์Œ") #endif } - - private func workoutDataMigration() { - let workoutRepository = WorkoutDataRepositoryImpl() - let shoesRepository = ShoesDataRepositoryImpl() - - Task { - do { - let workouts = try await workoutRepository.fetchSavedWorkoutData() - let shoes = try await shoesRepository.fetchAllShoes() - - if !shoes.isEmpty, workouts.isEmpty { - var workouts = [Workout]() - - shoes.forEach { - $0.workouts.forEach { - workouts.append($0) - } - } - - try await workoutRepository.saveWorkoutData(workouts: workouts) - } - } catch { - // TODO: Error ์ฒ˜๋ฆฌ - } - } - } } extension AppDelegate: UNUserNotificationCenterDelegate { From e1df98a60c441456d0f09fb961861bd8c17e133c Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Thu, 26 Jun 2025 14:30:24 +0900 Subject: [PATCH 32/54] =?UTF-8?q?[#37]=20=EC=9A=B4=EB=8F=99=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=8B=9C=20=EC=A4=91=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/UseCases/ChooseShoesUseCase.swift | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift index b5e1f98..06df50f 100644 --- a/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift +++ b/Run Mile/Domain/UseCases/ChooseShoesUseCase.swift @@ -27,9 +27,20 @@ final class DefaultChooseShoesUseCase: ChooseShoesUseCase { } public func registerWorkouts(shoes: Shoes, workouts: [Workout]) async throws { + let currentWorkouts = shoes.workouts + var newWorkouts = shoes.workouts - if newWorkouts.contains(workouts) { + var isDuplicated = false + + for currentWorkout in currentWorkouts { + if workouts.contains(where: { $0.id == currentWorkout.id }) { + isDuplicated = true + break + } + } + + if isDuplicated { if workouts.count == 1 { await NavigationCoordinator.shared.push( .init( @@ -40,9 +51,9 @@ final class DefaultChooseShoesUseCase: ChooseShoesUseCase { ) ) } else { - workouts.forEach { - if !newWorkouts.contains($0) { - newWorkouts.append($0) + workouts.forEach { workout in + if !currentWorkouts.contains(where: { $0.id == workout.id }) { + newWorkouts.append(workout) } } From 0358f096bc1cf20575c00daa9600a1bba6b42eb2 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Thu, 26 Jun 2025 22:33:12 +0900 Subject: [PATCH 33/54] =?UTF-8?q?[#37]=20Multiple=20Notification=20?= =?UTF-8?q?=ED=98=84=EC=83=81=20=EC=88=98=EC=A0=95(=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EC=8B=A4=ED=96=89=20=EB=AC=B8=EC=A0=9C,?= =?UTF-8?q?=20nested=20query=EB=AC=B8=EC=97=90=EC=84=9C=20=EB=8B=A8?= =?UTF-8?q?=EC=9D=BC=20anchoredquery=20=EC=82=AC=EC=9A=A9=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile/Helper/Delegate/AppDelegate.swift | 153 ++++++++++-------- Run Mile/Helper/UserDefaults+.swift | 27 ++-- .../2.Workout/WorkoutListViewModel.swift | 2 +- 3 files changed, 95 insertions(+), 87 deletions(-) diff --git a/Run Mile/Helper/Delegate/AppDelegate.swift b/Run Mile/Helper/Delegate/AppDelegate.swift index 40c4b52..582e8fb 100644 --- a/Run Mile/Helper/Delegate/AppDelegate.swift +++ b/Run Mile/Helper/Delegate/AppDelegate.swift @@ -6,8 +6,8 @@ // import UIKit -import HealthKit import SwiftUI +import HealthKit import RealmSwift @@ -20,7 +20,8 @@ final class AppDelegate: NSObject, UIApplicationDelegate { Task { await self.userNotificationAuthorize() - await AppDelegate.setHealthBackgroundTask() + await Self.setBackgroundDelivery() + self.setHealthBackgroundQueryTask() } return true @@ -39,6 +40,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate { extension AppDelegate { + /// UserNotification ๊ถŒํ•œ ํ—ˆ์šฉ private func userNotificationAuthorize() async { let notiCenter = UNUserNotificationCenter.current() @@ -55,84 +57,89 @@ extension AppDelegate { } } - public static func setHealthBackgroundTask() async { + /// ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ Health ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ ์—…๋ฐ์ดํŠธ ์„ค์ • + public static func setBackgroundDelivery() async { let store = HKHealthStore() do { try await store.enableBackgroundDelivery(for: .workoutType(), frequency: .immediate) - let query = HKObserverQuery( - sampleType: .workoutType(), - predicate: nil - ) { query, completionHandler, error in - if let error = error { - print(error) - return - } - - let sort = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) - let sampleQuery = HKSampleQuery(queryDescriptors: [.init(sampleType: .workoutType(), predicate: nil)], limit: 1, sortDescriptors: [sort]) { _, samples, error in - if let error = error { - print(error) - return - } - - defer { - UserDefaults.standard.isFirstLaunch = true - } - - guard let workout = samples?.first as? HKWorkout else { - return - } - - guard case .running = workout.workoutActivityType else { - return - } - - let workoutId = workout.uuid.uuidString - let currentId = UserDefaults.standard.recentWorkoutID - - if !UserDefaults.standard.isFirstLaunch { - UserDefaults.standard.recentWorkoutID = workoutId - } else { - if workoutId != currentId { - let distance = workout.getKilometerDistance() - if !UserDefaults.standard.selectedShoesID.isEmpty { - - UserNotificationsManager.requestNotification( - category: .autoRegister(workout.toEntity), - title: String(format: "%.2fkm ๋Ÿฌ๋‹ ์™„๋ฃŒ ๐Ÿ”ฅ๐Ÿ”ฅ", distance!), - body: distance == nil - ? "์‹ ๋ฐœ์— ์ž๋™ ๋“ฑ๋ก์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!" - : String(format: "์‹ ๋ฐœ์— ์ž๋™ ๋“ฑ๋ก์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋Ÿฌ๋‹ ํ›„ ์ŠคํŠธ๋ ˆ์นญ ๊ผญ ์žŠ์ง€ ๋งˆ์„ธ์š”!", distance!) - ) - - autoRegisterShoes(workout: workout) - } else { - UserNotificationsManager.requestNotification( - category: .manualRegister(workout.toEntity), - title: String(format: "%.2fkm ๋Ÿฌ๋‹ ์™„๋ฃŒ ๐Ÿ”ฅ๐Ÿ”ฅ", distance!), - body: distance == nil - ? "์‹ ๋ฐœ ๋งˆ์ผ๋ฆฌ์ง€๋ฅผ ๋“ฑ๋กํ•  ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋“ฑ๋กํ•˜๋Ÿฌ ๊ฐ€๋ณผ๊นŒ์š”?" - : String(format: "%.2fkm, ์žŠ์ง€ ๋ง๊ณ  ๋งˆ์ผ๋ฆฌ์ง€๋ฅผ ๋“ฑ๋กํ•˜๋Ÿฌ ์˜ค์„ธ์š”!", distance!) - ) - } - UserDefaults.standard.recentWorkoutID = workoutId - } - } - } - - store.execute(sampleQuery) - - completionHandler() + } catch { + print(error.localizedDescription) + } + } + + /// ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‚ฌ์šฉํ•  HealthKit Query ์„ค์ • + private func setHealthBackgroundQueryTask() { + let store = HKHealthStore() + + let anchoredQuery = HKAnchoredObjectQuery( + type: .workoutType(), + predicate: nil, + anchor: UserDefaults.standard.lastAnchor, + limit: HKObjectQueryNoLimit + ) { query, samples, deletedObjects, anchor, error in + if UserDefaults.standard.lastAnchor != anchor { + UserDefaults.standard.lastAnchor = anchor + } + } + + anchoredQuery.updateHandler = { [weak self] query, samples, deletedObjects, anchor, error in + let currentAnchor = UserDefaults.standard.lastAnchor + + if currentAnchor != anchor { + UserDefaults.standard.lastAnchor = anchor + } else { + return } - store.execute(query) - } catch { - print(error) + if let error = error { + print(error) + return + } + + guard let samples = samples as? [HKWorkout], + !samples.isEmpty + else { + print(#function) + return + } + + guard let workout = samples.first else { + return + } + + guard case .running = workout.workoutActivityType else { + return + } + + let distance = workout.getKilometerDistance() + + if !UserDefaults.standard.selectedShoesID.isEmpty { + UserNotificationsManager.requestNotification( + category: .autoRegister(workout.toEntity), + title: String(format: "%.2fkm ๋Ÿฌ๋‹ ์™„๋ฃŒ ๐Ÿ”ฅ๐Ÿ”ฅ", distance!), + body: distance == nil + ? "์‹ ๋ฐœ์— ์ž๋™ ๋“ฑ๋ก์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!" + : String(format: "์‹ ๋ฐœ์— ์ž๋™ ๋“ฑ๋ก์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋Ÿฌ๋‹ ํ›„ ์ŠคํŠธ๋ ˆ์นญ ๊ผญ ์žŠ์ง€ ๋งˆ์„ธ์š”!", distance!) + ) + + self?.autoRegisterShoes(workout: workout) + } else { + UserNotificationsManager.requestNotification( + category: .manualRegister(workout.toEntity), + title: String(format: "%.2fkm ๋Ÿฌ๋‹ ์™„๋ฃŒ ๐Ÿ”ฅ๐Ÿ”ฅ", distance!), + body: distance == nil + ? "์‹ ๋ฐœ ๋งˆ์ผ๋ฆฌ์ง€๋ฅผ ๋“ฑ๋กํ•  ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋“ฑ๋กํ•˜๋Ÿฌ ๊ฐ€๋ณผ๊นŒ์š”?" + : String(format: "%.2fkm, ์žŠ์ง€ ๋ง๊ณ  ๋งˆ์ผ๋ฆฌ์ง€๋ฅผ ๋“ฑ๋กํ•˜๋Ÿฌ ์˜ค์„ธ์š”!", distance!) + ) + } } + + store.execute(anchoredQuery) } - private static func autoRegisterShoes(workout: HKWorkout) { + /// ์—…๋ฐ์ดํŠธ๋œ ์šด๋™ ์ž๋™ ๋“ฑ๋ก ๋ฉ”์†Œ๋“œ + private func autoRegisterShoes(workout: HKWorkout) { let shoesDataRepository: ShoesDataRepository = ShoesDataRepositoryImpl() Task { @@ -163,10 +170,13 @@ extension AppDelegate { } } + /// Realm ์Šคํ‚ค๋งˆ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ private func realmMigration() { let config = Realm.Configuration( schemaVersion: 1, migrationBlock: { migration, oldSchemaVersion in + /// Version 1 + /// WorkoutDTO : ์—ญ๊ด€๊ณ„ ์ถ”๊ฐ€ <-> ShoesDTO if oldSchemaVersion < 1 { } @@ -176,6 +186,7 @@ extension AppDelegate { Realm.Configuration.defaultConfiguration = config #if DEBUG + // Debug์šฉ print print(Realm.Configuration.defaultConfiguration.fileURL ?? "์•Œ ์ˆ˜ ์—†์Œ") #endif } diff --git a/Run Mile/Helper/UserDefaults+.swift b/Run Mile/Helper/UserDefaults+.swift index 8f8d1e8..bc75e3f 100644 --- a/Run Mile/Helper/UserDefaults+.swift +++ b/Run Mile/Helper/UserDefaults+.swift @@ -6,33 +6,30 @@ // import Foundation +import HealthKit extension UserDefaults { - public var isFirstLaunch: Bool { - get { - self.bool(forKey: "isFirstLaunch") - } - set { - self.set(newValue, forKey: "isFirstLaunch") - } - } - - public var recentWorkoutID: String { + public var selectedShoesID: String { get { - self.string(forKey: "recentWorkoutID") ?? "" + self.string(forKey: "selectedShoesID") ?? "" } set { - self.set(newValue, forKey: "recentWorkoutID") + self.set(newValue, forKey: "selectedShoesID") } } - public var selectedShoesID: String { + public var lastAnchor: HKQueryAnchor? { get { - self.string(forKey: "selectedShoesID") ?? "" + self.data(forKey: "anchor").flatMap { + try? NSKeyedUnarchiver.unarchivedObject(ofClass: HKQueryAnchor.self, from: $0) + } } set { - self.set(newValue, forKey: "selectedShoesID") + if let anchor = newValue, + let data = try? NSKeyedArchiver.archivedData(withRootObject: anchor, requiringSecureCoding: true) { + self.set(data, forKey: "anchor") + } } } } diff --git a/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift b/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift index 2738ff8..b7aab59 100644 --- a/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift +++ b/Run Mile/Presentation/2.Workout/WorkoutListViewModel.swift @@ -49,7 +49,7 @@ extension WorkoutListViewModel { self.classifyWorkoutsByDate(workouts: workouts) - await AppDelegate.setHealthBackgroundTask() + await AppDelegate.setBackgroundDelivery() } catch { if let error = error as? HealthError, error == .unknownError || error == .notAvailableDevice { From d2912fdb3f118f00c7b83214965fb2fac7f26997 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Thu, 26 Jun 2025 23:19:21 +0900 Subject: [PATCH 34/54] =?UTF-8?q?[#39]=20=ED=82=A4=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=ED=88=B4=EB=B0=94=20=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80(?= =?UTF-8?q?=EC=9C=84,=20=EC=95=84=EB=9E=98,=20=EC=99=84=EB=A3=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1.Shoes/1-1.AddShoes/AddShoesView.swift | 26 +++++++ .../1-1.AddShoes/AddShoesViewModel.swift | 67 ++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesView.swift b/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesView.swift index 20a8c99..de41290 100644 --- a/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesView.swift +++ b/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesView.swift @@ -69,6 +69,32 @@ struct AddShoesView: View { .onDisappear { dismissAction() } + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + HStack { + Button { + viewModel.keyboardToolbarUpButtonTapped(&textFieldFocus) + } label: { + Image(systemName: "chevron.up") + } + .disabled(textFieldFocus?.isUpButtonDisabled ?? false) + + Button { + viewModel.keyboardToolbarDownButtonTapped(&textFieldFocus) + } label: { + Image(systemName: "chevron.down") + } + .disabled(textFieldFocus?.isDownButtonDisabled ?? false) + + + Spacer() + + Button("์™„๋ฃŒ") { + viewModel.keyboardToolbarCompleteButtonTapped(&textFieldFocus) + } + } + } + } } } diff --git a/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesViewModel.swift b/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesViewModel.swift index 67c49af..6ba1f39 100644 --- a/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesViewModel.swift +++ b/Run Mile/Presentation/1.Shoes/1-1.AddShoes/AddShoesViewModel.swift @@ -50,7 +50,9 @@ final class AddShoesViewModel { init(useCase: AddShoesUseCase) { self.useCase = useCase } - +} + +extension AddShoesViewModel { enum TextFieldCategory: Hashable { case name case nickname @@ -70,6 +72,50 @@ final class AddShoesViewModel { return "์ฃผํ–‰ ๋งˆ์ผ๋ฆฌ์ง€(Optional)" } } + + public var isUpButtonDisabled: Bool { + switch self { + case .name: + true + default: + false + } + } + + public var isDownButtonDisabled: Bool { + switch self { + case .runMileage: + true + default: + false + } + } + + public var next: Self { + switch self { + case .name: + .nickname + case .nickname: + .goalMileage + case .goalMileage: + .runMileage + case .runMileage: + .runMileage + } + } + + public var previous: Self { + switch self { + case .name: + .name + case .nickname: + .name + case .goalMileage: + .nickname + case .runMileage: + .goalMileage + } + } } } @@ -132,6 +178,25 @@ extension AddShoesViewModel { } } + @MainActor + public func keyboardToolbarUpButtonTapped(_ textField: inout TextFieldCategory?) { + if let _ = textField { + textField = textField?.previous + } + } + + @MainActor + public func keyboardToolbarDownButtonTapped(_ textField: inout TextFieldCategory?) { + if let _ = textField { + textField = textField?.next + } + } + + @MainActor + public func keyboardToolbarCompleteButtonTapped(_ textField: inout TextFieldCategory?) { + textField = nil + } + public func saveButtonTapped() { let shoes = Shoes( id: .init(), From f78858d5f8f19ae154c3bf0a470ca611a7a22422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=9D=B8=EB=B2=94?= <116792524+mooninbeom@users.noreply.github.com> Date: Thu, 26 Jun 2025 23:44:32 +0900 Subject: [PATCH 35/54] =?UTF-8?q?[Fix]=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index dca5719..de3542e 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -4,6 +4,9 @@ on: push: branches: - main + pull_request: + branches: + - main jobs: build-upload-testflight: @@ -26,10 +29,10 @@ jobs: # - name: Set up Xcode # uses: maxim-lobanov/setup-xcode@v1.6.0 # with: - # xcode-version: '16.2' + # xcode-version: '16.3' - name: Select Xcode version - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer - name: Check Xcode version run: xcodebuild -version From e7a1499da186341ad32a67a9782a353bca5dff99 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Fri, 27 Jun 2025 13:25:40 +0900 Subject: [PATCH 36/54] =?UTF-8?q?[#42]=20=EC=9D=B8=EC=A6=9D=EC=84=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Run Mile.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj index b09dae7..9745616 100644 --- a/Run Mile.xcodeproj/project.pbxproj +++ b/Run Mile.xcodeproj/project.pbxproj @@ -444,7 +444,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 202506231; DEVELOPMENT_TEAM = ""; @@ -473,7 +473,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -491,7 +491,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Run Mile/Run Mile.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 202506231; DEVELOPMENT_TEAM = ""; @@ -520,7 +520,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mooni.Run-Mile"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.mooni.Run-Mile"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.mooni.Run-Mile"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; From d3ce38c2033ee5287e9fae4291b6bb4aef296013 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Fri, 27 Jun 2025 13:39:46 +0900 Subject: [PATCH 37/54] =?UTF-8?q?[#42]=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95(=ED=8A=B8=EB=A6=AC?= =?UTF-8?q?=EA=B1=B0=20=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index de3542e..1710328 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -1,9 +1,8 @@ name: ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ ๐Ÿš€ on: - push: - branches: - - main + # main ๋ธŒ๋žœ์น˜๋กœ PR ์‹œ ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ์—…๋กœ๋“œ + # CD ์„ฑ๊ณต ์—ฌ๋ถ€๋กœ ์›Œํฌํ”Œ๋กœ์šฐ ์ˆ˜์ • ์ง„ํ–‰ pull_request: branches: - main From 6327dceecbaa49967f39f93680c86fc773579a8d Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Fri, 27 Jun 2025 14:48:39 +0900 Subject: [PATCH 38/54] =?UTF-8?q?[#42]=20=EC=83=88=EB=A1=9C=EA=B3=A0?= =?UTF-8?q?=EC=B9=A8=20UI=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2.Workout/WorkoutListView.swift | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/Run Mile/Presentation/2.Workout/WorkoutListView.swift b/Run Mile/Presentation/2.Workout/WorkoutListView.swift index 98ff086..3ee11c3 100644 --- a/Run Mile/Presentation/2.Workout/WorkoutListView.swift +++ b/Run Mile/Presentation/2.Workout/WorkoutListView.swift @@ -21,16 +21,20 @@ struct WorkoutListView: View { WorkoutNavigationView(viewModel: viewModel) switch viewModel.viewStatus { - case .none, .selection: + case .none, .selection, .loading: WorkoutScrollView(viewModel: $viewModel) - case .loading: - WorkoutLoadingView() case .empty: WorkoutEmptyView( viewModel: viewModel ) } } + .overlay { + if viewModel.viewStatus == .loading { + ProgressView() + .progressViewStyle(.circular) + } + } .task { await viewModel.onAppear() } @@ -119,18 +123,6 @@ private struct WorkoutScrollView: View { } -private struct WorkoutLoadingView: View { - var body: some View { - Group { - Spacer() - ProgressView() - .progressViewStyle(.circular) - Spacer() - } - } -} - - private struct WorkoutEmptyView: View { let viewModel: WorkoutListViewModel From 7094cffb8cbe926209229a52b7589ead424d85cb Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sat, 28 Jun 2025 14:48:14 +0900 Subject: [PATCH 39/54] =?UTF-8?q?[#44]=20Fastfile=EC=88=98=EC=A0=95(Slack?= =?UTF-8?q?=20->=20Discord=20=EC=9B=B9=ED=9B=85=20=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 1 + Gemfile | 3 +++ Gemfile.lock | 36 ++++++++++++++++++++++++++++++++ fastlane/Fastfile | 32 +++++++++++++++++++++++++++- fastlane/Pluginfile | 5 +++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 fastlane/Pluginfile diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index 1710328..7b6ecb1 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -6,6 +6,7 @@ on: pull_request: branches: - main + - develop jobs: build-upload-testflight: diff --git a/Gemfile b/Gemfile index 7a118b4..cdd3a6b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,6 @@ source "https://rubygems.org" gem "fastlane" + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock index bb63134..6219db7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -37,9 +37,19 @@ GEM declarative (0.0.20) digest-crc (0.7.0) rake (>= 12.0.0, < 14.0.0) + discordrb (3.3.0) + discordrb-webhooks (~> 3.3.0) + ffi (>= 1.9.24) + opus-ruby + rbnacl (~> 3.4.0) + rest-client (>= 2.1.0.rc1) + websocket-client-simple (>= 0.3.0) + discordrb-webhooks (3.3.0) + rest-client (>= 2.1.0.rc1) domain_name (0.6.20240107) dotenv (2.8.1) emoji_regex (3.2.3) + event_emitter (0.2.6) excon (0.112.0) faraday (1.10.4) faraday-em_http (~> 1.0) @@ -112,8 +122,12 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.4.1) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-discord_notifier (0.1.7) + discordrb (~> 3.3.0) fastlane-sirp (1.0.0) sysrandom (~> 1.0) + ffi (1.17.2) + ffi (1.17.2-arm64-darwin) gh_inspector (1.1.3) google-apis-androidpublisher_v3 (0.54.0) google-apis-core (>= 0.11.0, < 2.a) @@ -152,6 +166,7 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) + http-accept (1.7.0) http-cookie (1.0.8) domain_name (~> 0.5) httpclient (2.9.0) @@ -161,6 +176,10 @@ GEM jwt (2.10.1) base64 logger (1.7.0) + mime-types (3.7.0) + logger + mime-types-data (~> 3.2025, >= 3.2025.0507) + mime-types-data (3.2025.0624) mini_magick (4.13.2) mini_mime (1.1.5) multi_json (1.15.0) @@ -168,16 +187,26 @@ GEM mutex_m (0.3.0) nanaimo (0.4.0) naturally (2.3.0) + netrc (0.11.0) nkf (0.2.0) optparse (0.6.0) + opus-ruby (1.0.1) + ffi os (1.1.4) plist (3.7.2) public_suffix (6.0.2) rake (13.3.0) + rbnacl (3.4.0) + ffi representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) retriable (3.1.2) rexml (3.4.1) rouge (3.28.0) @@ -203,6 +232,12 @@ GEM tty-cursor (~> 0.7) uber (0.1.0) unicode-display_width (2.6.0) + websocket (1.2.11) + websocket-client-simple (0.9.0) + base64 + event_emitter + mutex_m + websocket word_wrap (1.0.0) xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) @@ -222,6 +257,7 @@ PLATFORMS DEPENDENCIES fastlane + fastlane-plugin-discord_notifier BUNDLED WITH 2.6.9 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index da94af5..7cf9059 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -97,11 +97,17 @@ platform :ios do ) slack( - message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค€๋น„ ์‹œ์ž‘", + message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค€๋น„", channel: "#run_mile", slack_url: "#{ENV["SLACK_URL"]}" ) + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "Run Mile ๋ฐฐํฌ ์ค€๋น„", + description: "Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์œ„ํ•ด ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค." + ) + setup_ci match(type: "appstore") @@ -112,6 +118,12 @@ platform :ios do slack_url: "#{ENV["SLACK_URL"]}" ) + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "Run Mile ์ธ์ฆ์„œ ์™„๋ฃŒ", + description: "Build Number: #{new_build_number} ์ธ์ฆ์„œ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + ) + build_app( scheme: "Run Mile", xcodebuild_formatter: "xcpretty" @@ -123,6 +135,12 @@ platform :ios do slack_url: "#{ENV["SLACK_URL"]}" ) + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "Run Mile ์•„์นด์ด๋ธŒ ์™„๋ฃŒ", + description: "Build Number: #{new_build_number} ์•„์นด์ด๋ธŒ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + ) + upload_to_testflight slack( @@ -130,6 +148,12 @@ platform :ios do channel: "#run_mile", slack_url: "#{ENV["SLACK_URL"]}" ) + + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "Run Mile Testflight ๋ฐฐํฌ ์™„๋ฃŒ", + description: "Build Number: #{new_build_number} ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + ) end error do |lane, exception, options| @@ -139,5 +163,11 @@ platform :ios do success: false, slack_url: "#{ENV["SLACK_URL"]}" ) + + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "Testflight ๋ฐฐํฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", + description: "์—๋Ÿฌ ๋ฉ”์‹œ์ง€\n#{exception}" + ) end end diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile new file mode 100644 index 0000000..7ef2b02 --- /dev/null +++ b/fastlane/Pluginfile @@ -0,0 +1,5 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-discord_notifier' From 05520021dbd7191b742e717a98b8f7dfac1761b7 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sat, 28 Jun 2025 14:55:30 +0900 Subject: [PATCH 40/54] =?UTF-8?q?[#44]=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95(=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index 7b6ecb1..27d037a 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -56,6 +56,7 @@ jobs: APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} SLACK_URL: ${{ secrets.SLACK_URL }} + DISCORD_URL: ${{ secrets.DISCORD_URL }} run: bundle exec fastlane testflight - name: Upload Artifacts From 94d7f918f17e5ce256c297715f7131b7f35896d9 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sat, 28 Jun 2025 15:23:15 +0900 Subject: [PATCH 41/54] =?UTF-8?q?[#44]=20CI/CD=20=EC=88=98=EC=A0=95(Slack?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C,=20lane=EB=AA=85=20=EB=B3=80=EA=B2=BD,=20?= =?UTF-8?q?=EA=B9=83=ED=97=88=EB=B8=8C=20=ED=8A=B8=EB=A6=AC=EA=B1=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testflight.yml | 4 +--- fastlane/Fastfile | 33 +------------------------------- 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/.github/workflows/testflight.yml b/.github/workflows/testflight.yml index 27d037a..89129bb 100644 --- a/.github/workflows/testflight.yml +++ b/.github/workflows/testflight.yml @@ -6,7 +6,6 @@ on: pull_request: branches: - main - - develop jobs: build-upload-testflight: @@ -55,9 +54,8 @@ jobs: APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} - SLACK_URL: ${{ secrets.SLACK_URL }} DISCORD_URL: ${{ secrets.DISCORD_URL }} - run: bundle exec fastlane testflight + run: bundle exec fastlane upload_testflight - name: Upload Artifacts uses: actions/upload-artifact@v4 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 7cf9059..c7d44f2 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -61,7 +61,7 @@ platform :ios do desc "Push a new beta build to TestFlight" - lane :testflight do + lane :upload_testflight do app_store_connect_api_key( key_id: "#{ENV["APP_STORE_CONNECT_KEY_ID"]}", @@ -96,12 +96,6 @@ platform :ios do build_number: new_build_number ) - slack( - message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์ค€๋น„", - channel: "#run_mile", - slack_url: "#{ENV["SLACK_URL"]}" - ) - discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "Run Mile ๋ฐฐํฌ ์ค€๋น„", @@ -112,12 +106,6 @@ platform :ios do match(type: "appstore") - slack( - message: "Build Number: #{new_build_number} ์ธ์ฆ์„œ ์ค€๋น„ ์™„๋ฃŒ", - channel: "#run_mile", - slack_url: "#{ENV["SLACK_URL"]}" - ) - discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "Run Mile ์ธ์ฆ์„œ ์™„๋ฃŒ", @@ -129,12 +117,6 @@ platform :ios do xcodebuild_formatter: "xcpretty" ) - slack( - message: "Build Number: #{new_build_number} ์•„์นด์ด๋ธŒ ์™„๋ฃŒ", - channel: "#run_mile", - slack_url: "#{ENV["SLACK_URL"]}" - ) - discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "Run Mile ์•„์นด์ด๋ธŒ ์™„๋ฃŒ", @@ -143,12 +125,6 @@ platform :ios do upload_to_testflight - slack( - message: "Build Number: #{new_build_number} ๋ฐฐํฌ ์„ฑ๊ณต!", - channel: "#run_mile", - slack_url: "#{ENV["SLACK_URL"]}" - ) - discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "Run Mile Testflight ๋ฐฐํฌ ์™„๋ฃŒ", @@ -157,13 +133,6 @@ platform :ios do end error do |lane, exception, options| - slack( - message: "Testflight ๋ฐฐํฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. #{exception}", - channel: "#run_mile", - success: false, - slack_url: "#{ENV["SLACK_URL"]}" - ) - discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "Testflight ๋ฐฐํฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", From a719497c37eb871ce0022bfdc9be5aa2435c620a Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sat, 28 Jun 2025 23:55:06 +0900 Subject: [PATCH 42/54] =?UTF-8?q?[#47]=20Fastfile=20=EC=88=98=EC=A0=95(App?= =?UTF-8?q?store=20=EB=B0=B0=ED=8F=AC=20=EB=A0=88=EC=9D=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 131 +++++++++++++++++++++++++++++++++------------- 1 file changed, 96 insertions(+), 35 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c7d44f2..39ed4af 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -16,13 +16,17 @@ default_platform(:ios) platform :ios do - lane :local do + + desc "Testflight ์ž๋™ ๋ฐฐํฌ lane" + lane :upload_testflight do + app_store_connect_api_key( - key_id: "", - issuer_id: "", - key_content: "" + key_id: "#{ENV["APP_STORE_CONNECT_KEY_ID"]}", + issuer_id: "#{ENV["APP_STORE_CONNECT_ISSUER_ID"]}", + key_content: "#{ENV["APP_STORE_CONNECT_KEY"]}", + is_key_content_base64: true ) - + today = Time.now.strftime("%Y%m%d") current_build_number = "#{latest_testflight_build_number}" @@ -49,27 +53,110 @@ platform :ios do build_number: new_build_number ) + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "[Testflight] Run Mile ๋ฐฐํฌ ์ค€๋น„", + description: "[Testflight] Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์œ„ํ•ด ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค." + ) + + setup_ci + match(type: "appstore") + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "[Testflight] Run Mile ์ธ์ฆ์„œ ์™„๋ฃŒ", + description: "[Testflight] Build Number: #{new_build_number} ์ธ์ฆ์„œ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + ) + build_app( scheme: "Run Mile", xcodebuild_formatter: "xcpretty" ) + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "[Testflight] Run Mile ์•„์นด์ด๋ธŒ ์™„๋ฃŒ", + description: "[Testflight] Build Number: #{new_build_number} ์•„์นด์ด๋ธŒ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + ) + upload_to_testflight + + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "[Testflight] Run Mile Testflight ๋ฐฐํฌ ์™„๋ฃŒ", + description: "[Testflight] Build Number: #{new_build_number} ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + ) end - - - desc "Push a new beta build to TestFlight" - lane :upload_testflight do + + desc "Appstore ์ž๋™ ๋ฐฐํฌ lane" + lane :upload_appstore do app_store_connect_api_key( key_id: "#{ENV["APP_STORE_CONNECT_KEY_ID"]}", issuer_id: "#{ENV["APP_STORE_CONNECT_ISSUER_ID"]}", key_content: "#{ENV["APP_STORE_CONNECT_KEY"]}", is_key_content_base64: true ) + + current_build_number = "#{latest_testflight_build_number}" + + increment_build_number( + xcodeproj: "Run Mile.xcodeproj", + build_number: current_build_number + ) + + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "[Appstore] Run Mile ๋ฐฐํฌ ์ค€๋น„", + description: "[Appstore] Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์œ„ํ•ด ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค." + ) + + setup_ci + + match(type: "appstore") + + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "[Appstore] Run Mile ์ธ์ฆ์„œ ์™„๋ฃŒ", + description: "[Appstore] Build Number: #{new_build_number} ์ธ์ฆ์„œ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + ) + + build_app( + scheme: "Run Mile", + xcodebuild_formatter: "xcpretty" + ) + + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "[Appstore] Run Mile ์•„์นด์ด๋ธŒ ์™„๋ฃŒ", + description: "[Appstore] Build Number: #{new_build_number} ์•„์นด์ด๋ธŒ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + ) + + upload_to_app_store( + force: true, + submit_for_review: true, + skip_screenshots: true, + skip_metadata: true, + automatic_release: true + ) + + discord_notifier( + webhook_url: "#{ENV["DISCORD_URL"]}", + title: "[Appstore] Run Mile Appstore ๋ฐฐํฌ ์™„๋ฃŒ", + description: "[Appstore] Build Number: #{new_build_number} ์•ฑ์Šคํ† ์–ด ๋ฐฐํฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + ) + end + + desc "๋กœ์ปฌ ํ…Œ์ŠคํŠธ์šฉ lane" + lane :local do + app_store_connect_api_key( + key_id: "", + issuer_id: "", + key_content: "" + ) + today = Time.now.strftime("%Y%m%d") current_build_number = "#{latest_testflight_build_number}" @@ -96,40 +183,14 @@ platform :ios do build_number: new_build_number ) - discord_notifier( - webhook_url: "#{ENV["DISCORD_URL"]}", - title: "Run Mile ๋ฐฐํฌ ์ค€๋น„", - description: "Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์œ„ํ•ด ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค." - ) - - setup_ci - match(type: "appstore") - discord_notifier( - webhook_url: "#{ENV["DISCORD_URL"]}", - title: "Run Mile ์ธ์ฆ์„œ ์™„๋ฃŒ", - description: "Build Number: #{new_build_number} ์ธ์ฆ์„œ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." - ) - build_app( scheme: "Run Mile", xcodebuild_formatter: "xcpretty" ) - discord_notifier( - webhook_url: "#{ENV["DISCORD_URL"]}", - title: "Run Mile ์•„์นด์ด๋ธŒ ์™„๋ฃŒ", - description: "Build Number: #{new_build_number} ์•„์นด์ด๋ธŒ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." - ) - upload_to_testflight - - discord_notifier( - webhook_url: "#{ENV["DISCORD_URL"]}", - title: "Run Mile Testflight ๋ฐฐํฌ ์™„๋ฃŒ", - description: "Build Number: #{new_build_number} ํ…Œ์ŠคํŠธ ํ”Œ๋ผ์ดํŠธ ๋ฐฐํฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." - ) end error do |lane, exception, options| From b85934b56b1d0d2fe3c7073925f7cbad49ac4347 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 00:05:54 +0900 Subject: [PATCH 43/54] =?UTF-8?q?[#47]=20CI=EC=9A=A9=20=EB=A6=B4=EB=A6=AC?= =?UTF-8?q?=EC=A6=88=20=EB=85=B8=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReleaseNote/v1.0.1.md | 4 ++++ Run Mile.xcodeproj/project.pbxproj | 6 ++++++ 2 files changed, 10 insertions(+) create mode 100644 ReleaseNote/v1.0.1.md diff --git a/ReleaseNote/v1.0.1.md b/ReleaseNote/v1.0.1.md new file mode 100644 index 0000000..c8fe435 --- /dev/null +++ b/ReleaseNote/v1.0.1.md @@ -0,0 +1,4 @@ +# What's new +- ์šด๋™ ๋“ฑ๋ก ์•Œ๋žŒ์„ ๋ˆŒ๋ €์„ ๋•Œ ๋ฐ”๋กœ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. +- ์•Œ๋žŒ์ด ์—ฌ๋Ÿฌ๋ฒˆ ์˜ค๋Š” ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. +- ๊ทธ ์™ธ ์ž‘์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. diff --git a/Run Mile.xcodeproj/project.pbxproj b/Run Mile.xcodeproj/project.pbxproj index 9745616..d25d9f0 100644 --- a/Run Mile.xcodeproj/project.pbxproj +++ b/Run Mile.xcodeproj/project.pbxproj @@ -62,6 +62,11 @@ path = fastlane; sourceTree = ""; }; + 2CA9201C2E103B150049926D /* ReleaseNote */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = ReleaseNote; + sourceTree = ""; + }; 2CE46DF52DAE7780008C6FBC /* Run Mile */ = { isa = PBXFileSystemSynchronizedRootGroup; path = "Run Mile"; @@ -108,6 +113,7 @@ 2CE46DEA2DAE7780008C6FBC = { isa = PBXGroup; children = ( + 2CA9201C2E103B150049926D /* ReleaseNote */, 2C7635AF2E0051AD0005D988 /* fastlane */, 2C7635B02E0051AD0005D988 /* Gemfile */, 2C7635B12E0051AD0005D988 /* Gemfile.lock */, From 5677845108dcb9e266330d3654e780a323699166 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 14:31:23 +0900 Subject: [PATCH 44/54] =?UTF-8?q?[#47]=20=EC=95=B1=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EC=96=B4=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=ED=99=94=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/appstore.yml | 62 ++++++++++++++++++++++++++++++++++ fastlane/Fastfile | 19 ++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/appstore.yml diff --git a/.github/workflows/appstore.yml b/.github/workflows/appstore.yml new file mode 100644 index 0000000..3faa590 --- /dev/null +++ b/.github/workflows/appstore.yml @@ -0,0 +1,62 @@ +# Appstore ์ž๋™ ๋ฐฐํฌ ์›Œํฌํ”Œ๋กœ์šฐ + + +on: + pull_request: + branches: + - release + + +jobs: + upload-appstore: + runs-on: macos-15 + + steps: + - uses: actions/checkout@4 + + - name: Set up SSH + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.SSH_PRIVATE_KEY }} + known_hosts: ${{ secrets.KNOWN_HOSTS}} + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + + - name: Select Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer + + - name: Check Xcode version + run: xcodebuild -version + + - name: Install Bundler + run: gem install bundler + + - name: Install Fastlane + run: brew install fastlane + + - name: Check Fastlane + run: fastlane --version + + - name: Install Dependencies + run: bundle install + + - name: Upload to Appstore ๐Ÿš€ + env: + APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY}} + APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} + APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + DISCORD_URL: ${{ secrets.DISCORD_URL }} + run: bundle exec fastalne upload_appstore + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: ipa-and-dsym + path: | + ./Run\ Mile.ipa + ./Run\ Mile.app.dSYM.zip + diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 39ed4af..af3568f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -106,6 +106,14 @@ platform :ios do build_number: current_build_number ) + current_marketing_version = get_version_number + current_release_note_path = "ReleaseNote/v#{current_marketing_version}.md" + current_release_note = "# Whats new" + + if File.exists?(current_release_note_path) + current_release_note = File.read(current_release_note_path) + end + discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "[Appstore] Run Mile ๋ฐฐํฌ ์ค€๋น„", @@ -138,7 +146,16 @@ platform :ios do submit_for_review: true, skip_screenshots: true, skip_metadata: true, - automatic_release: true + automatic_release: true, + precheck_include_in_app_purchases: false, + copyright: "ยฉ #{Time.now.year} Mooninbeom", + release_notes: current_release_note, + submission_information: { + add_id_info_uses_idfa: false, + export_compliance_encryption_updated: false, + export_compliance_uses_encryption: false, + content_rights_contains_third_party_content: false + } ) discord_notifier( From 3afb93162ce4b1de38e31fcd1bd5f274fab0e62f Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 15:00:41 +0900 Subject: [PATCH 45/54] =?UTF-8?q?[#51]=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=951(=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/appstore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/appstore.yml b/.github/workflows/appstore.yml index 3faa590..2325a34 100644 --- a/.github/workflows/appstore.yml +++ b/.github/workflows/appstore.yml @@ -12,7 +12,7 @@ jobs: runs-on: macos-15 steps: - - uses: actions/checkout@4 + - uses: actions/checkout@v4 - name: Set up SSH uses: shimataro/ssh-key-action@v2 From 439bddd77f3f5676ae407378714b4025c9e21540 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 15:03:15 +0900 Subject: [PATCH 46/54] =?UTF-8?q?[#51]=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=952(=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/appstore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/appstore.yml b/.github/workflows/appstore.yml index 2325a34..37d924f 100644 --- a/.github/workflows/appstore.yml +++ b/.github/workflows/appstore.yml @@ -50,7 +50,7 @@ jobs: APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} DISCORD_URL: ${{ secrets.DISCORD_URL }} - run: bundle exec fastalne upload_appstore + run: bundle exec fastlane upload_appstore - name: Upload Artifacts uses: actions/upload-artifact@v4 From 1ee2d5c1dd103722dc669a65956e1a00bfaf0dcb Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 15:05:48 +0900 Subject: [PATCH 47/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EB=A6=AC=EB=84=A4=EC=9D=B4=EB=B0=8D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index af3568f..84c552e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -117,7 +117,7 @@ platform :ios do discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "[Appstore] Run Mile ๋ฐฐํฌ ์ค€๋น„", - description: "[Appstore] Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์œ„ํ•ด ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค." + description: "[Appstore] Build Number: #{current_build_number} ๋ฐฐํฌ๋ฅผ ์œ„ํ•ด ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค." ) setup_ci @@ -127,7 +127,7 @@ platform :ios do discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "[Appstore] Run Mile ์ธ์ฆ์„œ ์™„๋ฃŒ", - description: "[Appstore] Build Number: #{new_build_number} ์ธ์ฆ์„œ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + description: "[Appstore] Build Number: #{current_build_number} ์ธ์ฆ์„œ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." ) build_app( @@ -138,7 +138,7 @@ platform :ios do discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "[Appstore] Run Mile ์•„์นด์ด๋ธŒ ์™„๋ฃŒ", - description: "[Appstore] Build Number: #{new_build_number} ์•„์นด์ด๋ธŒ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + description: "[Appstore] Build Number: #{current_build_number} ์•„์นด์ด๋ธŒ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." ) upload_to_app_store( @@ -161,7 +161,7 @@ platform :ios do discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "[Appstore] Run Mile Appstore ๋ฐฐํฌ ์™„๋ฃŒ", - description: "[Appstore] Build Number: #{new_build_number} ์•ฑ์Šคํ† ์–ด ๋ฐฐํฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + description: "[Appstore] Build Number: #{current_build_number} ์•ฑ์Šคํ† ์–ด ๋ฐฐํฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." ) end From ec5761a089f8b9ee96f9fb3940d4a00729cc0ccd Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 15:20:55 +0900 Subject: [PATCH 48/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(pri?= =?UTF-8?q?nt=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 84c552e..2190c62 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -114,6 +114,9 @@ platform :ios do current_release_note = File.read(current_release_note_path) end + puts "Version: #{current_marketing_version}" + puts "Release Notes\n#{current_release_note}" + discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "[Appstore] Run Mile ๋ฐฐํฌ ์ค€๋น„", From a1e263e595a5e01d91ca676ebea2ed075f7dbe21 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 16:25:29 +0900 Subject: [PATCH 49/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(?= =?UTF-8?q?=EB=A6=B4=EB=A6=AC=EC=A6=88=20=EB=85=B8=ED=8A=B8=20=ED=8C=A8?= =?UTF-8?q?=EC=8A=A4=20=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 2190c62..f83fa7f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -107,7 +107,7 @@ platform :ios do ) current_marketing_version = get_version_number - current_release_note_path = "ReleaseNote/v#{current_marketing_version}.md" + current_release_note_path = "../ReleaseNote/v#{current_marketing_version}.md" current_release_note = "# Whats new" if File.exists?(current_release_note_path) From f3656bb42ce501ecc99ee3873370f3377a7f2041 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 16:32:04 +0900 Subject: [PATCH 50/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(upl?= =?UTF-8?q?oad=5Fto=5Fappstore=20=EC=95=84=EA=B7=9C=EB=A8=BC=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f83fa7f..f69a276 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -152,7 +152,9 @@ platform :ios do automatic_release: true, precheck_include_in_app_purchases: false, copyright: "ยฉ #{Time.now.year} Mooninbeom", - release_notes: current_release_note, + release_notes: { + "ko_KR" => current_release_note + }, submission_information: { add_id_info_uses_idfa: false, export_compliance_encryption_updated: false, From 10b1fa93ea545b4f7c7f893b38c19b84db32b458 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 16:34:14 +0900 Subject: [PATCH 51/54] =?UTF-8?q?[#51]=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95(=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/appstore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/appstore.yml b/.github/workflows/appstore.yml index 37d924f..67f619e 100644 --- a/.github/workflows/appstore.yml +++ b/.github/workflows/appstore.yml @@ -1,5 +1,5 @@ # Appstore ์ž๋™ ๋ฐฐํฌ ์›Œํฌํ”Œ๋กœ์šฐ - +name: ์•ฑ์Šคํ† ์–ด ๋ฐฐํฌ ๐Ÿƒ on: pull_request: From 67776efab164cdc17d6e72b5aebdd44ab035300c Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 16:46:04 +0900 Subject: [PATCH 52/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EB=84=98=EB=B2=84=20=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f69a276..049fae8 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -99,11 +99,30 @@ platform :ios do is_key_content_base64: true ) + today = Time.now.strftime("%Y%m%d") + current_build_number = "#{latest_testflight_build_number}" + + if current_build_number.length == 9 + current_date = current_build_number[0,8] # ์•ž 8์ž๋ฆฌ๊ฐ€ ๋‚ ์งœ + current_count = current_build_number[8].to_i # ๋งˆ์ง€๋ง‰ 1์ž๋ฆฌ๊ฐ€ ์ˆซ์ž + else + # ๋นŒ๋“œ ๋„˜๋ฒ„๊ฐ€ ์—†๊ฑฐ๋‚˜ ํ˜•์‹์ด ๋‹ค๋ฅด๋ฉด ์ดˆ๊ธฐํ™” + current_date = "" + current_count = 0 + end + + if current_date == today + next_count = current_count + 1 + else + next_count = 1 + end + + new_build_number = "#{today}#{next_count}" increment_build_number( xcodeproj: "Run Mile.xcodeproj", - build_number: current_build_number + build_number: new_build_number ) current_marketing_version = get_version_number @@ -120,7 +139,7 @@ platform :ios do discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "[Appstore] Run Mile ๋ฐฐํฌ ์ค€๋น„", - description: "[Appstore] Build Number: #{current_build_number} ๋ฐฐํฌ๋ฅผ ์œ„ํ•ด ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค." + description: "[Appstore] Build Number: #{new_build_number} ๋ฐฐํฌ๋ฅผ ์œ„ํ•ด ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค." ) setup_ci @@ -130,7 +149,7 @@ platform :ios do discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "[Appstore] Run Mile ์ธ์ฆ์„œ ์™„๋ฃŒ", - description: "[Appstore] Build Number: #{current_build_number} ์ธ์ฆ์„œ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + description: "[Appstore] Build Number: #{new_build_number} ์ธ์ฆ์„œ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." ) build_app( @@ -141,7 +160,7 @@ platform :ios do discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "[Appstore] Run Mile ์•„์นด์ด๋ธŒ ์™„๋ฃŒ", - description: "[Appstore] Build Number: #{current_build_number} ์•„์นด์ด๋ธŒ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + description: "[Appstore] Build Number: #{new_build_number} ์•„์นด์ด๋ธŒ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." ) upload_to_app_store( @@ -166,7 +185,7 @@ platform :ios do discord_notifier( webhook_url: "#{ENV["DISCORD_URL"]}", title: "[Appstore] Run Mile Appstore ๋ฐฐํฌ ์™„๋ฃŒ", - description: "[Appstore] Build Number: #{current_build_number} ์•ฑ์Šคํ† ์–ด ๋ฐฐํฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + description: "[Appstore] Build Number: #{new_build_number} ์•ฑ์Šคํ† ์–ด ๋ฐฐํฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." ) end From 0d0841a2ba11062b0348bd6df7ed18f2b767954e Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 21:09:48 +0900 Subject: [PATCH 53/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(?= =?UTF-8?q?=EC=96=B8=EC=96=B4=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 049fae8..5a4b8b1 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -164,6 +164,7 @@ platform :ios do ) upload_to_app_store( + languages: ["ko"], force: true, submit_for_review: true, skip_screenshots: true, @@ -172,7 +173,7 @@ platform :ios do precheck_include_in_app_purchases: false, copyright: "ยฉ #{Time.now.year} Mooninbeom", release_notes: { - "ko_KR" => current_release_note + "ko" => current_release_note }, submission_information: { add_id_info_uses_idfa: false, From b240f3f2fab3dfcaec225979211b24d4aed7a978 Mon Sep 17 00:00:00 2001 From: mooninbeom Date: Sun, 29 Jun 2025 21:18:50 +0900 Subject: [PATCH 54/54] =?UTF-8?q?[#51]=20Fastfile=20=EC=88=98=EC=A0=95(met?= =?UTF-8?q?adata=20=3D>=20false)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 5a4b8b1..ccdaa31 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -168,7 +168,7 @@ platform :ios do force: true, submit_for_review: true, skip_screenshots: true, - skip_metadata: true, + skip_metadata: false, automatic_release: true, precheck_include_in_app_purchases: false, copyright: "ยฉ #{Time.now.year} Mooninbeom",