diff --git a/Targets/D3N/Sources/Domain/News/Entity/NewsEntity.swift b/Targets/D3N/Sources/Domain/News/Entity/NewsEntity.swift index ab2a221..8df9bf5 100644 --- a/Targets/D3N/Sources/Domain/News/Entity/NewsEntity.swift +++ b/Targets/D3N/Sources/Domain/News/Entity/NewsEntity.swift @@ -44,6 +44,45 @@ public struct NewsEntity: Equatable { self.mediaCompanyName = mediaCompanyName //FIXME: 풀었던 뉴스 아이디 저장 로직 내부 구현 -// self.isAlreadySolved = LocalStorageRepository.loadAlreadySolvedNewsIds().contains(id) + // self.isAlreadySolved = LocalStorageRepository.loadAlreadySolvedNewsIds().contains(id) } } + +public extension NewsEntity { + static let mock = NewsEntity( + id: 11605391, + field: .society, + type: .koreanNews, + title: "강원도 임금근로자 2명 중 1명 ‘비정규직’…역대 최고치", + summary: "[앵커] 강원도 내 임금근로자 2명 가운데 한 명은 비정규직이라는 분석이 나왔습니다. 강원도 내 비정규직 비율이 계속 늘어 처음으로 50%를 넘긴 건데요. 경기침체가 길어지는 만큼, 좋은 일자리 찾기가 더 어려워질거란...", + url: "https://n.news.naver.com/mnews/article/056/0011605391?sid=101", + mediaCompanyId: "056", + mediaCompanyLogo: "https://mimgnews.pstatic.net/image/upload/office_logo/056/2021/07/15/logo_056_57_20210715101841.png", + mediaCompanyName: "KBS" + ) + + static let mocks = [ + NewsEntity( + id: 5622923, + field: .society, + type: .koreanNews, + title: "[사설]미적대는 의대 입학정원 수요조사 발표, 이유 뭔가", + summary: "정부가 의과대학 입학정원 수요 조사 결과 발표를 두 차례나 연기하며 주저하는 모습을 보이고 있다. 보건복지부는 의대 정원 수요 조사 결과를 지난 13일 발표하겠다더니 돌연 연기한 데 이어 늦어도 17일까지는...", + url: "https://n.news.naver.com/mnews/article/018/0005622923?sid=110", + mediaCompanyId: "018", + mediaCompanyLogo: "https://mimgnews.pstatic.net/image/upload/office_logo/018/2018/08/08/logo_018_57_20180808174308.png", + mediaCompanyName: "이데일리" + ), + NewsEntity( + id: 11605391, + field: .society, + type: .koreanNews, + title: "강원도 임금근로자 2명 중 1명 ‘비정규직’…역대 최고치", + summary: "[앵커] 강원도 내 임금근로자 2명 가운데 한 명은 비정규직이라는 분석이 나왔습니다. 강원도 내 비정규직 비율이 계속 늘어 처음으로 50%를 넘긴 건데요. 경기침체가 길어지는 만큼, 좋은 일자리 찾기가 더 어려워질거란...", + url: "https://n.news.naver.com/mnews/article/056/0011605391?sid=101", + mediaCompanyId: "056", + mediaCompanyLogo: "https://mimgnews.pstatic.net/image/upload/office_logo/056/2021/07/15/logo_056_57_20210715101841.png", + mediaCompanyName: "KBS" + ) + ] +} diff --git a/Targets/D3N/Sources/Domain/News/NewsClient.swift b/Targets/D3N/Sources/Domain/News/NewsClient.swift index 097b56d..f991899 100644 --- a/Targets/D3N/Sources/Domain/News/NewsClient.swift +++ b/Targets/D3N/Sources/Domain/News/NewsClient.swift @@ -11,10 +11,6 @@ import Foundation import ComposableArchitecture import Moya -public enum NewsError: Error, Equatable { - case no -} - struct NewsClient { var fetchNewsList: (Int, Int) async -> Result<[NewsEntity], D3NAPIError> var fetchQuizList: (Int) async -> Result<[QuizEntity], D3NAPIError> diff --git a/Targets/D3N/Sources/Domain/Quiz/DTO/Request/SubmitQuizListRequest.swift b/Targets/D3N/Sources/Domain/Quiz/DTO/Request/SubmitQuizListRequest.swift new file mode 100644 index 0000000..48f9f54 --- /dev/null +++ b/Targets/D3N/Sources/Domain/Quiz/DTO/Request/SubmitQuizListRequest.swift @@ -0,0 +1,20 @@ +// +// SubmitQuizListRequest.swift +// D3N +// +// Created by 송영모 on 11/20/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +public struct SubmitQuizListRequestElement: Codable { + let quizId, selectedAnswer: Int + + public init(quizId: Int, selectedAnswer: Int) { + self.quizId = quizId + self.selectedAnswer = selectedAnswer + } +} + +public typealias SubmitQuizListRequestDTO = [SubmitQuizListRequestElement] diff --git a/Targets/D3N/Sources/Domain/News/DTO/Response/FetchQuizListResponseDTO.swift b/Targets/D3N/Sources/Domain/Quiz/DTO/Response/FetchQuizListResponseDTO.swift similarity index 100% rename from Targets/D3N/Sources/Domain/News/DTO/Response/FetchQuizListResponseDTO.swift rename to Targets/D3N/Sources/Domain/Quiz/DTO/Response/FetchQuizListResponseDTO.swift diff --git a/Targets/D3N/Sources/Domain/Quiz/DTO/Response/SubmitQuizListResponse.swift b/Targets/D3N/Sources/Domain/Quiz/DTO/Response/SubmitQuizListResponse.swift new file mode 100644 index 0000000..eda63aa --- /dev/null +++ b/Targets/D3N/Sources/Domain/Quiz/DTO/Response/SubmitQuizListResponse.swift @@ -0,0 +1,15 @@ +// +// SubmitQuizListResponse.swift +// D3N +// +// Created by 송영모 on 11/20/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +public struct SubmitQuizListResponseElement: Codable { + let quizId: Int +} + +public typealias SubmitQuizListResponseDTO = [SubmitQuizListResponseElement] diff --git a/Targets/D3N/Sources/Domain/News/Entity/QuizEntity.swift b/Targets/D3N/Sources/Domain/Quiz/Entity/QuizEntity.swift similarity index 90% rename from Targets/D3N/Sources/Domain/News/Entity/QuizEntity.swift rename to Targets/D3N/Sources/Domain/Quiz/Entity/QuizEntity.swift index 3637bc0..2942e37 100644 --- a/Targets/D3N/Sources/Domain/News/Entity/QuizEntity.swift +++ b/Targets/D3N/Sources/Domain/Quiz/Entity/QuizEntity.swift @@ -15,5 +15,7 @@ public struct QuizEntity: Equatable { let answer: Int let reason: String + let secondTime: Int = 70 + var userAnswer: Int? = nil } diff --git a/Targets/D3N/Sources/Domain/Quiz/Model/QuizModel.swift b/Targets/D3N/Sources/Domain/Quiz/Model/QuizModel.swift new file mode 100644 index 0000000..3edc573 --- /dev/null +++ b/Targets/D3N/Sources/Domain/Quiz/Model/QuizModel.swift @@ -0,0 +1,9 @@ +// +// QuizModel.swift +// D3N +// +// Created by 송영모 on 11/20/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation diff --git a/Targets/D3N/Sources/Domain/Quiz/QuizClient.swift b/Targets/D3N/Sources/Domain/Quiz/QuizClient.swift new file mode 100644 index 0000000..95b70b1 --- /dev/null +++ b/Targets/D3N/Sources/Domain/Quiz/QuizClient.swift @@ -0,0 +1,55 @@ +// +// QuizClient.swift +// D3N +// +// Created by 송영모 on 11/20/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +import ComposableArchitecture +import Moya + +struct QuizClient { + var fetch: (Int) async -> Result<[QuizEntity], D3NAPIError> + var submit: ([QuizEntity]) async -> Result<[Int], D3NAPIError> +} + +extension QuizClient: TestDependencyKey { + static let previewValue = Self( + fetch: { _ in .failure(.none) }, + submit: { _ in .failure(.none) } + ) + + static let testValue = Self( + fetch: unimplemented("\(Self.self).fetch"), + submit: unimplemented("\(Self.self).submit") + ) +} + +extension DependencyValues { + var quizClient: QuizClient { + get { self[QuizClient.self] } + set { self[QuizClient.self] = newValue } + } +} + +extension QuizClient: DependencyKey { + static let liveValue = QuizClient( + fetch: { newsId in + let target: TargetType = NewsService.fetchQuizList(newsId: newsId) + let response: Result = await D3NAPIkProvider.reqeust(target: target) + + return response.map { $0.map { $0.toEntity() } } + }, + submit: { quizs in + let target: TargetType = QuizService.submit(quizs: quizs) + let response: Result = await D3NAPIkProvider.reqeust(target: target) + + return response.map { + $0.map { $0.quizId } + } + } + ) +} diff --git a/Targets/D3N/Sources/Domain/Quiz/QuizService.swift b/Targets/D3N/Sources/Domain/Quiz/QuizService.swift new file mode 100644 index 0000000..6af339e --- /dev/null +++ b/Targets/D3N/Sources/Domain/Quiz/QuizService.swift @@ -0,0 +1,52 @@ +// +// QuizService.swift +// D3N +// +// Created by 송영모 on 11/20/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation + +import Moya + +public enum QuizService { + case fetch(newsId: Int) + case submit(quizs: [QuizEntity]) +} + +extension QuizService: TargetType { + public var baseURL: URL { URL(string: Environment.baseURL)! } + + public var path: String { + switch self { + case .fetch: return "quiz/list" + case .submit: return "quiz/list/submit" + } + } + + public var method: Moya.Method { + switch self { + case .fetch: return .get + case .submit: return .post + } + } + + public var task: Task { + switch self { + case let .fetch(newsId: id): + return .requestParameters(parameters: ["newsId": id], encoding: URLEncoding.queryString) + case let .submit(quizs): + let dto: SubmitQuizListRequestDTO = quizs.compactMap { + if let userAnswer = $0.userAnswer { + return .init(quizId: $0.id, selectedAnswer: userAnswer) + } else { + return nil + } + } + return .requestJSONEncodable(dto) + } + } + + public var headers: [String: String]? { nil } +} diff --git a/Targets/D3N/Sources/Feature/Onboarding/Birth/OnboardingBirthView.swift b/Targets/D3N/Sources/Feature/Onboarding/Birth/OnboardingBirthView.swift index d15d447..f51444b 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/Birth/OnboardingBirthView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/Birth/OnboardingBirthView.swift @@ -34,7 +34,11 @@ struct OnboardingBirthView: View { Spacer() - D3NSubmitButton(activeTitle: "선택 완료", inactiveTitle: "생일을 선택해주세요", isActive: viewStore.birthDate != nil) { _ in + D3NSubmitButton( + activeTitle: "선택 완료", + inactiveTitle: "생일을 선택해주세요", + isActive: viewStore.birthDate != nil + ) { viewStore.send(.comfirmButtonTapped) } } diff --git a/Targets/D3N/Sources/Feature/Onboarding/NewsField/OnboardingNewsFieldView.swift b/Targets/D3N/Sources/Feature/Onboarding/NewsField/OnboardingNewsFieldView.swift index 882531a..654beda 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/NewsField/OnboardingNewsFieldView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/NewsField/OnboardingNewsFieldView.swift @@ -38,7 +38,7 @@ struct OnboardingNewsFieldView: View { activeTitle: "이제 끝이에요!", inactiveTitle: "선택해주세요", isActive: !viewStore.state.newsFields.isEmpty - ) { _ in + ) { viewStore.send(.submitButtonTapped) } } diff --git a/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift index 5033bcc..a453990 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameStore.swift @@ -51,7 +51,7 @@ public struct OnboardingNicknameStore: Reducer { return .none case .confirmButtonTapped: - if !state.nickname.isEmpty { + if state.isConfirmButtonActive { state.focus = nil return .send(.delegate(.submit(state.nickname))) } diff --git a/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift index 30ea208..e411cbb 100644 --- a/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift +++ b/Targets/D3N/Sources/Feature/Onboarding/Nickname/OnboardingNicknameView.swift @@ -52,10 +52,8 @@ struct OnboardingNicknameView: View { activeTitle: "다음으로", inactiveTitle: "닉네임을 입력해주세요", isActive: viewStore.state.isConfirmButtonActive, - action: { isActive in - if isActive { - viewStore.send(.confirmButtonTapped) - } + action: { + viewStore.send(.confirmButtonTapped) } ) } diff --git a/Targets/D3N/Sources/Feature/Quiz/List/Cell/QuizListItemCellStore.swift b/Targets/D3N/Sources/Feature/Quiz/List/Cell/QuizListItemCellStore.swift index d64342e..7d05a08 100644 --- a/Targets/D3N/Sources/Feature/Quiz/List/Cell/QuizListItemCellStore.swift +++ b/Targets/D3N/Sources/Feature/Quiz/List/Cell/QuizListItemCellStore.swift @@ -12,35 +12,41 @@ import ComposableArchitecture public struct QuizListItemCellStore: Reducer { public struct State: Equatable, Identifiable { - public var id: UUID - public var quizEntity: QuizEntity + public var id: Int - var choiceListItems: IdentifiedArrayOf = [] + var question: String + var choices: [String] + var answer: Int + var reason: String + var userAnswer: Int? - public init( - id: UUID = .init(), - quizEntity: QuizEntity + init( + id: Int = .init(), + question: String, + choices: [String], + answer: Int, + reason: String, + userAnswer: Int? = nil ) { self.id = id - self.quizEntity = quizEntity - self.choiceListItems = .init( - uniqueElements: quizEntity.choiceList.map { choice in - return .init(choice: choice) - } - ) + self.question = question + self.choices = choices + self.answer = answer + self.reason = reason + self.userAnswer = userAnswer } } public enum Action: Equatable { case onAppear - case tapped + case answered(Int) + case submitButtonTappped case delegate(Delegate) - case choiceListItems(id: ChoiceListItemCellStore.State.ID, action: ChoiceListItemCellStore.Action) public enum Delegate: Equatable { - case userAnswered(Int) + case submit(Int) } } @@ -50,25 +56,23 @@ public struct QuizListItemCellStore: Reducer { case .onAppear: return .none - case let .choiceListItems(id: id, action: .delegate(action)): - switch action { - case .tapped: - for id in state.choiceListItems.ids { - state.choiceListItems[id: id]?.isSelected = false - } - state.choiceListItems[id: id]?.isSelected = true - if let index = state.choiceListItems.index(id: id) { - return .send(.delegate(.userAnswered(index))) - } - return .none + case let .answered(userAnswer): + if state.userAnswer == userAnswer { + state.userAnswer = nil + } else { + state.userAnswer = userAnswer + } + return .none + + case .submitButtonTappped: + if let userAnswer = state.userAnswer { + return .send(.delegate(.submit(userAnswer))) } + return .none default: return .none } } - .forEach(\.choiceListItems, action: /Action.choiceListItems(id:action:)) { - ChoiceListItemCellStore() - } } } diff --git a/Targets/D3N/Sources/Feature/Quiz/List/Cell/QuizListItemCellView.swift b/Targets/D3N/Sources/Feature/Quiz/List/Cell/QuizListItemCellView.swift index 2b3130c..b8c12ea 100644 --- a/Targets/D3N/Sources/Feature/Quiz/List/Cell/QuizListItemCellView.swift +++ b/Targets/D3N/Sources/Feature/Quiz/List/Cell/QuizListItemCellView.swift @@ -20,37 +20,30 @@ public struct QuizListItemCellView: View { public var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in - VStack(spacing: 15) { - titleView(viewStore: viewStore) - - choiceListItemView() - } - .minimalBackgroundStyle() - } - } - - private func headerView(viewStore: ViewStoreOf) -> some View { - HStack { - Text("") - } - } - - private func titleView(viewStore: ViewStoreOf) -> some View { - VStack(alignment: .leading) { - HStack { - Text(viewStore.state.quizEntity.question) - .font(.title3) - .fontWeight(.semibold) + VStack { + Text(viewStore.state.question) + .padding(.top, 40) Spacer() - } - } - } - - private func choiceListItemView() -> some View { - VStack(spacing: 10) { - ForEachStore(self.store.scope(state: \.choiceListItems, action: QuizListItemCellStore.Action.choiceListItems(id:action:))) { - ChoiceListItemCellView(store: $0) + + ForEach(Array(viewStore.choices.enumerated()), id: \.offset) { index, choice in + D3NIconAnimationButton( + icon: .resolved(index: index), + title: choice, + isSelected: index == viewStore.state.userAnswer + ) { + viewStore.send(.answered(index), animation: .default) + } + } + + D3NSubmitButton( + activeTitle: "제출하기", + inactiveTitle: "답을 선택해주세요", + isActive: viewStore.state.userAnswer != nil + ) { + viewStore.send(.submitButtonTappped) + } + .padding() } } } diff --git a/Targets/D3N/Sources/Feature/Quiz/List/QuizListStore.swift b/Targets/D3N/Sources/Feature/Quiz/List/QuizListStore.swift index 9ed1486..f6da6f1 100644 --- a/Targets/D3N/Sources/Feature/Quiz/List/QuizListStore.swift +++ b/Targets/D3N/Sources/Feature/Quiz/List/QuizListStore.swift @@ -12,9 +12,9 @@ import ComposableArchitecture public struct QuizListStore: Reducer { public struct State: Equatable { - let quizEntityList: [QuizEntity] + var quizEntityList: [QuizEntity] - var currentTab: UUID = .init() + var currentTab: Int = 0 var currentIndex: Int = 0 var isActive: Bool = false @@ -24,8 +24,15 @@ public struct QuizListStore: Reducer { self.quizEntityList = quizEntityList self.quizListItems = .init( - uniqueElements: quizEntityList.map { quizEntity in - return .init(quizEntity: quizEntity) + uniqueElements: quizEntityList.map { + return .init( + id: $0.id, + question: $0.question, + choices: $0.choiceList, + answer: $0.answer, + reason: $0.reason, + userAnswer: $0.userAnswer + ) } ) } @@ -34,9 +41,12 @@ public struct QuizListStore: Reducer { public enum Action: Equatable { case onAppear - case setTab(UUID) + case setTab(Int) case solvedButtonTapped + case submitQuizListRequest([QuizEntity]) + case submitQuizListResponse(Result<[Int], D3NAPIError>) + case quizListItems(id: QuizListItemCellStore.State.ID, action: QuizListItemCellStore.Action) case delegate(Delegate) @@ -45,6 +55,8 @@ public struct QuizListStore: Reducer { } } + @Dependency(\.quizClient) var quizClient + public var body: some ReducerOf { Reduce { state, action in switch action { @@ -53,21 +65,20 @@ public struct QuizListStore: Reducer { case let .setTab(tab): state.currentTab = tab - state.currentIndex = state.quizListItems.index(id: tab) ?? 0 return .none - case .solvedButtonTapped: - if state.isActive { - let quizEntityList = state.quizListItems.map { return $0.quizEntity } - return .send(.delegate(.solved(quizEntityList))) + case let .submitQuizListRequest(quizs): + return .run { send in + await send(.submitQuizListResponse(quizClient.submit(quizs))) } - return .none case let .quizListItems(id: id, action: .delegate(action)): switch action { - case let .userAnswered(answer): - state.quizListItems[id: id]?.quizEntity.userAnswer = answer - state.isActive = !state.quizListItems.contains(where: { $0.quizEntity.userAnswer == nil }) + case let .submit(userAnswer): + if let index = state.quizEntityList.firstIndex(where: { $0.id == id }) { + state.quizEntityList[index].userAnswer = userAnswer + return .send(.submitQuizListRequest(state.quizEntityList)) + } return .none } diff --git a/Targets/D3N/Sources/Feature/Quiz/List/QuizListView.swift b/Targets/D3N/Sources/Feature/Quiz/List/QuizListView.swift index 70f6c05..05fa4ce 100644 --- a/Targets/D3N/Sources/Feature/Quiz/List/QuizListView.swift +++ b/Targets/D3N/Sources/Feature/Quiz/List/QuizListView.swift @@ -20,35 +20,37 @@ public struct QuizListView: View { public var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in - VStack(alignment: .leading) { - titleView(current: viewStore.state.currentIndex, whole: viewStore.state.quizListItems.count) + VStack { + D3NProgressBar( + items: viewStore.state.quizEntityList.map { + return .init(secondTime: $0.secondTime) + }, + currentIndex: viewStore.state.currentIndex + ) + .padding([.horizontal, .top]) - quizListItemView(tab: viewStore.binding(get: \.currentTab, send: QuizListStore.Action.setTab)) - - Spacer() - - MinimalButton(title: "완료", isActive: viewStore.state.isActive, action: { - viewStore.send(.solvedButtonTapped) - }) - .padding() - } - } - } - - private func titleView(current: Int, whole: Int) -> some View { - Text("\(current + 1) / \(whole)") - .font(.title) - .fontWeight(.semibold) - .padding([.top, .horizontal]) - } - - private func quizListItemView(tab: Binding) -> some View { - TabView(selection: tab) { - ForEachStore(self.store.scope(state: \.quizListItems, action: QuizListStore.Action.quizListItems(id:action:))) { - QuizListItemCellView(store: $0) - .padding(.horizontal) + TabView( + selection: viewStore.binding( + get: \.currentTab, + send: QuizListStore.Action.setTab + ) + ) { + ForEachStore( + self.store.scope( + state: \.quizListItems, + action: QuizListStore.Action.quizListItems(id:action:) + ) + ) { + QuizListItemCellView(store: $0) + .padding(.horizontal) + } + } + .tabViewStyle(.page(indexDisplayMode: .never)) } } - .tabViewStyle(.page(indexDisplayMode: .never)) } } + +#Preview { + QuizListView(store: .init(initialState: QuizListStore.State(quizEntityList: []), reducer: { QuizListStore() })) +} diff --git a/Targets/D3N/Sources/Feature/Quiz/Main/QuizMainView.swift b/Targets/D3N/Sources/Feature/Quiz/Main/QuizMainView.swift index 8910ee5..962ec07 100644 --- a/Targets/D3N/Sources/Feature/Quiz/Main/QuizMainView.swift +++ b/Targets/D3N/Sources/Feature/Quiz/Main/QuizMainView.swift @@ -25,14 +25,15 @@ public struct QuizMainView: View { VStack { Spacer() - HStack { Spacer() - - Button("풀기", action: { + D3NTextButton( + activeTitle: "풀기", + inactiveTitle: "풀기", + isActive: true + ) { viewStore.send(.solveButtonTapped) - }) - .minimalBackgroundStyle() + } } } .padding() @@ -40,6 +41,7 @@ public struct QuizMainView: View { .onAppear { viewStore.send(.onAppear) } + .ignoresSafeArea(edges: .bottom) .sheet( store: self.store.scope( state: \.$quizList, @@ -47,9 +49,13 @@ public struct QuizMainView: View { ) ) { QuizListView(store: $0) - .presentationDetents([.medium]) + .presentationDetents([.large]) } .toolbar(.hidden, for: .tabBar) } } } + +#Preview { + QuizMainView(store: .init(initialState: QuizMainStore.State(newsEntity: .mock), reducer: { QuizMainStore() })) +} diff --git a/Targets/D3N/Sources/Feature/Today/Main/Cell/TodayListItemCellView.swift b/Targets/D3N/Sources/Feature/Today/Main/Cell/TodayListItemCellView.swift index 64e0e3e..f60e8e0 100644 --- a/Targets/D3N/Sources/Feature/Today/Main/Cell/TodayListItemCellView.swift +++ b/Targets/D3N/Sources/Feature/Today/Main/Cell/TodayListItemCellView.swift @@ -48,11 +48,9 @@ public struct TodayListItemCellView: View { } }, doubleTapTimeoutout: 1, - doubleTapAction: { - }, + doubleTapAction: { }, longPressTime: 0, - longPressAction: { - }, + longPressAction: { }, endAction: { withAnimation { isPressed = false diff --git a/Targets/D3N/Sources/Shared/DesignSystem/D3NIcon.swift b/Targets/D3N/Sources/Shared/DesignSystem/D3NIcon.swift index 6550381..79b6256 100644 --- a/Targets/D3N/Sources/Shared/DesignSystem/D3NIcon.swift +++ b/Targets/D3N/Sources/Shared/DesignSystem/D3NIcon.swift @@ -24,3 +24,24 @@ public struct D3NIcon: View { } } } + +public extension D3NIcon { + static func resolved(index: Int) -> D3NIcon { + self.resolved(number: index + 1) + } + + static func resolved(number: Int) -> D3NIcon { + switch number { + case 1: return .one + case 2: return .two + case 3: return .three + case 4: return .four + default: return .one + } + } + + static let one: D3NIcon = .init(systemImageName: "1.circle.fill", color: .pink) + static let two: D3NIcon = .init(systemImageName: "2.circle.fill", color: .mint) + static let three: D3NIcon = .init(systemImageName: "3.circle.fill", color: .orange) + static let four: D3NIcon = .init(systemImageName: "4.circle.fill", color: .purple) +} diff --git a/Targets/D3N/Sources/Shared/DesignSystem/D3NIconAnimationButton.swift b/Targets/D3N/Sources/Shared/DesignSystem/D3NIconAnimationButton.swift new file mode 100644 index 0000000..8fc4299 --- /dev/null +++ b/Targets/D3N/Sources/Shared/DesignSystem/D3NIconAnimationButton.swift @@ -0,0 +1,89 @@ +// +// D3NAnimationButton.swift +// D3N +// +// Created by 송영모 on 11/20/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation +import SwiftUI + +struct D3NIconAnimationButton: View { + let icon: D3NIcon? + let title: String + let content: String + + var action: () -> () + + var isSelected: Bool = false + @State var isPressed: Bool = false + + init( + icon: D3NIcon? = nil, + title: String = "", + content: String = "", + isSelected: Bool = false, + + action: @escaping () -> () + ) { + self.icon = icon + self.title = title + self.content = content + self.isSelected = isSelected +// self.isPressed = isSelected + + self.action = action + } + + var body: some View { + Button(action: { + action() + }, label: { + HStack { + self.icon + + VStack(alignment: .leading, spacing: 5) { + if !title.isEmpty { + Text(title) + .fontWeight(.semibold) + } + if !content.isEmpty { + Text(content) + .font(.footnote) + .foregroundStyle(.gray) + .lineLimit(1) + } + } + + Spacer() + } + .contentShape(.rect) + }) + .buttonStyle( + ScrollViewGestureButtonStyle( + pressAction: { + withAnimation { + isPressed = true + } + }, + doubleTapTimeoutout: 1, + doubleTapAction: { + }, + longPressTime: 0, + longPressAction: { + }, + endAction: { + withAnimation { + isPressed = false + } + } + ) + ) + .padding(10) + .background(isSelected ? Color.systemGray6 : Color.background) + .cornerRadius(20) + .clipped() + .scaleEffect(isSelected ? 0.95 : 1) + } +} diff --git a/Targets/D3N/Sources/Shared/DesignSystem/D3NProgressBar.swift b/Targets/D3N/Sources/Shared/DesignSystem/D3NProgressBar.swift new file mode 100644 index 0000000..82f0a33 --- /dev/null +++ b/Targets/D3N/Sources/Shared/DesignSystem/D3NProgressBar.swift @@ -0,0 +1,49 @@ +// +// D3NProgressBar.swift +// D3N +// +// Created by 송영모 on 11/20/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation +import SwiftUI + +public struct ProgressItem { + let secondTime: Int? +} + +public struct D3NProgressBar: View { + let items: [ProgressItem] + let currentIndex: Int + + init( + items: [ProgressItem] = [], + currentIndex: Int = 0 + ) { + self.items = items + self.currentIndex = currentIndex + } + + public var body: some View { + HStack { + ForEach(Array(self.items.enumerated()), id: \.offset) { index, item in + progressItemView(index: index, item: item) + } + } + } + + private func progressItemView(index: Int, item: ProgressItem) -> some View { + VStack { + RoundedRectangle(cornerRadius: 4) + .fill(index == currentIndex ? Color.black : Color.gray) + .frame(height: 8) + + if let secondTime = item.secondTime { + Text("\(secondTime)") + .font(.caption2) + .foregroundStyle(index == currentIndex ? Color.black : Color.gray) + } + } + } +} diff --git a/Targets/D3N/Sources/Shared/DesignSystem/D3NSubmitButton.swift b/Targets/D3N/Sources/Shared/DesignSystem/D3NSubmitButton.swift index c61b636..eaeb3eb 100644 --- a/Targets/D3N/Sources/Shared/DesignSystem/D3NSubmitButton.swift +++ b/Targets/D3N/Sources/Shared/DesignSystem/D3NSubmitButton.swift @@ -7,7 +7,6 @@ // import Foundation - import SwiftUI public struct D3NSubmitButton: View { @@ -15,13 +14,13 @@ public struct D3NSubmitButton: View { public let inactiveTitle: LocalizedStringKey public let isActive: Bool - public var action: (Bool) -> () + public var action: () -> () init( activeTitle: LocalizedStringKey = "", inactiveTitle: LocalizedStringKey = "", isActive: Bool = false, - action: @escaping (Bool) -> Void + action: @escaping () -> Void ) { self.activeTitle = activeTitle self.inactiveTitle = inactiveTitle @@ -31,7 +30,7 @@ public struct D3NSubmitButton: View { public var body: some View { Button(action: { - action(self.isActive) + action() }, label: { HStack { Spacer() diff --git a/Targets/D3N/Sources/Shared/DesignSystem/D3NTextButton.swift b/Targets/D3N/Sources/Shared/DesignSystem/D3NTextButton.swift new file mode 100644 index 0000000..1030597 --- /dev/null +++ b/Targets/D3N/Sources/Shared/DesignSystem/D3NTextButton.swift @@ -0,0 +1,50 @@ +// +// D3NTextButton.swift +// D3N +// +// Created by 송영모 on 11/20/23. +// Copyright © 2023 sju. All rights reserved. +// + +import Foundation +import SwiftUI + +public struct D3NTextButton: View { + public let activeTitle: LocalizedStringKey + public let inactiveTitle: LocalizedStringKey + public let isActive: Bool + + public var action: () -> () + + init( + activeTitle: LocalizedStringKey = "", + inactiveTitle: LocalizedStringKey = "", + isActive: Bool = false, + action: @escaping () -> Void + ) { + self.activeTitle = activeTitle + self.inactiveTitle = inactiveTitle + self.isActive = isActive + self.action = action + } + + public var body: some View { + Button(action: { + action() + }, label: { + HStack { + Text(self.isActive ? self.activeTitle : self.inactiveTitle) + .fontWeight(.semibold) + .foregroundStyle(isActive ? .white : .black) + } + }) + .padding(10) + .background(isActive ? Color.blue : Color.systemGray5) + .clipShape( + RoundedRectangle( + cornerRadius: 8, + style: .continuous + ) + ) + } +} diff --git a/Tuist/Signing/D3N.Debug.mobileprovision b/Tuist/Signing/D3N.Debug.mobileprovision new file mode 100644 index 0000000..4bd68bd Binary files /dev/null and b/Tuist/Signing/D3N.Debug.mobileprovision differ diff --git a/Tuist/Signing/D3N.Release.mobileprovision b/Tuist/Signing/D3N.Release.mobileprovision new file mode 100644 index 0000000..cd68bd7 Binary files /dev/null and b/Tuist/Signing/D3N.Release.mobileprovision differ diff --git a/Tuist/Signing/development.cer.encrypted b/Tuist/Signing/development.cer.encrypted new file mode 100644 index 0000000..c2f6d67 --- /dev/null +++ b/Tuist/Signing/development.cer.encrypted @@ -0,0 +1 @@ +KA9p7G7bZWG6uf/yDgvziQ==-SjJC7AJALP/RGPkm880t7RigVW+mQVmeqpkRGOX9PHn16rejC47dged+MKR7+Y/haB69IkkkwbVP94oESqh9JRKdbBNAwZO09+yd/lq+X2cm5LEs2v1RiRVnGVSuyyJ27hPr+LsEs7XMaoHcwfqX6wohCrchL+x9dopy1lrmHJZzR1inCO1QFk200dEjKqzY9lW6gfwoOanbxCSkEE7zp8XCnEaXaZs56zorEIBYvaDnQ7KuO6gww4r9b93SG6EITsHMTwOa2N4tcwWr3ZOflYnwaUyu6DtUG/Wetqhbu6gTdD5DlJ5fWMva6F3uFKrvfY5gDXtHwNJO7VZwiIM1fjcV9JfErTrLtysyefQA4LqsJHj4q2LVMFp8AIXUhmZ3laMuN2xoNq1k00lNHEr9Z7HS/QPzwP4uOSfBs61L6wAj+GHqLdySlbQ/noboIY4jGJ/AjztKXpi33csO2MiLldPrH3TgE6BMSZJ5VWwyFFeTfNwmz2xGaz5tYDgEnN1Qwgn/7mKwH/q9vZ6SjvamlHO4Hjr5n/mSyJBdrsu+OCZhV1X02THPBEXu6pMqZbdFxqQcbQUjKhKsVHrkDv1533W8sCI3wWrRk/fVKXQgfsY+Nnz23FfII4Pkv5HG3+JdouHDrDlr5oJvhSrTXp9XWmhAwTRRq2b7NFQLf3WK7V3h/VQWb0PyYM39Xerw02WEl3ki/xaZauhGq+9kNh7xzKLIrSOvNd4oiNJ5ERfs3rGWp958gdqiiXhpk+icIGrPcqe43cJGnXFV5CA0InJWahZB93+gWUrp4fHzWAF7ksS4rDjXG5Bui5MwsSRo0cMYncvoLGklyYLk/SswcHJdMNdOCA5TCDO2NoNJiqMhtgjwmNxzG89rZ+3izLJT7+it8oBRhk3vDO2pagVoetrwkHznOu+F2xr6yDndB1ZrU7lu8LGYC0y1hrevwLmUkTEK0numFPszZnkiAeYGUsAneUKHhyvVp3nmfMN18J13SvIyUm6/wckPDz3snl2R4HIfzsTgmLz0ZdxUC+KfLVtSfXbO8CDmxoVFyXgh4ojSmlWZl1Wen89df7nm55nfrj1VpDZeAQ6K++WNqMSpINTWZmYvjzbAWbEAQQrHD4xYjzPnaIJhY3GwBdh33VD52/MQJHO4sCLxM0BjH2MIqudEaRUl/YOV+rXNuG0J5tcZXAkk+qU3IqdsudFHUxa9ZHFrzLz8CFgxv1PB1pHmFNGmoNNrORDopDu7FgmOXsRDMP4wplXcIdhb3Y5bARCe0Sf+E7oYd824wCJxKkFm4qfagfl1mIvCTD5Xd6VBkrZHMumuCEuxE80sYroGUS+be6SAnXbYsB6mXyLNfA2I4nBi4WfsJIWwTx3epDWUHgAgQ7HJIE8UCjYSsH8kpzucT3Jf0k0dk0f2KKd+e51gl7ExQPIY4tzMsPWM5LIRgHllDLwJU72UcORXknhS4m0XoDj4Br6kOipYTLXWAtphTMDSG2iFd0TKFuVjdIFIaRYP7f4C2kZBz5lJFOJw5FMGsF+x6amrEEt7Hw5fhFnYko7SGV0ejNlUgSESXwntYUAz39YO+BTLPtIzgkZk/UmgffgHhbqfWGHJJlwyrF1gv5QsiLpiQXaoHAmWWFZXeyHcvpbjdN+/yPqP8g/XwHL+UBtsd7TxrsNUgyfT7HBLfu8GnkKwsV07z/SwKx+5nkOaR4HFvFutFHNx2QgdYpfVeox2UNQ7V+ENqDDFh7lu6LMyaS8eH1RX94pKgbyuHN2mlffh1XPots9B4WzzYn5Oxa24nroyR658p5EBG1bftEX3OY6UM7h8tw3fE/Ly3R8LZvP1fBm+uALDiFNscoO93bD6WbTCTa0/KFS1xzebV1CiZlzEejoj5V7dkUoCdIHdZIxvgCF2OGX7wigilT6aewjm5EB+BXYdiswtxFb4v5ZsgBefbUkunFQ9yJ+NHb5xYkXwAVoAfe6uboc7 \ No newline at end of file diff --git a/Tuist/Signing/development.p12.encrypted b/Tuist/Signing/development.p12.encrypted new file mode 100644 index 0000000..4c9ae91 --- /dev/null +++ b/Tuist/Signing/development.p12.encrypted @@ -0,0 +1 @@ +a1lcdOzT58/1DxZ1glyLjg==-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 \ No newline at end of file diff --git a/Tuist/Signing/release.cer.encrypted b/Tuist/Signing/release.cer.encrypted new file mode 100644 index 0000000..ca1d5c2 --- /dev/null +++ b/Tuist/Signing/release.cer.encrypted @@ -0,0 +1 @@ +8C5ebpf+eF8HCeWpe+cFyw==-HdiBE3Va4xf2dKOGA324i32e27QFzESBSgMtrgPA5VTtxG/WGyIzsHG3UUycJvu9FG+AaaTnvLU0P4I/CrVodX1Rq0uj7vbpSiLY1mZy/29a2l8MuFSyXK1v2+Z4u8FKLDLcq8jnR1LXYbzXg97/wSCx4Rk8RyrukV0tdW+ydPWyn+KrhgPrcxW5Mh0jJCeAZmwF1odr5cq8d+QG/2PCGVknYKGi9vdNdzzfQIIxHKIDbtwVt3LeimoxHNyCqH21xu5qWZQsYixh3eiaY9B3y5UU39DBOvUfOf/R7hodB53w+2dp+UI/cjARkjMxCExn3lDGQpE9GXjC2Vv3KPQ0M6Cxan9v9W2Cu7NNm/tbzZWwhk89DunLFuoBTaL8nMvC1ws3maxMu0sOAKPmO5RgjJFCuhvAp6r43eDKGVjRW09wzIyOI3CyeLNmj3CPUiCLYK80uAHWtYNG2qxFLN4Rj0EVZAgMDERLEI4uG6Dz31R+OBrNwWW3r2MJm1WbCb38LvDbS6BzISfOAPcScmPWbFG6K3JYf63A7jHIg6rZnOoLYDcrx1ppTPslBg1g0XDxPWTKDYsptsuY9v2hzsroXb6Fbeno/thkhkCK1pDpdzRuJFZFPBk6bEbJCU4sL/uswme8IbQf8Tzj/xbWNgtgwf4tk0iFCIZMQSdvHAgt5U+I2ToyTqJuLn78w6rjsVpEAhLcFXcPiHsLGlQGRYLuAoc/orBQCKToUZTxJnENGB8kEKoFaE2v155l0ajAayhoiR5XD3pmPgxcl8jNyEkdqVooEZk4kBZNWFT3rRC9SwANBiif3/m4k0yUsN6G8oCakPfMxNivk9F6sSHF3IDflWavk/yDRGxsO8i9DEJuxc2pJ4QG82TUI2hwtqLW565al1CdS+20MxD0syZ0OQyM+aTtr4i+jDf5ElvWvOsxsgYDdre7HUo8SQIg5OBHeo60uVs+rVGW2R3IyMl1iGqc1QtUwEOOY/DskU+Sw306Zkbov/W2bZx4KpVPncarsDEnIg+0bLs3j/6c12ezZElOeNX/lB79ztzyixf5iNWjHnOwSQNtaTtyVItwPGP/yTcDIgd7ZFg2gs8p92a1iBavMHRxlsyK9/qT+IYkJMCz//b+UTaH9akqItjUJdMYQmP9fa7R/uQVzUsFNi1jaMkZq/q2Dk6HQjhdtPmY1CQRYSIdEw8MvVqwhqBIoFh6bCzCEVvwxb7IW4tGN+78ovYyU/z4yBC9IHBx3YsL6Fw45D1DYytd445DiAoWgCUlalUZE4/9N88uGLqqu59B/TgJxVhfmXlHY4ipXVBPGzkc6bvKYrTUTupJJQhrWXXf2Kco1k91vB4Sz2VMulA+3TTuQkAPUReHiB92WAKpCvWM7eZfwEQykOWFqbD+Twl6WWWwvYRcNsUBRnyAHQQvFpGSP6BJ2BgBvZB/uqX0USKIirlEQw2nwWpzzUMQr+YyHQxo1t+SB7g1Iuti734Pe/sKzs6y/mQHzph26obtJrEQzpmu6NCo0E9+qAEFQ5Xkq7GAsBZPU6JXSPy71KSS+dEc8YF0dP/4LZuBBOKuD1HkYwie1FF1ZP4j/I7Udz0s3JGPX+Fhixn9h1jdvtXCCbt4kJ/4f+B0doLOdzkf0MqNq+l9+QVawUoJWe0bjSYuVZkcGNzWHaQFVgdu6yyFx6nqrmhHCB6Lw+7rTZCUadVIqFbTwDBF1qKV24iau2vq4Moye1sZIaZuEkWFEDmwYgUcyZJjTB+kyFstzyT5dhUHTJYfHY3q0ubAY6NeoLK422ouoojoll4tOfht5ASWyM1PfK7C3PjrIeTvYqSzsujTjMeEmllBo7LcCqMyqj/FF2JfGdhZM8qdeC2A+YPDtW0Nx/1IvHFS4P3+hL4p9mtrHu4xpQCcUomZy4UgaJ/tlH6M6ZC0tHF0U0VQ32UHGAeWiwJB6cVd07+DZ5tH+EGSR2GWOnG/Y44mAEljnw== \ No newline at end of file diff --git a/Tuist/Signing/release.p12.encrypted b/Tuist/Signing/release.p12.encrypted new file mode 100644 index 0000000..3632f09 --- /dev/null +++ b/Tuist/Signing/release.p12.encrypted @@ -0,0 +1 @@ +xTSjOcPEzoQHbake/mbqjA==-ozck8/JFrwQEVV0EaO7Cd0j6PnPmgyOQi1iSuRVQqFNqIg3ySCkY87m7/ABm5evOUtGOS9d5aDJymoR7E500hpDSm4rUf00c2coW3aEEBsfoL+JekHDyk9DNciojj7zLiGQOe+F+pPHl5Z2H7B6u9DSg059IZoTNm+vUgwvI8rSSUKCZV/yDAmyk+Pb6Oc4/wl8IwVuCvBiGlMAuZTc4xZB0v+dGbx+LBPJZi+88mrudy4wONh3ZsllxXmvD/znoENZ3JoLUD6t1LphEQJAQ8HuqAV63cGF8ywh9l69jK06GHtiWMAedAmKARHPtL/HN1EBW/B2q3XBovSYK8MAlz/YZV9mqcbR1a1dCcmcXdh4gfIxGib3yf9Fq1Lkva2/2QGs4GzofbS1A64J9hYx73bIWTB4clvuCDC0MZZ08iepQc5x07i3I7smy81kV3hvCaynaOp5u1ZXkX9AZmmNt5BRpbOYl14o46QVLhxPsd9X+eoYIAsIW19t8qMQle8QZEtBOmjSMbQl8MUqZ+jYfuYD7pC3JF+eQOaKlxFE2u4pdYEKTQx9dTff/OO+LgVd/VN96dTbWJt66/10inUTkbtQe+u7hqechYesCo6pytzc+GVbX7NjQj8mqFyDsfpcovYVrPDibC1rQddRWmFHwuym5Aj/7j2Raf+u25TcZNP5CrtQh9fEkxWFG7fVtSGVDhYEASh9wluYu44Yc4uu5el1/4kCTA4aCDb1V00oZLHFs9JSKFnB0Jh/khCEs9bWtyL5yucfk1aB/X0ev4MqNOgy00zmrc/uCsGiIKPFALtVO6O9ps60FauYtyeJslc8UsDGAJF7jQV5nW0J7SyzAfjD2zckEruNC5O71U1ujssHhYHUKITwWGdMi05COk/lZkjqcNdVNVmVUIvdQ269qg2DhpDjVY8UVwgKXQsSTomyHl//RykIv3bTrymqWVES1OwJ+oZBWt/aXrRd+UQhL+cgmMDKdBe6sf/vmQz6y1tyPkdIgSaiCFuTxwu1f/Jbz9RTHwiAfVOYvAXdqdkeDsG0s5mQ47vyiq3Q4RiY6lhCiExjRehHjxWUD8UtKyx+TTLfn4M0/PbreetP6jBwU8zapTASL9xWIEgF8zYFVruei9zBI7AHH0ShzYcpjxrR0vjIxNa6Ogjic9k4qu7K4ctoYlJh1ydKlzrEh2vwWfbVrK6aZMufVFIX0tjXbXzPX0LwpU9NRxl+uK0rWaZRVI6PqK8QRUyHaBJcQ6F2L8L16M81vo+IxzLF1hhq4byQdJkL3qfxSp081p5HcRrOUgJX7IiFfAeymn4Kjlvw0empncfkqihtRixxS5Mhg06WXPNEb4CaKF/DW7Cv+c3upNvWbIq1JmU2htH6T2uVKx6nt6RdiIq5qHnHMYs47ec6eiGDoMrGWPc1NHCzCQf4LrkNPzK8ZAjSNa7Bc/AZcN8IRlkX/9hIMltATc7vZSUrkVRJlmua8ybidaabZ53h2BeMpfYxKTrIjvdf956L6HQ4HBHZtcTB+4uMegctbKcyI6J7TTZ84ukQZgYqTgxvWN8KM1+sNKz/VhkrlWCKegT25OF5K6jt0ZxZSIxvhOlr8IOM9SZ38Lq3wIyrPQgdycyQqH+yt4UvSAB1MJgbzQh7qlI/euZrnQBq2mF+b496bHUC/IpYlO/bPKNPv41T/7lPs1VfW5Qqn35lPCV5hejONRJnxDLJkxKM9miBr3kBLM2ajStdFD9TvRU0dBkgyZcUQYj+QeFe7RBgcNI5rW9VOLKAt/7qUl7VeC9lA9oq547rH5Q5ogjY6HPS95iRx2vYkB6qTlS84dyf3SPfR1p4Vqd30AgYDTl4kFyG8znj0V5TkZvhxlXwJEAnNSKMLgrSoB5KApOgxRQYVfgq0BtJhv+doySRQQ7yyFkgKJxCUmlpSUONz9Acs5P10VxlJdbve/oYe6smwJNrjNNEpnIVUUyxJt3S3PQTvujeD4mWtjrNIntqGUMRhh8bzOYSA7mVXHpS4TAls1w5hOW3NQhGQGva5yjkPjyMrDB5r3EwgQ1M57b7Iu45NrimqBscZa1nAE+4JlVt0y8HOjlzU1ac/3/SqJEyUMhG4nRql9aBV1abQN/0oVGD4WalH3LzeW1ZNG6F+3x0S/tCSBGk5pvWLF5cteWACDnWirjBX0a/FwBbLg9SgznyuGmu6BD7ISUQFMMpPXH7zcZhE6iTD1zdgGQnR9231mtbmMrAQEP9ElsOW3EftidFaLCtsFZnWS6S6/aVTQPcHYJ0NjniNHoMHdNUOSZadfUnCyeVrYVTkw7n3R8kH+VS8VbimGTDmeMzvPxdjJ91lkBzmgzHfqm5/ptYUuWOUGWh5U3mU6Ax+Q9pl1YoqNtUW1D9RnwYIYFOki2BMIeMcpDDQ08OAXDMPVYbLjtnNA2KhMkEsIQwSo92QIx1Ch0Nn7V9Hzgo6QBnFRoyBbTMLti5d7IS38DJxXuMONIWW3L/ci+Z2E8DoqtzyQiHXXD+hTbrN+2nYbm2o4skqE4h+cEE8I7MB++ocwiD2vAu77DZ0c1QqsMdTge8Ku1JGoiEQuuvT7WjsLgtNM2A/6yS4WF2wwKQquKdGXr1xDIBhVBX3H78exPq58mTOToZdVqIVUHN8N0Y5UThrMNSGf4gVJelu2nDxVp4ET8kQ0lbprbVaAsMg9v88nB2+jrTSSZinof4jZAMBujRuEIAcy1rhEvtCwRr8Yc8bHLTsoPkV/0pECj5+t1MkYozT8WYib/BhsIH5QeVyZulKnzCXDskt8ZayioL3GDXt6xsiOpUAaX5afTh1u60TPrDrE9rvdWUkrrOfyVBGHx0abOB1a/q3vy60pkK5tAkeeyFqlp67TYgVyDVmVGDT4RWRczfHWHZUKxMHJl1HCTxaxcbxOj8/tpYAydrD3TD7T5M4RiYfhU0jhJ2gKjavcYQ7juPQZCbp2Y91MM//thkSi7Zu/5LtGVxhHH83wH0FI+UvczLK/wO8h+ihR1j3eOVtgHsoBqRnB2xEeot+cBEoZ7pGTJUu5oaOoBRa5nOsJNwcThrzAFtarSpGkgPC0rWPEFNuWTksa9UmVBuE5n/k4IgqLZBDBQ25DUSJhiT6K2GG1c78TqzgD0p/ZxQn9k1/u+TtYO6EKHILTPt6aP6qGVmbPRQuHOCkZATx7h1BVAO93ewFxDVrTfti1ZzPy0s4u0A1nsmjUapoqfbguriDp0RIf2373czOp9N5iQlbenEYcYshNd1axTXHB+BmYa5IjB97qUoTG/B8EpTmmCoTV1+XsWj1PxzzWNLZE7YxNv3tzKyIC1AE+lYpxJLU5XolhiE3cFMJ+NGJh7tVWz/qB7D99nMyTFWqmOZoNLBHE7HHntzfng0ENKNTqxNA95S1bIfQv6wk2kukPEmyQ1wtbx2sqgBm9eaNBcijsanRoVNLj7df6T7vDFDuRycD6E52HHGWNbpQ45oBLaHTroB+2Mp3yvJxTgjqn9ptp18pIS24Ph33gKAE6J7n6D2uD/oLRqnOa/DxnLX691OGX+vmtxyW0eKoZwsVFlhhjz7bkJaFxDGG3rIDrZzaPxOmhmWVNV8p0+uScQDjLKls80LkDVAteIjpvQRvBrkBFXmH318+EN4H3AujN1WxyBwmYdO/Uc/st7TAvlJK/6lZHTWtx6RlTD6/i6hs3yuvR1fJhtuHregywUIXq3Rcv1lLJRVjRa2jTwHi/g7ymGao1zuj9a4rXeH/hILSV+i/4lUyLPgtZSJqot8rl3tSDIadj7H9TM472hIEgoJFT2WSLY+9TwqyCkHu+rq1yLA7WyccmNXpyrjrhZ0OtUFaR3WjJKDDS54jxF0I7pf1fOo9PC/klHwrGBbtt4Ev2YUsGnl4F0BEN1ZzfUOrAgf3jS+yalnBro148z7neQZXZmaHLRkKF6hPsLlsbORW0fD/BjR5SAF3j1pF3CYmqiYGPDqsVPbd2klyWwryuCgLEDDHmCGiQL7N/a8azurh0MK/c2t3udlx5oTh6LQw78A3ZaV4QB9jPjphDg7OqA3PD6ccraOUzPYSq596DmmBUKX5CZ41Z5UuRxZiYxKT74kYmsUOSoPEBhQL1J8DOb9CFI/3YsS2ZpGL5zwT2fNjD44KFwig/CMJ18a9zavuXxbDF9+R+hYk5MjRsHDnyPVys2nDHkc3R1Dp0Q5QXZ4zawxH5tv48HnwO/DiJ6Z2Zh/mzkiNIEkKd+GozyX3yTtZFazX9WrT6GgPWOJQNa1sH0CakSM8oFONAAIe6/doPIFzC9nRnFW7i0BWEVXK4NDy9AVFF6IZPXfzIkpnWuXbGY5Ix849CCGFQ/xUmNrxFpwOIQzbUlqmNig= \ No newline at end of file