From 5a8f0e14795a4bd135261f09105b332c4a4c7639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=EB=AF=BC=EA=B1=B4?= <111111595+PushedGun@users.noreply.github.com> Date: Tue, 28 Nov 2023 10:59:16 +0900 Subject: [PATCH 01/27] [iOS] SpotScene (#114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :memo: RewindJourney 패키지 파일 생성 및 필요한 패키지 의존성 추가 * :memo: 구현에 필요한 UIKit 가져오기 * :memo: 경로 변경에 따른 package 파일 수정 * :sparkles: UI Scene 구현 * :memo: Package 파일 생성 * :sparkles: Scene UI 화면 구현 --------- Co-authored-by: mingun --- .github/workflows/Xcode_build_test.yml | 92 ++- iOS/.swiftlint.yml | 6 + iOS/Features/JourneyList/.gitignore | 8 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcschemes/JourneyList.xcscheme | 66 ++ iOS/Features/JourneyList/Package.swift | 27 + .../JourneyList/Model/DTOConvertor.swift | 37 + .../Sources/JourneyList/Model/Journey.swift | 44 ++ .../Sources/JourneyList/Model/Song.swift | 15 + .../Sources/JourneyList/Model/Spot.swift | 14 + .../JourneyListViewController.swift | 251 +++++++ .../Presentation/JourneyListViewModel.swift | 56 ++ iOS/Features/SaveJourney/.gitignore | 8 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcschemes/SaveJourney.xcscheme | 66 ++ iOS/Features/SaveJourney/Package.swift | 23 + .../Sources/SaveJourney/Journey.swift | 14 + .../SaveJourneyViewController.swift | 281 ++++++++ .../Presentation/SaveJourneyViewModel.swift | 52 ++ .../View/SaveJourneyBackgroundView.swift | 39 ++ .../View/SaveJourneyHeaderView.swift | 62 ++ .../View/SaveJourneyMusicCell.swift | 133 ++++ .../Sources/SaveJourney/Spot.swift | 12 + iOS/Features/Spot/.gitignore | 8 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + iOS/Features/Spot/Package.swift | 53 ++ .../Sources/SpotView/SpotViewController.swift | 240 +++++++ .../Sources/MSNetworking/APIURL.swift | 20 - .../DTO/Fragment/CoordinateDTO.swift | 13 - .../MSNetworking/DTO/Fragment/SongDTO.swift | 16 - .../MSNetworking/DTO/Fragment/SpotDTO.swift | 17 - .../Sources/MSNetworking/DTO/JourneyDTO.swift | 20 - .../Sources/MSNetworking/DTO/PersonDTO.swift | 17 - .../Sources/MSNetworking/HTTPBody.swift | 20 +- .../Sources/MSNetworking/HTTPHeader.swift | 11 + .../Sources/MSNetworking/HTTPMethod.swift | 4 +- .../Sources/MSNetworking/MSNetworkError.swift | 27 +- .../Sources/MSNetworking/MSNetworking.swift | 58 +- .../Sources/MSNetworking/MSRouter.swift | 42 -- .../MSNetworking/Protocol/Router.swift | 34 +- .../Sources/MSNetworking/Session.swift | 6 +- .../MSNetworkingTests/MSNetworkingTests.swift | 97 ++- .../Mock/MockMSNetworking.swift | 50 -- .../MSNetworkingTests/Mock/MockRouter.swift | 21 +- .../Mock/MockURLProtocol.swift | 54 ++ iOS/MSData/.gitignore | 8 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/MSData.xcscheme | 66 ++ .../xcschemes/MSDataTests.xcscheme | 53 ++ iOS/MSData/Package.swift | 26 + .../MSData/DTO/Fragment/CoordinateDTO.swift | 13 + .../DTO/Fragment/JourneyMetadataDTO.swift | 4 +- .../Sources/MSData/DTO/Fragment/SongDTO.swift | 17 + .../Sources/MSData/DTO/Fragment/SpotDTO.swift | 16 + .../Sources/MSData/DTO/JourneyDTO.swift | 20 + iOS/MSData/Sources/MSData/DTO/PersonDTO.swift | 17 + .../MSData/Repository/JourneyRepository.swift | 69 ++ .../Sources/MSData/Resources/APIInfo.plist | 8 + .../Sources/MSData/Resources/MockJourney.json | 653 ++++++++++++++++++ .../Router/Journey/JourneyRouter+Body.swift | 18 + .../Router/Journey/JourneyRouter+Header.swift | 18 + .../Router/Journey/JourneyRouter+Method.swift | 18 + .../Router/Journey/JourneyRouter+URL.swift | 31 + .../MSData/Router/Journey/JourneyRouter.swift | 14 + .../Tests/MSDataTests/MSDataTests.swift | 12 + .../xcschemes/MSUserDefaults.xcscheme | 66 ++ .../Tests/MSLoggerTests/MSLoggerTests.swift | 4 +- .../xcschemes/MSDesignSystem.xcscheme | 66 ++ .../xcschemes/MSDesignSystemTests.xcscheme | 53 ++ .../xcshareddata/xcschemes/MSUIKit.xcscheme | 66 ++ iOS/MSUIKit/Package.swift | 2 +- .../Sources/MSDesignSystem/MSColor.swift | 3 + .../Sources/MSDesignSystem/MSFont.swift | 2 + .../Contents.json | 56 ++ .../Component Typo.colorset/Contents.json | 56 ++ .../Calendar.pdf} | Bin .../Contents.json | 2 +- .../Calender.imageset/Calender.pdf | Bin 4018 -> 0 bytes .../Cells/JourneyCell/JourneyCell.swift | 160 +++++ .../Cells/JourneyCell/JourneyCellModel.swift | 51 ++ .../Cells/JourneyCell/JourneyInfoView.swift | 133 ++++ .../Cells/JourneyCell/MusicInfoView.swift | 131 ++++ .../JourneyCell/SpotPhotoImageView.swift | 85 +++ .../Sources/MSUIKit/MSUIComponents.swift | 8 + iOS/MSUIKit/Sources/MSUIKit/Spacer.swift | 26 + .../contents.xcworkspacedata | 13 +- .../MusicSpot.xcodeproj/project.pbxproj | 63 +- .../xcshareddata/xcschemes/MusicSpot.xcscheme | 77 +++ iOS/MusicSpot/MusicSpot/SceneDelegate.swift | 10 +- iOS/commit | 13 +- 90 files changed, 4040 insertions(+), 353 deletions(-) create mode 100644 iOS/Features/JourneyList/.gitignore create mode 100644 iOS/Features/JourneyList/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 iOS/Features/JourneyList/.swiftpm/xcode/xcshareddata/xcschemes/JourneyList.xcscheme create mode 100644 iOS/Features/JourneyList/Package.swift create mode 100644 iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift create mode 100644 iOS/Features/JourneyList/Sources/JourneyList/Model/Journey.swift create mode 100644 iOS/Features/JourneyList/Sources/JourneyList/Model/Song.swift create mode 100644 iOS/Features/JourneyList/Sources/JourneyList/Model/Spot.swift create mode 100644 iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift create mode 100644 iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift create mode 100644 iOS/Features/SaveJourney/.gitignore create mode 100644 iOS/Features/SaveJourney/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 iOS/Features/SaveJourney/.swiftpm/xcode/xcshareddata/xcschemes/SaveJourney.xcscheme create mode 100644 iOS/Features/SaveJourney/Package.swift create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyBackgroundView.swift create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyHeaderView.swift create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Spot.swift create mode 100644 iOS/Features/Spot/.gitignore create mode 100644 iOS/Features/Spot/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 iOS/Features/Spot/Package.swift create mode 100644 iOS/Features/Spot/Sources/SpotView/SpotViewController.swift delete mode 100644 iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift delete mode 100644 iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/CoordinateDTO.swift delete mode 100644 iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SongDTO.swift delete mode 100644 iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SpotDTO.swift delete mode 100644 iOS/MSCoreKit/Sources/MSNetworking/DTO/JourneyDTO.swift delete mode 100644 iOS/MSCoreKit/Sources/MSNetworking/DTO/PersonDTO.swift create mode 100644 iOS/MSCoreKit/Sources/MSNetworking/HTTPHeader.swift delete mode 100644 iOS/MSCoreKit/Sources/MSNetworking/MSRouter.swift delete mode 100644 iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockMSNetworking.swift create mode 100644 iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockURLProtocol.swift create mode 100644 iOS/MSData/.gitignore create mode 100644 iOS/MSData/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSData.xcscheme create mode 100644 iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSDataTests.xcscheme create mode 100644 iOS/MSData/Package.swift create mode 100644 iOS/MSData/Sources/MSData/DTO/Fragment/CoordinateDTO.swift rename iOS/{MSCoreKit/Sources/MSNetworking => MSData/Sources/MSData}/DTO/Fragment/JourneyMetadataDTO.swift (63%) create mode 100644 iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift create mode 100644 iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift create mode 100644 iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift create mode 100644 iOS/MSData/Sources/MSData/DTO/PersonDTO.swift create mode 100644 iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift create mode 100644 iOS/MSData/Sources/MSData/Resources/APIInfo.plist create mode 100644 iOS/MSData/Sources/MSData/Resources/MockJourney.json create mode 100644 iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Body.swift create mode 100644 iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Header.swift create mode 100644 iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Method.swift create mode 100644 iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift create mode 100644 iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter.swift create mode 100644 iOS/MSData/Tests/MSDataTests/MSDataTests.swift create mode 100644 iOS/MSFoundation/.swiftpm/xcode/xcshareddata/xcschemes/MSUserDefaults.xcscheme create mode 100644 iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystem.xcscheme create mode 100644 iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystemTests.xcscheme create mode 100644 iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSUIKit.xcscheme create mode 100644 iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Component Background.colorset/Contents.json create mode 100644 iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Component Typo.colorset/Contents.json rename iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/{Calender.imageset/Calender 1.pdf => Calendar.imageset/Calendar.pdf} (100%) rename iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/{Calender.imageset => Calendar.imageset}/Contents.json (84%) delete mode 100644 iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calender.imageset/Calender.pdf create mode 100644 iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift create mode 100644 iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCellModel.swift create mode 100644 iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyInfoView.swift create mode 100644 iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift create mode 100644 iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/SpotPhotoImageView.swift create mode 100644 iOS/MSUIKit/Sources/MSUIKit/MSUIComponents.swift create mode 100644 iOS/MSUIKit/Sources/MSUIKit/Spacer.swift create mode 100644 iOS/MusicSpot/MusicSpot.xcodeproj/xcshareddata/xcschemes/MusicSpot.xcscheme diff --git a/.github/workflows/Xcode_build_test.yml b/.github/workflows/Xcode_build_test.yml index d61884a..116eb3d 100644 --- a/.github/workflows/Xcode_build_test.yml +++ b/.github/workflows/Xcode_build_test.yml @@ -1,7 +1,7 @@ name: Xcode_build_test env: - PACKAGES_JSON: '["MSCoreKit", "MSFoundation", "MSUIKit"]' + WORKSPACE: iOS/MusicSpot.xcworkspace on: pull_request: @@ -14,7 +14,7 @@ jobs: prepare-matrix: runs-on: macos-13 outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} + matrix: ${{ steps.generate-matrix.outputs.matrix }} steps: - uses: actions/checkout@v4 @@ -24,45 +24,21 @@ jobs: with: xcode-version: '15.0.1' - - name: Install jq - run: brew install jq - - name: Generate matrix - id: set-matrix + id: generate-matrix run: | - cd iOS matrix="{\"include\":[" - packages=$(echo $PACKAGES_JSON | jq -r '.[]') first_entry=true - for package in $packages; do - cd $package - for scheme in $(xcodebuild -list | grep -E '^[[:space:]]*Schemes:' -A 10 | tail -n +2 | grep -v '^$'); do - if [[ $scheme != *"-Package" ]] && [[ $scheme != *"Tests" ]]; then - if [ "$first_entry" = true ]; then - first_entry=false - else - matrix+="," - fi - matrix+="{\"package\":\"$package\", \"scheme\":\"$scheme\"}" + for scheme in $(xcodebuild -workspace ${{ env.WORKSPACE }} -list | grep -A 100 "Schemes:" | grep -v "Schemes:" | sed '/^$/d' | sed 's/^[ \t]*//'); do + if [[ $scheme != *"-Package" ]] && [[ $scheme != *"Tests" ]]; then + if [ "$first_entry" = true ]; then + first_entry=false + else + matrix+="," fi - done - cd .. + matrix+="{\"scheme\":\"$scheme\"}" + fi done - # cd Features - # for package in JourneyList SaveJourney; do - # cd $package - # for scheme in $(xcodebuild -list | grep -E '^[[:space:]]*Schemes:' -A 10 | tail -n +2 | grep -v '^$'); do - # if [[ $scheme != *"-Package" ]]; then - # if [ "$first_entry" = true ]; then - # first_entry=false - # else - # matrix+="," - # fi - # matrix+="{\"package\":\"$package\", \"scheme\":\"$scheme\"}" - # fi - # done - # cd .. - # done matrix+="]}" echo "matrix=$matrix" >> $GITHUB_OUTPUT @@ -82,11 +58,10 @@ jobs: xcode-version: '15.0.1' - name: 🛠️ Build ${{ matrix.scheme }} - if: ${{ !contains(matrix.scheme, 'Tests') }} run: | - echo "🛠️ Building ${{ matrix.package }} - Scheme: ${{ matrix.scheme }}" - cd iOS/${{ matrix.package }} + echo "🛠️ Building ${{ matrix.scheme }}" xcodebuild \ + -workspace ${{ env.WORKSPACE }} \ -scheme ${{ matrix.scheme }} \ -sdk 'iphonesimulator' \ -destination 'platform=iOS Simulator,OS=17.0.1,name=iPhone 15 Pro' \ @@ -95,12 +70,33 @@ jobs: prepare-test-matrix: runs-on: macos-13 outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} + matrix: ${{ steps.generate-test-matrix.outputs.matrix }} steps: - - id: set-matrix - run: | - matrix="{\"package\": $PACKAGES_JSON}" - echo "matrix=$matrix" >> $GITHUB_OUTPUT + - uses: actions/checkout@v4 + + - name: Setup Xcode + if: ${{ !env.ACT }} + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.0.1' + + - name: Generate test matrix + id: generate-test-matrix + run: | + matrix="{\"include\":[" + first_entry=true + for scheme in $(xcodebuild -workspace ${{ env.WORKSPACE }} -list | grep -A 100 "Schemes:" | grep -v "Schemes:" | sed '/^$/d' | sed 's/^[ \t]*//'); do + if [[ $scheme == *"Tests" ]]; then + if [ "$first_entry" = true ]; then + first_entry=false + else + matrix+="," + fi + matrix+="{\"scheme\":\"$scheme\"}" + fi + done + matrix+="]}" + echo "matrix=$matrix" >> $GITHUB_OUTPUT xcode-test: needs: prepare-test-matrix @@ -117,12 +113,12 @@ jobs: with: xcode-version: '15.0.1' - - name: 🧪 Test ${{ matrix.package }} + - name: 🧪 Test ${{ matrix.scheme }} run: | - echo "🧪 Testing ${{ matrix.package }}" - cd iOS/${{ matrix.package }} + echo "🧪 Testing ${{ matrix.scheme }}" xcodebuild \ - -scheme ${{ matrix.package }}-Package \ + -workspace ${{ env.WORKSPACE }} \ + -scheme ${{ matrix.scheme }} \ -sdk 'iphonesimulator' \ -destination 'platform=iOS Simulator,OS=17.0.1,name=iPhone 15 Pro' \ - clean test + test diff --git a/iOS/.swiftlint.yml b/iOS/.swiftlint.yml index 57a62cf..3a9fefa 100644 --- a/iOS/.swiftlint.yml +++ b/iOS/.swiftlint.yml @@ -26,3 +26,9 @@ type_name: max_length: warning: 40 error: 1000 + +# 중첩 타입 +nesting: + type_level: + warning: 3 + error: 4 diff --git a/iOS/Features/JourneyList/.gitignore b/iOS/Features/JourneyList/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/iOS/Features/JourneyList/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/iOS/Features/JourneyList/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iOS/Features/JourneyList/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/iOS/Features/JourneyList/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/iOS/Features/JourneyList/.swiftpm/xcode/xcshareddata/xcschemes/JourneyList.xcscheme b/iOS/Features/JourneyList/.swiftpm/xcode/xcshareddata/xcschemes/JourneyList.xcscheme new file mode 100644 index 0000000..e56683b --- /dev/null +++ b/iOS/Features/JourneyList/.swiftpm/xcode/xcshareddata/xcschemes/JourneyList.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/Features/JourneyList/Package.swift b/iOS/Features/JourneyList/Package.swift new file mode 100644 index 0000000..0920ee1 --- /dev/null +++ b/iOS/Features/JourneyList/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "JourneyList", + platforms: [ + .iOS(.v15) + ], + products: [ + .library(name: "JourneyList", + targets: ["JourneyList"]) + ], + dependencies: [ + .package(name: "MSData", + path: "../../MSData"), + .package(name: "MSUIKit", + path: "../../MSUIKit"), + .package(name: "MSNetworking", + path: "../../MSCoreKit") + ], + targets: [ + .target(name: "JourneyList", + dependencies: ["MSData", "MSUIKit", "MSNetworking"]) + ] +) diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift new file mode 100644 index 0000000..7b411a9 --- /dev/null +++ b/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift @@ -0,0 +1,37 @@ +// +// DTOConvertor.swift +// JourneyList +// +// Created by 이창준 on 2023.11.27. +// + +import MSData + +extension Journey { + + init(dto: JourneyDTO) { + self.id = dto.id + self.location = dto.location + self.date = dto.metaData.date + self.spots = dto.spots.map { Spot(dto: $0) } + self.song = Song(dto: dto.song) + } + +} + +extension Spot { + + init(dto: SpotDTO) { + self.photoURLs = dto.photoURLs + } + +} + +extension Song { + + init(dto: SongDTO) { + self.title = dto.title + self.artist = dto.artist + } + +} diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Model/Journey.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/Journey.swift new file mode 100644 index 0000000..f7a64e1 --- /dev/null +++ b/iOS/Features/JourneyList/Sources/JourneyList/Model/Journey.swift @@ -0,0 +1,44 @@ +// +// Journey.swift +// JourneyList +// +// Created by 이창준 on 11/23/23. +// + +import Foundation + +struct Journey: Hashable, Decodable { + + // MARK: - Properties + + let id: UUID + let location: String + let date: Date + let spots: [Spot] + let song: Song + + // MARK: - Initializer + + init(id: UUID = UUID(), location: String, date: Date, spots: [Spot], song: Song) { + self.id = id + self.location = location + self.date = date + self.spots = spots + self.song = song + } + +} + +// MARK: - Hashable + +extension Journey { + + static func == (lhs: Journey, rhs: Journey) -> Bool { + return lhs.id == rhs.id + } + + func hash(into hasher: inout Hasher) { + hasher.combine(self.id) + } + +} diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Model/Song.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/Song.swift new file mode 100644 index 0000000..cf990e9 --- /dev/null +++ b/iOS/Features/JourneyList/Sources/JourneyList/Model/Song.swift @@ -0,0 +1,15 @@ +// +// Song.swift +// JourneyList +// +// Created by 이창준 on 2023.11.27. +// + +import Foundation + +struct Song: Decodable { + + let artist: String + let title: String + +} diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Model/Spot.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/Spot.swift new file mode 100644 index 0000000..cd2eb00 --- /dev/null +++ b/iOS/Features/JourneyList/Sources/JourneyList/Model/Spot.swift @@ -0,0 +1,14 @@ +// +// Spot.swift +// JourneyList +// +// Created by 이창준 on 11/23/23. +// + +import Foundation + +struct Spot: Decodable { + + let photoURLs: [String] + +} diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift new file mode 100644 index 0000000..62d666a --- /dev/null +++ b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift @@ -0,0 +1,251 @@ +// +// JourneyListViewController.swift +// JourneyList +// +// Created by 이창준 on 11/22/23. +// + +import Combine +import UIKit + +import MSUIKit + +public final class JourneyListViewController: UIViewController { + + typealias JourneyListDataSource = UICollectionViewDiffableDataSource + typealias JourneyCellRegistration = UICollectionView.CellRegistration + typealias JourneySnapshot = NSDiffableDataSourceSnapshot + + // MARK: - Constants + + private enum Typo { + static let title: String = "지난 여정" + } + + private enum Metric { + static let titleStackSpacing: CGFloat = 4.0 + static let collectionViewHorizontalInset: CGFloat = 10.0 + static let collectionViewVerticalInset: CGFloat = 24.0 + static let interGroupSpacing: CGFloat = 12.0 + } + + // MARK: - Properties + + private var viewModel: JourneyListViewModel + + private var dataSource: JourneyListDataSource? + + private var currentSnapshot: JourneySnapshot? { + return self.dataSource?.snapshot() + } + + private var cancellables: Set = [] + + // MARK: - UI Components + + private let titleStack: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = Metric.titleStackSpacing + return stackView + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.headerTitle) + label.textColor = .msColor(.primaryTypo) + label.text = Typo.title + return label + }() + + private let subtitleLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.caption) + label.textColor = .msColor(.secondaryTypo) + label.text = "현재 위치에 0개의 여정이 있습니다." + return label + }() + + private let collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, + collectionViewLayout: UICollectionViewLayout()) + collectionView.backgroundColor = .clear + return collectionView + }() + + // MARK: - Initializer + + public init(viewModel: JourneyListViewModel, + nibName nibNameOrNil: String? = nil, + bundle nibBundleOrNil: Bundle? = nil) { + self.viewModel = viewModel + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + required init?(coder: NSCoder) { + fatalError("MusicSpot은 code-based로만 작업 중입니다.") + } + + // MARK: - Life Cycle + + public override func viewDidLoad() { + super.viewDidLoad() + self.configureStyle() + self.configureLayout() + self.configureCollectionView() + self.bind() + self.viewModel.trigger(.viewNeedsLoaded) + } + + // MARK: - Combine Binding + + func bind() { + self.viewModel.state.journeys + .receive(on: DispatchQueue.main) + .sink { journeys in + self.subtitleLabel.text = "현재 위치에 \(journeys.count)개의 여정이 있습니다." + var snapshot = JourneySnapshot() + snapshot.appendSections([.zero]) + snapshot.appendItems(journeys, toSection: .zero) + self.dataSource?.apply(snapshot) + } + .store(in: &self.cancellables) + } + +} + +// MARK: - CollectionView + +private extension JourneyListViewController { + + func configureCollectionView() { + let layout = UICollectionViewCompositionalLayout(sectionProvider: { _, _ in + return self.configureSection() + }) + + self.collectionView.setCollectionViewLayout(layout, animated: false) + self.dataSource = self.configureDataSource() + } + + func configureSection() -> NSCollectionLayoutSection { + let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: .fractionalHeight(1.0)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: .absolute(268.0)) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, + subitems: [item]) + + let section = NSCollectionLayoutSection(group: group) + section.interGroupSpacing = Metric.interGroupSpacing + + return section + } + + private func fetchImage(url: URL) async throws -> Data { + let request = URLRequest(url: url) + let (data, response) = try await URLSession.shared.data(for: request) + + guard let statusCode = (response as? HTTPURLResponse)?.statusCode, + (200...299).contains(statusCode) else { throw NSError(domain: "fetch error", code: 1004) } + + return data + } + + func configureDataSource() -> JourneyListDataSource { + // TODO: 최적화 & 캐싱 + let cellRegistration = JourneyCellRegistration { cell, _, itemIdentifier in + let cellModel = JourneyCellModel(location: itemIdentifier.location, + date: itemIdentifier.date, + songTitle: itemIdentifier.song.title, + songArtist: itemIdentifier.song.artist) + cell.update(with: cellModel) + let photoURLs = itemIdentifier.spots + .flatMap { $0.photoURLs } + .compactMap { URL(string: $0) } + + Task { + cell.addImageView(count: photoURLs.count) + + await withTaskGroup(of: (index: Int, imageData: Data).self) { group in + for (index, photoURL) in photoURLs.enumerated() { + group.addTask(priority: .background) { [weak self] in + guard let imageData = try? await self?.fetchImage(url: photoURL) else { + return (0, Data()) + } + return (index, imageData) + } + } + + for await (index, imageData) in group { + cell.updateImages(imageData: imageData, atIndex: index) + } + } + } + } + + let dataSource = JourneyListDataSource(collectionView: self.collectionView, + cellProvider: { collectionView, indexPath, item in + return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, + for: indexPath, + item: item) + }) + + return dataSource + } + +} + +// MARK: - UI Configuration + +private extension JourneyListViewController { + + func configureStyle() { + self.view.backgroundColor = .msColor(.primaryBackground) + } + + func configureLayout() { + self.view.addSubview(self.titleStack) + self.titleStack.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.titleStack.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), + self.titleStack.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor), + self.titleStack.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor) + ]) + + [ + self.titleLabel, + self.subtitleLabel + ].forEach { + self.titleStack.addArrangedSubview($0) + } + + self.view.addSubview(self.collectionView) + self.collectionView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.collectionView.topAnchor.constraint(equalTo: self.titleStack.bottomAnchor, + constant: Metric.collectionViewVerticalInset), + self.collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, + constant: Metric.collectionViewHorizontalInset), + self.collectionView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor), + self.collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, + constant: -Metric.collectionViewHorizontalInset) + ]) + } + +} + +// MARK: - Preview + +import MSData +import MSDesignSystem +import MSNetworking +@available(iOS 17, *) +#Preview { + MSFont.registerFonts() + let journeyRepository = JourneyRepositoryImplementation() + let testViewModel = JourneyListViewModel(repository: journeyRepository) + let testViewController = JourneyListViewController(viewModel: testViewModel) + return testViewController +} diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift new file mode 100644 index 0000000..d9c64f8 --- /dev/null +++ b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift @@ -0,0 +1,56 @@ +// +// JourneyListViewModel.swift +// JourneyList +// +// Created by 이창준 on 11/23/23. +// + +import Combine +import Foundation + +import MSData + +public final class JourneyListViewModel { + + public enum Action { + case viewNeedsLoaded + } + + public struct State { + var journeys = CurrentValueSubject<[Journey], Never>([]) + + public init() { } + } + + // MARK: - Properties + + public var state = State() + + private let repository: JourneyRepository + + // MARK: - Initializer + + public init(repository: JourneyRepository) { + self.repository = repository + } + + // MARK: - Functions + + func trigger(_ action: Action) { + switch action { + case .viewNeedsLoaded: + Task { + let result = await self.repository.fetchJourneyList() + + switch result { + case .success(let journeys): + let journeys = journeys.map { Journey(dto: $0) } + self.state.journeys.send(journeys) + case .failure(let error): + print(error) + } + } + } + } + +} diff --git a/iOS/Features/SaveJourney/.gitignore b/iOS/Features/SaveJourney/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/iOS/Features/SaveJourney/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/iOS/Features/SaveJourney/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iOS/Features/SaveJourney/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/iOS/Features/SaveJourney/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/iOS/Features/SaveJourney/.swiftpm/xcode/xcshareddata/xcschemes/SaveJourney.xcscheme b/iOS/Features/SaveJourney/.swiftpm/xcode/xcshareddata/xcschemes/SaveJourney.xcscheme new file mode 100644 index 0000000..3b465cc --- /dev/null +++ b/iOS/Features/SaveJourney/.swiftpm/xcode/xcshareddata/xcschemes/SaveJourney.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/Features/SaveJourney/Package.swift b/iOS/Features/SaveJourney/Package.swift new file mode 100644 index 0000000..490cd31 --- /dev/null +++ b/iOS/Features/SaveJourney/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "SaveJourney", + platforms: [ + .iOS(.v15) + ], + products: [ + .library(name: "SaveJourney", + targets: ["SaveJourney"]) + ], + dependencies: [ + .package(name: "MSUIKit", + path: "../../MSUIKit") + ], + targets: [ + .target(name: "SaveJourney", + dependencies: ["MSUIKit"]) + ] +) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift new file mode 100644 index 0000000..a5c1e7e --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift @@ -0,0 +1,14 @@ +// +// Journey.swift +// JourneyList +// +// Created by 이창준 on 11/23/23. +// + +import Foundation + +struct Journey: Hashable { + let locatoin: String + let date: String + let spot: Spot +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift new file mode 100644 index 0000000..840c77d --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -0,0 +1,281 @@ +// +// SaveJourneyViewController.swift +// SaveJourney +// +// Created by 이창준 on 11/24/23. +// + +import Combine +import MapKit +import UIKit + +import MSUIKit + +enum SaveJourneySection { + case music + case spot +} + +enum SaveJourneyItem: Hashable { + case music(String) + case spot(Journey) +} + +public final class SaveJourneyViewController: UIViewController { + + typealias SaveJourneyDataSource = UICollectionViewDiffableDataSource + typealias HeaderRegistration = UICollectionView.SupplementaryRegistration + typealias MusicCellRegistration = UICollectionView.CellRegistration + typealias JourneyCellRegistration = UICollectionView.CellRegistration + typealias SaveJourneySnapshot = NSDiffableDataSourceSnapshot + typealias MusicSnapshot = NSDiffableDataSourceSectionSnapshot + typealias SpotSnapshot = NSDiffableDataSourceSectionSnapshot + + // MARK: - Constants + + private enum Metric { + static let horizontalInset: CGFloat = 24.0 + static let verticalInset: CGFloat = 12.0 + static let innerGroupSpacing: CGFloat = 12.0 + static let headerTopInset: CGFloat = 24.0 + } + + // MARK: - Properties + + private let viewModel: SaveJourneyViewModel + + private var dataSource: SaveJourneyDataSource? + + private var cancellables: Set = [] + + // MARK: - UI Components + + private let mapView: MKMapView = { + let mapView = MKMapView() + return mapView + }() + + private lazy var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, + collectionViewLayout: UICollectionViewLayout()) + collectionView.backgroundColor = .clear + collectionView.contentInset = UIEdgeInsets(top: self.view.frame.width, + left: .zero, + bottom: .zero, + right: .zero) + collectionView.delegate = self + return collectionView + }() + + private var mapViewHeightConstraint: NSLayoutConstraint? + + // MARK: - Initializer + + public init(viewModel: SaveJourneyViewModel, + nibName nibNameOrNil: String? = nil, + bundle nibBundleOrNil: Bundle? = nil) { + self.viewModel = viewModel + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + required init?(coder: NSCoder) { + fatalError("MusicSpot은 code-based로만 작업 중입니다.") + } + + // MARK: - Life Cycle + + public override func viewDidLoad() { + super.viewDidLoad() + self.configureStyles() + self.configureLayout() + self.configureCollectionView() + self.bind() + self.viewModel.trigger(.viewNeedsLoaded) + } + + // MARK: - Combine Binding + + func bind() { + self.viewModel.state.music + .sink { music in + var snapshot = MusicSnapshot() + snapshot.append([.music(music)]) + self.dataSource?.apply(snapshot, to: .music) + } + .store(in: &self.cancellables) + + self.viewModel.state.journeys + .sink { journeys in + var snapshot = SpotSnapshot() + snapshot.append(journeys.map { .spot($0) }) + self.dataSource?.apply(snapshot, to: .spot) + } + .store(in: &self.cancellables) + } + +} + +// MARK: - Collection View + +private extension SaveJourneyViewController { + + func configureCollectionView() { + let layout = UICollectionViewCompositionalLayout(sectionProvider: { sectionIndex, _ in + guard let section = self.dataSource?.sectionIdentifier(for: sectionIndex) else { return .none } + return self.configureSection(for: section) + }) + layout.register(SaveJourneyBackgroundView.self, + forDecorationViewOfKind: SaveJourneyBackgroundView.elementKind) + + self.collectionView.setCollectionViewLayout(layout, animated: false) + self.dataSource = self.configureDataSource() + } + + func configureSection(for section: SaveJourneySection) -> NSCollectionLayoutSection { + let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: .fractionalHeight(1.0)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + let groupHeight: NSCollectionLayoutDimension = switch section { + case .music: .estimated(SaveJourneyMusicCell.estimatedHeight) + case .spot: .estimated(JourneyCell.estimatedHeight) + } + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: groupHeight) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, + subitems: [item]) + + let section = NSCollectionLayoutSection(group: group) + section.interGroupSpacing = Metric.innerGroupSpacing + section.contentInsets = NSDirectionalEdgeInsets(top: Metric.verticalInset, + leading: Metric.horizontalInset, + bottom: Metric.verticalInset, + trailing: Metric.horizontalInset) + + let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: .absolute(SaveJourneyHeaderView.estimatedHeight)) + let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, + elementKind: SaveJourneyHeaderView.elementKind, + alignment: .top) + header.contentInsets = NSDirectionalEdgeInsets(top: -Metric.headerTopInset, + leading: .zero, + bottom: .zero, + trailing: .zero) + section.boundarySupplementaryItems = [header] + + let backgroundView = NSCollectionLayoutDecorationItem.background( + elementKind: SaveJourneyBackgroundView.elementKind) + section.decorationItems = [backgroundView] + + return section + } + + func configureDataSource() -> SaveJourneyDataSource { + let musicCellRegistration = MusicCellRegistration { cell, _, itemIdentifier in + cell.update(with: itemIdentifier) + } + + let journeyCellRegistration = JourneyCellRegistration { cell, _, itemIdentifier in + + } + + let headerRegistration = HeaderRegistration(elementKind: SaveJourneyHeaderView.elementKind, + handler: { header, _, indexPath in + header.update(with: "헤더") + }) + + let dataSource = SaveJourneyDataSource(collectionView: self.collectionView, + cellProvider: { collectionView, indexPath, item in + switch item { + case .music(let music): + return collectionView.dequeueConfiguredReusableCell(using: musicCellRegistration, + for: indexPath, + item: music) + case .spot(let journey): + return collectionView.dequeueConfiguredReusableCell(using: journeyCellRegistration, + for: indexPath, + item: journey) + } + }) + + dataSource.supplementaryViewProvider = { collectionView, _, indexPath in + return collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, + for: indexPath) + } + + var snapshot = SaveJourneySnapshot() + snapshot.appendSections([.music, .spot]) + dataSource.apply(snapshot) + + return dataSource + } + +} + +// MARK: - CollectionView Scroll + +extension SaveJourneyViewController: UICollectionViewDelegate { + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + let offset = scrollView.contentOffset + self.updateProfileViewLayout(by: offset, name: "Change Me!") + } + + func updateProfileViewLayout(by offset: CGPoint, name: String) { + let collectionViewHeight = self.collectionView.frame.height + let collectionViewContentInset = collectionViewHeight - self.view.safeAreaInsets.top + let assistanceValue = collectionViewHeight - collectionViewContentInset + let isContentBelowTopOfScreen = offset.y < 0 + + if isContentBelowTopOfScreen { + self.navigationItem.title = nil + self.mapViewHeightConstraint?.constant = assistanceValue + offset.y.magnitude + } else if !isContentBelowTopOfScreen { + self.navigationItem.title = name + self.mapViewHeightConstraint?.constant = 0 + } else { + self.mapViewHeightConstraint?.constant = offset.y.magnitude + } + } + +} + +// MARK: - UI Configuration + +private extension SaveJourneyViewController { + + func configureStyles() { + self.view.backgroundColor = .msColor(.primaryBackground) + } + + func configureLayout() { + self.view.addSubview(self.mapView) + self.mapView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.mapView.topAnchor.constraint(equalTo: self.view.topAnchor), + self.mapView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + self.mapView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) + ]) + self.mapViewHeightConstraint = self.mapView.heightAnchor.constraint(equalTo: self.mapView.widthAnchor) + self.mapViewHeightConstraint?.isActive = true + + self.view.addSubview(self.collectionView) + self.collectionView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor), + self.collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), + self.collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) + ]) + } + +} + +// MARK: - Preview + +@available(iOS 17, *) +#Preview { + let saveJourneyViewModel = SaveJourneyViewModel() + let saveJourneyViewController = SaveJourneyViewController(viewModel: saveJourneyViewModel) + return saveJourneyViewController +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift new file mode 100644 index 0000000..837cd55 --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift @@ -0,0 +1,52 @@ +// +// SaveJourneyViewModel.swift +// SaveJourney +// +// Created by 이창준 on 11/24/23. +// + +import Combine + +public final class SaveJourneyViewModel { + + public enum Action { + case viewNeedsLoaded + } + + public struct State { + var music = CurrentValueSubject("") + var journeys = CurrentValueSubject<[Journey], Never>([]) + } + + // MARK: - Properties + + public var state = State() + + // MARK: - Initializer + + public init() { } + + // MARK: - Functions + + func trigger(_ action: Action) { + switch action { + case .viewNeedsLoaded: + self.fetchInitialJourneys() + } + } + +} + +private extension SaveJourneyViewModel { + + func fetchInitialJourneys() { + self.state.music.send("NewJeans") + self.state.journeys.send([Journey(locatoin: "여정 위치", + date: "2023. 01. 01", + spot: Spot(images: ["sdlkj", "sdklfj"])), + Journey(locatoin: "여정 위치", + date: "2023. 01. 02", + spot: Spot(images: ["slkjc", "llskl", "llskldf", "llskl5", "llskl12"]))]) + } + +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyBackgroundView.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyBackgroundView.swift new file mode 100644 index 0000000..b6a0974 --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyBackgroundView.swift @@ -0,0 +1,39 @@ +// +// SaveJourneyBackgroundView.swift +// SaveJourney +// +// Created by 이창준 on 11/24/23. +// + +import UIKit + +import MSDesignSystem + +final class SaveJourneyBackgroundView: UICollectionReusableView { + + // MARK: - Constants + + static let elementKind = "SaveJourneyBackgroundView" + + // MARK: - Initializer + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureStyles() + } + + required init?(coder: NSCoder) { + fatalError("MusicSpot은 code-based로만 작업 중입니다.") + } + +} + +// MARK: - UI Configuration + +private extension SaveJourneyBackgroundView { + + func configureStyles() { + self.backgroundColor = .msColor(.primaryBackground) + } + +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyHeaderView.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyHeaderView.swift new file mode 100644 index 0000000..3535ad9 --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyHeaderView.swift @@ -0,0 +1,62 @@ +// +// SaveJourneyHeaderView.swift +// SaveJourney +// +// Created by 이창준 on 11/25/23. +// + +import UIKit + +import MSDesignSystem + +final class SaveJourneyHeaderView: UICollectionReusableView { + + // MARK: - Constant + + static let elementKind = "SaveJourneyHeaderView" + static let estimatedHeight: CGFloat = 33.0 + + // MARK: - UI Components + + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.duperTitle) + label.textColor = .msColor(.primaryTypo) + label.text = "헤더" + return label + }() + + // MARK: - Initializer + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("MusicSpot은 code-based로만 작업 중입니다.") + } + + // MARK: - Functions + + func update(with title: String) { + self.titleLabel.text = title + } + +} + +// MARK: - UI Configuration + +private extension SaveJourneyHeaderView { + + func configureLayout() { + self.addSubview(self.titleLabel) + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.titleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.titleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor) + ]) + } + +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift new file mode 100644 index 0000000..93486c8 --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift @@ -0,0 +1,133 @@ +// +// SaveJourneyMusicCell.swift +// SaveJourney +// +// Created by 이창준 on 11/25/23. +// + +import UIKit + +import MSDesignSystem + +final class SaveJourneyMusicCell: UICollectionViewCell { + + // MARK: - Constants + + static let estimatedHeight: CGFloat = 152.0 + + private enum Metric { + static let cornerRadius: CGFloat = 12.0 + static let imageViewCornerRadius: CGFloat = 5.0 + static let imageViewSize: CGFloat = 128.0 + static let stackViewSpacing: CGFloat = 4.0 + static let iconImageSize: CGFloat = 24.0 + } + + // MARK: - UI Components + + private let albumArtImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + imageView.layer.cornerRadius = Metric.imageViewCornerRadius + imageView.clipsToBounds = true + imageView.backgroundColor = .systemBlue + return imageView + }() + + private let stackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = Metric.stackViewSpacing + stackView.alignment = .leading + return stackView + }() + + private let audioIconImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = .msIcon(.voice) + return imageView + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.boldCaption) + label.textColor = .msColor(.primaryTypo) + label.text = "Super Shy" + return label + }() + + private let artistLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.caption) + label.textColor = .msColor(.primaryTypo) + label.text = "NewJeans" + return label + }() + + // MARK: - Initializer + + public override init(frame: CGRect) { + super.init(frame: frame) + self.configureStyles() + self.configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("MusicSpot은 code-based로만 작업 중입니다.") + } + + // MARK: - Functions + + func update(with data: String) { + + } + +} + +// MARK: - UI Configuration + +private extension SaveJourneyMusicCell { + + func configureStyles() { + self.backgroundColor = .msColor(.componentBackground) + self.layer.cornerRadius = Metric.cornerRadius + self.clipsToBounds = true + } + + func configureLayout() { + self.addSubview(self.albumArtImageView) + self.albumArtImageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.albumArtImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor), + self.albumArtImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor, + constant: 12.0), + self.albumArtImageView.widthAnchor.constraint(equalToConstant: Metric.imageViewSize), + self.albumArtImageView.heightAnchor.constraint(equalToConstant: Metric.imageViewSize) + ]) + + self.addSubview(self.stackView) + self.stackView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor), + self.stackView.leadingAnchor.constraint(equalTo: self.albumArtImageView.trailingAnchor, + constant: 12.0), + self.stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor, + constant: 12.0) + ]) + + [ + self.audioIconImageView, + self.titleLabel, + self.artistLabel + ].forEach { + self.stackView.addArrangedSubview($0) + } + + self.audioIconImageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.audioIconImageView.widthAnchor.constraint(equalToConstant: Metric.iconImageSize), + self.audioIconImageView.heightAnchor.constraint(equalToConstant: Metric.iconImageSize) + ]) + } + +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Spot.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Spot.swift new file mode 100644 index 0000000..76cd258 --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Spot.swift @@ -0,0 +1,12 @@ +// +// Spot.swift +// JourneyList +// +// Created by 이창준 on 11/23/23. +// + +import Foundation + +struct Spot: Hashable { + let images: [String] +} diff --git a/iOS/Features/Spot/.gitignore b/iOS/Features/Spot/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/iOS/Features/Spot/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/iOS/Features/Spot/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iOS/Features/Spot/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/iOS/Features/Spot/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/iOS/Features/Spot/Package.swift b/iOS/Features/Spot/Package.swift new file mode 100644 index 0000000..a209d7a --- /dev/null +++ b/iOS/Features/Spot/Package.swift @@ -0,0 +1,53 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +// MARK: - Constants + +extension String { + static let package = "Spot" + static let spotView = "SpotView" + static let msUIKit = "MSUIKit" + static let msFoundation = "MSFoundation" + static let msDesignsystem = "MSDesignSystem" + static let msLogger = "MSLogger" + + var testTarget: String { + return self + "Tests" + } + + var path: String { + return "../../" + self + } + +} + +// MARK: - Package + +let package = Package( + name: .package, + platforms: [ + .iOS(.v15) + ], + products: [ + .library( + name: .spotView, + targets: [.spotView]) + ], + dependencies: [ + .package(path: .msUIKit.path), + .package(path: .msFoundation.path) + ], + targets: [ + // Codes + .target( + name: .spotView, + dependencies: [ + .product(name: .msUIKit, package: .msUIKit), + .product(name: .msDesignsystem, package: .msUIKit), + .product(name: .msLogger, package: .msFoundation)]) + + // Tests + ] +) diff --git a/iOS/Features/Spot/Sources/SpotView/SpotViewController.swift b/iOS/Features/Spot/Sources/SpotView/SpotViewController.swift new file mode 100644 index 0000000..60a49b5 --- /dev/null +++ b/iOS/Features/Spot/Sources/SpotView/SpotViewController.swift @@ -0,0 +1,240 @@ +// +// SpotViewController.swift +// Spot +// +// Created by 전민건 on 11/22/23. +// + +import UIKit + +import MSDesignSystem +import MSLogger +import MSUIKit + +public final class SpotViewController: UIViewController { + + // MARK: - Constants + + private enum Metric { + + // image view + enum ImageView { + static let height: CGFloat = 486.0 + static let inset: CGFloat = 4.0 + static let defaultIndex: Int = 0 + } + + // labels + enum TextLabel { + static let height: CGFloat = 24.0 + static let topInset: CGFloat = 30.0 + } + + enum SubTextLabel { + static let height: CGFloat = 42.0 + static let topInset: CGFloat = 12.0 + } + + // buttons + enum Button { + static let height: CGFloat = 120.0 + static let width: CGFloat = 120.0 + static let insetFromCenterX: CGFloat = 26.0 + static let bottomInset: CGFloat = 55.0 + } + + } + private enum Default { + + static let text: String = "이 사진을 스팟! 할까요?" + static let subText: String = "확정된 스팟은 변경할 수 없으며 \n 삭제만 가능합니다." + + } + + // MARK: - Properties + + private let imageView = UIImageView() + private let textView = UIView() + private let textLabel = UILabel() + private let subTextLabel = UILabel() + private let cancelButton = MSRectButton.large(isBrandColored: false) + private let completeButton = MSRectButton.large() + public var image: UIImage? { + didSet { + self.configureImageViewValue() + } + } + + // MARK: - Configure + + func configure() { + self.configureLayout() + self.configureStyle() + self.configureAction() + self.configureValue() + } + + // MARK: - UI Components: Layout + + private func configureLayout() { + self.configureImageViewLayout() + self.configureTextViewLayout() + self.configureLabelsLayout() + self.configureButtonsLayout() + } + + private func configureImageViewLayout() { + self.view.addSubview(self.imageView) + self.imageView.backgroundColor = .black + self.imageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.imageView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), + self.imageView.heightAnchor.constraint(equalToConstant: Metric.ImageView.height), + self.imageView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor), + self.imageView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor) + ]) + } + + private func configureTextViewLayout() { + self.view.addSubview(self.textView) + self.textView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.textView.topAnchor.constraint(equalTo: self.imageView.bottomAnchor), + self.textView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor), + self.textView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor), + self.textView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor) + ]) + } + + private func configureLabelsLayout() { + let labels: [UILabel] = [self.textLabel, self.subTextLabel] + labels.forEach { label in + self.view.addSubview(label) + label.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: self.view.centerXAnchor) + ]) + } + NSLayoutConstraint.activate([ + self.textLabel.heightAnchor.constraint(equalToConstant: Metric.TextLabel.height), + self.subTextLabel.heightAnchor.constraint(equalToConstant: Metric.SubTextLabel.height), + + self.textLabel.topAnchor.constraint(equalTo: self.imageView.bottomAnchor, + constant: Metric.TextLabel.topInset), + self.subTextLabel.topAnchor.constraint(equalTo: self.textLabel.bottomAnchor, + constant: Metric.SubTextLabel.topInset) + ]) + self.subTextLabel.textAlignment = .center + } + + private func configureButtonsLayout() { + let buttons: [MSRectButton] = [self.cancelButton, self.completeButton] + buttons.forEach { button in + self.view.addSubview(button) + button.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + button.heightAnchor.constraint(equalToConstant: Metric.Button.height), + button.widthAnchor.constraint(equalToConstant: Metric.Button.width), + button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, + constant: -Metric.Button.bottomInset) + ]) + } + NSLayoutConstraint.activate([ + self.cancelButton.trailingAnchor.constraint(equalTo: self.view.centerXAnchor, + constant: -Metric.Button.insetFromCenterX), + self.completeButton.leadingAnchor.constraint(equalTo: self.view.centerXAnchor, + constant: Metric.Button.insetFromCenterX) + ]) + } + + // MARK: - UI Components: Style + + private func configureStyle() { + self.view.backgroundColor = .msColor(.primaryBackground) + self.configureImageViewStyle() + self.configureLabelsStyle() + self.configureButtonsStyle() + } + + private func configureImageViewStyle() { + self.imageView.contentMode = .scaleAspectFill + } + + private func configureLabelsStyle() { + self.textLabel.font = .msFont(.subtitle) + self.subTextLabel.font = .msFont(.caption) + self.subTextLabel.textColor = .msColor(.secondaryTypo) + } + + private func configureButtonsStyle() { + self.cancelButton.image = .msIcon(.close) + self.completeButton.image = .msIcon(.check) + } + + // MARK: - Configure: Action + + private func configureAction() { + self.configureCancelAction() + self.configureCompleteAction() + } + + private func configureCancelAction() { + let cancelButtonAction = UIAction(handler: { _ in + self.cancelButtonTapped() + }) + self.cancelButton.addAction(cancelButtonAction, for: .touchUpInside) + } + + private func configureCompleteAction() { + let completeButtonAction = UIAction(handler: { _ in + self.completeButtonTapped() + }) + self.completeButton.addAction(completeButtonAction, for: .touchUpInside) + } + + // MARK: - Configure: Value + + private func configureValue() { + self.configureImageViewValue() + self.configureLabelsValue() + } + + private func configureImageViewValue() { + self.imageView.image = self.image + } + + private func configureLabelsValue() { + self.textLabel.text = Default.text + self.subTextLabel.text = Default.subText + + let multiLineConstant = 0 + self.subTextLabel.numberOfLines = multiLineConstant + } + + // MARK: - Life Cycle + + public override func viewDidLoad() { + super.viewDidLoad() + self.configure() + } + + // MARK: Actions + + private func cancelButtonTapped() { + + } + + private func completeButtonTapped() { + + } + +} + +// MARK: - Preview + +@available(iOS 17, *) +#Preview { + MSFont.registerFonts() + let viewController = SpotViewController() + return viewController +} diff --git a/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift b/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift deleted file mode 100644 index 66f3035..0000000 --- a/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// APIURL.swift -// MSCoreKit -// -// Created by 전민건 on 11/16/23. -// - -import Foundation - -enum APIbaseURL: String { - - //base URL 추가 - case none = "" -} - -enum APIpathURL: String { - - //path URL 추가 - case none = "" -} diff --git a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/CoordinateDTO.swift b/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/CoordinateDTO.swift deleted file mode 100644 index 2a90dad..0000000 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/CoordinateDTO.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// CoordinateDTO.swift -// MSCoreKit -// -// Created by 전민건 on 11/16/23. -// - -struct CoordinateDTO: Codable { - - let latitude: Double - let longitude: Double - -} diff --git a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SongDTO.swift b/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SongDTO.swift deleted file mode 100644 index 596e17b..0000000 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SongDTO.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// SongDTO.swift -// MSCoreKit -// -// Created by 전민건 on 11/16/23. -// - -import Foundation - -struct SongDTO: Codable { - - let id: UUID - let title: String - let artwork: String - -} diff --git a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SpotDTO.swift b/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SpotDTO.swift deleted file mode 100644 index 6ab1c68..0000000 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SpotDTO.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// SpotDTO.swift -// MSCoreKit -// -// Created by 전민건 on 11/16/23. -// - -import Foundation - -struct SpotDTO: Codable { - - let spotIdentifier: UUID - let coordinate: [Double] - let photo: Data? - let w3w: String - -} diff --git a/iOS/MSCoreKit/Sources/MSNetworking/DTO/JourneyDTO.swift b/iOS/MSCoreKit/Sources/MSNetworking/DTO/JourneyDTO.swift deleted file mode 100644 index 55f90fa..0000000 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/JourneyDTO.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// JourneyDTO.swift -// MSCoreKit -// -// Created by 전민건 on 11/16/23. -// - -import Foundation - -struct JourneyDTO: Codable { - - let journeyIdentifier: UUID - let title: String - let metaData: JourneyMetadataDTO - let spots: [SpotDTO] - let coordinates: [CoordinateDTO] - let song: SongDTO? - let lineColor: String - -} diff --git a/iOS/MSCoreKit/Sources/MSNetworking/DTO/PersonDTO.swift b/iOS/MSCoreKit/Sources/MSNetworking/DTO/PersonDTO.swift deleted file mode 100644 index 6735144..0000000 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/PersonDTO.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// PersonDTO.swift -// MSCoreKit -// -// Created by 전민건 on 11/16/23. -// - -import Foundation - -struct PersonDTO: Codable { - - let personIdentifier: UUID - let nickname: String - let journeys: [JourneyDTO] - let email: String - -} diff --git a/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift b/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift index 72d3761..59470c8 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift @@ -1,21 +1,33 @@ // // HTTPBody.swift -// +// MSCoreKit // // Created by 전민건 on 11/16/23. // import Foundation -struct HTTPBody { +public struct HTTPBody { + + // MARK: - Properties private let encoder = JSONEncoder() var content: Encodable? - func contentToData() -> Data? { - guard let content, let data = try? encoder.encode(content) else { + // MARK: - Initializer + + public init(content: Encodable? = nil) { + self.content = content + } + + // MARK: - Functions + + func data() -> Data? { + guard let content, + let data = try? self.encoder.encode(content) else { return nil } + return data } diff --git a/iOS/MSCoreKit/Sources/MSNetworking/HTTPHeader.swift b/iOS/MSCoreKit/Sources/MSNetworking/HTTPHeader.swift new file mode 100644 index 0000000..67d4fe0 --- /dev/null +++ b/iOS/MSCoreKit/Sources/MSNetworking/HTTPHeader.swift @@ -0,0 +1,11 @@ +// +// HTTPHeader.swift +// MSCoreKit +// +// Created by 이창준 on 11/26/23. +// + +public typealias HTTPHeaderKey = String +public typealias HTTPHeaderValue = String +public typealias HTTPHeader = (key: HTTPHeaderKey, value: HTTPHeaderValue) +public typealias HTTPHeaders = [HTTPHeader] diff --git a/iOS/MSCoreKit/Sources/MSNetworking/HTTPMethod.swift b/iOS/MSCoreKit/Sources/MSNetworking/HTTPMethod.swift index 23936eb..5afe787 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/HTTPMethod.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/HTTPMethod.swift @@ -5,9 +5,7 @@ // Created by 전민건 on 11/16/23. // -import Foundation - -enum HTTPMethod: String { +public enum HTTPMethod: String { case get = "GET" case post = "POST" diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift index 966c709..ff5d183 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift @@ -7,9 +7,30 @@ import Foundation -enum MSNetworkError: Error { +public enum MSNetworkError: Error { - case noResponse - case responseCode + case invalidRouter + case unknownResponse + case invalidStatusCode(statusCode: Int, description: String) + case timeout + +} + +extension MSNetworkError: Equatable { + + public static func == (lhs: MSNetworkError, rhs: MSNetworkError) -> Bool { + switch (lhs, rhs) { + case (.invalidRouter, .invalidRouter): + return true + case (.unknownResponse, .unknownResponse): + return true + case let (.invalidStatusCode(lhsStatusCode, _), .invalidStatusCode(rhsStatusCode, _)): + return lhsStatusCode == rhsStatusCode + case (.timeout, .timeout): + return true + default: + return false + } + } } diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift index 0a4cb98..43d027d 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift @@ -4,32 +4,66 @@ // // Created by 전민건 on 11/16/23. // + import Foundation import Combine public struct MSNetworking { - private let encoder = JSONEncoder() + public typealias TimeoutInterval = DispatchQueue.SchedulerTimeType.Stride + + // MARK: - Constants + + public static let dispatchQueueLabel = "com.MSNetworking.MSCoreKit.MusicSpot" + + // MARK: - Properties + + private let encoder: JSONEncoder = { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + return encoder + }() + + private let decoder: JSONDecoder = { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + return decoder + }() + private let session: Session + public let queue: DispatchQueue - func request(router: Router, type: T.Type) -> AnyPublisher? { - - guard let request: URLRequest = router.asURLRequest() else { - return nil + // MARK: - Initializer + + public init(session: Session) { + self.session = session + self.queue = DispatchQueue(label: MSNetworking.dispatchQueueLabel, qos: .background) + } + + // MARK: - Functions + + public func request(_ type: T.Type, + router: Router, + timeoutInterval: TimeoutInterval = .seconds(3)) -> AnyPublisher { + guard let request = router.request else { + return Fail(error: MSNetworkError.invalidRouter).eraseToAnyPublisher() } return session .dataTaskPublisher(for: request) - .tryMap { result -> T in - let value = try JSONDecoder().decode(T.self, from: result.data) - guard let response = result.response as? HTTPURLResponse else { - throw MSNetworkError.noResponse + .timeout(timeoutInterval, scheduler: self.queue) + .tryMap { data, response -> Data in + guard let response = response as? HTTPURLResponse else { + throw MSNetworkError.unknownResponse } - guard (200...299).contains(response.statusCode) else { - throw MSNetworkError.responseCode + guard 200..<300 ~= response.statusCode else { + throw MSNetworkError.invalidStatusCode(statusCode: response.statusCode, + description: response.description) } - return value + return data } + .decode(type: T.self, decoder: self.decoder) .eraseToAnyPublisher() } + } diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSRouter.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSRouter.swift deleted file mode 100644 index 62158bc..0000000 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSRouter.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// MSRouter.swift -// MSCoreKit -// -// Created by 전민건 on 11/16/23. -// - -import Foundation - -public struct RouterType { - - private var encodable: Encodable? - - //기능별 MSRouter - public var getJourney: MSRouter { - MSRouter(baseURL: .none, pathURL: .none, method: .get, body: HTTPBody(content: encodable)) - } - public var getPerson: MSRouter { - MSRouter(baseURL: .none, pathURL: .none, method: .get, body: HTTPBody(content: encodable)) - } - -} - -public struct MSRouter: Router { - - let baseURL: APIbaseURL - let pathURL: APIpathURL - let method: HTTPMethod - var body: HTTPBody - - func asURLRequest() -> URLRequest? { - guard let url = URL(string: baseURL.rawValue + pathURL.rawValue) else { - return nil - } - var request = URLRequest(url: url) - request.httpMethod = method.rawValue - request.httpBody = body.contentToData() - - return request - } - -} diff --git a/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift b/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift index f55dbe6..48e802c 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift @@ -7,12 +7,36 @@ import Foundation -protocol Router { +public protocol Router { - var baseURL: APIbaseURL { get } - var pathURL: APIpathURL { get } + var baseURL: String { get } + var pathURL: String { get } var method: HTTPMethod { get } - var body: HTTPBody { get } + var body: HTTPBody? { get } + var headers: HTTPHeaders? { get } + + var request: URLRequest? { get } + +} + +extension Router { + + public var request: URLRequest? { + guard let baseURL = URL(string: self.baseURL) else { return nil } + let url = baseURL.appendingPathComponent(self.pathURL) + + var request = URLRequest(url: url) + request.httpMethod = self.method.rawValue + if let body = self.body { + request.httpBody = body.data() + } + if let headers = self.headers { + headers.forEach { key, value in + request.addValue(value, forHTTPHeaderField: key) + } + } + + return request + } - func asURLRequest() -> URLRequest? } diff --git a/iOS/MSCoreKit/Sources/MSNetworking/Session.swift b/iOS/MSCoreKit/Sources/MSNetworking/Session.swift index af485bb..7ce10c3 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/Session.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/Session.swift @@ -7,12 +7,10 @@ import Foundation -protocol Session { +public protocol Session { func dataTaskPublisher(for request: URLRequest) -> URLSession.DataTaskPublisher } -extension URLSession: Session { - -} +extension URLSession: Session { } diff --git a/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift b/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift index 0f8e587..8fe0af0 100644 --- a/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift +++ b/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift @@ -5,33 +5,94 @@ // Created by 이창준 on 11/14/23. // -import XCTest import Combine -@testable import struct MSNetworking.JourneyDTO +import XCTest + +@testable import MSNetworking final class MSNetworkingTests: XCTestCase { - private let mockNetworking = MockMSNetworking() - private var subscriber: Set = [] - func test_get_JourneyDTO_성공() { + // MARK: - Properties + + private var networking: MSNetworking! + + private var cancellables: Set = [] + + // MARK: - Setup + + override func setUp() { + URLProtocol.registerClass(MockURLProtocol.self) + let configuration: URLSessionConfiguration = .ephemeral + configuration.protocolClasses?.insert(MockURLProtocol.self, at: .zero) + let session = URLSession(configuration: configuration) + self.networking = MSNetworking(session: session) + } + + // MARK: - Tests + + func test_MSNetworking_응답코드가_200번대일경우_정상() throws { // Arrange - let getJourneyRouter = MockRouterType().getJourney + let router = MockRouter() + let response = "Success" + let data = try JSONEncoder().encode(response) + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: URL(string: "https://api.codesquad.kr/api")!, + statusCode: 200, + httpVersion: nil, + headerFields: ["Content-Type": "application/json"])! + return (response, data) + } + + let expectation = XCTestExpectation() // Act - mockNetworking.request(mockRouter: getJourneyRouter, type: JourneyDTO.self) - .sink { response in - switch response { - case .failure: - XCTFail("no response") - default: - return + self.networking.request(String.self, router: router) + .receive(on: self.networking.queue) + .sink { completion in + if case .failure = completion { + XCTFail("200 번대 status code를 포함한 응답은 성공해야 합니다.") } - } receiveValue: { journeyDTO in - - // Assert - XCTAssertNotNil(journeyDTO) + } receiveValue: { value in + XCTAssertEqual("Success", value, "응답 값이 일치하지 않습니다.") + expectation.fulfill() } - .store(in: &subscriber) + .store(in: &self.cancellables) + + // Assert + wait(for: [expectation], timeout: 5.0) + } + + func test_MSNetworking_응답코드가_200번대가아닐경우_에러() { + // Arrange + let router = MockRouter() + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: URL(string: "https://api.codesquad.kr/api")!, + statusCode: 404, + httpVersion: nil, + headerFields: ["Content-Type": "application/json"])! + return (response, Data()) + } + + let expectation = XCTestExpectation() + + // Act + self.networking.request(String.self, router: router) + .receive(on: self.networking.queue) + .sink { completion in + if case .failure(let error) = completion { + //swiftlint:disable force_cast + XCTAssertEqual(error as! MSNetworkError, + MSNetworkError.invalidStatusCode(statusCode: 404, description: ""), + "404 status code 응답은 invalidStatusCode 에러를 발생시켜야 합니다.") + expectation.fulfill() + } + } receiveValue: { _ in + XCTFail("200 ~ 299 외의 status code를 포함한 응답은 에러를 발생시켜야 합니다.") + } + .store(in: &self.cancellables) + + // Assert + wait(for: [expectation], timeout: 5.0) } } diff --git a/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockMSNetworking.swift b/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockMSNetworking.swift deleted file mode 100644 index fd3dc61..0000000 --- a/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockMSNetworking.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// MockMSNetworking.swift -// MSCoreKit -// -// Created by 전민건 on 11/16/23. -// - -import Foundation -import Combine -@testable import struct MSNetworking.JourneyDTO -@testable import struct MSNetworking.JourneyMetadataDTO - -public class MockMSNetworking { - - var expectedSuccessJourneyGetPublisher: Result.Publisher { - let journey = JourneyDTO(journeyIdentifier: UUID(), - title: "", - metaData: JourneyMetadataDTO(date: .now), - spots: [], - coordinates: [], - song: nil, - lineColor: "") - - guard let jsonData = try? JSONEncoder().encode(journey) else { - return Just(Data()).setFailureType(to: Error.self) - } - - let request = Just(jsonData) - .setFailureType(to: Error.self) - - return request - } - - func request(mockRouter: MockRouter, type: T.Type) -> AnyPublisher { - - var publisher: Result.Publisher - - switch mockRouter { - case .getJourney: - publisher = expectedSuccessJourneyGetPublisher - } - - return publisher.tryMap { result -> T in - let value = try JSONDecoder().decode(T.self, from: result) - return value - } - .eraseToAnyPublisher() - } - -} diff --git a/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockRouter.swift b/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockRouter.swift index 3d01652..9ebe2ec 100644 --- a/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockRouter.swift +++ b/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockRouter.swift @@ -6,15 +6,24 @@ // import Foundation +@testable import MSNetworking -public struct MockRouterType { +struct MockRouter: Router { - public let getJourney = MockRouter.getJourney + var baseURL: String { + return "https://www.naver.com" + } -} - -public enum MockRouter { + var pathURL: String { + return "api" + } + + var method: HTTPMethod { + return .get + } + + var body: HTTPBody? - case getJourney + var headers: HTTPHeaders? } diff --git a/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockURLProtocol.swift b/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockURLProtocol.swift new file mode 100644 index 0000000..74893b1 --- /dev/null +++ b/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockURLProtocol.swift @@ -0,0 +1,54 @@ +// +// MockURLProtocol.swift +// MSCoreKit +// +// Created by 이창준 on 2023.11.27. +// + +import Foundation + +final class MockURLProtocol: URLProtocol { + + static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))? + static var delaySimulation: TimeInterval = 0 + static var requestObserver: ((URLRequest) -> Void)? + + override class func canInit(with request: URLRequest) -> Bool { + MockURLProtocol.requestObserver?(request) + return true + } + + override class func canonicalRequest(for request: URLRequest) -> URLRequest { + return request + } + + override func startLoading() { + DispatchQueue.global().asyncAfter(deadline: .now() + MockURLProtocol.delaySimulation) { + guard let handler = MockURLProtocol.requestHandler else { + assertionFailure("Received unexpected request with no handler set") + return + } + + do { + let (response, data) = try handler(self.request) + self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + self.client?.urlProtocol(self, didLoad: data) + self.client?.urlProtocolDidFinishLoading(self) + } catch { + self.client?.urlProtocol(self, didFailWithError: error) + } + } + } + + override func stopLoading() { } + + static func simulateDelay(_ delay: TimeInterval) { + self.delaySimulation = delay + } + + static func observeRequests(_ observer: @escaping (URLRequest) -> Void) { + self.requestObserver = observer + } + +} + diff --git a/iOS/MSData/.gitignore b/iOS/MSData/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/iOS/MSData/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/iOS/MSData/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iOS/MSData/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/iOS/MSData/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSData.xcscheme b/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSData.xcscheme new file mode 100644 index 0000000..a2a888c --- /dev/null +++ b/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSData.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSDataTests.xcscheme b/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSDataTests.xcscheme new file mode 100644 index 0000000..c0fb639 --- /dev/null +++ b/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSDataTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/MSData/Package.swift b/iOS/MSData/Package.swift new file mode 100644 index 0000000..44287f7 --- /dev/null +++ b/iOS/MSData/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "MSData", + platforms: [ + .iOS(.v15) + ], + products: [ + .library(name: "MSData", + targets: ["MSData"]) + ], + dependencies: [ + .package(name: "MSNetworking", + path: "../MSCoreKit") + ], + targets: [ + .target(name: "MSData", + dependencies: ["MSNetworking"], + resources: [.process("Resources")]), + .testTarget(name: "MSDataTests", + dependencies: ["MSData"]) + ] +) diff --git a/iOS/MSData/Sources/MSData/DTO/Fragment/CoordinateDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/CoordinateDTO.swift new file mode 100644 index 0000000..354ec8c --- /dev/null +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/CoordinateDTO.swift @@ -0,0 +1,13 @@ +// +// CoordinateDTO.swift +// MSCoreKit +// +// Created by 전민건 on 11/16/23. +// + +public struct CoordinateDTO: Codable { + + public let latitude: Double + public let longitude: Double + +} diff --git a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/JourneyMetadataDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/JourneyMetadataDTO.swift similarity index 63% rename from iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/JourneyMetadataDTO.swift rename to iOS/MSData/Sources/MSData/DTO/Fragment/JourneyMetadataDTO.swift index 19e8ba3..6d0e5f5 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/JourneyMetadataDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/JourneyMetadataDTO.swift @@ -7,8 +7,8 @@ import Foundation -struct JourneyMetadataDTO: Codable { +public struct JourneyMetadataDTO: Codable { - let date: Date + public let date: Date } diff --git a/iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift new file mode 100644 index 0000000..af9efcc --- /dev/null +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift @@ -0,0 +1,17 @@ +// +// SongDTO.swift +// MSCoreKit +// +// Created by 전민건 on 11/16/23. +// + +import Foundation + +public struct SongDTO: Codable, Identifiable { + + public let id: UUID + public let title: String + public let artwork: String + public let artist: String + +} diff --git a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift new file mode 100644 index 0000000..3e00aa6 --- /dev/null +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift @@ -0,0 +1,16 @@ +// +// SpotDTO.swift +// MSCoreKit +// +// Created by 전민건 on 11/16/23. +// + +import Foundation + +public struct SpotDTO: Codable, Identifiable { + + public let id: UUID + public let coordinate: [Double] + public let photoURLs: [String] + +} diff --git a/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift b/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift new file mode 100644 index 0000000..a52949a --- /dev/null +++ b/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift @@ -0,0 +1,20 @@ +// +// JourneyDTO.swift +// MSCoreKit +// +// Created by 전민건 on 11/16/23. +// + +import Foundation + +public struct JourneyDTO: Codable, Identifiable { + + public let id: UUID + public let location: String + public let metaData: JourneyMetadataDTO + public let spots: [SpotDTO] + public let coordinates: [CoordinateDTO] + public let song: SongDTO + public let lineColor: String + +} diff --git a/iOS/MSData/Sources/MSData/DTO/PersonDTO.swift b/iOS/MSData/Sources/MSData/DTO/PersonDTO.swift new file mode 100644 index 0000000..1319988 --- /dev/null +++ b/iOS/MSData/Sources/MSData/DTO/PersonDTO.swift @@ -0,0 +1,17 @@ +// +// PersonDTO.swift +// MSCoreKit +// +// Created by 전민건 on 11/16/23. +// + +import Foundation + +public struct PersonDTO: Codable, Identifiable { + + public let id: UUID + public let nickname: String + public let journeys: [JourneyDTO] + public let email: String + +} diff --git a/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift b/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift new file mode 100644 index 0000000..aa28ce4 --- /dev/null +++ b/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift @@ -0,0 +1,69 @@ +// +// JourneyRepository.swift +// MSData +// +// Created by 이창준 on 11/26/23. +// + +import Combine +import Foundation + +import MSNetworking + +public protocol JourneyRepository { + func fetchJourneyList() async -> Result<[JourneyDTO], Error> +} + +public struct JourneyRepositoryImplementation: JourneyRepository { + + // MARK: - Properties + + private let networking: MSNetworking + + // MARK: - Initializer + + public init(session: URLSession = URLSession(configuration: .default)) { + self.networking = MSNetworking(session: session) + } + + // MARK: - Functions + + public func fetchJourneyList() async -> Result<[JourneyDTO], Error> { + #if DEBUG + guard let jsonURL = Bundle.module.url(forResource: "MockJourney", withExtension: "json") else { + return .failure((MSNetworkError.invalidRouter)) + } + do { + let jsonData = try Data(contentsOf: jsonURL) + let decoder = JSONDecoder() + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + decoder.dateDecodingStrategy = .formatted(dateFormatter) + let journeys = try decoder.decode([JourneyDTO].self, from: jsonData) + return .success(journeys) + } catch { + print(error) + } + #else + return await withCheckedContinuation { continuation in + var cancellable: AnyCancellable? + cancellable = self.networking.request([JourneyDTO].self, router: JourneyRouter.journeyList) + .sink { completion in + switch completion { + case .finished: + continuation.resume(returning: .failure(MSNetworkError.timeout)) + case .failure(let error): + continuation.resume(returning: .failure(error)) + } + cancellable?.cancel() + } receiveValue: { journeys in + continuation.resume(returning: .success(journeys)) + cancellable?.cancel() + } + } + #endif + + return .failure(MSNetworkError.unknownResponse) + } + +} diff --git a/iOS/MSData/Sources/MSData/Resources/APIInfo.plist b/iOS/MSData/Sources/MSData/Resources/APIInfo.plist new file mode 100644 index 0000000..a788343 --- /dev/null +++ b/iOS/MSData/Sources/MSData/Resources/APIInfo.plist @@ -0,0 +1,8 @@ + + + + + BaseURL + https://api.com + + diff --git a/iOS/MSData/Sources/MSData/Resources/MockJourney.json b/iOS/MSData/Sources/MSData/Resources/MockJourney.json new file mode 100644 index 0000000..6160ff9 --- /dev/null +++ b/iOS/MSData/Sources/MSData/Resources/MockJourney.json @@ -0,0 +1,653 @@ +[ + { + "id": "20008aad-362f-4e43-b4e4-4adfee08a30d", + "location": "서울시 강남구", + "metaData": { + "date": "2023-10-28T00:00:00" + }, + "spots": [ + { + "id": "389354bc-7c14-402b-90ce-ff9b1e6aec13", + "coordinate": [ + 74.34012768689522, + -20.0591261579182 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "ede775e3-7ec4-4b92-a314-a8682d00c3f6", + "coordinate": [ + 39.79236622936409, + -20.288943745440292 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "82fff66b-538b-4c4d-9f0c-cf0b10098374", + "coordinate": [ + -61.2994089681573, + 147.81682997676376 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + } + ], + "coordinates": [ + { + "latitude": 57.465627107728835, + "longitude": 21.259425432982624 + }, + { + "latitude": -53.09543651781735, + "longitude": -127.7430923166473 + }, + { + "latitude": -77.73569106610123, + "longitude": 175.41162341151937 + }, + { + "latitude": 5.183073888694608, + "longitude": -97.39973919319664 + } + ], + "song": { + "id": "421a73d5-64b5-4907-9726-fc263da4e34b", + "title": "Super Shy", + "artwork": "Artwork URL", + "artist": "NewJeans" + }, + "lineColor": "Color 0" + }, + { + "id": "8f98238a-3dec-458d-9a03-984e8cae332d", + "location": "부천시 원미구", + "metaData": { + "date": "2022-04-04T00:00:00" + }, + "spots": [ + { + "id": "8e4fd5a5-d5bc-4fe7-aaf5-e0de9da96685", + "coordinate": [ + 31.52421333174138, + -43.89463635754254 + ], + "photoURLs": [ + "https://picsum.photos/200/300" + ] + }, + { + "id": "272fdb4c-6aca-45dc-b00c-528e6204e294", + "coordinate": [ + -68.4588807563729, + 43.959766770510186 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "d210ee40-8895-46d5-8f5b-85c31b441d8a", + "coordinate": [ + -34.28043419715898, + -164.73303980456268 + ], + "photoURLs": [ + "https://picsum.photos/200/300" + ] + }, + { + "id": "e330d5f1-45e4-4dfa-a2a6-291e6be56f35", + "coordinate": [ + -66.19095010133054, + -8.569810942887017 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "68140671-237f-48d1-9888-4eb2d0d02b2c", + "coordinate": [ + -55.72402145238744, + -18.60154681858242 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + } + ], + "coordinates": [ + { + "latitude": 43.82013766554985, + "longitude": 173.00171490769134 + }, + { + "latitude": -29.320190959438754, + "longitude": -28.21501987990979 + }, + { + "latitude": 87.99211291043426, + "longitude": 50.670604577768586 + }, + { + "latitude": 28.003576208636588, + "longitude": -164.59912441639696 + } + ], + "song": { + "id": "f4ec694c-c577-4e1b-a7f8-c5d984537881", + "title": "OMG", + "artwork": "Artwork URL", + "artist": "NewJeans" + }, + "lineColor": "Color 1" + }, + { + "id": "2367bcf6-972f-432d-837a-0cc4d434eb9a", + "location": "수원시 팔달구", + "metaData": { + "date": "2023-11-07T00:00:00" + }, + "spots": [ + { + "id": "d76c335b-e410-4fea-9270-e417c4f57c0f", + "coordinate": [ + -64.81468160689631, + -76.60199610190303 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "dc7436fc-58d2-422e-85ea-f7542198c0d5", + "coordinate": [ + 73.79998313510268, + 68.35171492227144 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "6732642e-1972-4c85-8697-3d3ddc2eb1c0", + "coordinate": [ + 8.909582214448818, + -151.7541955219229 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + } + ], + "coordinates": [ + { + "latitude": 33.314649021558964, + "longitude": -144.60966101329134 + }, + { + "latitude": -17.28334467846844, + "longitude": 96.71348559491207 + } + ], + "song": { + "id": "c7ffc956-fd68-45fa-9523-0a365b379d90", + "title": "Either Way", + "artwork": "Artwork URL", + "artist": "IVE" + }, + "lineColor": "Color 2" + }, + { + "id": "d2765c97-f216-4ab2-80e2-0f947145aaf0", + "location": "서울시 마포구", + "metaData": { + "date": "2023-11-03T00:00:00" + }, + "spots": [ + { + "id": "6d3ee7b2-32af-4d64-8704-a8b0fd2254cb", + "coordinate": [ + 53.33285523016107, + 158.99249150635194 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + } + ], + "coordinates": [ + { + "latitude": -57.08651016285746, + "longitude": 125.44987777466031 + }, + { + "latitude": -65.0654246205996, + "longitude": 82.56458972940237 + } + ], + "song": { + "id": "0fe57f3a-4623-4064-88ac-a1fe09ecf7e9", + "title": "이브, 프시케 그리고 푸른 수염의 아내", + "artwork": "Artwork URL", + "artist": "LE SSERAFIM" + }, + "lineColor": "Color 3" + }, + { + "id": "53c0e005-c721-44ca-9765-ee4b742cd675", + "location": "고양시 덕양구", + "metaData": { + "date": "2023-02-13T00:00:00" + }, + "spots": [ + { + "id": "1f25a310-5e5d-42c7-918f-a61a0187db68", + "coordinate": [ + -65.01383977190413, + 25.7298686850134 + ], + "photoURLs": [ + "https://picsum.photos/200/300" + ] + }, + { + "id": "833719bd-7b54-489b-b3e7-9bc6b667dc5e", + "coordinate": [ + -2.9616202692170788, + -90.07641195961408 + ], + "photoURLs": [ + "https://picsum.photos/200/300" + ] + } + ], + "coordinates": [ + { + "latitude": -67.51343427177022, + "longitude": 17.431624937453194 + }, + { + "latitude": 17.539925914331036, + "longitude": 127.209259846463 + }, + { + "latitude": -19.192151423397974, + "longitude": -94.047267104666 + } + ], + "song": { + "id": "35c87451-46ea-4bce-96ac-57604530994d", + "title": "Attention", + "artwork": "Artwork URL", + "artist": "NewJeans" + }, + "lineColor": "Color 4" + }, + { + "id": "a4a00895-0afc-43fd-9ad9-b215dddbe816", + "location": "서울시 영등포구", + "metaData": { + "date": "2023-01-19T00:00:00" + }, + "spots": [ + { + "id": "24ca84aa-6a80-47c0-8302-6a0924791c80", + "coordinate": [ + 47.16742284659284, + 13.225978927134577 + ], + "photoURLs": [ + "https://picsum.photos/200/300" + ] + }, + { + "id": "2f812435-5d79-4e49-bee1-51a541b7e2bc", + "coordinate": [ + 52.537466139282714, + -91.92574821724574 + ], + "photoURLs": [ + "https://picsum.photos/200/300" + ] + }, + { + "id": "b52aa54e-8ca2-4581-aa03-b3b50fc9e031", + "coordinate": [ + -34.68755334180694, + -104.4740708009648 + ], + "photoURLs": [ + "https://picsum.photos/200/300" + ] + }, + { + "id": "0a416cf3-f957-4372-9cae-5a1997161c3b", + "coordinate": [ + 81.38993174770837, + -119.94575572765291 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "3a3869bf-a149-4ae2-8936-61ef4025921f", + "coordinate": [ + 26.00204566583929, + -38.88354925355324 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + } + ], + "coordinates": [ + { + "latitude": -31.696373986551137, + "longitude": -44.326735402336965 + }, + { + "latitude": -36.571803691977, + "longitude": 79.89271047045634 + }, + { + "latitude": -50.98418938820977, + "longitude": -37.3881976010955 + }, + { + "latitude": 41.7173317046923, + "longitude": -165.96283512867097 + } + ], + "song": { + "id": "d1488750-4120-4a9a-999b-a87a45953ca5", + "title": "Drama", + "artwork": "Artwork URL", + "artist": "aespa" + }, + "lineColor": "Color 5" + }, + { + "id": "93260b82-153a-4f5a-a5d8-ec58f1a7ad71", + "location": "인천시 연수구", + "metaData": { + "date": "2020-12-07T00:00:00" + }, + "spots": [ + { + "id": "5ee04c1e-d914-4fe0-bafc-6410415323a0", + "coordinate": [ + -72.15636930638713, + -136.0116257911593 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "e4445b1a-d4b8-4215-a50e-c3088d18d7d8", + "coordinate": [ + 86.14073588308193, + 71.3073252728758 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "d8af22f2-e004-4d37-a6ca-6ad99c6c7d89", + "coordinate": [ + 77.87222808724792, + -3.792694490590094 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + } + ], + "coordinates": [ + { + "latitude": 8.80229997752501, + "longitude": 47.63625800444882 + }, + { + "latitude": -35.63401796013899, + "longitude": 118.02930151637929 + }, + { + "latitude": 4.774969630280225, + "longitude": -38.19033863439523 + }, + { + "latitude": 17.40994536703667, + "longitude": -123.27816118997819 + } + ], + "song": { + "id": "87e57ea4-ea53-405b-9631-c4d10f6dc4fc", + "title": "Chill Kill", + "artwork": "Artwork URL", + "artist": "Red Velvet" + }, + "lineColor": "Color 6" + }, + { + "id": "d26426b1-aec5-4f51-8a23-6f1401fffb52", + "location": "인천시 중구", + "metaData": { + "date": "2022-10-14T00:00:00" + }, + "spots": [ + { + "id": "70a868dc-52d4-4a6c-aefa-8f4d2db72bbd", + "coordinate": [ + -67.2052661143819, + -103.79187685194488 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + } + ], + "coordinates": [ + { + "latitude": -17.049476729920784, + "longitude": -95.60934011188559 + }, + { + "latitude": -86.08561003130464, + "longitude": -3.220089719655789 + }, + { + "latitude": -4.114875686387336, + "longitude": 89.03707513766813 + } + ], + "song": { + "id": "99955ba8-a619-468a-8d82-5f49b7494c56", + "title": "DIE 4 YOU", + "artwork": "Artwork URL", + "artist": "DEAN" + }, + "lineColor": "Color 7" + }, + { + "id": "7311e698-f313-4acc-bf18-a080233b78ae", + "location": "천안시 서북구", + "metaData": { + "date": "2023-12-25T00:00:00" + }, + "spots": [ + { + "id": "ce8fb5a9-cf2e-44bb-a695-07ef19df8684", + "coordinate": [ + 26.754371020802807, + -136.87790951732427 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "2a5bcf5d-ff5c-458f-ad3d-1f3560a01f05", + "coordinate": [ + -31.05255299691919, + 109.12110052412726 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "b8736196-293e-4674-9b49-882419739bc2", + "coordinate": [ + 44.11921868914504, + -1.3713120422063696 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + }, + { + "id": "4fac5c54-a3d3-4a2e-b7bc-b9a24f6f716f", + "coordinate": [ + -9.650099854349605, + 63.87852947086256 + ], + "photoURLs": [ + "https://picsum.photos/200/300" + ] + }, + { + "id": "24f129a0-fdef-41e3-8590-a70de1568f09", + "coordinate": [ + -54.74979632677334, + 165.518236994507 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + } + ], + "coordinates": [ + { + "latitude": 14.132893106998878, + "longitude": -22.231429270383558 + }, + { + "latitude": 1.7423769434293916, + "longitude": -35.145660538473976 + }, + { + "latitude": 55.22007564767648, + "longitude": 44.828484292139535 + } + ], + "song": { + "id": "d9698b7c-20ad-4af5-97d9-42f83a1654c5", + "title": "All I Want for Christmas Is You", + "artwork": "Artwork URL", + "artist": "Mariah Carey" + }, + "lineColor": "Color 8" + }, + { + "id": "bec2b75d-7782-47b6-a67e-0067e49bbca8", + "location": "전주시 완산구", + "metaData": { + "date": "2023-07-14T00:00:00" + }, + "spots": [ + { + "id": "25ac53b9-86d2-4935-99a7-1cf2f362f6e1", + "coordinate": [ + -12.855147544861794, + -44.31218853533764 + ], + "photoURLs": [ + "https://picsum.photos/200/300", + "https://picsum.photos/200/300", + "https://picsum.photos/200/300" + ] + } + ], + "coordinates": [ + { + "latitude": 23.29047112271624, + "longitude": -120.86503806882197 + }, + { + "latitude": 6.1062416592369715, + "longitude": -116.13348365253415 + }, + { + "latitude": -4.550860523478789, + "longitude": -168.32578052239512 + }, + { + "latitude": -61.40086403396403, + "longitude": 101.95422471280716 + } + ], + "song": { + "id": "d21f4f50-6fee-4584-ab23-3a5a73879156", + "title": "ETA", + "artwork": "Artwork URL", + "artist": "NewJeans" + }, + "lineColor": "Color 9" + } +] diff --git a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Body.swift b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Body.swift new file mode 100644 index 0000000..e855e5e --- /dev/null +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Body.swift @@ -0,0 +1,18 @@ +// +// JourneyRouter+Body.swift +// MSData +// +// Created by 이창준 on 11/26/23. +// + +import MSNetworking + +extension JourneyRouter { + + public var body: HTTPBody? { + switch self { + case .journeyList: return nil + } + } + +} diff --git a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Header.swift b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Header.swift new file mode 100644 index 0000000..99da37c --- /dev/null +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Header.swift @@ -0,0 +1,18 @@ +// +// JourneyRouter+Header.swift +// MSData +// +// Created by 이창준 on 11/26/23. +// + +import MSNetworking + +extension JourneyRouter { + + public var headers: HTTPHeaders? { + switch self { + case .journeyList: return nil + } + } + +} diff --git a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Method.swift b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Method.swift new file mode 100644 index 0000000..5a3f632 --- /dev/null +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Method.swift @@ -0,0 +1,18 @@ +// +// JourneyRouter+Method.swift +// MSData +// +// Created by 이창준 on 11/26/23. +// + +import MSNetworking + +extension JourneyRouter { + + public var method: HTTPMethod { + switch self { + case .journeyList: return .get + } + } + +} diff --git a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift new file mode 100644 index 0000000..01fc237 --- /dev/null +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift @@ -0,0 +1,31 @@ +// +// JourneyRouter+BaseURL.swift +// MSData +// +// Created by 이창준 on 11/26/23. +// + +import Foundation + +import MSNetworking + +extension JourneyRouter { + + public var baseURL: String { + guard let url = Bundle.module.url(forResource: "APIInfo", withExtension: "plist"), + let data = try? Data(contentsOf: url), + let dict = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any], + let urlString = dict["BaseURL"] as? String else { + fatalError("BaseURL을 가져오는데 실패했습니다.") + } + + return urlString + } + + public var pathURL: String { + switch self { + case .journeyList: return "" + } + } + +} diff --git a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter.swift b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter.swift new file mode 100644 index 0000000..51f6462 --- /dev/null +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter.swift @@ -0,0 +1,14 @@ +// +// JourneyRouter.swift +// JourneyList +// +// Created by 이창준 on 11/26/23. +// + +import MSNetworking + +public enum JourneyRouter: Router { + + case journeyList + +} diff --git a/iOS/MSData/Tests/MSDataTests/MSDataTests.swift b/iOS/MSData/Tests/MSDataTests/MSDataTests.swift new file mode 100644 index 0000000..18cf9e1 --- /dev/null +++ b/iOS/MSData/Tests/MSDataTests/MSDataTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import MSData + +final class MSDataTests: XCTestCase { + func testExample() throws { + // XCTest Documentation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + } +} diff --git a/iOS/MSFoundation/.swiftpm/xcode/xcshareddata/xcschemes/MSUserDefaults.xcscheme b/iOS/MSFoundation/.swiftpm/xcode/xcshareddata/xcschemes/MSUserDefaults.xcscheme new file mode 100644 index 0000000..8d8af66 --- /dev/null +++ b/iOS/MSFoundation/.swiftpm/xcode/xcshareddata/xcschemes/MSUserDefaults.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/MSFoundation/Tests/MSLoggerTests/MSLoggerTests.swift b/iOS/MSFoundation/Tests/MSLoggerTests/MSLoggerTests.swift index b01caba..48d5b00 100644 --- a/iOS/MSFoundation/Tests/MSLoggerTests/MSLoggerTests.swift +++ b/iOS/MSFoundation/Tests/MSLoggerTests/MSLoggerTests.swift @@ -11,10 +11,10 @@ import MSLogger final class MSLoggerTests: XCTestCase { func test_Logger객체_잘_생성되는지_성공() { - //arrange, act + // arrange, act let logger = MSLogger.make(category: .login) - //assert + // assert XCTAssertNotNil(logger) } } diff --git a/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystem.xcscheme b/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystem.xcscheme new file mode 100644 index 0000000..e15b2aa --- /dev/null +++ b/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystem.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystemTests.xcscheme b/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystemTests.xcscheme new file mode 100644 index 0000000..fea2408 --- /dev/null +++ b/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystemTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSUIKit.xcscheme b/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSUIKit.xcscheme new file mode 100644 index 0000000..d686240 --- /dev/null +++ b/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSUIKit.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/MSUIKit/Package.swift b/iOS/MSUIKit/Package.swift index 0e5acca..cbdd1bc 100644 --- a/iOS/MSUIKit/Package.swift +++ b/iOS/MSUIKit/Package.swift @@ -33,7 +33,7 @@ let package = Package( // Codes .target(name: .designSystem, resources: [ - .process("../MSDesignSystem/Resources") + .process("../\(String.designSystem)/Resources") ]), .target(name: .uiKit, dependencies: ["MSDesignSystem"]), diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift b/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift index 4a901e4..93222f7 100644 --- a/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift +++ b/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift @@ -18,6 +18,9 @@ public enum MSColor: String { case secondaryTypo = "Typo Secondary" case secondaryButtonTypo = "Button Typo Secondary" + case componentBackground = "Component Background" + case componentTypo = "Component Typo" + case musicSpot = "MusicSpot" } diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/MSFont.swift b/iOS/MSUIKit/Sources/MSDesignSystem/MSFont.swift index 721bd37..77374bb 100644 --- a/iOS/MSUIKit/Sources/MSDesignSystem/MSFont.swift +++ b/iOS/MSUIKit/Sources/MSDesignSystem/MSFont.swift @@ -14,6 +14,7 @@ public enum MSFont { case subtitle case buttonTitle case paragraph + case boldCaption case caption // MARK: - Functions @@ -26,6 +27,7 @@ public enum MSFont { case .subtitle: return ("Pretendard-Bold", 20.0) case .buttonTitle: return ("Pretendard-SemiBold", 20.0) case .paragraph: return ("Pretendard-Regular", 17.0) + case .boldCaption: return ("Pretendard-SemiBold", 13.0) case .caption: return ("Pretendard-Regular", 13.0) } } diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Component Background.colorset/Contents.json b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Component Background.colorset/Contents.json new file mode 100644 index 0000000..8daabe6 --- /dev/null +++ b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Component Background.colorset/Contents.json @@ -0,0 +1,56 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.700", + "blue" : "0xF1", + "green" : "0xED", + "red" : "0xF0" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.700", + "blue" : "0x3C", + "green" : "0x38", + "red" : "0x3E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Component Typo.colorset/Contents.json b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Component Typo.colorset/Contents.json new file mode 100644 index 0000000..03bca55 --- /dev/null +++ b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Component Typo.colorset/Contents.json @@ -0,0 +1,56 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.600", + "blue" : "0x43", + "green" : "0x3C", + "red" : "0x3C" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.600", + "blue" : "0xF5", + "green" : "0xEB", + "red" : "0xEB" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calender.imageset/Calender 1.pdf b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calendar.imageset/Calendar.pdf similarity index 100% rename from iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calender.imageset/Calender 1.pdf rename to iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calendar.imageset/Calendar.pdf diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calender.imageset/Contents.json b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calendar.imageset/Contents.json similarity index 84% rename from iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calender.imageset/Contents.json rename to iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calendar.imageset/Contents.json index 5413f5e..b70f44b 100644 --- a/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calender.imageset/Contents.json +++ b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calendar.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Calender.pdf", + "filename" : "Calendar.pdf", "idiom" : "iphone" } ], diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calender.imageset/Calender.pdf b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calender.imageset/Calender.pdf deleted file mode 100644 index 88304860eadd0b5156d61d69bf15113f536951ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4018 zcmbtXTaOzx6n>vy;g?D+QXJdISE;H*w-g~jlr3)+4AH90t zj+=*J{bKy?lCr)#0?= z4^EqCP4dd>;DgCQJphZv$6^(JG{I9)q1mJ76VxQxdFic4h6O%cgXs z%r*$kb|^X!M6lP5&%#0q#n1)ReiV({0dzCReAq2O@xa=vU|0@1f8BG)b64T8G)wN% z1X={7(@Caa3R2~*w;6u4gxa9V1TqY@tOF{Pi;KNDU`j;7k#7UgQH(h$K#5NUCg=8B z(uT_^0z$#C46r60P4N^Js+TBbA_Xja;{q_N$r?(5u|hsZuU1Z{8c1*>^f3@K3%CPl zLrET#!VS^Igt$~ zQ^HkiV#x?G+()URDWx~i@7d#n;Zi6>#U&1ZLlECtS!Y~%>Q+3=Jc~25+y*vdvq;WNh6cqShXs%DM7nnT+9ndx~@}k+htUY zrv)AN6Uu~=U>I1i4o;R*v5cG-Ho2ilKNQXAMRy_kl(07;tmHVsuvSh^Sw`PCPQMyQs6i;3MWhuG_XIgxPFY9OfX(8FnEC!A~FkXQ4t;G zHY^Tvy+f_MPE|XiF7q_K#6BwF9a={wJqi?|;BI&r0&*J? zMnwl}slY*QYwRTKAgm2kPlKekmta9=VgcPOjYnHm(X|SM!19=z1PY44Vu)MsFcuKl z#ty6vAz0J2Twr3(1={RhNEDxe&AJ8Sm3IYl%-(+WW%CA`5c3lA{L}NM5Be201MUM< zJQ4%gMrR{BgpbK5v^EIPRS}z+L2p7p70-^NN*P2`A`>;nh@3@ZvD^SQ!rI_)5;~%S z&g9J1goZ&6Ii@6C42GU%0l3p(+XjZY(9s`QHxOkbtOSVN2Lmu5c`Z?}l@e7QQ>5BH z5p4>7`$W*D*%3~KW zwjKP23$h%mXD%mGH)dO+92hbd0aJ)vTepiW{Evl$0o``zCvSnUaU!Mp@PKk`7!n@Q z5?;lrH+;YRNy*C9Z`<2hFzHf}B#7u^zGg03Vlrm8#E@N{5z^kW>%;~@xRq)bFLtDO z!y~UsS|Cjy^L-qL+JRrzfS(cnxql39+RXyApUfSJ8=y9xB)Hrc_3K$d(cuEfGj(xK z=M^}GwM!F7v{u&ZJalFWUsoY<@1_U@d1A7FfSQwC+5(bnbULxGr-PqY?d>!_wD?Um zJ-~3eO!EFh74&!6`s@Ava6YLwzv7*SZ~enkI{pR`MZ}q0%lf-oA z=VID@Y?M_>xqt_m$4iwwyla{c}N9ZXg1 z!L6G0+#EiD*IwCw0-SL~vd9ypbyJY?QIKCZ>O2brH{8wSl;)(;f zeHg#>r1k<~-d#et#Nz?SpHPu6A2Z_8jm%wAC0zAp=zQGV@5bYRSM~b47YN7W)8S!! sq8{JhJ(+^CzC9eyiXE;2UcbKocZBuzb-g*Bt2?+O#o^JTZ+?3DAM-LHR{#J2 diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift new file mode 100644 index 0000000..9323264 --- /dev/null +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift @@ -0,0 +1,160 @@ +// +// JourneyCell.swift +// MSUIKit +// +// Created by 이창준 on 11/24/23. +// + +import UIKit + +import MSDesignSystem + +public final class JourneyCell: UICollectionViewCell { + + // MARK: - Constants + + public static let estimatedHeight: CGFloat = 268.0 + + private enum Metric { + static let cornerRadius: CGFloat = 12.0 + static let spacing: CGFloat = 5.0 + static let verticalInset: CGFloat = 20.0 + static let horizontalInset: CGFloat = 16.0 + } + + // MARK: - UI Components + + private let infoView = JourneyInfoView() + + private let scrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.showsVerticalScrollIndicator = false + scrollView.showsHorizontalScrollIndicator = false + scrollView.alwaysBounceHorizontal = true + return scrollView + }() + + let spotImageStack: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = Metric.spacing + return stackView + }() + + // MARK: - Initializer + + public override init(frame: CGRect) { + super.init(frame: frame) + self.configureStyles() + self.configureLayout() + } + + public required init?(coder: NSCoder) { + fatalError("MusicSpot은 code-based로만 작업 중입니다.") + } + + public override func prepareForReuse() { + self.spotImageStack.arrangedSubviews.forEach { + $0.removeFromSuperview() + } + } + + // MARK: - Functions + + public func update(with model: JourneyCellModel) { + self.infoView.update(location: model.location, + date: model.date, + title: model.song.title, + artist: model.song.artist) + } + + @MainActor + public func addImageView(count: Int) { + (1...count).forEach { _ in + let imageView = SpotPhotoImageView() + self.spotImageStack.addArrangedSubview(imageView) + } + } + + @MainActor + public func updateImages(imageData: Data, atIndex index: Int) { + guard self.spotImageStack.arrangedSubviews.count > index else { + return + } + guard let imageView = self.spotImageStack.arrangedSubviews[index] as? SpotPhotoImageView else { + return + } + + imageView.update(with: imageData) + } + +} + +// MARK: - UI Configuration + +private extension JourneyCell { + + func configureStyles() { + self.backgroundColor = .msColor(.componentBackground) + self.layer.cornerRadius = Metric.cornerRadius + self.clipsToBounds = true + } + + func configureLayout() { + self.addSubview(self.infoView) + self.infoView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.infoView.topAnchor.constraint(equalTo: self.topAnchor, + constant: Metric.verticalInset), + self.infoView.leadingAnchor.constraint(equalTo: self.leadingAnchor, + constant: Metric.horizontalInset), + self.infoView.trailingAnchor.constraint(equalTo: self.trailingAnchor, + constant: -Metric.horizontalInset) + ]) + + self.addSubview(self.scrollView) + self.scrollView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.scrollView.topAnchor.constraint(equalTo: self.infoView.bottomAnchor, + constant: Metric.spacing), + self.scrollView.leadingAnchor.constraint(equalTo: self.leadingAnchor, + constant: Metric.horizontalInset), + self.scrollView.trailingAnchor.constraint(equalTo: self.trailingAnchor, + constant: -Metric.horizontalInset), + self.scrollView.bottomAnchor.constraint(equalTo: self.bottomAnchor, + constant: -Metric.verticalInset) + ]) + + self.scrollView.addSubview(self.spotImageStack) + self.spotImageStack.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.spotImageStack.topAnchor.constraint(equalTo: self.scrollView.topAnchor), + self.spotImageStack.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor), + self.spotImageStack.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor), + self.spotImageStack.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor), + self.spotImageStack.heightAnchor.constraint(equalTo: self.scrollView.heightAnchor) + ]) + } + +} + +// MARK: - Preview + +@available(iOS 17, *) +#Preview(traits: .fixedLayout(width: 373.0, height: 268.0)) { + MSFont.registerFonts() + + let cell = JourneyCell() + NSLayoutConstraint.activate([ + cell.widthAnchor.constraint(equalToConstant: 373.0), + cell.heightAnchor.constraint(equalToConstant: 268.0) + ]) + + (1...10).forEach { _ in + let imageView = SpotPhotoImageView() + imageView.backgroundColor = .systemBlue + cell.spotImageStack.addArrangedSubview(imageView) + } + + return cell +} diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCellModel.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCellModel.swift new file mode 100644 index 0000000..c63d7e7 --- /dev/null +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCellModel.swift @@ -0,0 +1,51 @@ +// +// JourneyCellModel.swift +// JourneyList +// +// Created by 이창준 on 11/23/23. +// + +import Foundation + +public struct JourneyCellModel: Hashable { + + public struct Song { + let artist: String + let title: String + } + + // MARK: - Properties + + let id: UUID + let location: String + let date: Date + let song: Song + + // MARK: - Initializer + + public init(id: UUID = UUID(), + location: String, + date: Date, + songTitle: String, + songArtist: String) { + self.id = id + self.location = location + self.date = date + self.song = Song(artist: songArtist, title: songTitle) + } + +} + +// MARK: - Hashable + +extension JourneyCellModel { + + public static func == (lhs: JourneyCellModel, rhs: JourneyCellModel) -> Bool { + return lhs.id == rhs.id + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(self.id) + } + +} diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyInfoView.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyInfoView.swift new file mode 100644 index 0000000..8c20d87 --- /dev/null +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyInfoView.swift @@ -0,0 +1,133 @@ +// +// JourneyInfoView.swift +// MSUIKit +// +// Created by 이창준 on 11/24/23. +// + +import UIKit + +import MSDesignSystem + +final class JourneyInfoView: UIView { + + // MARK: - Constants + + private enum Metric { + static let contentSpacing: CGFloat = 5.0 + static let labelStackSpacing: CGFloat = 4.0 + static let subLabelStackSpacing: CGFloat = 8.0 + } + + // MARK: - UI Components + + private let contentStack: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = Metric.contentSpacing + return stackView + }() + + private let titleLabelStack: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = Metric.labelStackSpacing + stackView.alignment = .leading + return stackView + }() + + private let subLabelStack: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = Metric.subLabelStackSpacing + return stackView + }() + + private let locationTitleLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.subtitle) + label.textColor = .msColor(.primaryTypo) + label.setContentHuggingPriority(.defaultHigh, for: .vertical) + label.text = "여정 위치" + return label + }() + + private let dateLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.caption) + label.textColor = .msColor(.secondaryTypo) + label.setContentHuggingPriority(.defaultHigh, for: .vertical) + label.text = "2023. 01. 01" + return label + }() + + private let w3wLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.caption) + label.textColor = .msColor(.secondaryTypo) + label.text = "하늘.사다.비싼" + return label + }() + + private let musicInfoView = MusicInfoView() + + // MARK: - Initializer + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("MusicSpot은 code-based로만 작업 중입니다.") + } + + // MARK: - Functions + + func update(location: String, + date: Date, + w3w: String = "", + title: String, + artist: String) { + self.locationTitleLabel.text = location + self.dateLabel.text = date.formatted(date: .abbreviated, time: .omitted) + self.w3wLabel.text = w3w + self.musicInfoView.update(artist: artist, title: title) + } + +} + +// MARK: - UI Configuration + +private extension JourneyInfoView { + + func configureLayout() { + self.addSubview(self.contentStack) + self.contentStack.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.contentStack.topAnchor.constraint(equalTo: self.topAnchor), + self.contentStack.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.contentStack.trailingAnchor.constraint(equalTo: self.trailingAnchor), + self.contentStack.bottomAnchor.constraint(equalTo: self.bottomAnchor) + ]) + + self.contentStack.addArrangedSubview(self.titleLabelStack) + self.contentStack.addArrangedSubview(self.musicInfoView) + + self.titleLabelStack.addArrangedSubview(self.locationTitleLabel) + self.titleLabelStack.addArrangedSubview(self.subLabelStack) + + self.subLabelStack.addArrangedSubview(self.dateLabel) + self.subLabelStack.addArrangedSubview(self.w3wLabel) + } + +} + +// MARK: - Preview + +@available(iOS 17.0, *) +#Preview(traits: .fixedLayout(width: 341.0, height: 73.0)) { + MSFont.registerFonts() + let header = JourneyInfoView() + return header +} diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift new file mode 100644 index 0000000..c53caf6 --- /dev/null +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift @@ -0,0 +1,131 @@ +// +// MusicInfoView.swift +// JourneyList +// +// Created by 이창준 on 11/23/23. +// + +import UIKit + +final class MusicInfoView: UIView { + + // MARK: - Constants + + private enum Metric { + static let spacing: CGFloat = 8.0 + static let iconSize: CGFloat = 24.0 + } + + // MARK: - UI Components + + private let stackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = Metric.spacing + return stackView + }() + + private let musicInfoStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + return stackView + }() + + private let iconImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = .msIcon(.voice) + imageView.tintColor = .msColor(.primaryTypo) + return imageView + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.boldCaption) + label.textColor = .msColor(.primaryTypo) + label.text = "Title" + return label + }() + + private let dividerLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.caption) + label.textColor = .msColor(.primaryTypo) + label.text = "・" + return label + }() + + private let artistLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.caption) + label.textColor = .msColor(.primaryTypo) + label.text = "Artist" + return label + }() + + // MARK: - Initializer + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("MusicSpot은 code-based로만 작업 중입니다.") + } + + // MARK: - Functions + + func update(artist: String, title: String) { + self.artistLabel.text = artist + self.titleLabel.text = title + } + +} + +// MARK: - UI Configuration + +private extension MusicInfoView { + + func configureLayout() { + self.addSubview(self.stackView) + self.stackView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.stackView.topAnchor.constraint(equalTo: self.topAnchor), + self.stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor), + self.stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor) + ]) + + [ + Spacer(.horizontal), + self.iconImageView, + self.musicInfoStackView + ].forEach { + self.stackView.addArrangedSubview($0) + } + self.iconImageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.iconImageView.widthAnchor.constraint(equalToConstant: Metric.iconSize), + self.iconImageView.heightAnchor.constraint(equalToConstant: Metric.iconSize) + ]) + + [ + self.titleLabel, + self.dividerLabel, + self.artistLabel + ].forEach { + self.musicInfoStackView.addArrangedSubview($0) + } + } + +} + +// MARK: - Preview + +import MSDesignSystem +@available(iOS 17.0, *) +#Preview(traits: .fixedLayout(width: 341.0, height: 24.0)) { + MSFont.registerFonts() + let musicInfoView = MusicInfoView() + return musicInfoView +} diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/SpotPhotoImageView.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/SpotPhotoImageView.swift new file mode 100644 index 0000000..bbf2a88 --- /dev/null +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/SpotPhotoImageView.swift @@ -0,0 +1,85 @@ +// +// SpotPhotoImageView.swift +// JourneyList +// +// Created by 이창준 on 11/23/23. +// + +import UIKit + +import MSDesignSystem + +final class SpotPhotoImageView: UIView { + + // MARK: - Constants + + private enum Metric { + static let width: CGFloat = 120.0 + static let height: CGFloat = 150.0 + static let cornerRadius: CGFloat = 5.0 + } + + // MARK: - UI Components + + private let imageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + return imageView + }() + + // MARK: - Initializer + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureStyle() + self.configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("MusicSpot은 code-based로만 작업 중입니다.") + } + + // MARK: - Functions + + func update(with imageData: Data) { + self.imageView.image = UIImage(data: imageData) + } + +} + +// MARK: - UI Configuration + +private extension SpotPhotoImageView { + + func configureStyle() { + self.backgroundColor = .msColor(.secondaryBackground) + self.layer.cornerRadius = Metric.cornerRadius + self.clipsToBounds = true + } + + func configureLayout() { + self.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.widthAnchor.constraint(equalToConstant: Metric.width), + self.heightAnchor.constraint(equalToConstant: Metric.height) + ]) + + self.addSubview(self.imageView) + self.imageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.imageView.topAnchor.constraint(equalTo: self.topAnchor), + self.imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor) + ]) + } + +} + +// MARK: - Preview + +@available(iOS 17.0, *) +#Preview { + let cell = SpotPhotoImageView() + return cell +} diff --git a/iOS/MSUIKit/Sources/MSUIKit/MSUIComponents.swift b/iOS/MSUIKit/Sources/MSUIKit/MSUIComponents.swift new file mode 100644 index 0000000..dc701bb --- /dev/null +++ b/iOS/MSUIKit/Sources/MSUIKit/MSUIComponents.swift @@ -0,0 +1,8 @@ +// +// MSUIComponents.swift +// MSUIKit +// +// Created by 이창준 on 11/14/23. +// + +import UIKit diff --git a/iOS/MSUIKit/Sources/MSUIKit/Spacer.swift b/iOS/MSUIKit/Sources/MSUIKit/Spacer.swift new file mode 100644 index 0000000..aef78ca --- /dev/null +++ b/iOS/MSUIKit/Sources/MSUIKit/Spacer.swift @@ -0,0 +1,26 @@ +// +// Spacer.swift +// MSUIKit +// +// Created by 이창준 on 11/23/23. +// + +import UIKit + +public final class Spacer: UIView { + + private override init(frame: CGRect = .zero) { + super.init(frame: frame) + } + + public convenience init(_ axis: NSLayoutConstraint.Axis) { + self.init() + setContentHuggingPriority(.defaultLow, for: axis) + backgroundColor = .clear + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + +} diff --git a/iOS/MusicSpot.xcworkspace/contents.xcworkspacedata b/iOS/MusicSpot.xcworkspace/contents.xcworkspacedata index 8cad135..397a516 100644 --- a/iOS/MusicSpot.xcworkspace/contents.xcworkspacedata +++ b/iOS/MusicSpot.xcworkspace/contents.xcworkspacedata @@ -1,22 +1,25 @@ + + + location = "group:MSUIKit/../MusicSpot/MusicSpot.xcodeproj"> + location = "group:MSData"> + location = "group:MSUIKit"> + location = "group:MSCoreKit"> + location = "group:MSFoundation"> diff --git a/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj b/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj index 2a1c470..4754a22 100644 --- a/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj +++ b/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj @@ -3,18 +3,16 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ - 21B1B63D2B04C7CF00A63287 /* FoundationExt in Frameworks */ = {isa = PBXBuildFile; productRef = 21B1B63C2B04C7CF00A63287 /* FoundationExt */; }; - 21B1B63F2B04C7CF00A63287 /* MSLogger in Frameworks */ = {isa = PBXBuildFile; productRef = 21B1B63E2B04C7CF00A63287 /* MSLogger */; }; - 21B1B6412B04C7CF00A63287 /* MSUserDefaults in Frameworks */ = {isa = PBXBuildFile; productRef = 21B1B6402B04C7CF00A63287 /* MSUserDefaults */; }; - 21B1B6442B04D64A00A63287 /* MSNetwork in Frameworks */ = {isa = PBXBuildFile; productRef = 21B1B6432B04D64A00A63287 /* MSNetwork */; }; DD73F8592B024C4900EE9BF2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73F8582B024C4900EE9BF2 /* AppDelegate.swift */; }; DD73F85B2B024C4900EE9BF2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73F85A2B024C4900EE9BF2 /* SceneDelegate.swift */; }; DD73F8622B024C4B00EE9BF2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DD73F8612B024C4B00EE9BF2 /* Assets.xcassets */; }; DD73F8652B024C4B00EE9BF2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DD73F8632B024C4B00EE9BF2 /* LaunchScreen.storyboard */; }; + DDE77E192B131FFE005927B0 /* MSDesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = DDE77E182B131FFE005927B0 /* MSDesignSystem */; }; + DDE77E1C2B13200B005927B0 /* JourneyList in Frameworks */ = {isa = PBXBuildFile; productRef = DDE77E1B2B13200B005927B0 /* JourneyList */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -32,28 +30,20 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 21B1B63F2B04C7CF00A63287 /* MSLogger in Frameworks */, - 21B1B6412B04C7CF00A63287 /* MSUserDefaults in Frameworks */, - 21B1B63D2B04C7CF00A63287 /* FoundationExt in Frameworks */, - 21B1B6442B04D64A00A63287 /* MSNetwork in Frameworks */, + DDE77E192B131FFE005927B0 /* MSDesignSystem in Frameworks */, + DDE77E1C2B13200B005927B0 /* JourneyList in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 2137DBFD2B063A1500395C06 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; DD73F84C2B024C4900EE9BF2 = { isa = PBXGroup; children = ( DD73F8572B024C4900EE9BF2 /* MusicSpot */, DD73F8562B024C4900EE9BF2 /* Products */, + DDE77E1D2B1320B9005927B0 /* Frameworks */, ); sourceTree = ""; }; @@ -77,6 +67,13 @@ path = MusicSpot; sourceTree = ""; }; + DDE77E1D2B1320B9005927B0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -94,10 +91,8 @@ ); name = MusicSpot; packageProductDependencies = ( - 21B1B63C2B04C7CF00A63287 /* FoundationExt */, - 21B1B63E2B04C7CF00A63287 /* MSLogger */, - 21B1B6402B04C7CF00A63287 /* MSUserDefaults */, - 21B1B6432B04D64A00A63287 /* MSNetwork */, + DDE77E182B131FFE005927B0 /* MSDesignSystem */, + DDE77E1B2B13200B005927B0 /* JourneyList */, ); productName = MusicSpot; productReference = DD73F8552B024C4900EE9BF2 /* MusicSpot.app */; @@ -128,8 +123,8 @@ ); mainGroup = DD73F84C2B024C4900EE9BF2; packageReferences = ( - 21B1B63B2B04C7CF00A63287 /* XCLocalSwiftPackageReference "../MSFoundation" */, - 21B1B6422B04D64A00A63287 /* XCLocalSwiftPackageReference "../MSNetwork" */, + DDE77E172B131FFE005927B0 /* XCLocalSwiftPackageReference "../MSUIKit" */, + DDE77E1A2B13200B005927B0 /* XCLocalSwiftPackageReference "../Features/JourneyList" */, ); productRefGroup = DD73F8562B024C4900EE9BF2 /* Products */; projectDirPath = ""; @@ -388,32 +383,24 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 21B1B63B2B04C7CF00A63287 /* XCLocalSwiftPackageReference "../MSFoundation" */ = { + DDE77E172B131FFE005927B0 /* XCLocalSwiftPackageReference "../MSUIKit" */ = { isa = XCLocalSwiftPackageReference; - relativePath = ../MSFoundation; + relativePath = ../MSUIKit; }; - 21B1B6422B04D64A00A63287 /* XCLocalSwiftPackageReference "../MSNetwork" */ = { + DDE77E1A2B13200B005927B0 /* XCLocalSwiftPackageReference "../Features/JourneyList" */ = { isa = XCLocalSwiftPackageReference; - relativePath = ../MSNetwork; + relativePath = ../Features/JourneyList; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 21B1B63C2B04C7CF00A63287 /* FoundationExt */ = { - isa = XCSwiftPackageProductDependency; - productName = FoundationExt; - }; - 21B1B63E2B04C7CF00A63287 /* MSLogger */ = { - isa = XCSwiftPackageProductDependency; - productName = MSLogger; - }; - 21B1B6402B04C7CF00A63287 /* MSUserDefaults */ = { + DDE77E182B131FFE005927B0 /* MSDesignSystem */ = { isa = XCSwiftPackageProductDependency; - productName = MSUserDefaults; + productName = MSDesignSystem; }; - 21B1B6432B04D64A00A63287 /* MSNetwork */ = { + DDE77E1B2B13200B005927B0 /* JourneyList */ = { isa = XCSwiftPackageProductDependency; - productName = MSNetwork; + productName = JourneyList; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/iOS/MusicSpot/MusicSpot.xcodeproj/xcshareddata/xcschemes/MusicSpot.xcscheme b/iOS/MusicSpot/MusicSpot.xcodeproj/xcshareddata/xcschemes/MusicSpot.xcscheme new file mode 100644 index 0000000..7eb04dc --- /dev/null +++ b/iOS/MusicSpot/MusicSpot.xcodeproj/xcshareddata/xcschemes/MusicSpot.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift index b01638c..caa9c12 100644 --- a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift +++ b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift @@ -7,6 +7,10 @@ import UIKit +import JourneyList +import MSData +import MSDesignSystem + class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? @@ -18,7 +22,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let window = UIWindow(windowScene: windowScene) defer { self.window = window } - let testViewController = UIViewController() + MSFont.registerFonts() + + let journeyRepository = JourneyRepositoryImplementation() + let testViewModel = JourneyListViewModel(repository: journeyRepository) + let testViewController = JourneyListViewController(viewModel: testViewModel) window.rootViewController = testViewController window.makeKeyAndVisible() } diff --git a/iOS/commit b/iOS/commit index 144cec4..8a1701c 100755 --- a/iOS/commit +++ b/iOS/commit @@ -1,13 +1,16 @@ #!/bin/sh - +​ LINTPATH='.swiftlint.yml' -declare -a PATHS=("MSCoreKit" "MSFoundation" "MSUIKit" "MusicSpot") +declare -a PATHS=("MSCoreKit" "MSFoundation" "MSUIKit" "MusicSpot" "MSData" "Features") failures="" - +​ for path in "${PATHS[@]}"; do if [ -d "$path" ]; then echo "👀 Running SwiftLint for $path" result=$(swiftlint lint --progress --config $LINTPATH $path) + if [[ "$result" == *warning* ]]; then + echo "🚧 Lint Warning: \n$result" + fi if [[ ! "$result" == *error* ]]; then echo "✅ Lint succeed for $path\n" else @@ -19,11 +22,11 @@ for path in "${PATHS[@]}"; do exit 1 fi done - +​ if [ ! -z "$failures" ]; then echo "$failures" exit 1 else echo "✨ All linting checks passed. Ready to commit." gitmoji -c -fi +fi \ No newline at end of file From bfbdfec01ab6a095e6f68c932752f9a991d648a9 Mon Sep 17 00:00:00 2001 From: mingun Date: Wed, 29 Nov 2023 22:05:28 +0900 Subject: [PATCH 02/27] =?UTF-8?q?:sparkles:=20=EC=B9=B4=EB=A9=94=EB=9D=BC?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contents.xcworkspacedata | 7 + iOS/Features/Spot/Package.swift | 13 +- .../Spot/Sources/CameraView/CameraView.swift | 45 ++++ .../CameraView/CameraViewController.swift | 249 ++++++++++++++++++ .../Sources/CameraView/CameraViewModel.swift | 145 ++++++++++ .../CameraView/Protocol/Positionable.swift | 21 ++ .../CameraViewModel/CameraViewModel.swift | 23 ++ .../Sources/SpotView/SpotViewController.swift | 3 +- .../Sources/MSLogger/MSLogCategory.swift | 1 + 9 files changed, 504 insertions(+), 3 deletions(-) create mode 100644 iOS/Features/RewindJourney/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata create mode 100644 iOS/Features/Spot/Sources/CameraView/CameraView.swift create mode 100644 iOS/Features/Spot/Sources/CameraView/CameraViewController.swift create mode 100644 iOS/Features/Spot/Sources/CameraView/CameraViewModel.swift create mode 100644 iOS/Features/Spot/Sources/CameraView/Protocol/Positionable.swift create mode 100644 iOS/Features/Spot/Sources/CameraViewModel/CameraViewModel.swift diff --git a/iOS/Features/RewindJourney/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/iOS/Features/RewindJourney/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/iOS/Features/RewindJourney/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/iOS/Features/Spot/Package.swift b/iOS/Features/Spot/Package.swift index a209d7a..79e614b 100644 --- a/iOS/Features/Spot/Package.swift +++ b/iOS/Features/Spot/Package.swift @@ -8,6 +8,7 @@ import PackageDescription extension String { static let package = "Spot" static let spotView = "SpotView" + static let cameraView = "CameraView" static let msUIKit = "MSUIKit" static let msFoundation = "MSFoundation" static let msDesignsystem = "MSDesignSystem" @@ -33,7 +34,10 @@ let package = Package( products: [ .library( name: .spotView, - targets: [.spotView]) + targets: [.spotView]), + .library( + name: .cameraView, + targets: [.cameraView]) ], dependencies: [ .package(path: .msUIKit.path), @@ -43,11 +47,16 @@ let package = Package( // Codes .target( name: .spotView, + dependencies: [ + .product(name: .msUIKit, package: .msUIKit), + .product(name: .msDesignsystem, package: .msUIKit), + .product(name: .msLogger, package: .msFoundation)]), + .target( + name: .cameraView, dependencies: [ .product(name: .msUIKit, package: .msUIKit), .product(name: .msDesignsystem, package: .msUIKit), .product(name: .msLogger, package: .msFoundation)]) - // Tests ] ) diff --git a/iOS/Features/Spot/Sources/CameraView/CameraView.swift b/iOS/Features/Spot/Sources/CameraView/CameraView.swift new file mode 100644 index 0000000..e44a76b --- /dev/null +++ b/iOS/Features/Spot/Sources/CameraView/CameraView.swift @@ -0,0 +1,45 @@ +// +// CameraView.swift +// Spot +// +// Created by 전민건 on 11/28/23. +// + +import AVFoundation +import UIKit + +import MSLogger + +final class CameraView: UIView { + + private enum Metric { + static let cornerRadius: CGFloat = 15.0 + } + + // MARK: - Initializer + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLayer() + self.configureStyle() + } + + required init?(coder: NSCoder) { + fatalError("MusicSpot은 code-based로만 작업 중입니다.") + } + +} + +// MARK: UI Configuration + +private extension CameraView { + + func configureLayer() { + + } + + func configureStyle() { + self.layer.cornerRadius = Metric.cornerRadius + self.clipsToBounds = true + } +} diff --git a/iOS/Features/Spot/Sources/CameraView/CameraViewController.swift b/iOS/Features/Spot/Sources/CameraView/CameraViewController.swift new file mode 100644 index 0000000..e1fd6c9 --- /dev/null +++ b/iOS/Features/Spot/Sources/CameraView/CameraViewController.swift @@ -0,0 +1,249 @@ +// +// SpotViewController.swift +// Spot +// +// Created by 전민건 on 11/22/23. +// + +import UIKit + +import MSDesignSystem +import MSLogger +import MSUIKit + +public final class CameraViewController: UIViewController { + + // MARK: - Constants + + private enum Metric { + + // camera view + enum CameraView { + static let bottomInset: CGFloat = 50.0 + } + + // shot button + enum ShotButton { + static let height: CGFloat = 70.0 + static let width: CGFloat = 70.0 + static let bottomInset: CGFloat = 60.0 + static let radius = width / 2 + static let borderWidth: CGFloat = 5.0 + } + + // gallery button + enum GalleryButton { + static let height: CGFloat = 35.0 + static let width: CGFloat = 35.0 + static let bottomInset: CGFloat = 0.0 + static let leadingInset: CGFloat = 30.0 + static let radius: CGFloat = 6.0 + } + + // gallery button + enum SwapButton { + static let height: CGFloat = 35.0 + static let width: CGFloat = 35.0 + static let bottomInset: CGFloat = 0.0 + static let trailingInset: CGFloat = 30.0 + static let radius: CGFloat = width / 2 + } + } + + // MARK: - Properties + + private let cameraViewModel = CameraViewModel() + + // MARK: - UI Components + + private let cameraView = CameraView() + private let shotButton = UIButton() + private let galleryButton = UIButton() + private let swapButton = UIButton() + + // MARK: - Life Cycle + + public override func viewDidLoad() { + super.viewDidLoad() + self.configure() + } + + // MARK: - Configure + + func configure() { + self.configureLayout() + self.configureStyles() + self.configureAction() + self.configureShotDelegate() + } + + // MARK: - Actions + + private func configureAction() { + self.configureCameraSetting() + self.configureShotButtonAction() + self.configureSwapButtonAction() + self.configureGalleryButtonAction() + } + + private func configureShotButtonAction() { + let shotButtonAction = UIAction(handler: { _ in + self.shotButtonTapped() + }) + self.shotButton.addAction(shotButtonAction, for: .touchUpInside) + } + + private func shotButtonTapped() { + self.cameraViewModel.shot() + } + + private func configureSwapButtonAction() { + let swapButtonAction = UIAction(handler: { _ in + self.swapButtonTapped() + }) + self.swapButton.addAction(swapButtonAction, for: .touchUpInside) + } + + private func swapButtonTapped() { + self.cameraViewModel.swap() + } + + private func configureGalleryButtonAction() { + let galleryButtonAction = UIAction(handler: { _ in + self.galleryButtonTapped() + }) + self.galleryButton.addAction(galleryButtonAction, for: .touchUpInside) + } + + private func galleryButtonTapped() { + let picker = UIImagePickerController() + picker.sourceType = .photoLibrary + picker.allowsEditing = true + self.present(picker, animated: false) + } + + private func configureCameraSetting() { + self.cameraViewModel.preset(screen: cameraView) + } + +} + +// MARK: UI Configuration - Layout + +private extension CameraViewController { + + func configureLayout() { + [ self.cameraView, + self.shotButton, + self.galleryButton, + self.swapButton ].forEach { uiComponent in + self.view.addSubview(uiComponent) + uiComponent.translatesAutoresizingMaskIntoConstraints = false + } + + self.configureCameraViewLayout() + self.configureShotButtonLayout() + self.configureGalleryButtonLayout() + self.configureSwapButtonLayout() + } + + func configureCameraViewLayout() { + NSLayoutConstraint.activate([ + self.cameraView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), + self.cameraView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, + constant: -Metric.CameraView.bottomInset), + self.cameraView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor), + self.cameraView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor) + ]) + } + + func configureShotButtonLayout() { + NSLayoutConstraint.activate([ + self.shotButton.heightAnchor.constraint(equalToConstant: Metric.ShotButton.height), + self.shotButton.widthAnchor.constraint(equalToConstant: Metric.ShotButton.width), + + self.shotButton.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor), + self.shotButton.centerYAnchor.constraint(equalTo: self.cameraView.safeAreaLayoutGuide.bottomAnchor, + constant: -Metric.ShotButton.bottomInset), + ]) + } + + func configureGalleryButtonLayout() { + NSLayoutConstraint.activate([ + self.galleryButton.heightAnchor.constraint(equalToConstant: Metric.GalleryButton.height), + self.galleryButton.widthAnchor.constraint(equalToConstant: Metric.GalleryButton.width), + + self.galleryButton.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, + constant: Metric.GalleryButton.leadingInset), + self.galleryButton.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, + constant: -Metric.GalleryButton.bottomInset), + ]) + } + + func configureSwapButtonLayout() { + NSLayoutConstraint.activate([ + self.swapButton.heightAnchor.constraint(equalToConstant: Metric.SwapButton.height), + self.swapButton.widthAnchor.constraint(equalToConstant: Metric.SwapButton.width), + + self.swapButton.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, + constant: -Metric.SwapButton.trailingInset), + self.swapButton.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, + constant: -Metric.SwapButton.bottomInset), + ]) + } + + // MARK: UI Configuration - Style + + func configureStyles() { + self.view.backgroundColor = .black + + self.shotButton.backgroundColor = .white + self.shotButton.layer.cornerRadius = Metric.ShotButton.radius + self.shotButton.layer.borderColor = UIColor.msColor(.musicSpot).cgColor + self.shotButton.layer.borderWidth = Metric.ShotButton.borderWidth + + self.galleryButton.backgroundColor = .msColor(.musicSpot) + self.galleryButton.layer.cornerRadius = Metric.GalleryButton.radius + self.galleryButton.setImage(UIImage(systemName: "photo.fill"), for: .normal) + self.galleryButton.tintColor = .white + + self.swapButton.backgroundColor = .darkGray + self.swapButton.layer.cornerRadius = Metric.SwapButton.radius + self.swapButton.setImage(UIImage(systemName: "arrow.triangle.2.circlepath"), for: .normal) + self.swapButton.tintColor = .white + } + +} + +// MARK: ConfigureDelegate + +private extension CameraViewController { + + func configureShotDelegate() { + self.cameraViewModel.delegate = self + } + +} + +// MARK: ShotDelegate + +extension CameraViewController: ShotDelegate { + + func update(imageData: Data?) { + guard let imageData else { + MSLogger.make(category: .camera).debug("촬영된 image가 저장되지 않았습니다.") + return + } + self.cameraView.layer.contents = UIImage(data: imageData) + } + +} + +// MARK: - Preview + +@available(iOS 17, *) +#Preview { + MSFont.registerFonts() + let viewController = CameraViewController() + return viewController +} diff --git a/iOS/Features/Spot/Sources/CameraView/CameraViewModel.swift b/iOS/Features/Spot/Sources/CameraView/CameraViewModel.swift new file mode 100644 index 0000000..1f6d204 --- /dev/null +++ b/iOS/Features/Spot/Sources/CameraView/CameraViewModel.swift @@ -0,0 +1,145 @@ +// +// CameraViewModel.swift +// Spot +// +// Created by 전민건 on 11/22/23. +// + +import AVFoundation + +import MSLogger + +protocol ShotDelegate: AnyObject { + + func update(imageData: Data?) + +} + +final class CameraViewModel: NSObject { + + // MARK: SwapMode + + enum SwapMode { + case front + case back + + var device: AVCaptureDevice? { + guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, + for: .video, + position: self == .front ? .front : .back) else { + MSLogger.make(category: .camera).error("해당 위치의 camera device가 존재하지 않습니다.") + return nil + } + return device + } + + var input: AVCaptureDeviceInput? { + guard let device = self.device, + let input = try? AVCaptureDeviceInput(device: device) else { + MSLogger.make(category: .camera).debug("해당 device로 input을 생성할 수 없습니다.") + return nil + } + return input + } + + } + + // MARK: Properties + + weak var delegate: ShotDelegate? + var swapMode: SwapMode = .back { + didSet { + self.configureSwapMode() + } + } + let frontCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) + let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) + let session = AVCaptureSession() + var input: AVCaptureDeviceInput? + let output = AVCapturePhotoOutput() + let settings = AVCapturePhotoSettings() + +} + +// MARK: Interface + +internal extension CameraViewModel { + + func preset(screen: Positionable) { + self.presetCamera(screen: screen) + } + + func shot() { + self.output.capturePhoto(with: settings, delegate: self) + } + + func swap() { + self.swapMode = self.swapMode == .back ? .front : .back + } + + func gallery() { + guard let input = self.swapMode.input else { + return + } + self.session.beginConfiguration() + self.session.inputs.forEach { input in + self.session.removeInput(input) + } + self.session.addInput(input) + self.session.commitConfiguration() + } + +} + +// MARK: Setting Camera + +private extension CameraViewModel { + + func presetCamera(screen: Positionable) { + guard let input = self.swapMode.input else { return } + self.session.sessionPreset = .photo + self.session.addInput(input) + self.session.addOutput(self.output) + + let previewLayer = AVCaptureVideoPreviewLayer(session: self.session) + + DispatchQueue.global(qos: .background).async { + self.session.startRunning() + } + + DispatchQueue.main.async { + previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill + previewLayer.frame = screen.bounds + screen.layer.addSublayer(previewLayer) + } + } + + func configureSwapMode() { + guard let input = self.swapMode.input else { + return + } + self.session.beginConfiguration() + self.session.inputs.forEach { + self.session.removeInput($0) + } + self.session.addInput(input) + self.session.commitConfiguration() + } + +} + +// MARK: - AVCapturePhotoCaptureDelegate + +extension CameraViewModel: AVCapturePhotoCaptureDelegate { + func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { + guard let imageData = photo.fileDataRepresentation() else { + MSLogger.make(category: .camera).debug("image Data가 없습니다.") + return + } + self.delegate?.update(imageData: imageData) + DispatchQueue.global(qos: .background).async { + self.session.stopRunning() + } + } +} + diff --git a/iOS/Features/Spot/Sources/CameraView/Protocol/Positionable.swift b/iOS/Features/Spot/Sources/CameraView/Protocol/Positionable.swift new file mode 100644 index 0000000..de86e2e --- /dev/null +++ b/iOS/Features/Spot/Sources/CameraView/Protocol/Positionable.swift @@ -0,0 +1,21 @@ +// +// Positionable.swift +// Spot +// +// Created by 전민건 on 11/29/23. +// + +import Foundation +import QuartzCore +import UIKit + +public protocol Positionable: AnyObject { + + var bounds: CGRect { get set } + var layer: CALayer { get } + +} + +extension UIView: Positionable { + +} diff --git a/iOS/Features/Spot/Sources/CameraViewModel/CameraViewModel.swift b/iOS/Features/Spot/Sources/CameraViewModel/CameraViewModel.swift new file mode 100644 index 0000000..4614899 --- /dev/null +++ b/iOS/Features/Spot/Sources/CameraViewModel/CameraViewModel.swift @@ -0,0 +1,23 @@ +// +// CameraViewModel.swift +// Spot +// +// Created by 전민건 on 11/22/23. +// + +import AVFoundation + +import MSLogger + +final class CameraViewModel { + + + +} + +// MARK: Preset Camera + +private extension CameraViewModel { + + +} diff --git a/iOS/Features/Spot/Sources/SpotView/SpotViewController.swift b/iOS/Features/Spot/Sources/SpotView/SpotViewController.swift index 60a49b5..d7bc80f 100644 --- a/iOS/Features/Spot/Sources/SpotView/SpotViewController.swift +++ b/iOS/Features/Spot/Sources/SpotView/SpotViewController.swift @@ -150,6 +150,8 @@ public final class SpotViewController: UIViewController { // MARK: - UI Components: Style private func configureStyle() { + + MSFont.registerFonts() self.view.backgroundColor = .msColor(.primaryBackground) self.configureImageViewStyle() self.configureLabelsStyle() @@ -234,7 +236,6 @@ public final class SpotViewController: UIViewController { @available(iOS 17, *) #Preview { - MSFont.registerFonts() let viewController = SpotViewController() return viewController } diff --git a/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift b/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift index 013c81d..17a1032 100644 --- a/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift +++ b/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift @@ -15,4 +15,5 @@ public enum MSLogCategory: String { case checkJourney case login case setting + case camera } From fc2024f29badcf690907ed143f7d0c79842fee2e Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 14:14:56 +0900 Subject: [PATCH 03/27] =?UTF-8?q?:memo:=20CameraView=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/Spot/Package.swift | 18 ++--- .../CameraViewModel/CameraViewModel.swift | 23 ------- .../{CameraView => Spot}/CameraView.swift | 7 +- .../Protocol/Positionable.swift | 0 .../SpotSaveViewController.swift} | 28 ++++---- .../SpotViewController.swift} | 65 ++++++++++++++----- .../SpotViewModel.swift} | 18 +++-- 7 files changed, 80 insertions(+), 79 deletions(-) delete mode 100644 iOS/Features/Spot/Sources/CameraViewModel/CameraViewModel.swift rename iOS/Features/Spot/Sources/{CameraView => Spot}/CameraView.swift (86%) rename iOS/Features/Spot/Sources/{CameraView => Spot}/Protocol/Positionable.swift (100%) rename iOS/Features/Spot/Sources/{SpotView/SpotViewController.swift => Spot/SpotSaveViewController.swift} (96%) rename iOS/Features/Spot/Sources/{CameraView/CameraViewController.swift => Spot/SpotViewController.swift} (80%) rename iOS/Features/Spot/Sources/{CameraView/CameraViewModel.swift => Spot/SpotViewModel.swift} (94%) diff --git a/iOS/Features/Spot/Package.swift b/iOS/Features/Spot/Package.swift index 79e614b..1cbdb8d 100644 --- a/iOS/Features/Spot/Package.swift +++ b/iOS/Features/Spot/Package.swift @@ -7,8 +7,7 @@ import PackageDescription extension String { static let package = "Spot" - static let spotView = "SpotView" - static let cameraView = "CameraView" + static let spot = "Spot" static let msUIKit = "MSUIKit" static let msFoundation = "MSFoundation" static let msDesignsystem = "MSDesignSystem" @@ -33,11 +32,8 @@ let package = Package( ], products: [ .library( - name: .spotView, - targets: [.spotView]), - .library( - name: .cameraView, - targets: [.cameraView]) + name: .spot, + targets: [.spot]) ], dependencies: [ .package(path: .msUIKit.path), @@ -46,13 +42,7 @@ let package = Package( targets: [ // Codes .target( - name: .spotView, - dependencies: [ - .product(name: .msUIKit, package: .msUIKit), - .product(name: .msDesignsystem, package: .msUIKit), - .product(name: .msLogger, package: .msFoundation)]), - .target( - name: .cameraView, + name: .spot, dependencies: [ .product(name: .msUIKit, package: .msUIKit), .product(name: .msDesignsystem, package: .msUIKit), diff --git a/iOS/Features/Spot/Sources/CameraViewModel/CameraViewModel.swift b/iOS/Features/Spot/Sources/CameraViewModel/CameraViewModel.swift deleted file mode 100644 index 4614899..0000000 --- a/iOS/Features/Spot/Sources/CameraViewModel/CameraViewModel.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// CameraViewModel.swift -// Spot -// -// Created by 전민건 on 11/22/23. -// - -import AVFoundation - -import MSLogger - -final class CameraViewModel { - - - -} - -// MARK: Preset Camera - -private extension CameraViewModel { - - -} diff --git a/iOS/Features/Spot/Sources/CameraView/CameraView.swift b/iOS/Features/Spot/Sources/Spot/CameraView.swift similarity index 86% rename from iOS/Features/Spot/Sources/CameraView/CameraView.swift rename to iOS/Features/Spot/Sources/Spot/CameraView.swift index e44a76b..c5d85de 100644 --- a/iOS/Features/Spot/Sources/CameraView/CameraView.swift +++ b/iOS/Features/Spot/Sources/Spot/CameraView.swift @@ -20,7 +20,6 @@ final class CameraView: UIView { override init(frame: CGRect) { super.init(frame: frame) - self.configureLayer() self.configureStyle() } @@ -30,14 +29,10 @@ final class CameraView: UIView { } -// MARK: UI Configuration +// MARK: - UI Configuration: Style private extension CameraView { - func configureLayer() { - - } - func configureStyle() { self.layer.cornerRadius = Metric.cornerRadius self.clipsToBounds = true diff --git a/iOS/Features/Spot/Sources/CameraView/Protocol/Positionable.swift b/iOS/Features/Spot/Sources/Spot/Protocol/Positionable.swift similarity index 100% rename from iOS/Features/Spot/Sources/CameraView/Protocol/Positionable.swift rename to iOS/Features/Spot/Sources/Spot/Protocol/Positionable.swift diff --git a/iOS/Features/Spot/Sources/SpotView/SpotViewController.swift b/iOS/Features/Spot/Sources/Spot/SpotSaveViewController.swift similarity index 96% rename from iOS/Features/Spot/Sources/SpotView/SpotViewController.swift rename to iOS/Features/Spot/Sources/Spot/SpotSaveViewController.swift index d7bc80f..dafc954 100644 --- a/iOS/Features/Spot/Sources/SpotView/SpotViewController.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotSaveViewController.swift @@ -11,7 +11,7 @@ import MSDesignSystem import MSLogger import MSUIKit -public final class SpotViewController: UIViewController { +public final class SpotSaveViewController: UIViewController { // MARK: - Constants @@ -65,6 +65,13 @@ public final class SpotViewController: UIViewController { } } + // MARK: - Life Cycle + + public override func viewDidLoad() { + super.viewDidLoad() + self.configure() + } + // MARK: - Configure func configure() { @@ -112,7 +119,7 @@ public final class SpotViewController: UIViewController { self.view.addSubview(label) label.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ - label.centerXAnchor.constraint(equalTo: self.view.centerXAnchor) + label.centerXAnchor.constraint(equalTo: self.view.centerXAnchor) ]) } NSLayoutConstraint.activate([ @@ -213,13 +220,6 @@ public final class SpotViewController: UIViewController { self.subTextLabel.numberOfLines = multiLineConstant } - // MARK: - Life Cycle - - public override func viewDidLoad() { - super.viewDidLoad() - self.configure() - } - // MARK: Actions private func cancelButtonTapped() { @@ -234,8 +234,8 @@ public final class SpotViewController: UIViewController { // MARK: - Preview -@available(iOS 17, *) -#Preview { - let viewController = SpotViewController() - return viewController -} +//@available(iOS 17, *) +//#Preview { +//// let view = SpotSaveView() +//// return view +//} diff --git a/iOS/Features/Spot/Sources/CameraView/CameraViewController.swift b/iOS/Features/Spot/Sources/Spot/SpotViewController.swift similarity index 80% rename from iOS/Features/Spot/Sources/CameraView/CameraViewController.swift rename to iOS/Features/Spot/Sources/Spot/SpotViewController.swift index e1fd6c9..8a05d07 100644 --- a/iOS/Features/Spot/Sources/CameraView/CameraViewController.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotViewController.swift @@ -11,7 +11,7 @@ import MSDesignSystem import MSLogger import MSUIKit -public final class CameraViewController: UIViewController { +public final class SpotViewController: UIViewController, UINavigationControllerDelegate { // MARK: - Constants @@ -52,7 +52,8 @@ public final class CameraViewController: UIViewController { // MARK: - Properties - private let cameraViewModel = CameraViewModel() + private let spotViewModel = SpotViewModel() + private let picker = UIImagePickerController() // MARK: - UI Components @@ -74,9 +75,27 @@ public final class CameraViewController: UIViewController { self.configureLayout() self.configureStyles() self.configureAction() - self.configureShotDelegate() + self.configureState() + self.configureDelegate() + } + + // MARK: - Configure Delegate + + private func configureDelegate() { + self.picker.delegate = self + } + + // MARK: - Configure State + + private func configureState() { + self.configurePickerState() } + private func configurePickerState() { + self.picker.sourceType = .photoLibrary + self.picker.allowsEditing = true + } + // MARK: - Actions private func configureAction() { @@ -94,7 +113,7 @@ public final class CameraViewController: UIViewController { } private func shotButtonTapped() { - self.cameraViewModel.shot() + self.spotViewModel.shot() } private func configureSwapButtonAction() { @@ -105,7 +124,7 @@ public final class CameraViewController: UIViewController { } private func swapButtonTapped() { - self.cameraViewModel.swap() + self.spotViewModel.swap() } private func configureGalleryButtonAction() { @@ -116,21 +135,18 @@ public final class CameraViewController: UIViewController { } private func galleryButtonTapped() { - let picker = UIImagePickerController() - picker.sourceType = .photoLibrary - picker.allowsEditing = true - self.present(picker, animated: false) + self.present(self.picker, animated: false) } private func configureCameraSetting() { - self.cameraViewModel.preset(screen: cameraView) + self.spotViewModel.preset(screen: cameraView) } } // MARK: UI Configuration - Layout -private extension CameraViewController { +private extension SpotViewController { func configureLayout() { [ self.cameraView, @@ -217,17 +233,17 @@ private extension CameraViewController { // MARK: ConfigureDelegate -private extension CameraViewController { +private extension SpotViewController { func configureShotDelegate() { - self.cameraViewModel.delegate = self + self.spotViewModel.delegate = self } } // MARK: ShotDelegate -extension CameraViewController: ShotDelegate { +extension SpotViewController: ShotDelegate { func update(imageData: Data?) { guard let imageData else { @@ -239,11 +255,30 @@ extension CameraViewController: ShotDelegate { } +// MARK: ImagePickerDelegate + +extension SpotViewController: UIImagePickerControllerDelegate { + + public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + guard let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage else { + return + } + self.spotViewModel.stopCamera() + let spotSaveViewController = SpotSaveViewController() + spotSaveViewController.image = image + spotSaveViewController.modalPresentationStyle = .fullScreen + picker.dismiss(animated: true) + self.presentedViewController?.dismiss(animated: true) + self.present(spotSaveViewController, animated: false) + } + +} + // MARK: - Preview @available(iOS 17, *) #Preview { MSFont.registerFonts() - let viewController = CameraViewController() + let viewController = SpotViewController() return viewController } diff --git a/iOS/Features/Spot/Sources/CameraView/CameraViewModel.swift b/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift similarity index 94% rename from iOS/Features/Spot/Sources/CameraView/CameraViewModel.swift rename to iOS/Features/Spot/Sources/Spot/SpotViewModel.swift index 1f6d204..350dbda 100644 --- a/iOS/Features/Spot/Sources/CameraView/CameraViewModel.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift @@ -15,7 +15,7 @@ protocol ShotDelegate: AnyObject { } -final class CameraViewModel: NSObject { +final class SpotViewModel: NSObject { // MARK: SwapMode @@ -63,7 +63,7 @@ final class CameraViewModel: NSObject { // MARK: Interface -internal extension CameraViewModel { +internal extension SpotViewModel { func preset(screen: Positionable) { self.presetCamera(screen: screen) @@ -89,11 +89,17 @@ internal extension CameraViewModel { self.session.commitConfiguration() } + func stopCamera() { + DispatchQueue.global(qos: .background).async { + self.session.stopRunning() + } + } + } // MARK: Setting Camera -private extension CameraViewModel { +private extension SpotViewModel { func presetCamera(screen: Positionable) { guard let input = self.swapMode.input else { return } @@ -130,16 +136,14 @@ private extension CameraViewModel { // MARK: - AVCapturePhotoCaptureDelegate -extension CameraViewModel: AVCapturePhotoCaptureDelegate { +extension SpotViewModel: AVCapturePhotoCaptureDelegate { func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { guard let imageData = photo.fileDataRepresentation() else { MSLogger.make(category: .camera).debug("image Data가 없습니다.") return } self.delegate?.update(imageData: imageData) - DispatchQueue.global(qos: .background).async { - self.session.stopRunning() - } + self.stopCamera() } } From f1354721f858239829ba45b575dab12821e6e567 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 14:18:23 +0900 Subject: [PATCH 04/27] =?UTF-8?q?:sparkles:=20=EC=B9=B4=EB=A9=94=EB=9D=BC?= =?UTF-8?q?=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Spot/SpotViewController.swift | 196 ++++++++++-------- 1 file changed, 112 insertions(+), 84 deletions(-) diff --git a/iOS/Features/Spot/Sources/Spot/SpotViewController.swift b/iOS/Features/Spot/Sources/Spot/SpotViewController.swift index 8a05d07..eb320c4 100644 --- a/iOS/Features/Spot/Sources/Spot/SpotViewController.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotViewController.swift @@ -12,9 +12,9 @@ import MSLogger import MSUIKit public final class SpotViewController: UIViewController, UINavigationControllerDelegate { - + // MARK: - Constants - + private enum Metric { // camera view @@ -49,10 +49,11 @@ public final class SpotViewController: UIViewController, UINavigationControllerD static let radius: CGFloat = width / 2 } } - + // MARK: - Properties private let spotViewModel = SpotViewModel() + private let spotSaveViewController = SpotSaveViewController() private let picker = UIImagePickerController() // MARK: - UI Components @@ -61,14 +62,19 @@ public final class SpotViewController: UIViewController, UINavigationControllerD private let shotButton = UIButton() private let galleryButton = UIButton() private let swapButton = UIButton() - + // MARK: - Life Cycle - + public override func viewDidLoad() { super.viewDidLoad() self.configure() } + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.spotViewModel.startCamera() + } + // MARK: - Configure func configure() { @@ -79,72 +85,9 @@ public final class SpotViewController: UIViewController, UINavigationControllerD self.configureDelegate() } - // MARK: - Configure Delegate - - private func configureDelegate() { - self.picker.delegate = self - } - - // MARK: - Configure State - - private func configureState() { - self.configurePickerState() - } - - private func configurePickerState() { - self.picker.sourceType = .photoLibrary - self.picker.allowsEditing = true - } - - // MARK: - Actions - - private func configureAction() { - self.configureCameraSetting() - self.configureShotButtonAction() - self.configureSwapButtonAction() - self.configureGalleryButtonAction() - } - - private func configureShotButtonAction() { - let shotButtonAction = UIAction(handler: { _ in - self.shotButtonTapped() - }) - self.shotButton.addAction(shotButtonAction, for: .touchUpInside) - } - - private func shotButtonTapped() { - self.spotViewModel.shot() - } - - private func configureSwapButtonAction() { - let swapButtonAction = UIAction(handler: { _ in - self.swapButtonTapped() - }) - self.swapButton.addAction(swapButtonAction, for: .touchUpInside) - } - - private func swapButtonTapped() { - self.spotViewModel.swap() - } - - private func configureGalleryButtonAction() { - let galleryButtonAction = UIAction(handler: { _ in - self.galleryButtonTapped() - }) - self.galleryButton.addAction(galleryButtonAction, for: .touchUpInside) - } - - private func galleryButtonTapped() { - self.present(self.picker, animated: false) - } - - private func configureCameraSetting() { - self.spotViewModel.preset(screen: cameraView) - } - } -// MARK: UI Configuration - Layout +// MARK: - UI Configuration: Layout private extension SpotViewController { @@ -202,13 +145,17 @@ private extension SpotViewController { self.swapButton.widthAnchor.constraint(equalToConstant: Metric.SwapButton.width), self.swapButton.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, - constant: -Metric.SwapButton.trailingInset), + constant: -Metric.SwapButton.trailingInset), self.swapButton.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, - constant: -Metric.SwapButton.bottomInset), + constant: -Metric.SwapButton.bottomInset), ]) } - // MARK: UI Configuration - Style +} + + // MARK: UI Configuration: Style + +private extension SpotViewController { func configureStyles() { self.view.backgroundColor = .black @@ -227,35 +174,109 @@ private extension SpotViewController { self.swapButton.layer.cornerRadius = Metric.SwapButton.radius self.swapButton.setImage(UIImage(systemName: "arrow.triangle.2.circlepath"), for: .normal) self.swapButton.tintColor = .white + + self.spotSaveViewController.modalPresentationStyle = .fullScreen } } -// MARK: ConfigureDelegate +// MARK: - Configure: Actions private extension SpotViewController { - func configureShotDelegate() { + func configureAction() { + self.configureCameraSetting() + self.configureShotButtonAction() + self.configureSwapButtonAction() + self.configureGalleryButtonAction() + } + + func configureShotButtonAction() { + let shotButtonAction = UIAction(handler: { _ in + self.shotButtonTapped() + }) + self.shotButton.addAction(shotButtonAction, for: .touchUpInside) + } + + func configureSwapButtonAction() { + let swapButtonAction = UIAction(handler: { _ in + self.swapButtonTapped() + }) + self.swapButton.addAction(swapButtonAction, for: .touchUpInside) + } + + func configureGalleryButtonAction() { + let galleryButtonAction = UIAction(handler: { _ in + self.galleryButtonTapped() + }) + self.galleryButton.addAction(galleryButtonAction, for: .touchUpInside) + } + + private func configureCameraSetting() { + self.spotViewModel.preset(screen: cameraView) + } + +} + +// MARK: - Configure: State + +private extension SpotViewController { + + func configureState() { + self.configurePickerState() + } + + func configurePickerState() { + self.picker.sourceType = .photoLibrary + self.picker.allowsEditing = true + } + +} + +// MARK: - Button Actions + +private extension SpotViewController { + + func shotButtonTapped() { + self.spotViewModel.shot() + } + + func swapButtonTapped() { + self.spotViewModel.swap() + } + + func galleryButtonTapped() { + self.present(self.picker, animated: false) + } +} + +// MARK: - Delegate + +private extension SpotViewController { + + func configureDelegate() { + self.picker.delegate = self self.spotViewModel.delegate = self } } -// MARK: ShotDelegate +// MARK: - Delegate: ShotDelegate extension SpotViewController: ShotDelegate { func update(imageData: Data?) { - guard let imageData else { + guard let imageData, let image = UIImage(data: imageData) else { MSLogger.make(category: .camera).debug("촬영된 image가 저장되지 않았습니다.") return } - self.cameraView.layer.contents = UIImage(data: imageData) + self.cameraView.layer.contents = imageData + self.presentSpotSaveViewController(with: image) } } -// MARK: ImagePickerDelegate +// MARK: - Delegate: ImagePickerDelegate extension SpotViewController: UIImagePickerControllerDelegate { @@ -263,13 +284,20 @@ extension SpotViewController: UIImagePickerControllerDelegate { guard let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage else { return } + self.presentSpotSaveViewController(with: image) + } + +} + +// MARK: - Functions + +private extension SpotViewController { + + func presentSpotSaveViewController(with image: UIImage) { self.spotViewModel.stopCamera() - let spotSaveViewController = SpotSaveViewController() - spotSaveViewController.image = image - spotSaveViewController.modalPresentationStyle = .fullScreen - picker.dismiss(animated: true) - self.presentedViewController?.dismiss(animated: true) - self.present(spotSaveViewController, animated: false) + self.spotSaveViewController.image = image + self.presentingViewController?.dismiss(animated: true) + self.present(self.spotSaveViewController, animated: true) } } From 6d15c87d9aaa6bb4a0cdebb3fc87b1a5a045ceab Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 14:19:13 +0900 Subject: [PATCH 05/27] =?UTF-8?q?:sparkles:=20=EC=B9=B4=EB=A9=94=EB=9D=BC?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Spot/Sources/Spot/SpotViewModel.swift | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift b/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift index 350dbda..d259bec 100644 --- a/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift @@ -17,7 +17,7 @@ protocol ShotDelegate: AnyObject { final class SpotViewModel: NSObject { - // MARK: SwapMode + // MARK: - Type: SwapMode enum SwapMode { case front @@ -44,7 +44,7 @@ final class SpotViewModel: NSObject { } - // MARK: Properties + // MARK: - Properties weak var delegate: ShotDelegate? var swapMode: SwapMode = .back { @@ -57,11 +57,10 @@ final class SpotViewModel: NSObject { let session = AVCaptureSession() var input: AVCaptureDeviceInput? let output = AVCapturePhotoOutput() - let settings = AVCapturePhotoSettings() } -// MARK: Interface +// MARK: - Interface internal extension SpotViewModel { @@ -70,6 +69,7 @@ internal extension SpotViewModel { } func shot() { + let settings = AVCapturePhotoSettings() self.output.capturePhoto(with: settings, delegate: self) } @@ -95,9 +95,15 @@ internal extension SpotViewModel { } } + func startCamera() { + DispatchQueue.global(qos: .background).async { + self.session.startRunning() + } + } + } -// MARK: Setting Camera +// MARK: - Actions: Camera private extension SpotViewModel { @@ -109,10 +115,7 @@ private extension SpotViewModel { let previewLayer = AVCaptureVideoPreviewLayer(session: self.session) - DispatchQueue.global(qos: .background).async { - self.session.startRunning() - } - + self.startCamera() DispatchQueue.main.async { previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill previewLayer.frame = screen.bounds @@ -134,16 +137,18 @@ private extension SpotViewModel { } -// MARK: - AVCapturePhotoCaptureDelegate +// MARK: - Delegate: Camera extension SpotViewModel: AVCapturePhotoCaptureDelegate { + func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { guard let imageData = photo.fileDataRepresentation() else { MSLogger.make(category: .camera).debug("image Data가 없습니다.") return } - self.delegate?.update(imageData: imageData) self.stopCamera() + self.delegate?.update(imageData: imageData) } + } From d812937bbc460723540c2d0504fcec51e907d7cc Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 14:20:04 +0900 Subject: [PATCH 06/27] =?UTF-8?q?:recycle:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Spot/SpotSaveViewController.swift | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/iOS/Features/Spot/Sources/Spot/SpotSaveViewController.swift b/iOS/Features/Spot/Sources/Spot/SpotSaveViewController.swift index dafc954..07447c2 100644 --- a/iOS/Features/Spot/Sources/Spot/SpotSaveViewController.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotSaveViewController.swift @@ -32,7 +32,7 @@ public final class SpotSaveViewController: UIViewController { enum SubTextLabel { static let height: CGFloat = 42.0 - static let topInset: CGFloat = 12.0 + static let topInset: CGFloat = 2.0 } // buttons @@ -53,17 +53,21 @@ public final class SpotSaveViewController: UIViewController { // MARK: - Properties + public var image: UIImage? { + didSet { + self.configureImageViewState() + } + } + private let spotSaveViewModel = SpotSaveViewModel() + + // MARK: - UI Components + private let imageView = UIImageView() private let textView = UIView() private let textLabel = UILabel() private let subTextLabel = UILabel() private let cancelButton = MSRectButton.large(isBrandColored: false) private let completeButton = MSRectButton.large() - public var image: UIImage? { - didSet { - self.configureImageViewValue() - } - } // MARK: - Life Cycle @@ -78,7 +82,7 @@ public final class SpotSaveViewController: UIViewController { self.configureLayout() self.configureStyle() self.configureAction() - self.configureValue() + self.configureState() } // MARK: - UI Components: Layout @@ -201,18 +205,18 @@ public final class SpotSaveViewController: UIViewController { self.completeButton.addAction(completeButtonAction, for: .touchUpInside) } - // MARK: - Configure: Value + // MARK: - Configure: State - private func configureValue() { - self.configureImageViewValue() - self.configureLabelsValue() + private func configureState() { + self.configureImageViewState() + self.configureLabelsState() } - private func configureImageViewValue() { + private func configureImageViewState() { self.imageView.image = self.image } - private func configureLabelsValue() { + private func configureLabelsState() { self.textLabel.text = Default.text self.subTextLabel.text = Default.subText @@ -220,22 +224,26 @@ public final class SpotSaveViewController: UIViewController { self.subTextLabel.numberOfLines = multiLineConstant } - // MARK: Actions + // MARK: - Button Actions private func cancelButtonTapped() { - + self.presentingViewController?.dismiss(animated: true) } private func completeButtonTapped() { - + guard let data = self.image?.pngData() else { + MSLogger.make(category: .recordingJourney).debug("현재 이미지를 Data로 변환할 수 없습니다.") + return + } + self.spotSaveViewModel.upload(data: data) } } // MARK: - Preview -//@available(iOS 17, *) -//#Preview { -//// let view = SpotSaveView() -//// return view -//} +@available(iOS 17, *) +#Preview { + let spotView = SpotSaveViewController() + return spotView +} From 2315684977eaed1eed6cc5195236bcb0565f8d05 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 14:20:48 +0900 Subject: [PATCH 07/27] =?UTF-8?q?:memo:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=86=A1=EC=B6=9C=ED=95=B4=EC=A3=BC=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20=EB=8B=A4=EB=A3=B0=20viewModel=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Spot/Sources/Spot/SpotSaveViewModel.swift | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 iOS/Features/Spot/Sources/Spot/SpotSaveViewModel.swift diff --git a/iOS/Features/Spot/Sources/Spot/SpotSaveViewModel.swift b/iOS/Features/Spot/Sources/Spot/SpotSaveViewModel.swift new file mode 100644 index 0000000..b3bdc44 --- /dev/null +++ b/iOS/Features/Spot/Sources/Spot/SpotSaveViewModel.swift @@ -0,0 +1,24 @@ +// +// SpotSaveViewModel.swift +// Spot +// +// Created by 전민건 on 11/29/23. +// + +import Foundation + +final class SpotSaveViewModel { + + // MARK: - Properties + +} + +// MARK: - Interface + +internal extension SpotSaveViewModel { + + func upload(data: Data) { + // Networking.. + } + +} From ee0d36d51b72bf55dadd2f55d3b6da6d3d57dcff Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 14:26:23 +0900 Subject: [PATCH 08/27] =?UTF-8?q?:art:=20Lint=20=EC=BB=A8=EB=B0=B4?= =?UTF-8?q?=EC=85=98=EC=97=90=20=EB=A7=9E=EC=B6=98=20=EC=BD=94=EB=93=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 --- iOS/Features/Spot/Sources/Spot/SpotViewController.swift | 9 +++++---- iOS/Features/Spot/Sources/Spot/SpotViewModel.swift | 3 +-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/iOS/Features/Spot/Sources/Spot/SpotViewController.swift b/iOS/Features/Spot/Sources/Spot/SpotViewController.swift index eb320c4..e201900 100644 --- a/iOS/Features/Spot/Sources/Spot/SpotViewController.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotViewController.swift @@ -123,7 +123,7 @@ private extension SpotViewController { self.shotButton.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor), self.shotButton.centerYAnchor.constraint(equalTo: self.cameraView.safeAreaLayoutGuide.bottomAnchor, - constant: -Metric.ShotButton.bottomInset), + constant: -Metric.ShotButton.bottomInset) ]) } @@ -135,7 +135,7 @@ private extension SpotViewController { self.galleryButton.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: Metric.GalleryButton.leadingInset), self.galleryButton.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, - constant: -Metric.GalleryButton.bottomInset), + constant: -Metric.GalleryButton.bottomInset) ]) } @@ -147,7 +147,7 @@ private extension SpotViewController { self.swapButton.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, constant: -Metric.SwapButton.trailingInset), self.swapButton.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, - constant: -Metric.SwapButton.bottomInset), + constant: -Metric.SwapButton.bottomInset) ]) } @@ -280,7 +280,8 @@ extension SpotViewController: ShotDelegate { extension SpotViewController: UIImagePickerControllerDelegate { - public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo + info: [UIImagePickerController.InfoKey: Any]) { guard let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage else { return } diff --git a/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift b/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift index d259bec..89b3258 100644 --- a/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift @@ -24,7 +24,7 @@ final class SpotViewModel: NSObject { case back var device: AVCaptureDevice? { - guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, + guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: self == .front ? .front : .back) else { MSLogger.make(category: .camera).error("해당 위치의 camera device가 존재하지 않습니다.") @@ -151,4 +151,3 @@ extension SpotViewModel: AVCapturePhotoCaptureDelegate { } } - From 044732ea2f35c3e8ec3b7a34e1cbecd98d9f18ba Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 14:37:44 +0900 Subject: [PATCH 09/27] =?UTF-8?q?:art:=20Commit=20=ED=8C=8C=EC=9D=BC=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 --- iOS/commit | 4 ---- 1 file changed, 4 deletions(-) diff --git a/iOS/commit b/iOS/commit index 6d71063..8eac4c3 100755 --- a/iOS/commit +++ b/iOS/commit @@ -1,11 +1,7 @@ #!/bin/sh ​ LINTPATH='.swiftlint.yml' -<<<<<<< HEAD -declare -a PATHS=("MSCoreKit" "MSFoundation" "MSUIKit" "MusicSpot" "MSData" "Features") -======= declare -a PATHS=("MSCoreKit" "MSFoundation" "MSUIKit" "MSData" "MusicSpot" "Features") ->>>>>>> 48567b56ec07f40e5003caf774569cbdae8f356b failures="" ​ for path in "${PATHS[@]}"; do From ff406ae11e2744b70e8e9833c329785a4cd5eeec Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 14:44:02 +0900 Subject: [PATCH 10/27] =?UTF-8?q?:art:=20Lint=20=EA=B7=9C=EC=B9=99?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EC=B6=94=EC=96=B4=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20+=20Demo=20App=20=EC=85=8B=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpotDemo/SpotDemo/SceneDelegate.swift | 46 +++++-------------- .../SpotDemo/SpotDemo/ViewController.swift | 7 --- 2 files changed, 11 insertions(+), 42 deletions(-) diff --git a/iOS/Features/Spot/SpotDemo/SpotDemo/SceneDelegate.swift b/iOS/Features/Spot/SpotDemo/SpotDemo/SceneDelegate.swift index 0a7c806..af25008 100644 --- a/iOS/Features/Spot/SpotDemo/SpotDemo/SceneDelegate.swift +++ b/iOS/Features/Spot/SpotDemo/SpotDemo/SceneDelegate.swift @@ -7,46 +7,22 @@ import UIKit +import Spot + class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } - } - - func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + func scene(_ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = (scene as? UIWindowScene) else { return } + + window = UIWindow(windowScene: windowScene) + let spotVc = SpotViewController() + window?.rootViewController = spotVc + window?.makeKeyAndVisible() } - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - } - - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). - } - - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } - - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. - } - - } diff --git a/iOS/Features/Spot/SpotDemo/SpotDemo/ViewController.swift b/iOS/Features/Spot/SpotDemo/SpotDemo/ViewController.swift index 2edaf0c..82c12bf 100644 --- a/iOS/Features/Spot/SpotDemo/SpotDemo/ViewController.swift +++ b/iOS/Features/Spot/SpotDemo/SpotDemo/ViewController.swift @@ -9,11 +9,4 @@ import UIKit class ViewController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - } - - } - From 2e3fc79640ed2475f0195db284f660b7351360b8 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 14:49:10 +0900 Subject: [PATCH 11/27] =?UTF-8?q?:art:=20Lint=20=EC=BB=A8=EB=B0=B4?= =?UTF-8?q?=EC=85=98=20=EB=A7=9E=EC=B6=94=EC=96=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Spot/SpotDemo/SpotDemo/AppDelegate.swift | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/iOS/Features/Spot/SpotDemo/SpotDemo/AppDelegate.swift b/iOS/Features/Spot/SpotDemo/SpotDemo/AppDelegate.swift index b2afe12..ca0696e 100644 --- a/iOS/Features/Spot/SpotDemo/SpotDemo/AppDelegate.swift +++ b/iOS/Features/Spot/SpotDemo/SpotDemo/AppDelegate.swift @@ -9,14 +9,7 @@ import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { - - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - + // MARK: UISceneSession Lifecycle func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { @@ -25,12 +18,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. - } - - } - From 2956530f808f03657149d8c2d047e9dd79242d16 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 15:04:22 +0900 Subject: [PATCH 12/27] =?UTF-8?q?:memo:=20Demo=20App=20=EC=B9=B4=EB=A9=94?= =?UTF-8?q?=EB=9D=BC=20=ED=99=98=EA=B2=BD=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/Spot/SpotDemo/SpotDemo.xcodeproj/project.pbxproj | 2 ++ iOS/Features/Spot/SpotDemo/SpotDemo/Info.plist | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/iOS/Features/Spot/SpotDemo/SpotDemo.xcodeproj/project.pbxproj b/iOS/Features/Spot/SpotDemo/SpotDemo.xcodeproj/project.pbxproj index d7a2250..002a0cd 100644 --- a/iOS/Features/Spot/SpotDemo/SpotDemo.xcodeproj/project.pbxproj +++ b/iOS/Features/Spot/SpotDemo/SpotDemo.xcodeproj/project.pbxproj @@ -300,6 +300,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SpotDemo/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -331,6 +332,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SpotDemo/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; diff --git a/iOS/Features/Spot/SpotDemo/SpotDemo/Info.plist b/iOS/Features/Spot/SpotDemo/SpotDemo/Info.plist index dd3c9af..c52b344 100644 --- a/iOS/Features/Spot/SpotDemo/SpotDemo/Info.plist +++ b/iOS/Features/Spot/SpotDemo/SpotDemo/Info.plist @@ -2,6 +2,10 @@ + NSAppleMusicUsageDescription + using photos in album + NSCameraUsageDescription + take photo UIApplicationSceneManifest UIApplicationSupportsMultipleScenes From 50096b91f8f1fa62b645cfd9e0d1af10aac20530 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 15:05:06 +0900 Subject: [PATCH 13/27] =?UTF-8?q?:bug:=20=EC=A0=84=EC=97=90=20=EB=9D=84?= =?UTF-8?q?=EC=9B=A0=EB=8D=98=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=B2=97=EC=96=B4=EB=82=98=EC=A7=80=20=EB=AA=BB=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=ED=98=84=EC=83=81=20=EB=B2=84=EA=B7=B8=20=ED=94=BD?= =?UTF-8?q?=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/Spot/Sources/Spot/SpotViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iOS/Features/Spot/Sources/Spot/SpotViewController.swift b/iOS/Features/Spot/Sources/Spot/SpotViewController.swift index e201900..a37baf9 100644 --- a/iOS/Features/Spot/Sources/Spot/SpotViewController.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotViewController.swift @@ -297,7 +297,8 @@ private extension SpotViewController { func presentSpotSaveViewController(with image: UIImage) { self.spotViewModel.stopCamera() self.spotSaveViewController.image = image - self.presentingViewController?.dismiss(animated: true) + self.presentedViewController?.dismiss(animated: false) + self.presentingViewController?.dismiss(animated: false) self.present(self.spotSaveViewController, animated: true) } From e6ababa97a70f10ad0695304e529249cccf70c21 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 15:47:42 +0900 Subject: [PATCH 14/27] =?UTF-8?q?:memo:=20Package=20MSNetworking,=20MSData?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EA=B4=80=EB=A6=AC=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=ED=94=84=EB=A1=9C=ED=8D=BC=ED=8B=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/Spot/Package.swift | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/iOS/Features/Spot/Package.swift b/iOS/Features/Spot/Package.swift index 0bc07b7..3fcbb9c 100644 --- a/iOS/Features/Spot/Package.swift +++ b/iOS/Features/Spot/Package.swift @@ -27,10 +27,18 @@ private enum Target { private enum Dependency { + // package static let msUIKit = "MSUIKit" static let msFoundation = "MSFoundation" + static let msCoreKit = "MSCoreKit" + + // library static let msDesignsystem = "MSDesignSystem" static let msLogger = "MSLogger" + static let msNetworking = "MSNetworking" + + // package = library + static let msData = "MSData" } @@ -49,7 +57,9 @@ let package = Package( .package(name: Dependency.msUIKit, path: Dependency.msUIKit.fromRootPath), .package(name: Dependency.msFoundation, - path: Dependency.msFoundation.fromRootPath) + path: Dependency.msFoundation.fromRootPath), + .package(name: Dependency.msCoreKit, + path: Dependency.msCoreKit.fromRootPath) ], targets: [ .target(name: Target.spot, @@ -59,7 +69,11 @@ let package = Package( .product(name: Dependency.msDesignsystem, package: Dependency.msUIKit), .product(name: Dependency.msLogger, - package: Dependency.msFoundation) + package: Dependency.msFoundation), + .product(name: Dependency.msNetworking, + package: Dependency.msCoreKit), + .product(name: Dependency.msData, + package: Dependency.msData) ]) ] ) From 62dcb4471f6f7839dbcc8ffe831bc6066f4b3bb3 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 17:19:11 +0900 Subject: [PATCH 15/27] =?UTF-8?q?:memo:=20MSNetworking=20=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/Spot/Package.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOS/Features/Spot/Package.swift b/iOS/Features/Spot/Package.swift index 3fcbb9c..616cce9 100644 --- a/iOS/Features/Spot/Package.swift +++ b/iOS/Features/Spot/Package.swift @@ -59,7 +59,9 @@ let package = Package( .package(name: Dependency.msFoundation, path: Dependency.msFoundation.fromRootPath), .package(name: Dependency.msCoreKit, - path: Dependency.msCoreKit.fromRootPath) + path: Dependency.msCoreKit.fromRootPath), + .package(name: Dependency.msData, + path: Dependency.msData.fromRootPath) ], targets: [ .target(name: Target.spot, From 4b841ce9d45c3aa6f5489a65c3797b9f3bc3b2f4 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 17:19:55 +0900 Subject: [PATCH 16/27] =?UTF-8?q?:art:=20SpotDTO,=20=EB=B3=B4=EB=82=B4?= =?UTF-8?q?=EB=8A=94=20=EC=9A=A9=EA=B3=BC=20=EB=B0=9B=EB=8A=94=20=EC=9A=A9?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=83=80=EC=9E=85=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift index 3e00aa6..b8177d4 100644 --- a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift @@ -7,7 +7,15 @@ import Foundation -public struct SpotDTO: Codable, Identifiable { +public struct RequestableSpotDTO: Encodable, Identifiable { + + public let id: UUID + public let coordinate: [Double] + public let photoData: Data + +} + +public struct ResponsibleSpotDTO: Decodable, Identifiable { public let id: UUID public let coordinate: [Double] From 189a3b08ee862d39178c59064bc1942411b07e14 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 17:20:34 +0900 Subject: [PATCH 17/27] =?UTF-8?q?:art:=20Spot=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20router=20case=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/MSData/Router/Journey/JourneyRouter+Body.swift | 5 ++++- .../Sources/MSData/Router/Journey/JourneyRouter+Header.swift | 1 + .../Sources/MSData/Router/Journey/JourneyRouter+Method.swift | 1 + .../Sources/MSData/Router/Journey/JourneyRouter+URL.swift | 1 + iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter.swift | 1 + 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Body.swift b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Body.swift index e855e5e..c0e3bae 100644 --- a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Body.swift +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Body.swift @@ -11,7 +11,10 @@ extension JourneyRouter { public var body: HTTPBody? { switch self { - case .journeyList: return nil + case .journeyList: + return nil + case .spot: + return nil } } diff --git a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Header.swift b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Header.swift index 99da37c..9324d47 100644 --- a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Header.swift +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Header.swift @@ -12,6 +12,7 @@ extension JourneyRouter { public var headers: HTTPHeaders? { switch self { case .journeyList: return nil + case .spot: return nil } } diff --git a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Method.swift b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Method.swift index 5a3f632..c810438 100644 --- a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Method.swift +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+Method.swift @@ -12,6 +12,7 @@ extension JourneyRouter { public var method: HTTPMethod { switch self { case .journeyList: return .get + case .spot: return .post } } diff --git a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift index 01fc237..9a76056 100644 --- a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift @@ -25,6 +25,7 @@ extension JourneyRouter { public var pathURL: String { switch self { case .journeyList: return "" + case .spot: return "/spot" } } diff --git a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter.swift b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter.swift index 51f6462..bd2f441 100644 --- a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter.swift +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter.swift @@ -10,5 +10,6 @@ import MSNetworking public enum JourneyRouter: Router { case journeyList + case spot } From 3e43c97d5d2cbbf463f731376daf808a992ed6b7 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 17:26:08 +0900 Subject: [PATCH 18/27] =?UTF-8?q?:art:=20SpotDTO=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=ED=95=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift index b8177d4..e3eba17 100644 --- a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift @@ -13,6 +13,12 @@ public struct RequestableSpotDTO: Encodable, Identifiable { public let coordinate: [Double] public let photoData: Data + public init(id: UUID, coordinate: [Double], photoData: Data) { + self.id = id + self.coordinate = coordinate + self.photoData = photoData + } + } public struct ResponsibleSpotDTO: Decodable, Identifiable { @@ -21,4 +27,10 @@ public struct ResponsibleSpotDTO: Decodable, Identifiable { public let coordinate: [Double] public let photoURLs: [String] + public init(id: UUID, coordinate: [Double], photoURLs: [String]) { + self.id = id + self.coordinate = coordinate + self.photoURLs = photoURLs + } + } From 5186f9ac787e1c971c7f2d67b004066d03ac62b1 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 18:02:07 +0900 Subject: [PATCH 19/27] =?UTF-8?q?:art:=20SpotDTO=EC=97=90=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift index e3eba17..f7a6382 100644 --- a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift @@ -11,10 +11,12 @@ public struct RequestableSpotDTO: Encodable, Identifiable { public let id: UUID public let coordinate: [Double] + public let timestamp: String public let photoData: Data - public init(id: UUID, coordinate: [Double], photoData: Data) { + public init(id: UUID, timestamp: String, coordinate: [Double], photoData: Data) { self.id = id + self.timestamp = timestamp self.coordinate = coordinate self.photoData = photoData } @@ -26,11 +28,6 @@ public struct ResponsibleSpotDTO: Decodable, Identifiable { public let id: UUID public let coordinate: [Double] public let photoURLs: [String] - - public init(id: UUID, coordinate: [Double], photoURLs: [String]) { - self.id = id - self.coordinate = coordinate - self.photoURLs = photoURLs - } + public let w3w: String } From 1300cbab66f7c27c83d4d7fa9bc4bed78e3110ce Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 18:38:56 +0900 Subject: [PATCH 20/27] =?UTF-8?q?:art:=20ResponsibleDTO=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=EB=A1=9C=20Codable=20=EC=B2=98=EB=A6=AC(=ED=9A=8C?= =?UTF-8?q?=EC=9D=98=20=ED=95=84=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE.md | 10 -- ...0-\355\205\234\355\224\214\353\246\277.md" | 18 --- .github/PULL_REQUEST_TEMPLATE.md | 18 --- .github/workflows/Xcode_build_test.yml | 124 ------------------ .gitignore | 37 ------ .gitmojirc.json | 8 -- BE/.githooks/pre-commit | 3 - BE/README.md | 6 - README.md | 42 ------ .../contents.xcworkspacedata | 7 - .../Sources/Spot/SpotSaveViewController.swift | 18 ++- .../Spot/Sources/Spot/SpotSaveViewModel.swift | 34 ++++- .../Sources/Spot/SpotViewController.swift | 20 +++ .../Spot/Sources/Spot/SpotViewModel.swift | 1 + iOS/MSData/Package.swift | 4 - .../Sources/MSData/DTO/Fragment/SpotDTO.swift | 3 +- .../Sources/MSData/DTO/JourneyDTO.swift | 2 +- .../Sources/MSLogger/MSLogCategory.swift | 1 + setup | 22 ---- 19 files changed, 73 insertions(+), 305 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md delete mode 100644 ".github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\355\205\234\355\224\214\353\246\277.md" delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/workflows/Xcode_build_test.yml delete mode 100644 .gitignore delete mode 100644 .gitmojirc.json delete mode 100644 BE/.githooks/pre-commit delete mode 100644 BE/README.md delete mode 100644 README.md delete mode 100644 iOS/Features/RewindJourney/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata delete mode 100755 setup diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index e226a1e..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,10 +0,0 @@ - -## 📋 설명 - -- 이슈에서 구현할 내용 작성 - -## ✅ 체크리스트 - -> 구현해야하는 이슈 체크리스트 -- [ ] 구현되지 않은 내용 -- [x] 구현 완료된 내용 diff --git "a/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\355\205\234\355\224\214\353\246\277.md" "b/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\355\205\234\355\224\214\353\246\277.md" deleted file mode 100644 index 9ee2ecf..0000000 --- "a/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\355\205\234\355\224\214\353\246\277.md" +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: 이슈 템플릿 -about: 공통적으로 사용되는 이슈 템플릿 -title: '' -labels: '' -assignees: '' - ---- - -## 📋 설명 - -- 이슈에서 구현할 내용 작성 - -## ✅ 체크리스트 - -> 구현해야하는 이슈 체크리스트 -- [ ] 구현되지 않은 내용 -- [x] 구현 완료된 내용 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 3616c6d..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,18 +0,0 @@ - -## ❗ 배경 -> 작업 배경에 대한 설명을 작성합니다. -> Issue에 대한 링크를 첨부합니다. - -## 🔧 작업 내역 -> 작업한 내용들을 나열합니다. -> 간결하게 리스트 업하고, 자세한 설명은 아래 리뷰 노트에서 합니다. - -## 🧪 테스트 방법 -> 동작을 테스트할 수 있는 방법을 설명합니다. -> 앱 실행 방법일 수 있고, 유닛 테스트 실행 방법일 수 있습니다. - -## 📝 리뷰 노트 -> 작업 내역에 대한 자세한 설명을 작성합니다. - -## 📸 스크린샷 -> 작업한 내용에 대한 스크린샷, 영상 등을 첨부합니다. diff --git a/.github/workflows/Xcode_build_test.yml b/.github/workflows/Xcode_build_test.yml deleted file mode 100644 index 116eb3d..0000000 --- a/.github/workflows/Xcode_build_test.yml +++ /dev/null @@ -1,124 +0,0 @@ -name: Xcode_build_test - -env: - WORKSPACE: iOS/MusicSpot.xcworkspace - -on: - pull_request: - branches: - - 'iOS/release' - - 'iOS/epic/**' - types: [assigned, labeled, opened, synchronize, reopened] - -jobs: - prepare-matrix: - runs-on: macos-13 - outputs: - matrix: ${{ steps.generate-matrix.outputs.matrix }} - steps: - - uses: actions/checkout@v4 - - - name: Setup Xcode - if: ${{ !env.ACT }} - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '15.0.1' - - - name: Generate matrix - id: generate-matrix - run: | - matrix="{\"include\":[" - first_entry=true - for scheme in $(xcodebuild -workspace ${{ env.WORKSPACE }} -list | grep -A 100 "Schemes:" | grep -v "Schemes:" | sed '/^$/d' | sed 's/^[ \t]*//'); do - if [[ $scheme != *"-Package" ]] && [[ $scheme != *"Tests" ]]; then - if [ "$first_entry" = true ]; then - first_entry=false - else - matrix+="," - fi - matrix+="{\"scheme\":\"$scheme\"}" - fi - done - matrix+="]}" - echo "matrix=$matrix" >> $GITHUB_OUTPUT - - xcode-build: - needs: prepare-matrix - runs-on: macos-13 - strategy: - fail-fast: false - matrix: ${{fromJson(needs.prepare-matrix.outputs.matrix)}} - steps: - - uses: actions/checkout@v4 - - - name: Setup Xcode - if: ${{ !env.ACT }} - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '15.0.1' - - - name: 🛠️ Build ${{ matrix.scheme }} - run: | - echo "🛠️ Building ${{ matrix.scheme }}" - xcodebuild \ - -workspace ${{ env.WORKSPACE }} \ - -scheme ${{ matrix.scheme }} \ - -sdk 'iphonesimulator' \ - -destination 'platform=iOS Simulator,OS=17.0.1,name=iPhone 15 Pro' \ - clean build - - prepare-test-matrix: - runs-on: macos-13 - outputs: - matrix: ${{ steps.generate-test-matrix.outputs.matrix }} - steps: - - uses: actions/checkout@v4 - - - name: Setup Xcode - if: ${{ !env.ACT }} - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '15.0.1' - - - name: Generate test matrix - id: generate-test-matrix - run: | - matrix="{\"include\":[" - first_entry=true - for scheme in $(xcodebuild -workspace ${{ env.WORKSPACE }} -list | grep -A 100 "Schemes:" | grep -v "Schemes:" | sed '/^$/d' | sed 's/^[ \t]*//'); do - if [[ $scheme == *"Tests" ]]; then - if [ "$first_entry" = true ]; then - first_entry=false - else - matrix+="," - fi - matrix+="{\"scheme\":\"$scheme\"}" - fi - done - matrix+="]}" - echo "matrix=$matrix" >> $GITHUB_OUTPUT - - xcode-test: - needs: prepare-test-matrix - runs-on: macos-13 - strategy: - fail-fast: false - matrix: ${{fromJson(needs.prepare-test-matrix.outputs.matrix)}} - steps: - - uses: actions/checkout@v4 - - - name: Setup Xcode - if: ${{ !env.ACT }} - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '15.0.1' - - - name: 🧪 Test ${{ matrix.scheme }} - run: | - echo "🧪 Testing ${{ matrix.scheme }}" - xcodebuild \ - -workspace ${{ env.WORKSPACE }} \ - -scheme ${{ matrix.scheme }} \ - -sdk 'iphonesimulator' \ - -destination 'platform=iOS Simulator,OS=17.0.1,name=iPhone 15 Pro' \ - test diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 314044f..0000000 --- a/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -# Created by https://www.toptal.com/developers/gitignore/api/macos -# Edit at https://www.toptal.com/developers/gitignore?templates=macos - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### macOS Patch ### -# iCloud generated files -*.icloud - -# End of https://www.toptal.com/developers/gitignore/api/macos \ No newline at end of file diff --git a/.gitmojirc.json b/.gitmojirc.json deleted file mode 100644 index bba7d02..0000000 --- a/.gitmojirc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "autoAdd": false, - "emojiFormat": "code" , - "scopePrompt": false, - "messagePrompt": false, - "capitalizeTitle": false, - "gitmojisUrl": "https://gist.github.com/SwiftyJunnos/7a0b1b143e5bf30ef6307b5353c8a2f3/raw/gitmoji.json" -} \ No newline at end of file diff --git a/BE/.githooks/pre-commit b/BE/.githooks/pre-commit deleted file mode 100644 index c640339..0000000 --- a/BE/.githooks/pre-commit +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo "Backend" \ No newline at end of file diff --git a/BE/README.md b/BE/README.md deleted file mode 100644 index 183e91a..0000000 --- a/BE/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# 💾 감성일지도 BE - -|J037 김태우|J131 임정훈| -|:-:|:-:| -||| -[@twoo1999](https://github.com/twoo1999)|[@vvans](https://github.com/vvans)| diff --git a/README.md b/README.md deleted file mode 100644 index 15c61b5..0000000 --- a/README.md +++ /dev/null @@ -1,42 +0,0 @@ -

-
-
- 🎶 MusicSpot -
-

- -
- 당신의 여정을 음악과 함께 기억하다.
- 네이버 부스트캠프 웹・모바일 8기 그룹 프로젝트
- 2023.11.06 ~ 2023.12.15
-
- -
- -

-
- ⭐️ 프로젝트 소개 -
-

- -
- Team.과열은 지도에 관심을 갖고있는 사람들로 이루어져 있습니다.
- 지도를 활용해서 음악과 함께 여정을 기록하는 앱, MusicSpot를 소개합니다. -
- -
- -## 🔥 Team. 과열 🔥 - -|S023 윤동주|S034 전민건|S045 이창준|J037 김태우|J131 임정훈| -|:-:|:-:|:-:|:-:|:-:| -|||||| -|[@yoondj98](https://github.com/yoondj98)|[@PushedGun](https://github.com/PushedGun)|[@SwiftyJunnos](https://github.com/SwiftyJunnos)|[@twoo1999](https://github.com/twoo1999)|[@vvans](https://github.com/vvans)| - -
- -## 📔 문서 - -| 그라운드 룰 | 기획/디자인 | 템플릿 | 회의록 | -| :-: | :-: | :-: | :-: | -| 📚 [그라운드 룰](https://github.com/boostcampwm2023/iOS01-Maybe-Gamsung/wiki/%F0%9F%93%9A-%EA%B7%B8%EB%9D%BC%EC%9A%B4%EB%93%9C-%EB%A3%B0) | 🎨 [기획/디자인](https://www.figma.com/file/m3iCljtg84XAmVyy2RNdDj/UI-%EB%94%94%EC%9E%90%EC%9D%B8?type=design&node-id=789%3A1274&mode=design&t=5DxLFumMFIkZllqT-1)| 📃 [템플릿](https://github.com/boostcampwm2023/iOS01-Maybe-Gamsung/wiki/%F0%9F%93%83-%ED%83%AC%ED%94%8C%EB%A6%BF)| 📝 [회의록](https://musicspot.notion.site/0618a86927b342c0927d57826c4d685e?v=e0e955f12f0b41cab1c709ff6db06293&pvs=4) | diff --git a/iOS/Features/RewindJourney/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/iOS/Features/RewindJourney/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/iOS/Features/RewindJourney/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/iOS/Features/Spot/Sources/Spot/SpotSaveViewController.swift b/iOS/Features/Spot/Sources/Spot/SpotSaveViewController.swift index 07447c2..fee0088 100644 --- a/iOS/Features/Spot/Sources/Spot/SpotSaveViewController.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotSaveViewController.swift @@ -9,7 +9,9 @@ import UIKit import MSDesignSystem import MSLogger +import MSNetworking import MSUIKit +import MSData public final class SpotSaveViewController: UIViewController { @@ -60,6 +62,20 @@ public final class SpotSaveViewController: UIViewController { } private let spotSaveViewModel = SpotSaveViewModel() + // MARK: - Properties: Networking + + internal var spotRouter: Router? + internal var journeyID: UUID? { + didSet { + self.spotSaveViewModel.journeyID = self.journeyID + } + } + internal var coordinate: [Double]? { + didSet { + self.spotSaveViewModel.coordinate = self.coordinate + } + } + // MARK: - UI Components private let imageView = UIImageView() @@ -235,7 +251,7 @@ public final class SpotSaveViewController: UIViewController { MSLogger.make(category: .recordingJourney).debug("현재 이미지를 Data로 변환할 수 없습니다.") return } - self.spotSaveViewModel.upload(data: data) + self.spotSaveViewModel.upload(data: data, using: self.spotRouter) } } diff --git a/iOS/Features/Spot/Sources/Spot/SpotSaveViewModel.swift b/iOS/Features/Spot/Sources/Spot/SpotSaveViewModel.swift index b3bdc44..55585bf 100644 --- a/iOS/Features/Spot/Sources/Spot/SpotSaveViewModel.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotSaveViewModel.swift @@ -6,10 +6,19 @@ // import Foundation +import Combine + +import MSData +import MSNetworking +import MSLogger final class SpotSaveViewModel { // MARK: - Properties + private let msNetworking = MSNetworking(session: URLSession.shared) + private var subscriber: Set = [] + internal var journeyID: UUID? + internal var coordinate: [Double]? } @@ -17,8 +26,29 @@ final class SpotSaveViewModel { internal extension SpotSaveViewModel { - func upload(data: Data) { - // Networking.. + func upload(data: Data, using router: Router?) { + guard let router, let journeyID, let coordinate else { + MSLogger.make(category: .spot).debug("journeyID와 coordinate ID가 view model에 전달되지 않았습니다.") + return + } + let timestamp = Data().base64EncodedString() + print(timestamp) + let spotDTO = RequestableSpotDTO(id: journeyID, + timestamp: timestamp, + coordinate: coordinate, + photoData: data) + self.msNetworking.request(ResponsibleSpotDTO.self, router: router) + .sink { response in + switch response { + case .failure(let error): + MSLogger.make(category: .network).debug("\(error): 정상적으로 Spot을 서버에 보내지 못하였습니다.") + default: + return + } + } receiveValue: { spot in + // 받은 데이터 처리 + } + .store(in: &subscriber) } } diff --git a/iOS/Features/Spot/Sources/Spot/SpotViewController.swift b/iOS/Features/Spot/Sources/Spot/SpotViewController.swift index a37baf9..70c3d2f 100644 --- a/iOS/Features/Spot/Sources/Spot/SpotViewController.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotViewController.swift @@ -7,8 +7,10 @@ import UIKit +import MSData import MSDesignSystem import MSLogger +import MSNetworking import MSUIKit public final class SpotViewController: UIViewController, UINavigationControllerDelegate { @@ -56,6 +58,24 @@ public final class SpotViewController: UIViewController, UINavigationControllerD private let spotSaveViewController = SpotSaveViewController() private let picker = UIImagePickerController() + // MARK: - Properties: Networking + + internal var spotRouter: Router? { + didSet { + self.spotSaveViewController.spotRouter = self.spotRouter + } + } + private var journeyID: UUID? { + didSet { + self.spotSaveViewController.journeyID = self.journeyID + } + } + private var coordinate: [Double]? { + didSet { + self.spotSaveViewController.coordinate = self.coordinate + } + } + // MARK: - UI Components private let cameraView = CameraView() diff --git a/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift b/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift index 89b3258..11c65bf 100644 --- a/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift +++ b/iOS/Features/Spot/Sources/Spot/SpotViewModel.swift @@ -20,6 +20,7 @@ final class SpotViewModel: NSObject { // MARK: - Type: SwapMode enum SwapMode { + case front case back diff --git a/iOS/MSData/Package.swift b/iOS/MSData/Package.swift index e9294c5..bffa772 100644 --- a/iOS/MSData/Package.swift +++ b/iOS/MSData/Package.swift @@ -1,9 +1,5 @@ // swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. -<<<<<<< HEAD - -======= ->>>>>>> 48567b56ec07f40e5003caf774569cbdae8f356b import PackageDescription let package = Package( diff --git a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift index f7a6382..a630f1a 100644 --- a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift @@ -23,11 +23,10 @@ public struct RequestableSpotDTO: Encodable, Identifiable { } -public struct ResponsibleSpotDTO: Decodable, Identifiable { +public struct ResponsibleSpotDTO: Codable, Identifiable { public let id: UUID public let coordinate: [Double] public let photoURLs: [String] - public let w3w: String } diff --git a/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift b/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift index a52949a..8fc7337 100644 --- a/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift @@ -12,7 +12,7 @@ public struct JourneyDTO: Codable, Identifiable { public let id: UUID public let location: String public let metaData: JourneyMetadataDTO - public let spots: [SpotDTO] + public let spots: [ResponsibleSpotDTO] public let coordinates: [CoordinateDTO] public let song: SongDTO public let lineColor: String diff --git a/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift b/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift index 17a1032..848022b 100644 --- a/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift +++ b/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift @@ -16,4 +16,5 @@ public enum MSLogCategory: String { case login case setting case camera + case spot } diff --git a/setup b/setup deleted file mode 100755 index f3fbda1..0000000 --- a/setup +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -# Define the function to set the git hooks path -set_git_hooks_path() { - git config core.hooksPath "$1" -} - -# Check the argument and call the function with the appropriate path -case "$1" in - "ios") - echo "🍎 iOS 세팅으로 설정합니다." - set_git_hooks_path "./iOS/.githooks/" - ;; - "be") - echo "💽 BE 세팅으로 설정합니다." - set_git_hooks_path "./BE/.githooks/" - ;; - *) - echo "Invalid argument. Use 'ios' for iOS setup or 'be' for Backend setup." - exit 1 - ;; -esac \ No newline at end of file From 9906fba99c0d19a91cc4aeac2da29825cb279b17 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 20:49:18 +0900 Subject: [PATCH 21/27] =?UTF-8?q?:truck:=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE.md | 10 ++ ...0-\355\205\234\355\224\214\353\246\277.md" | 18 +++ .github/PULL_REQUEST_TEMPLATE.md | 18 +++ .github/workflows/Xcode_build_test.yml | 124 ++++++++++++++++++ .gitignore | 37 ++++++ .gitmojirc.json | 8 ++ BE/.githooks/pre-commit | 3 + BE/README.md | 6 + README.md | 42 ++++++ setup | 22 ++++ 10 files changed, 288 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 ".github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\355\205\234\355\224\214\353\246\277.md" create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/Xcode_build_test.yml create mode 100644 .gitignore create mode 100644 .gitmojirc.json create mode 100644 BE/.githooks/pre-commit create mode 100644 BE/README.md create mode 100644 README.md create mode 100755 setup diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..e226a1e --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,10 @@ + +## 📋 설명 + +- 이슈에서 구현할 내용 작성 + +## ✅ 체크리스트 + +> 구현해야하는 이슈 체크리스트 +- [ ] 구현되지 않은 내용 +- [x] 구현 완료된 내용 diff --git "a/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\355\205\234\355\224\214\353\246\277.md" "b/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\355\205\234\355\224\214\353\246\277.md" new file mode 100644 index 0000000..9ee2ecf --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\354\235\264\354\212\210-\355\205\234\355\224\214\353\246\277.md" @@ -0,0 +1,18 @@ +--- +name: 이슈 템플릿 +about: 공통적으로 사용되는 이슈 템플릿 +title: '' +labels: '' +assignees: '' + +--- + +## 📋 설명 + +- 이슈에서 구현할 내용 작성 + +## ✅ 체크리스트 + +> 구현해야하는 이슈 체크리스트 +- [ ] 구현되지 않은 내용 +- [x] 구현 완료된 내용 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..3616c6d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ + +## ❗ 배경 +> 작업 배경에 대한 설명을 작성합니다. +> Issue에 대한 링크를 첨부합니다. + +## 🔧 작업 내역 +> 작업한 내용들을 나열합니다. +> 간결하게 리스트 업하고, 자세한 설명은 아래 리뷰 노트에서 합니다. + +## 🧪 테스트 방법 +> 동작을 테스트할 수 있는 방법을 설명합니다. +> 앱 실행 방법일 수 있고, 유닛 테스트 실행 방법일 수 있습니다. + +## 📝 리뷰 노트 +> 작업 내역에 대한 자세한 설명을 작성합니다. + +## 📸 스크린샷 +> 작업한 내용에 대한 스크린샷, 영상 등을 첨부합니다. diff --git a/.github/workflows/Xcode_build_test.yml b/.github/workflows/Xcode_build_test.yml new file mode 100644 index 0000000..116eb3d --- /dev/null +++ b/.github/workflows/Xcode_build_test.yml @@ -0,0 +1,124 @@ +name: Xcode_build_test + +env: + WORKSPACE: iOS/MusicSpot.xcworkspace + +on: + pull_request: + branches: + - 'iOS/release' + - 'iOS/epic/**' + types: [assigned, labeled, opened, synchronize, reopened] + +jobs: + prepare-matrix: + runs-on: macos-13 + outputs: + matrix: ${{ steps.generate-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + + - name: Setup Xcode + if: ${{ !env.ACT }} + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.0.1' + + - name: Generate matrix + id: generate-matrix + run: | + matrix="{\"include\":[" + first_entry=true + for scheme in $(xcodebuild -workspace ${{ env.WORKSPACE }} -list | grep -A 100 "Schemes:" | grep -v "Schemes:" | sed '/^$/d' | sed 's/^[ \t]*//'); do + if [[ $scheme != *"-Package" ]] && [[ $scheme != *"Tests" ]]; then + if [ "$first_entry" = true ]; then + first_entry=false + else + matrix+="," + fi + matrix+="{\"scheme\":\"$scheme\"}" + fi + done + matrix+="]}" + echo "matrix=$matrix" >> $GITHUB_OUTPUT + + xcode-build: + needs: prepare-matrix + runs-on: macos-13 + strategy: + fail-fast: false + matrix: ${{fromJson(needs.prepare-matrix.outputs.matrix)}} + steps: + - uses: actions/checkout@v4 + + - name: Setup Xcode + if: ${{ !env.ACT }} + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.0.1' + + - name: 🛠️ Build ${{ matrix.scheme }} + run: | + echo "🛠️ Building ${{ matrix.scheme }}" + xcodebuild \ + -workspace ${{ env.WORKSPACE }} \ + -scheme ${{ matrix.scheme }} \ + -sdk 'iphonesimulator' \ + -destination 'platform=iOS Simulator,OS=17.0.1,name=iPhone 15 Pro' \ + clean build + + prepare-test-matrix: + runs-on: macos-13 + outputs: + matrix: ${{ steps.generate-test-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + + - name: Setup Xcode + if: ${{ !env.ACT }} + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.0.1' + + - name: Generate test matrix + id: generate-test-matrix + run: | + matrix="{\"include\":[" + first_entry=true + for scheme in $(xcodebuild -workspace ${{ env.WORKSPACE }} -list | grep -A 100 "Schemes:" | grep -v "Schemes:" | sed '/^$/d' | sed 's/^[ \t]*//'); do + if [[ $scheme == *"Tests" ]]; then + if [ "$first_entry" = true ]; then + first_entry=false + else + matrix+="," + fi + matrix+="{\"scheme\":\"$scheme\"}" + fi + done + matrix+="]}" + echo "matrix=$matrix" >> $GITHUB_OUTPUT + + xcode-test: + needs: prepare-test-matrix + runs-on: macos-13 + strategy: + fail-fast: false + matrix: ${{fromJson(needs.prepare-test-matrix.outputs.matrix)}} + steps: + - uses: actions/checkout@v4 + + - name: Setup Xcode + if: ${{ !env.ACT }} + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.0.1' + + - name: 🧪 Test ${{ matrix.scheme }} + run: | + echo "🧪 Testing ${{ matrix.scheme }}" + xcodebuild \ + -workspace ${{ env.WORKSPACE }} \ + -scheme ${{ matrix.scheme }} \ + -sdk 'iphonesimulator' \ + -destination 'platform=iOS Simulator,OS=17.0.1,name=iPhone 15 Pro' \ + test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..314044f --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Created by https://www.toptal.com/developers/gitignore/api/macos +# Edit at https://www.toptal.com/developers/gitignore?templates=macos + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +# End of https://www.toptal.com/developers/gitignore/api/macos \ No newline at end of file diff --git a/.gitmojirc.json b/.gitmojirc.json new file mode 100644 index 0000000..bba7d02 --- /dev/null +++ b/.gitmojirc.json @@ -0,0 +1,8 @@ +{ + "autoAdd": false, + "emojiFormat": "code" , + "scopePrompt": false, + "messagePrompt": false, + "capitalizeTitle": false, + "gitmojisUrl": "https://gist.github.com/SwiftyJunnos/7a0b1b143e5bf30ef6307b5353c8a2f3/raw/gitmoji.json" +} \ No newline at end of file diff --git a/BE/.githooks/pre-commit b/BE/.githooks/pre-commit new file mode 100644 index 0000000..c640339 --- /dev/null +++ b/BE/.githooks/pre-commit @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Backend" \ No newline at end of file diff --git a/BE/README.md b/BE/README.md new file mode 100644 index 0000000..183e91a --- /dev/null +++ b/BE/README.md @@ -0,0 +1,6 @@ +# 💾 감성일지도 BE + +|J037 김태우|J131 임정훈| +|:-:|:-:| +||| +[@twoo1999](https://github.com/twoo1999)|[@vvans](https://github.com/vvans)| diff --git a/README.md b/README.md new file mode 100644 index 0000000..15c61b5 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +

+
+
+ 🎶 MusicSpot +
+

+ +
+ 당신의 여정을 음악과 함께 기억하다.
+ 네이버 부스트캠프 웹・모바일 8기 그룹 프로젝트
+ 2023.11.06 ~ 2023.12.15
+
+ +
+ +

+
+ ⭐️ 프로젝트 소개 +
+

+ +
+ Team.과열은 지도에 관심을 갖고있는 사람들로 이루어져 있습니다.
+ 지도를 활용해서 음악과 함께 여정을 기록하는 앱, MusicSpot를 소개합니다. +
+ +
+ +## 🔥 Team. 과열 🔥 + +|S023 윤동주|S034 전민건|S045 이창준|J037 김태우|J131 임정훈| +|:-:|:-:|:-:|:-:|:-:| +|||||| +|[@yoondj98](https://github.com/yoondj98)|[@PushedGun](https://github.com/PushedGun)|[@SwiftyJunnos](https://github.com/SwiftyJunnos)|[@twoo1999](https://github.com/twoo1999)|[@vvans](https://github.com/vvans)| + +
+ +## 📔 문서 + +| 그라운드 룰 | 기획/디자인 | 템플릿 | 회의록 | +| :-: | :-: | :-: | :-: | +| 📚 [그라운드 룰](https://github.com/boostcampwm2023/iOS01-Maybe-Gamsung/wiki/%F0%9F%93%9A-%EA%B7%B8%EB%9D%BC%EC%9A%B4%EB%93%9C-%EB%A3%B0) | 🎨 [기획/디자인](https://www.figma.com/file/m3iCljtg84XAmVyy2RNdDj/UI-%EB%94%94%EC%9E%90%EC%9D%B8?type=design&node-id=789%3A1274&mode=design&t=5DxLFumMFIkZllqT-1)| 📃 [템플릿](https://github.com/boostcampwm2023/iOS01-Maybe-Gamsung/wiki/%F0%9F%93%83-%ED%83%AC%ED%94%8C%EB%A6%BF)| 📝 [회의록](https://musicspot.notion.site/0618a86927b342c0927d57826c4d685e?v=e0e955f12f0b41cab1c709ff6db06293&pvs=4) | diff --git a/setup b/setup new file mode 100755 index 0000000..f3fbda1 --- /dev/null +++ b/setup @@ -0,0 +1,22 @@ +#!/bin/sh + +# Define the function to set the git hooks path +set_git_hooks_path() { + git config core.hooksPath "$1" +} + +# Check the argument and call the function with the appropriate path +case "$1" in + "ios") + echo "🍎 iOS 세팅으로 설정합니다." + set_git_hooks_path "./iOS/.githooks/" + ;; + "be") + echo "💽 BE 세팅으로 설정합니다." + set_git_hooks_path "./BE/.githooks/" + ;; + *) + echo "Invalid argument. Use 'ios' for iOS setup or 'be' for Backend setup." + exit 1 + ;; +esac \ No newline at end of file From 85fca4efb69e075c5675ba360bebeb5433d7abd9 Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 21:02:27 +0900 Subject: [PATCH 22/27] =?UTF-8?q?:memo:=20Workspace=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contents.xcworkspacedata | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/iOS/MusicSpot.xcworkspace/contents.xcworkspacedata b/iOS/MusicSpot.xcworkspace/contents.xcworkspacedata index 318da93..17b1b2b 100644 --- a/iOS/MusicSpot.xcworkspace/contents.xcworkspacedata +++ b/iOS/MusicSpot.xcworkspace/contents.xcworkspacedata @@ -2,23 +2,7 @@ - - - - - - - - -======= location = "group:MusicSpot/MusicSpot.xcodeproj"> ->>>>>>> 48567b56ec07f40e5003caf774569cbdae8f356b -======= location = "group:MusicSpot/../MSFoundation"> ->>>>>>> 48567b56ec07f40e5003caf774569cbdae8f356b From bff86cd206b4234e4e677fc007c5124d0e4ab80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Thu, 30 Nov 2023 21:11:32 +0900 Subject: [PATCH 23/27] =?UTF-8?q?:bug:=20JourneyList=EC=9D=98=20DTO=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/JourneyList/Package.swift | 31 ++++++++++++++++++- .../JourneyList/Model/DTOConvertor.swift | 2 +- iOS/Features/SaveJourney/Package.swift | 13 -------- iOS/MusicSpot/MusicSpot/SceneDelegate.swift | 6 +--- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/iOS/Features/JourneyList/Package.swift b/iOS/Features/JourneyList/Package.swift index 0b44822..3b4aba7 100644 --- a/iOS/Features/JourneyList/Package.swift +++ b/iOS/Features/JourneyList/Package.swift @@ -9,6 +9,10 @@ private extension String { static let package = "FeatureJourneyList" + var fromRootPath: String { + return "../../" + self + } + } private enum Target { @@ -17,6 +21,15 @@ private enum Target { } +private enum Dependency { + + static let msUIKit = "MSUIKit" + static let msCacheStorage = "MSCacheStorage" + static let msCoreKit = "MSCoreKit" + static let msData = "MSData" + +} + // MARK: - Package let package = Package( @@ -28,7 +41,23 @@ let package = Package( .library(name: Target.journeyList, targets: [Target.journeyList]) ], + dependencies: [ + .package(name: Dependency.msUIKit, + path: Dependency.msUIKit.fromRootPath), + .package(name: Dependency.msCoreKit, + path: Dependency.msCoreKit.fromRootPath), + .package(name: Dependency.msData, + path: Dependency.msData.fromRootPath) + ], targets: [ - .target(name: Target.journeyList) + .target(name: Target.journeyList, + dependencies: [ + .product(name: Dependency.msUIKit, + package: Dependency.msUIKit), + .product(name: Dependency.msCacheStorage, + package: Dependency.msCoreKit), + .product(name: Dependency.msData, + package: Dependency.msData) + ]) ] ) diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift index 7b411a9..e5d9304 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift @@ -21,7 +21,7 @@ extension Journey { extension Spot { - init(dto: SpotDTO) { + init(dto: ResponsibleSpotDTO) { self.photoURLs = dto.photoURLs } diff --git a/iOS/Features/SaveJourney/Package.swift b/iOS/Features/SaveJourney/Package.swift index ff6d0e2..f9dec09 100644 --- a/iOS/Features/SaveJourney/Package.swift +++ b/iOS/Features/SaveJourney/Package.swift @@ -42,18 +42,6 @@ let package = Package( .iOS(.v15) ], products: [ -<<<<<<< HEAD - .library(name: "SaveJourney", - targets: ["SaveJourney"]) - ], - dependencies: [ - .package(name: "MSUIKit", - path: "../../MSUIKit") - ], - targets: [ - .target(name: "SaveJourney", - dependencies: ["MSUIKit"]) -======= .library(name: Target.saveJourney, targets: [Target.saveJourney]) ], @@ -67,6 +55,5 @@ let package = Package( .product(name: Dependency.msUIKit, package: Dependency.msUIKit) ]) ->>>>>>> 48567b56ec07f40e5003caf774569cbdae8f356b ] ) diff --git a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift index 2d228e1..f0ed3b8 100644 --- a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift +++ b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift @@ -7,8 +7,6 @@ import UIKit -import JourneyList -import MSData import MSDesignSystem class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -28,9 +26,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { MSFont.registerFonts() - let journeyRepository = JourneyRepositoryImplementation() - let testViewModel = JourneyListViewModel(repository: journeyRepository) - let testViewController = JourneyListViewController(viewModel: testViewModel) + let testViewController = UIViewController() window.rootViewController = testViewController window.makeKeyAndVisible() } From 5ce8bc48eb80d5c47101c96842cb6c8a5cdfc5ce Mon Sep 17 00:00:00 2001 From: mingun Date: Thu, 30 Nov 2023 21:17:01 +0900 Subject: [PATCH 24/27] :art: Resolve conflict --- .../xcschemes/JourneyList.xcscheme | 66 ------------------- iOS/Features/SaveJourney/Package.swift | 13 ---- 2 files changed, 79 deletions(-) delete mode 100644 iOS/Features/JourneyList/.swiftpm/xcode/xcshareddata/xcschemes/JourneyList.xcscheme diff --git a/iOS/Features/JourneyList/.swiftpm/xcode/xcshareddata/xcschemes/JourneyList.xcscheme b/iOS/Features/JourneyList/.swiftpm/xcode/xcshareddata/xcschemes/JourneyList.xcscheme deleted file mode 100644 index e56683b..0000000 --- a/iOS/Features/JourneyList/.swiftpm/xcode/xcshareddata/xcschemes/JourneyList.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/Features/SaveJourney/Package.swift b/iOS/Features/SaveJourney/Package.swift index ff6d0e2..f9dec09 100644 --- a/iOS/Features/SaveJourney/Package.swift +++ b/iOS/Features/SaveJourney/Package.swift @@ -42,18 +42,6 @@ let package = Package( .iOS(.v15) ], products: [ -<<<<<<< HEAD - .library(name: "SaveJourney", - targets: ["SaveJourney"]) - ], - dependencies: [ - .package(name: "MSUIKit", - path: "../../MSUIKit") - ], - targets: [ - .target(name: "SaveJourney", - dependencies: ["MSUIKit"]) -======= .library(name: Target.saveJourney, targets: [Target.saveJourney]) ], @@ -67,6 +55,5 @@ let package = Package( .product(name: Dependency.msUIKit, package: Dependency.msUIKit) ]) ->>>>>>> 48567b56ec07f40e5003caf774569cbdae8f356b ] ) From 16de33e44e6e4e599fba07e5cc1af9c40fff7f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Thu, 30 Nov 2023 22:02:36 +0900 Subject: [PATCH 25/27] =?UTF-8?q?:truck:=20Workspace=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/NavigateMap.xcscheme | 66 --------------- .../xcschemes/RecordJourney.xcscheme | 66 --------------- .../xcschemes/RewindJourney.xcscheme | 66 --------------- .../xcschemes/SaveJourney.xcscheme | 66 --------------- .../xcshareddata/xcschemes/Spot.xcscheme | 66 --------------- .../xcshareddata/xcschemes/MSData.xcscheme | 3 - .../xcschemes/FoundationExt.xcscheme | 66 --------------- .../xcschemes/MSUserDefaults.xcscheme | 3 - .../xcschemes/MSDesignSystem.xcscheme | 66 --------------- .../Calendar.imageset/Calender.pdf | Bin 4018 -> 0 bytes .../xcshareddata/xcschemes/MusicSpot.xcscheme | 77 ------------------ iOS/commit | 4 +- 12 files changed, 2 insertions(+), 547 deletions(-) delete mode 100644 iOS/Features/Home/.swiftpm/xcode/xcshareddata/xcschemes/NavigateMap.xcscheme delete mode 100644 iOS/Features/Home/.swiftpm/xcode/xcshareddata/xcschemes/RecordJourney.xcscheme delete mode 100644 iOS/Features/RewindJourney/.swiftpm/xcode/xcshareddata/xcschemes/RewindJourney.xcscheme delete mode 100644 iOS/Features/SaveJourney/.swiftpm/xcode/xcshareddata/xcschemes/SaveJourney.xcscheme delete mode 100644 iOS/Features/Spot/.swiftpm/xcode/xcshareddata/xcschemes/Spot.xcscheme delete mode 100644 iOS/MSFoundation/.swiftpm/xcode/xcshareddata/xcschemes/FoundationExt.xcscheme delete mode 100644 iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystem.xcscheme delete mode 100644 iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calendar.imageset/Calender.pdf delete mode 100644 iOS/MusicSpot/MusicSpot.xcodeproj/xcshareddata/xcschemes/MusicSpot.xcscheme diff --git a/iOS/Features/Home/.swiftpm/xcode/xcshareddata/xcschemes/NavigateMap.xcscheme b/iOS/Features/Home/.swiftpm/xcode/xcshareddata/xcschemes/NavigateMap.xcscheme deleted file mode 100644 index f4a8537..0000000 --- a/iOS/Features/Home/.swiftpm/xcode/xcshareddata/xcschemes/NavigateMap.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/Features/Home/.swiftpm/xcode/xcshareddata/xcschemes/RecordJourney.xcscheme b/iOS/Features/Home/.swiftpm/xcode/xcshareddata/xcschemes/RecordJourney.xcscheme deleted file mode 100644 index c01b3e3..0000000 --- a/iOS/Features/Home/.swiftpm/xcode/xcshareddata/xcschemes/RecordJourney.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/Features/RewindJourney/.swiftpm/xcode/xcshareddata/xcschemes/RewindJourney.xcscheme b/iOS/Features/RewindJourney/.swiftpm/xcode/xcshareddata/xcschemes/RewindJourney.xcscheme deleted file mode 100644 index b502766..0000000 --- a/iOS/Features/RewindJourney/.swiftpm/xcode/xcshareddata/xcschemes/RewindJourney.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/Features/SaveJourney/.swiftpm/xcode/xcshareddata/xcschemes/SaveJourney.xcscheme b/iOS/Features/SaveJourney/.swiftpm/xcode/xcshareddata/xcschemes/SaveJourney.xcscheme deleted file mode 100644 index 3b465cc..0000000 --- a/iOS/Features/SaveJourney/.swiftpm/xcode/xcshareddata/xcschemes/SaveJourney.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/Features/Spot/.swiftpm/xcode/xcshareddata/xcschemes/Spot.xcscheme b/iOS/Features/Spot/.swiftpm/xcode/xcshareddata/xcschemes/Spot.xcscheme deleted file mode 100644 index bfdca86..0000000 --- a/iOS/Features/Spot/.swiftpm/xcode/xcshareddata/xcschemes/Spot.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSData.xcscheme b/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSData.xcscheme index e0ec558..fdac714 100644 --- a/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSData.xcscheme +++ b/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/MSData.xcscheme @@ -28,8 +28,6 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" shouldAutocreateTestPlan = "YES"> -<<<<<<< HEAD -======= @@ -42,7 +40,6 @@ ->>>>>>> 48567b56ec07f40e5003caf774569cbdae8f356b - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/MSFoundation/.swiftpm/xcode/xcshareddata/xcschemes/MSUserDefaults.xcscheme b/iOS/MSFoundation/.swiftpm/xcode/xcshareddata/xcschemes/MSUserDefaults.xcscheme index ae47497..043e74f 100644 --- a/iOS/MSFoundation/.swiftpm/xcode/xcshareddata/xcschemes/MSUserDefaults.xcscheme +++ b/iOS/MSFoundation/.swiftpm/xcode/xcshareddata/xcschemes/MSUserDefaults.xcscheme @@ -28,8 +28,6 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" shouldAutocreateTestPlan = "YES"> -<<<<<<< HEAD -======= @@ -42,7 +40,6 @@ ->>>>>>> 48567b56ec07f40e5003caf774569cbdae8f356b - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calendar.imageset/Calender.pdf b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSIcon.xcassets/Calendar.imageset/Calender.pdf deleted file mode 100644 index 88304860eadd0b5156d61d69bf15113f536951ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4018 zcmbtXTaOzx6n>vy;g?D+QXJdISE;H*w-g~jlr3)+4AH90t zj+=*J{bKy?lCr)#0?= z4^EqCP4dd>;DgCQJphZv$6^(JG{I9)q1mJ76VxQxdFic4h6O%cgXs z%r*$kb|^X!M6lP5&%#0q#n1)ReiV({0dzCReAq2O@xa=vU|0@1f8BG)b64T8G)wN% z1X={7(@Caa3R2~*w;6u4gxa9V1TqY@tOF{Pi;KNDU`j;7k#7UgQH(h$K#5NUCg=8B z(uT_^0z$#C46r60P4N^Js+TBbA_Xja;{q_N$r?(5u|hsZuU1Z{8c1*>^f3@K3%CPl zLrET#!VS^Igt$~ zQ^HkiV#x?G+()URDWx~i@7d#n;Zi6>#U&1ZLlECtS!Y~%>Q+3=Jc~25+y*vdvq;WNh6cqShXs%DM7nnT+9ndx~@}k+htUY zrv)AN6Uu~=U>I1i4o;R*v5cG-Ho2ilKNQXAMRy_kl(07;tmHVsuvSh^Sw`PCPQMyQs6i;3MWhuG_XIgxPFY9OfX(8FnEC!A~FkXQ4t;G zHY^Tvy+f_MPE|XiF7q_K#6BwF9a={wJqi?|;BI&r0&*J? zMnwl}slY*QYwRTKAgm2kPlKekmta9=VgcPOjYnHm(X|SM!19=z1PY44Vu)MsFcuKl z#ty6vAz0J2Twr3(1={RhNEDxe&AJ8Sm3IYl%-(+WW%CA`5c3lA{L}NM5Be201MUM< zJQ4%gMrR{BgpbK5v^EIPRS}z+L2p7p70-^NN*P2`A`>;nh@3@ZvD^SQ!rI_)5;~%S z&g9J1goZ&6Ii@6C42GU%0l3p(+XjZY(9s`QHxOkbtOSVN2Lmu5c`Z?}l@e7QQ>5BH z5p4>7`$W*D*%3~KW zwjKP23$h%mXD%mGH)dO+92hbd0aJ)vTepiW{Evl$0o``zCvSnUaU!Mp@PKk`7!n@Q z5?;lrH+;YRNy*C9Z`<2hFzHf}B#7u^zGg03Vlrm8#E@N{5z^kW>%;~@xRq)bFLtDO z!y~UsS|Cjy^L-qL+JRrzfS(cnxql39+RXyApUfSJ8=y9xB)Hrc_3K$d(cuEfGj(xK z=M^}GwM!F7v{u&ZJalFWUsoY<@1_U@d1A7FfSQwC+5(bnbULxGr-PqY?d>!_wD?Um zJ-~3eO!EFh74&!6`s@Ava6YLwzv7*SZ~enkI{pR`MZ}q0%lf-oA z=VID@Y?M_>xqt_m$4iwwyla{c}N9ZXg1 z!L6G0+#EiD*IwCw0-SL~vd9ypbyJY?QIKCZ>O2brH{8wSl;)(;f zeHg#>r1k<~-d#et#Nz?SpHPu6A2Z_8jm%wAC0zAp=zQGV@5bYRSM~b47YN7W)8S!! sq8{JhJ(+^CzC9eyiXE;2UcbKocZBuzb-g*Bt2?+O#o^JTZ+?3DAM-LHR{#J2 diff --git a/iOS/MusicSpot/MusicSpot.xcodeproj/xcshareddata/xcschemes/MusicSpot.xcscheme b/iOS/MusicSpot/MusicSpot.xcodeproj/xcshareddata/xcschemes/MusicSpot.xcscheme deleted file mode 100644 index 7eb04dc..0000000 --- a/iOS/MusicSpot/MusicSpot.xcodeproj/xcshareddata/xcschemes/MusicSpot.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/commit b/iOS/commit index 8eac4c3..e43386d 100755 --- a/iOS/commit +++ b/iOS/commit @@ -1,9 +1,9 @@ #!/bin/sh -​ + LINTPATH='.swiftlint.yml' declare -a PATHS=("MSCoreKit" "MSFoundation" "MSUIKit" "MSData" "MusicSpot" "Features") failures="" -​ + for path in "${PATHS[@]}"; do if [ -d "$path" ]; then echo "👀 Running SwiftLint for $path" From 99f606474739fced7040c8ce7ac85a89fcc10c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Thu, 30 Nov 2023 22:09:21 +0900 Subject: [PATCH 26/27] =?UTF-8?q?:truck:=20MSDesignSystemTests=20=ED=83=80?= =?UTF-8?q?=EA=B2=9F=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcschemes/MSDesignSystemTests.xcscheme | 53 ------------------- 1 file changed, 53 deletions(-) delete mode 100644 iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystemTests.xcscheme diff --git a/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystemTests.xcscheme b/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystemTests.xcscheme deleted file mode 100644 index fea2408..0000000 --- a/iOS/MSUIKit/.swiftpm/xcode/xcshareddata/xcschemes/MSDesignSystemTests.xcscheme +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - From 19c7bc3856d21fd9d22380b4d76b5d9279babe1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Fri, 1 Dec 2023 00:53:51 +0900 Subject: [PATCH 27/27] =?UTF-8?q?:truck:=20=EC=82=AC=EC=9A=A9=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/MSNetworking/MSRouter.swift | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 iOS/MSCoreKit/Sources/MSNetworking/MSRouter.swift diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSRouter.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSRouter.swift deleted file mode 100644 index 787d47b..0000000 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSRouter.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// MSRouter.swift -// MSCoreKit -// -// Created by 전민건 on 11/16/23. -// - -import Foundation - -public struct RouterType { - - private var encodable: Encodable? - - // 기능별 MSRouter - public var getJourney: MSRouter { - MSRouter(baseURL: .none, pathURL: .none, method: .get, body: HTTPBody(content: encodable)) - } - public var getPerson: MSRouter { - MSRouter(baseURL: .none, pathURL: .none, method: .get, body: HTTPBody(content: encodable)) - } - -} - -public struct MSRouter: Router { - - let baseURL: APIbaseURL - let pathURL: APIpathURL - let method: HTTPMethod - var body: HTTPBody - - func asURLRequest() -> URLRequest? { - guard let url = URL(string: baseURL.rawValue + pathURL.rawValue) else { - return nil - } - var request = URLRequest(url: url) - request.httpMethod = method.rawValue - request.httpBody = body.contentToData() - - return request - } - -}