From 962803ebc079ed659d27948f35e15e1356f0bbc9 Mon Sep 17 00:00:00 2001 From: SeungHyun Hong Date: Fri, 22 Mar 2024 15:48:00 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=9A=80=20Add=20CreditsURLProtocol,=20?= =?UTF-8?q?CreditsEndPoint,=20CreditsService,=20and=20CreditsResponse.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIService/Package.swift | 22 +++++++++ .../Sources/CreditsAPI/CreditsEndPoint.swift | 35 ++++++++++++++ .../Sources/CreditsAPI/CreditsService.swift | 34 ++++++++++++++ .../Responses/CreditsResponse.swift | 17 +++++++ .../CreditsURLProtocol.swift | 47 +++++++++++++++++++ .../Mocks/CreditsResponse.json | 5 ++ 6 files changed, 160 insertions(+) create mode 100644 APIService/Sources/CreditsAPI/CreditsEndPoint.swift create mode 100644 APIService/Sources/CreditsAPI/CreditsService.swift create mode 100644 APIService/Sources/CreditsAPI/Responses/CreditsResponse.swift create mode 100644 APIService/Sources/CreditsAPISupport/CreditsURLProtocol.swift create mode 100644 APIService/Sources/CreditsAPISupport/Mocks/CreditsResponse.json diff --git a/APIService/Package.swift b/APIService/Package.swift index eb03b55..8aa794d 100644 --- a/APIService/Package.swift +++ b/APIService/Package.swift @@ -6,6 +6,14 @@ let package = Package( name: "APIService", platforms: [.iOS(.v17)], products: [ + .library( + name: "CreditsAPI", + targets: ["CreditsAPI"] + ), + .library( + name: "CreditsAPISupport", + targets: ["CreditsAPISupport"] + ), .library( name: "HomeAPI", targets: ["HomeAPI"] @@ -44,6 +52,20 @@ let package = Package( .package(path: "../Entity"), ], targets: [ + .target( + name: "CreditsAPI", + dependencies: [ + .product(name: "Network", package: "Core"), + .product(name: "Entity", package: "Entity"), + ] + ), + .target( + name: "CreditsAPISupport", + dependencies: [ + "CreditsAPI", + ], + resources: [.process("Mocks")] + ), .target( name: "HomeAPI", dependencies: [ diff --git a/APIService/Sources/CreditsAPI/CreditsEndPoint.swift b/APIService/Sources/CreditsAPI/CreditsEndPoint.swift new file mode 100644 index 0000000..6facf6b --- /dev/null +++ b/APIService/Sources/CreditsAPI/CreditsEndPoint.swift @@ -0,0 +1,35 @@ +// +// CreditsEndPoint.swift +// +// +// Created by 홍승현 on 1/25/24. +// + +import Foundation +import Network + +// MARK: - CreditsEndPoint + +public enum CreditsEndPoint { + case fetchCredits +} + +// MARK: EndPoint + +extension CreditsEndPoint: EndPoint { + public var method: HTTPMethod { + .get + } + + public var path: String { + "/v2/credits" + } + + public var parameters: HTTPParameter { + .plain + } + + public var headers: [String: String] { + ["Content-Type": "application/json"] + } +} diff --git a/APIService/Sources/CreditsAPI/CreditsService.swift b/APIService/Sources/CreditsAPI/CreditsService.swift new file mode 100644 index 0000000..6237a94 --- /dev/null +++ b/APIService/Sources/CreditsAPI/CreditsService.swift @@ -0,0 +1,34 @@ +// +// CreditsService.swift +// +// +// Created by 홍승현 on 1/31/24. +// + +import Entity +import Foundation +import Network + +// MARK: - HomeServiceRepresentable + +public protocol HomeServiceRepresentable { + func fetchCredits() async throws +} + +// MARK: - HomeService + +public struct HomeService { + private let network: Networking + + public init(network: Networking) { + self.network = network + } +} + +// MARK: HomeServiceRepresentable + +extension HomeService: HomeServiceRepresentable { + public func fetchCredits() async throws { + let response: CreditsResponse = try await network.request(with: CreditsEndPoint.fetchCredits) + } +} diff --git a/APIService/Sources/CreditsAPI/Responses/CreditsResponse.swift b/APIService/Sources/CreditsAPI/Responses/CreditsResponse.swift new file mode 100644 index 0000000..941e67b --- /dev/null +++ b/APIService/Sources/CreditsAPI/Responses/CreditsResponse.swift @@ -0,0 +1,17 @@ +// +// CreditsResponse.swift +// +// +// Created by 홍승현 on 1/31/24. +// + +import Entity +import Foundation + +// MARK: - ProductResponse + +struct CreditsResponse: Decodable { + let title: String + let body: String + let date: Date +} diff --git a/APIService/Sources/CreditsAPISupport/CreditsURLProtocol.swift b/APIService/Sources/CreditsAPISupport/CreditsURLProtocol.swift new file mode 100644 index 0000000..70a1e3e --- /dev/null +++ b/APIService/Sources/CreditsAPISupport/CreditsURLProtocol.swift @@ -0,0 +1,47 @@ +// +// CreditsURLProtocol.swift +// +// +// Created by 홍승현 on 1/25/24. +// + +import CreditsAPI +import Foundation +import Network + +public final class CreditsURLProtocol: URLProtocol { + private lazy var mockData: [String: Data?] = [ + CreditsEndPoint.fetchCredits.path: loadMockData(fileName: "CreditsResponse"), + ] + + override public class func canInit(with _: URLRequest) -> Bool { + true + } + + override public class func canonicalRequest(for request: URLRequest) -> URLRequest { + request + } + + override public func startLoading() { + defer { client?.urlProtocolDidFinishLoading(self) } + if let url = request.url, + let mockData = mockData[url.path(percentEncoded: true)], + let data = mockData, + let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil) { + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + client?.urlProtocol(self, didLoad: data) + } else { + client?.urlProtocol(self, didFailWithError: NetworkError.urlError) + } + } + + override public func stopLoading() {} + + private func loadMockData(fileName: String) -> Data? { + guard let url = Bundle.module.url(forResource: fileName, withExtension: "json") + else { + return nil + } + return try? Data(contentsOf: url) + } +} diff --git a/APIService/Sources/CreditsAPISupport/Mocks/CreditsResponse.json b/APIService/Sources/CreditsAPISupport/Mocks/CreditsResponse.json new file mode 100644 index 0000000..99ac366 --- /dev/null +++ b/APIService/Sources/CreditsAPISupport/Mocks/CreditsResponse.json @@ -0,0 +1,5 @@ +{ + "title": "만든 사람들", + "body": "편행을 함께 만든 사람들을 소개합니다.\r\n\r\n기획/디자인\r\n- 박상훈, 홍승현\r\n\r\n개발\r\n- 홍승현과 아이들\r\n\r\n다국어 지원\r\n- 영어\r\n- 일본어", + "date": "2024-03-20T01:07:55Z" +} From 7a5de1ab2c18ca61a4269f9abcf813c2fe86f3d5 Mon Sep 17 00:00:00 2001 From: SeungHyun Hong Date: Fri, 22 Mar 2024 15:57:22 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20Add=20Credits=20API=20and=20Sup?= =?UTF-8?q?port=20with=20networking=20setup,=20and=20Credits=20model=20for?= =?UTF-8?q?=20displaying=20creators.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/CreditsAPI/CreditsService.swift | 27 +++++++++++++------ Entity/Sources/Entity/Credits/Credits.swift | 26 ++++++++++++++++++ PyeonHaeng-iOS.xcodeproj/project.pbxproj | 14 ++++++++++ PyeonHaeng-iOS/Sources/Services.swift | 21 +++++++++++++++ 4 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 Entity/Sources/Entity/Credits/Credits.swift diff --git a/APIService/Sources/CreditsAPI/CreditsService.swift b/APIService/Sources/CreditsAPI/CreditsService.swift index 6237a94..d6dca44 100644 --- a/APIService/Sources/CreditsAPI/CreditsService.swift +++ b/APIService/Sources/CreditsAPI/CreditsService.swift @@ -9,15 +9,15 @@ import Entity import Foundation import Network -// MARK: - HomeServiceRepresentable +// MARK: - CreditsServiceRepresentable -public protocol HomeServiceRepresentable { - func fetchCredits() async throws +public protocol CreditsServiceRepresentable { + func fetchCredits() async throws -> Credits } -// MARK: - HomeService +// MARK: - CreditsService -public struct HomeService { +public struct CreditsService { private let network: Networking public init(network: Networking) { @@ -25,10 +25,21 @@ public struct HomeService { } } -// MARK: HomeServiceRepresentable +// MARK: CreditsServiceRepresentable -extension HomeService: HomeServiceRepresentable { - public func fetchCredits() async throws { +extension CreditsService: CreditsServiceRepresentable { + public func fetchCredits() async throws -> Credits { let response: CreditsResponse = try await network.request(with: CreditsEndPoint.fetchCredits) + return Credits(dto: response) + } +} + +private extension Credits { + init(dto: CreditsResponse) { + self.init( + title: dto.title, + body: dto.body, + date: dto.date + ) } } diff --git a/Entity/Sources/Entity/Credits/Credits.swift b/Entity/Sources/Entity/Credits/Credits.swift new file mode 100644 index 0000000..5ecc4c8 --- /dev/null +++ b/Entity/Sources/Entity/Credits/Credits.swift @@ -0,0 +1,26 @@ +// +// Credits.swift +// +// +// Created by 홍승현 on 3/22/24. +// + +import Foundation + +/// `만든사람들`을 보여주는 크레딧 장면에서 사용될 엔티티 모델 +public struct Credits { + /// 크레딧 제목 + public let title: String + + /// 크레딧 내용 + public let body: String + + /// 크레딧 생성 날짜 + public let date: Date + + public init(title: String, body: String, date: Date) { + self.title = title + self.body = body + self.date = date + } +} diff --git a/PyeonHaeng-iOS.xcodeproj/project.pbxproj b/PyeonHaeng-iOS.xcodeproj/project.pbxproj index a68cb7c..7572395 100644 --- a/PyeonHaeng-iOS.xcodeproj/project.pbxproj +++ b/PyeonHaeng-iOS.xcodeproj/project.pbxproj @@ -52,6 +52,8 @@ BAE159D82B65FA6F002DCF94 /* HomeProductDetailSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE159D72B65FA6F002DCF94 /* HomeProductDetailSelectionView.swift */; }; BAE159DA2B65FC35002DCF94 /* HomeProductListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE159D92B65FC35002DCF94 /* HomeProductListView.swift */; }; BAE159DE2B663A9A002DCF94 /* HomeProductSorterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE159DD2B663A9A002DCF94 /* HomeProductSorterView.swift */; }; + BAE5ADB02BAD61D000176ADE /* CreditsAPI in Frameworks */ = {isa = PBXBuildFile; productRef = BAE5ADAF2BAD61D000176ADE /* CreditsAPI */; }; + BAE5ADB22BAD61D000176ADE /* CreditsAPISupport in Frameworks */ = {isa = PBXBuildFile; productRef = BAE5ADB12BAD61D000176ADE /* CreditsAPISupport */; }; BAE7A1E62B9D7CA70022FEBB /* MailRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE7A1E52B9D7CA70022FEBB /* MailRowItem.swift */; }; BAE7A1E82B9DA0360022FEBB /* DeviceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE7A1E72B9DA0360022FEBB /* DeviceProvider.swift */; }; BAE7A1EA2B9DA4960022FEBB /* MockDeviceInformationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE7A1E92B9DA4960022FEBB /* MockDeviceInformationProvider.swift */; }; @@ -156,6 +158,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BAE5ADB22BAD61D000176ADE /* CreditsAPISupport in Frameworks */, BA05640D2B6248EA003D6DC7 /* Network in Frameworks */, BA4FB9C72BA1BC5B00ED03C4 /* NoticeAPISupport in Frameworks */, E57F2AA62B7717EA00E12B3D /* ProductInfoAPISupport in Frameworks */, @@ -165,6 +168,7 @@ BA05640B2B6248EA003D6DC7 /* HomeAPISupport in Frameworks */, BA0623D82B68E51400A0A3B2 /* Log in Frameworks */, BA0564092B6248EA003D6DC7 /* HomeAPI in Frameworks */, + BAE5ADB02BAD61D000176ADE /* CreditsAPI in Frameworks */, BAB569612B639F3000D1E0F9 /* DesignSystem in Frameworks */, E55DD5182B9370D100AA63C0 /* SearchAPI in Frameworks */, E55DD51A2B9370D100AA63C0 /* SearchAPISupport in Frameworks */, @@ -495,6 +499,8 @@ E55DD5192B9370D100AA63C0 /* SearchAPISupport */, BA4FB9C42BA1BC5B00ED03C4 /* NoticeAPI */, BA4FB9C62BA1BC5B00ED03C4 /* NoticeAPISupport */, + BAE5ADAF2BAD61D000176ADE /* CreditsAPI */, + BAE5ADB12BAD61D000176ADE /* CreditsAPISupport */, ); productName = "PyeonHaeng-iOS"; productReference = BAA4D9A92B5A1795005999F8 /* PyeonHaeng-iOS.app */; @@ -1090,6 +1096,14 @@ isa = XCSwiftPackageProductDependency; productName = DesignSystem; }; + BAE5ADAF2BAD61D000176ADE /* CreditsAPI */ = { + isa = XCSwiftPackageProductDependency; + productName = CreditsAPI; + }; + BAE5ADB12BAD61D000176ADE /* CreditsAPISupport */ = { + isa = XCSwiftPackageProductDependency; + productName = CreditsAPISupport; + }; E55DD5172B9370D100AA63C0 /* SearchAPI */ = { isa = XCSwiftPackageProductDependency; productName = SearchAPI; diff --git a/PyeonHaeng-iOS/Sources/Services.swift b/PyeonHaeng-iOS/Sources/Services.swift index 0a1be46..d6bb070 100644 --- a/PyeonHaeng-iOS/Sources/Services.swift +++ b/PyeonHaeng-iOS/Sources/Services.swift @@ -5,6 +5,8 @@ // Created by 홍승현 on 2/1/24. // +import CreditsAPI +import CreditsAPISupport import Foundation import HomeAPI import HomeAPISupport @@ -23,6 +25,8 @@ struct Services { let noticeService: NoticeServiceRepresentable let productInfoService: ProductInfoServiceRepresentable let searchService: SearchServiceRepresentable + let creditsService: CreditsServiceRepresentable + init() { let homeNetworking: Networking = { let configuration: URLSessionConfiguration @@ -35,6 +39,7 @@ struct Services { let provider = NetworkProvider(session: URLSession(configuration: configuration)) return provider }() + let noticeNetworking: Networking = { let configuration: URLSessionConfiguration #if DEBUG @@ -46,6 +51,7 @@ struct Services { let provider = NetworkProvider(session: URLSession(configuration: configuration)) return provider }() + let productInfoNetworking: Networking = { let configuration: URLSessionConfiguration #if DEBUG @@ -57,6 +63,7 @@ struct Services { let provider = NetworkProvider(session: URLSession(configuration: configuration)) return provider }() + let searchNetworking: Networking = { let configuration: URLSessionConfiguration #if DEBUG @@ -68,9 +75,23 @@ struct Services { let provider = NetworkProvider(session: URLSession(configuration: configuration)) return provider }() + + let creditsNetworking: Networking = { + let configuration: URLSessionConfiguration + #if DEBUG + configuration = .ephemeral + configuration.protocolClasses = [CreditsURLProtocol.self] + #else + configuration = .default + #endif + let provider = NetworkProvider(session: URLSession(configuration: configuration)) + return provider + }() + homeService = HomeService(network: homeNetworking) noticeService = NoticeService(network: noticeNetworking) productInfoService = ProductInfoService(network: productInfoNetworking) searchService = SearchService(network: searchNetworking) + creditsService = CreditsService(network: creditsNetworking) } } From a651968a983048f1806845157d86dde27e89198b Mon Sep 17 00:00:00 2001 From: SeungHyun Hong Date: Fri, 22 Mar 2024 16:29:16 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=A8=20Add=20CreditsView=20to=20Settin?= =?UTF-8?q?gsRow=20NavigationLink?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added CreditsView to the SettingsRow NavigationLink to enable navigation to the CreditsView from the Settings screen. --- PyeonHaeng-iOS.xcodeproj/project.pbxproj | 16 ++++-- .../SettingsScene/Credits/CreditsView.swift | 29 +++++++++++ .../Notice/View/NoticeView.swift | 30 +++++++++++- .../Scenes/SettingsScene/SettingsView.swift | 7 ++- .../Components/Images/MarkdownView.swift | 49 +++++-------------- 5 files changed, 85 insertions(+), 46 deletions(-) create mode 100644 PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Credits/CreditsView.swift rename PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Notice/View/NoticeDetailView.swift => Shared/Sources/DesignSystem/Components/Images/MarkdownView.swift (60%) diff --git a/PyeonHaeng-iOS.xcodeproj/project.pbxproj b/PyeonHaeng-iOS.xcodeproj/project.pbxproj index 7572395..609a226 100644 --- a/PyeonHaeng-iOS.xcodeproj/project.pbxproj +++ b/PyeonHaeng-iOS.xcodeproj/project.pbxproj @@ -36,7 +36,6 @@ BA4EA3602B6A37E10003DCE7 /* Entity in Frameworks */ = {isa = PBXBuildFile; productRef = BA4EA35F2B6A37E10003DCE7 /* Entity */; }; BA4FB9C52BA1BC5B00ED03C4 /* NoticeAPI in Frameworks */ = {isa = PBXBuildFile; productRef = BA4FB9C42BA1BC5B00ED03C4 /* NoticeAPI */; }; BA4FB9C72BA1BC5B00ED03C4 /* NoticeAPISupport in Frameworks */ = {isa = PBXBuildFile; productRef = BA4FB9C62BA1BC5B00ED03C4 /* NoticeAPISupport */; }; - BA5E51932B9AC8510036209A /* NoticeDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA5E51922B9AC8510036209A /* NoticeDetailView.swift */; }; BA849A342B8F4F36004495BF /* ProductConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8E83232B8EF83B00FE968C /* ProductConfiguration.swift */; }; BA849A372B8F4FC0004495BF /* MockPaginatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA849A362B8F4FC0004495BF /* MockPaginatable.swift */; }; BA8E83242B8EF83B00FE968C /* ProductConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8E83232B8EF83B00FE968C /* ProductConfiguration.swift */; }; @@ -54,6 +53,7 @@ BAE159DE2B663A9A002DCF94 /* HomeProductSorterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE159DD2B663A9A002DCF94 /* HomeProductSorterView.swift */; }; BAE5ADB02BAD61D000176ADE /* CreditsAPI in Frameworks */ = {isa = PBXBuildFile; productRef = BAE5ADAF2BAD61D000176ADE /* CreditsAPI */; }; BAE5ADB22BAD61D000176ADE /* CreditsAPISupport in Frameworks */ = {isa = PBXBuildFile; productRef = BAE5ADB12BAD61D000176ADE /* CreditsAPISupport */; }; + BAE5ADB92BAD660500176ADE /* CreditsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE5ADB82BAD660500176ADE /* CreditsView.swift */; }; BAE7A1E62B9D7CA70022FEBB /* MailRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE7A1E52B9D7CA70022FEBB /* MailRowItem.swift */; }; BAE7A1E82B9DA0360022FEBB /* DeviceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE7A1E72B9DA0360022FEBB /* DeviceProvider.swift */; }; BAE7A1EA2B9DA4960022FEBB /* MockDeviceInformationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE7A1E92B9DA4960022FEBB /* MockDeviceInformationProvider.swift */; }; @@ -115,7 +115,6 @@ BA28F1A72B6157E90052855E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BA402F7D2B85E31800E86AAD /* ConvenienceSelectBottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConvenienceSelectBottomSheetView.swift; sourceTree = ""; }; BA4EA35A2B6A00F70003DCE7 /* Entity */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Entity; sourceTree = ""; }; - BA5E51922B9AC8510036209A /* NoticeDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeDetailView.swift; sourceTree = ""; }; BA849A362B8F4FC0004495BF /* MockPaginatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPaginatable.swift; sourceTree = ""; }; BA8E83232B8EF83B00FE968C /* ProductConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductConfiguration.swift; sourceTree = ""; }; BAA4D9A92B5A1795005999F8 /* PyeonHaeng-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PyeonHaeng-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -131,6 +130,7 @@ BAE159D72B65FA6F002DCF94 /* HomeProductDetailSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeProductDetailSelectionView.swift; sourceTree = ""; }; BAE159D92B65FC35002DCF94 /* HomeProductListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeProductListView.swift; sourceTree = ""; }; BAE159DD2B663A9A002DCF94 /* HomeProductSorterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeProductSorterView.swift; sourceTree = ""; }; + BAE5ADB82BAD660500176ADE /* CreditsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditsView.swift; sourceTree = ""; }; BAE7A1E52B9D7CA70022FEBB /* MailRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailRowItem.swift; sourceTree = ""; }; BAE7A1E72B9DA0360022FEBB /* DeviceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProvider.swift; sourceTree = ""; }; BAE7A1E92B9DA4960022FEBB /* MockDeviceInformationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDeviceInformationProvider.swift; sourceTree = ""; }; @@ -219,7 +219,6 @@ isa = PBXGroup; children = ( BA1688DF2B99B85500A8F462 /* NoticeView.swift */, - BA5E51922B9AC8510036209A /* NoticeDetailView.swift */, ); path = View; sourceTree = ""; @@ -313,6 +312,7 @@ BA28F18C2B6155EC0052855E /* SettingsScene */ = { isa = PBXGroup; children = ( + BAE5ADB32BAD638200176ADE /* Credits */, BAB8C3AB2BAC7A09003DF3CC /* LeaveReview */, BA097F0C2B9CA820002D3E1E /* Mail */, BA1688EB2B99D0B700A8F462 /* Notice */, @@ -442,6 +442,14 @@ path = LeaveReview; sourceTree = ""; }; + BAE5ADB32BAD638200176ADE /* Credits */ = { + isa = PBXGroup; + children = ( + BAE5ADB82BAD660500176ADE /* CreditsView.swift */, + ); + path = Credits; + sourceTree = ""; + }; E5462C6B2B65CF7800E9FDF2 /* Components */ = { isa = PBXGroup; children = ( @@ -641,7 +649,6 @@ BAE7A1E62B9D7CA70022FEBB /* MailRowItem.swift in Sources */, E5028D5C2B96BA9400B36C16 /* SearchView.swift in Sources */, BAE159DA2B65FC35002DCF94 /* HomeProductListView.swift in Sources */, - BA5E51932B9AC8510036209A /* NoticeDetailView.swift in Sources */, BA402F7E2B85E31800E86AAD /* ConvenienceSelectBottomSheetView.swift in Sources */, BA19897C2BA95E8C0083B4BE /* DIContainer.swift in Sources */, BA28F1852B6155810052855E /* OnboardingView.swift in Sources */, @@ -649,6 +656,7 @@ BA28F1882B6155910052855E /* HomeView.swift in Sources */, E5F2EC452B64926100EE0838 /* PromotionTagView.swift in Sources */, BA28F18B2B6155BD0052855E /* ProductInfoView.swift in Sources */, + BAE5ADB92BAD660500176ADE /* CreditsView.swift in Sources */, E5462C662B65677B00E9FDF2 /* PromotionTag.swift in Sources */, E50584532B763C8C002FDACF /* ProductInfoViewModel.swift in Sources */, BAB720342B9325F200C2CA1A /* PromotionSelectBottomSheetView.swift in Sources */, diff --git a/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Credits/CreditsView.swift b/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Credits/CreditsView.swift new file mode 100644 index 0000000..bb127fd --- /dev/null +++ b/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Credits/CreditsView.swift @@ -0,0 +1,29 @@ +// +// CreditsView.swift +// PyeonHaeng-iOS +// +// Created by 홍승현 on 3/22/24. +// + +import DesignSystem +import SwiftUI + +struct CreditsView: View { + @Environment(\.injected) private var container + @State private var content: String = "" + + var body: some View { + MarkdownView(content: content) + .navigationTitle("Credits") + .onAppear { + Task { + let credits = try await container.services.creditsService.fetchCredits() + content = credits.body + } + } + } +} + +#Preview { + SettingsView() +} diff --git a/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Notice/View/NoticeView.swift b/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Notice/View/NoticeView.swift index 1230d44..bf6eb7d 100644 --- a/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Notice/View/NoticeView.swift +++ b/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Notice/View/NoticeView.swift @@ -25,8 +25,34 @@ struct NoticeView: View where ViewModel: NoticeViewModelRepresentable List(viewModel.state.noticeList) { notice in ZStack(alignment: .leading) { NavigationLink { - NoticeDetailView() - .toolbarRole(.editor) + // TODO: API 연결 시 삭제할 예정입니다. + MarkdownView( + content: """ + 안녕하세요 편행팀입니다~ + 새로워진 2024년을 맞아 편행이 새단장으로 여러분을 찾아왔습니다. 앞으로도 우리 자주 만나요! + + [업데이트 정보] + - 새로운 버전(v.2.0.0)으로 리뉴얼 되었습니다. + - 새로워진 편행 2.0에서 더욱 더 합리적인 소비 생활 되세요! + - 앱 디자인 및 전반적인 UX를 개선해서 사용하기 편하게 했어요! + - 합리적인 소비를 도와드리고자 행사 상품 가격의 변동 정보를 그래프로 보여드려요! + + - 기타 자잘한 버그들을 수정했습니다. + - 단말 안정화 적용 + - 화면 및 네트워크 최적화 + + - 2023년 3월 편의점 행사정보가 업데이트 되었습니다. + + [새로운 기능] + - 지도 기능이 추가되었습니다. + - 카테고리가 추가되었습니다. + + [주의사항] + - 본 어플리케이션에서 표시된 정보는 실제 행사정보와 차이가 있을수 있습니다. + - 행사는 편의점 브랜드 및 재고상태에 따라 다르게 적용될 수 있습니다. + """ + ) + .toolbarRole(.editor) } label: { EmptyView() } diff --git a/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/SettingsView.swift b/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/SettingsView.swift index 93102c2..7f2d17a 100644 --- a/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/SettingsView.swift +++ b/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/SettingsView.swift @@ -79,8 +79,11 @@ private struct SettingsRow: View { case .leaveReview: LeaveReviewView() - default: - NavigationLink {} label: { + case .credits: + NavigationLink { + CreditsView() + .toolbarRole(.editor) + } label: { subject } } diff --git a/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Notice/View/NoticeDetailView.swift b/Shared/Sources/DesignSystem/Components/Images/MarkdownView.swift similarity index 60% rename from PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Notice/View/NoticeDetailView.swift rename to Shared/Sources/DesignSystem/Components/Images/MarkdownView.swift index 8590c1e..830c32a 100644 --- a/PyeonHaeng-iOS/Sources/Scenes/SettingsScene/Notice/View/NoticeDetailView.swift +++ b/Shared/Sources/DesignSystem/Components/Images/MarkdownView.swift @@ -1,11 +1,10 @@ // -// NoticeDetailView.swift +// MarkdownView.swift // PyeonHaeng-iOS // // Created by 홍승현 on 3/8/24. // -import DesignSystem import SwiftUI // MARK: - TextBlock @@ -15,40 +14,20 @@ private struct TextBlock: Identifiable { let content: String } -// MARK: - NoticeDetailView +// MARK: - MarkdownView -struct NoticeDetailView: View { - // TODO: API 연결 시 지울 예정입니다. - private let noticeContent: String = """ - 안녕하세요 편행팀입니다~ - 새로워진 2024년을 맞아 편행이 새단장으로 여러분을 찾아왔습니다. 앞으로도 우리 자주 만나요! - - [업데이트 정보] - - 새로운 버전(v.2.0.0)으로 리뉴얼 되었습니다. - - 새로워진 편행 2.0에서 더욱 더 합리적인 소비 생활 되세요! - - 앱 디자인 및 전반적인 UX를 개선해서 사용하기 편하게 했어요! - - 합리적인 소비를 도와드리고자 행사 상품 가격의 변동 정보를 그래프로 보여드려요! - - - 기타 자잘한 버그들을 수정했습니다. - - 단말 안정화 적용 - - 화면 및 네트워크 최적화 - - - 2023년 3월 편의점 행사정보가 업데이트 되었습니다. - - [새로운 기능] - - 지도 기능이 추가되었습니다. - - 카테고리가 추가되었습니다. +public struct MarkdownView: View { + private let content: String - [주의사항] - - 본 어플리케이션에서 표시된 정보는 실제 행사정보와 차이가 있을수 있습니다. - - 행사는 편의점 브랜드 및 재고상태에 따라 다르게 적용될 수 있습니다. - """ + public init(content: String) { + self.content = content + } - var body: some View { + public var body: some View { ScrollView { VStack(alignment: .leading, spacing: .zero) { - ForEach(noticeContent.components(separatedBy: .newlines).map(TextBlock.init)) { block in - if block.content.starts(with: /\s*[-*]/) { + ForEach(content.components(separatedBy: .newlines).map(TextBlock.init)) { block in + if block.content.starts(with: #/\s*[-*]/#) { BulletTextView(content: block.content) .font(.body3) } else { @@ -76,7 +55,7 @@ private struct BulletTextView: View { /// /// - Parameter content: The bullet-prefixed content, optionally with leading spaces. init(content: String) { - guard let match = content.firstMatch(of: /(?\s*)[-*]\s*?(?[^\s].*)/) + guard let match = content.firstMatch(of: #/(?\s*)[-*]\s*?(?[^\s].*)/#) else { self.content = content spacingCount = 0 @@ -129,9 +108,3 @@ private enum Metrics { private enum Constants { static let bulletImageName: String = "circle.fill" } - -#if DEBUG - #Preview { - NoticeDetailView() - } -#endif