From 769a978664bd58e1672dbc2dbdc202369dcfc257 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: Mon, 27 Nov 2023 17:09:51 +0900 Subject: [PATCH 01/39] Update Spot.swift --- iOS/Features/JourneyList/Sources/JourneyList/Spot.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Spot.swift b/iOS/Features/JourneyList/Sources/JourneyList/Spot.swift index 76cd258..218ba82 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Spot.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Spot.swift @@ -8,5 +8,7 @@ import Foundation struct Spot: Hashable { + let images: [String] + } From 435e9a6babc5b92ad293f40d20c6df55756fb069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Thu, 30 Nov 2023 13:07:30 +0900 Subject: [PATCH 02/39] =?UTF-8?q?:art:=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xcode/package.xcworkspace/contents.xcworkspacedata | 7 +++++++ iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift | 2 ++ iOS/Features/SaveJourney/Sources/SaveJourney/Spot.swift | 2 ++ .../xcode/package.xcworkspace/contents.xcworkspacedata | 7 +++++++ .../xcode/package.xcworkspace/contents.xcworkspacedata | 7 +++++++ 5 files changed, 25 insertions(+) create mode 100644 iOS/Features/RewindJourney/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata create mode 100644 iOS/Features/Spot/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata create mode 100644 iOS/MSData/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 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/SaveJourney/Sources/SaveJourney/Journey.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift index a5c1e7e..2dc3609 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift @@ -8,7 +8,9 @@ import Foundation struct Journey: Hashable { + let locatoin: String let date: String let spot: Spot + } diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Spot.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Spot.swift index 76cd258..218ba82 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Spot.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Spot.swift @@ -8,5 +8,7 @@ import Foundation struct Spot: Hashable { + let images: [String] + } diff --git a/iOS/Features/Spot/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/iOS/Features/Spot/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/iOS/Features/Spot/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/iOS/MSData/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/iOS/MSData/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/iOS/MSData/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + From cd307978c5e405ecee509d930fcec6296beb9af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Thu, 30 Nov 2023 13:24:29 +0900 Subject: [PATCH 03/39] =?UTF-8?q?:bug:=20Dependency=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/JourneyList/Package.swift | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/iOS/Features/JourneyList/Package.swift b/iOS/Features/JourneyList/Package.swift index 0b44822..9b0093d 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,12 @@ private enum Target { } +private enum Dependency { + + static let msUIKit = "MSUIKit" + +} + // MARK: - Package let package = Package( @@ -28,7 +38,15 @@ let package = Package( .library(name: Target.journeyList, targets: [Target.journeyList]) ], + dependencies: [ + .package(name: Dependency.msUIKit, + path: Dependency.msUIKit.fromRootPath) + ], targets: [ - .target(name: Target.journeyList) + .target(name: Target.journeyList, + dependencies: [ + .product(name: Dependency.msUIKit, + package: Dependency.msUIKit) + ]) ] ) From c9ab95a6443a97c25c3c1dae9a637ed4a8222df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Thu, 30 Nov 2023 15:47:27 +0900 Subject: [PATCH 04/39] =?UTF-8?q?:art:=20=EC=97=AC=EC=A0=95=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20Demo=20=EC=95=B1=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SaveJourneyDemo.xcodeproj/project.pbxproj | 18 ------ .../Base.lproj/Main.storyboard | 24 ------- .../SaveJourneyDemo/Info.plist | 2 - .../SaveJourneyDemo/SceneDelegate.swift | 62 +++++++------------ .../SaveJourneyDemo/ViewController.swift | 19 ------ .../View/SaveJourneyHeaderView.swift | 2 +- .../View/SaveJourneyMusicCell.swift | 2 + 7 files changed, 27 insertions(+), 102 deletions(-) delete mode 100644 iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Base.lproj/Main.storyboard delete mode 100644 iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/ViewController.swift diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo.xcodeproj/project.pbxproj b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo.xcodeproj/project.pbxproj index 163320f..062e332 100644 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo.xcodeproj/project.pbxproj +++ b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo.xcodeproj/project.pbxproj @@ -9,8 +9,6 @@ /* Begin PBXBuildFile section */ DDAA4DC92B17356D002F0748 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA4DC82B17356D002F0748 /* AppDelegate.swift */; }; DDAA4DCB2B17356D002F0748 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA4DCA2B17356D002F0748 /* SceneDelegate.swift */; }; - DDAA4DCD2B17356D002F0748 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA4DCC2B17356D002F0748 /* ViewController.swift */; }; - DDAA4DD02B17356D002F0748 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DDAA4DCE2B17356D002F0748 /* Main.storyboard */; }; DDAA4DD22B17356E002F0748 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDAA4DD12B17356E002F0748 /* Assets.xcassets */; }; DDAA4DD52B17356E002F0748 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DDAA4DD32B17356E002F0748 /* LaunchScreen.storyboard */; }; DDAA4DDE2B17357F002F0748 /* SaveJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DDAA4DDD2B17357F002F0748 /* SaveJourney */; }; @@ -20,8 +18,6 @@ DDAA4DC52B17356D002F0748 /* SaveJourneyDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SaveJourneyDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; DDAA4DC82B17356D002F0748 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; DDAA4DCA2B17356D002F0748 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - DDAA4DCC2B17356D002F0748 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - DDAA4DCF2B17356D002F0748 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; DDAA4DD12B17356E002F0748 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; DDAA4DD42B17356E002F0748 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; DDAA4DD62B17356E002F0748 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -60,8 +56,6 @@ children = ( DDAA4DC82B17356D002F0748 /* AppDelegate.swift */, DDAA4DCA2B17356D002F0748 /* SceneDelegate.swift */, - DDAA4DCC2B17356D002F0748 /* ViewController.swift */, - DDAA4DCE2B17356D002F0748 /* Main.storyboard */, DDAA4DD12B17356E002F0748 /* Assets.xcassets */, DDAA4DD32B17356E002F0748 /* LaunchScreen.storyboard */, DDAA4DD62B17356E002F0748 /* Info.plist */, @@ -135,7 +129,6 @@ files = ( DDAA4DD52B17356E002F0748 /* LaunchScreen.storyboard in Resources */, DDAA4DD22B17356E002F0748 /* Assets.xcassets in Resources */, - DDAA4DD02B17356D002F0748 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -146,7 +139,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DDAA4DCD2B17356D002F0748 /* ViewController.swift in Sources */, DDAA4DC92B17356D002F0748 /* AppDelegate.swift in Sources */, DDAA4DCB2B17356D002F0748 /* SceneDelegate.swift in Sources */, ); @@ -155,14 +147,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - DDAA4DCE2B17356D002F0748 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - DDAA4DCF2B17356D002F0748 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; DDAA4DD32B17356E002F0748 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -304,7 +288,6 @@ INFOPLIST_FILE = SaveJourneyDemo/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -335,7 +318,6 @@ INFOPLIST_FILE = SaveJourneyDemo/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Base.lproj/Main.storyboard b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Base.lproj/Main.storyboard deleted file mode 100644 index 25a7638..0000000 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Base.lproj/Main.storyboard +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Info.plist b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Info.plist index dd3c9af..0eb786d 100644 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Info.plist +++ b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Info.plist @@ -15,8 +15,6 @@ Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift index defe329..6c13d3c 100644 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift +++ b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift @@ -7,46 +7,32 @@ import UIKit -class SceneDelegate: UIResponder, UIWindowSceneDelegate { +import MSDesignSystem +import SaveJourney +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + // MARK: - Properties + 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 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. + + // MARK: - Functions + + func scene(_ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = (scene as? UIWindowScene) else { return } + let window = UIWindow(windowScene: windowScene) + defer { self.window = window } + + MSFont.registerFonts() + + let saveJourneyViewModel = SaveJourneyViewModel() + let saveJourneyViewController = SaveJourneyViewController(viewModel: saveJourneyViewModel) + + window.rootViewController = saveJourneyViewController + window.makeKeyAndVisible() } - - 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/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/ViewController.swift b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/ViewController.swift deleted file mode 100644 index aa71b3c..0000000 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/ViewController.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// ViewController.swift -// SaveJourneyDemo -// -// Created by 이창준 on 2023.11.29. -// - -import UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - } - - -} - diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyHeaderView.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyHeaderView.swift index 3535ad9..cdeb57f 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyHeaderView.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyHeaderView.swift @@ -11,7 +11,7 @@ import MSDesignSystem final class SaveJourneyHeaderView: UICollectionReusableView { - // MARK: - Constant + // MARK: - Constants static let elementKind = "SaveJourneyHeaderView" static let estimatedHeight: CGFloat = 33.0 diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift index 93486c8..fc21968 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift @@ -16,11 +16,13 @@ final class SaveJourneyMusicCell: UICollectionViewCell { 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 From 01d3c4823acb9f0c14116175eae9e76a2701668b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Thu, 30 Nov 2023 16:02:28 +0900 Subject: [PATCH 05/39] =?UTF-8?q?:sparkles:=20=EC=97=AC=EC=A0=95=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=ED=99=94=EB=A9=B4=20=ED=95=98=EB=8B=A8?= =?UTF-8?q?=EC=97=90=20=EB=B2=84=ED=8A=BC=20=EC=8A=A4=ED=83=9D=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 --- .../SaveJourneyDemo/SceneDelegate.swift | 1 - .../SaveJourneyViewController.swift | 41 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift index 6c13d3c..de9d0b0 100644 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift +++ b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift @@ -35,4 +35,3 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } - diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index 6b4af67..73a99d0 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -33,11 +33,17 @@ public final class SaveJourneyViewController: UIViewController { // MARK: - Constants + private enum Typo { + static let nextButtonTitle = "다음" + } + 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 + static let buttonSpacing: CGFloat = 4.0 + static let buttonBottomInset: CGFloat = 24.0 } // MARK: - Properties @@ -69,6 +75,25 @@ public final class SaveJourneyViewController: UIViewController { private var mapViewHeightConstraint: NSLayoutConstraint? + private let buttonStack: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = Metric.buttonSpacing + return stackView + }() + + private let mediaControlButton: MSRectButton = { + let button = MSRectButton.small() + button.configuration?.image = .msIcon(.play) + return button + }() + + private let nextButton: MSButton = { + let button = MSButton.primary() + button.configuration?.title = Typo.nextButtonTitle + return button + }() + // MARK: - Initializer public init(viewModel: SaveJourneyViewModel, @@ -267,6 +292,22 @@ private extension SaveJourneyViewController { self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) + + self.view.addSubview(self.buttonStack) + self.buttonStack.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.buttonStack.leadingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.leadingAnchor), + self.buttonStack.trailingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.trailingAnchor), + self.buttonStack.bottomAnchor.constraint(equalTo: self.view.keyboardLayoutGuide.topAnchor, + constant: -Metric.buttonBottomInset) + ]) + + [ + self.mediaControlButton, + self.nextButton + ].forEach { + self.buttonStack.addArrangedSubview($0) + } } } From 4d3c997dca8d54590f78a882cf5c0ad5040640fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Thu, 30 Nov 2023 16:41:01 +0900 Subject: [PATCH 06/39] =?UTF-8?q?:package:=20=EC=97=AC=EC=A0=95=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=ED=8C=A8=ED=82=A4=EC=A7=80=EC=9D=98=20Dep?= =?UTF-8?q?endency=EC=97=90=20MSData=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/SaveJourney/Package.swift | 7 ++++++- .../SaveJourney/Sources/SaveJourney/Journey.swift | 2 +- .../Presentation/SaveJourneyViewModel.swift | 10 ++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/iOS/Features/SaveJourney/Package.swift b/iOS/Features/SaveJourney/Package.swift index f9dec09..e1c3743 100644 --- a/iOS/Features/SaveJourney/Package.swift +++ b/iOS/Features/SaveJourney/Package.swift @@ -27,6 +27,7 @@ private enum Target { private enum Dependency { + static let msData = "MSData" static let msUIKit = "MSUIKit" static let msFoundation = "MSFoundation" static let msDesignsystem = "MSDesignSystem" @@ -47,11 +48,15 @@ let package = Package( ], dependencies: [ .package(name: Dependency.msUIKit, - path: Dependency.msUIKit.fromRootPath) + path: Dependency.msUIKit.fromRootPath), + .package(name: Dependency.msData, + path: Dependency.msData.fromRootPath) ], targets: [ .target(name: Target.saveJourney, dependencies: [ + .product(name: Dependency.msData, + package: Dependency.msData), .product(name: Dependency.msUIKit, package: Dependency.msUIKit) ]) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift index 2dc3609..9861c12 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift @@ -9,7 +9,7 @@ import Foundation struct Journey: Hashable { - let locatoin: String + let location: String let date: String let spot: Spot diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift index 837cd55..1b83c2a 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift @@ -11,6 +11,8 @@ public final class SaveJourneyViewModel { public enum Action { case viewNeedsLoaded + case mediaControlButtonDidTap + case nextButtonDidTap } public struct State { @@ -32,6 +34,10 @@ public final class SaveJourneyViewModel { switch action { case .viewNeedsLoaded: self.fetchInitialJourneys() + case .mediaControlButtonDidTap: + print("Media Control Button Tap.") + case .nextButtonDidTap: + print("Next Button Tap.") } } @@ -41,10 +47,10 @@ private extension SaveJourneyViewModel { func fetchInitialJourneys() { self.state.music.send("NewJeans") - self.state.journeys.send([Journey(locatoin: "여정 위치", + self.state.journeys.send([Journey(location: "여정 위치", date: "2023. 01. 01", spot: Spot(images: ["sdlkj", "sdklfj"])), - Journey(locatoin: "여정 위치", + Journey(location: "여정 위치", date: "2023. 01. 02", spot: Spot(images: ["slkjc", "llskl", "llskldf", "llskl5", "llskl12"]))]) } From 18094ca424a51b482f8ad92f3403456dfa9bf2a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Sun, 26 Nov 2023 17:36:55 +0900 Subject: [PATCH 07/39] =?UTF-8?q?:package:=20MSData=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # iOS/Features/JourneyList/Package.swift # iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift # iOS/MSData/Package.swift # iOS/MusicSpot.xcworkspace/contents.xcworkspacedata # iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj --- .../Domain/JourneyListUseCase.swift | 12 ++++++ .../JourneyListViewController.swift | 4 +- .../Presentation/JourneyListViewModel.swift | 8 ++-- .../Sources/MSNetworking/MSNetworking.swift | 11 ++++- iOS/MSData/Resources/APIInfo.plist | 8 ++++ .../MSData/Repository/JourneyRepository.swift | 18 ++++++++ .../Sources/MSData/Router/JourneyRouter.swift | 42 +++++++++++++++++++ .../MusicSpot.xcodeproj/project.pbxproj | 7 ++++ iOS/MusicSpot/MusicSpot/SceneDelegate.swift | 6 ++- 9 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 iOS/Features/JourneyList/Sources/JourneyList/Domain/JourneyListUseCase.swift create mode 100644 iOS/MSData/Resources/APIInfo.plist create mode 100644 iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift create mode 100644 iOS/MSData/Sources/MSData/Router/JourneyRouter.swift diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Domain/JourneyListUseCase.swift b/iOS/Features/JourneyList/Sources/JourneyList/Domain/JourneyListUseCase.swift new file mode 100644 index 0000000..70ce088 --- /dev/null +++ b/iOS/Features/JourneyList/Sources/JourneyList/Domain/JourneyListUseCase.swift @@ -0,0 +1,12 @@ +// +// FetchJourneyListUseCase.swift +// JourneyList +// +// Created by 이창준 on 11/26/23. +// + +import Foundation + +struct FetchJourneyListUseCase { + +} diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift index e33be6a..08f9a96 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift @@ -197,10 +197,12 @@ private extension JourneyListViewController { // MARK: - Preview import MSDesignSystem +import MSNetworking @available(iOS 17, *) #Preview { MSFont.registerFonts() - let journeyListViewModel = JourneyListViewModel() + let session = URLSession(configuration: .default) + let journeyListViewModel = JourneyListViewModel(networking: MSNetworking(session: session)) let journeyListViewController = JourneyListViewController(viewModel: journeyListViewModel) return journeyListViewController } diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift index 93628db..d37b097 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift @@ -7,6 +7,8 @@ import Combine +import MSData + public final class JourneyListViewModel { public enum Action { @@ -21,12 +23,12 @@ public final class JourneyListViewModel { // MARK: - Properties - public var state: State + public var state = State() // MARK: - Initializer - public init(state: State = State()) { - self.state = state + public init(repository: JourneyRepository) { + } // MARK: - Functions diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift index 0a4cb98..98e0639 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift @@ -12,8 +12,15 @@ public struct MSNetworking { private let encoder = JSONEncoder() private let session: Session - func request(router: Router, type: T.Type) -> AnyPublisher? { - + // MARK: - Initializer + + public init(session: Session) { + self.session = session + } + + // MARK: - Functions + + public func request(router: Router) -> AnyPublisher? { guard let request: URLRequest = router.asURLRequest() else { return nil } diff --git a/iOS/MSData/Resources/APIInfo.plist b/iOS/MSData/Resources/APIInfo.plist new file mode 100644 index 0000000..a788343 --- /dev/null +++ b/iOS/MSData/Resources/APIInfo.plist @@ -0,0 +1,8 @@ + + + + + BaseURL + https://api.com + + diff --git a/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift b/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift new file mode 100644 index 0000000..12e6ea6 --- /dev/null +++ b/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift @@ -0,0 +1,18 @@ +// +// JourneyRepository.swift +// MSData +// +// Created by 이창준 on 11/26/23. +// + +import Foundation + +public protocol JourneyRepository { + +} + +public struct JourneyRepositoryImpl: JourneyRepository { + + public init() { } + +} diff --git a/iOS/MSData/Sources/MSData/Router/JourneyRouter.swift b/iOS/MSData/Sources/MSData/Router/JourneyRouter.swift new file mode 100644 index 0000000..6ea802f --- /dev/null +++ b/iOS/MSData/Sources/MSData/Router/JourneyRouter.swift @@ -0,0 +1,42 @@ +// +// JourneyRouter.swift +// JourneyList +// +// Created by 이창준 on 11/26/23. +// + +import Foundation + +import MSNetworking + +enum JourneyRouter: Router { + + var baseURL: APIbaseURL { + switch self { + + } + } + + var pathURL: APIpathURL { + switch self { + + } + } + + var method: HTTPMethod { + switch self { + + } + } + + var body: HTTPBody { + switch self { + + } + } + + func asURLRequest() -> URLRequest? { + <#code#> + } + +} diff --git a/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj b/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj index 8a5b2ac..96803c6 100644 --- a/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj +++ b/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj @@ -84,6 +84,13 @@ path = MusicSpot; sourceTree = ""; }; + DDE77E1D2B1320B9005927B0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ diff --git a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift index f0ed3b8..cdbb377 100644 --- a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift +++ b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift @@ -7,6 +7,8 @@ import UIKit +import JourneyList +import MSData import MSDesignSystem class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -26,7 +28,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { MSFont.registerFonts() - let testViewController = UIViewController() + let journeyRepository = JourneyRepositoryImpl() + let testViewModel = JourneyListViewModel(repository: journeyRepository) + let testViewController = JourneyListViewController(viewModel: testViewModel) window.rootViewController = testViewController window.makeKeyAndVisible() } From f102e849811dc91e5f7408359bfe74e0dbe2fd65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 27 Nov 2023 18:13:38 +0900 Subject: [PATCH 08/39] =?UTF-8?q?:truck:=20DTO=20=EA=B2=BD=EB=A1=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 --- .../Sources/MSData}/DTO/Fragment/CoordinateDTO.swift | 2 +- .../Sources/MSData}/DTO/Fragment/JourneyMetadataDTO.swift | 2 +- .../Sources/MSData}/DTO/Fragment/SongDTO.swift | 4 ++-- .../Sources/MSData}/DTO/Fragment/SpotDTO.swift | 7 +++---- .../Sources/MSData}/DTO/JourneyDTO.swift | 6 +++--- .../Sources/MSData}/DTO/PersonDTO.swift | 4 ++-- 6 files changed, 12 insertions(+), 13 deletions(-) rename iOS/{MSCoreKit/Sources/MSNetworking => MSData/Sources/MSData}/DTO/Fragment/CoordinateDTO.swift (79%) rename iOS/{MSCoreKit/Sources/MSNetworking => MSData/Sources/MSData}/DTO/Fragment/JourneyMetadataDTO.swift (76%) rename iOS/{MSCoreKit/Sources/MSNetworking => MSData/Sources/MSData}/DTO/Fragment/SongDTO.swift (68%) rename iOS/{MSCoreKit/Sources/MSNetworking => MSData/Sources/MSData}/DTO/Fragment/SpotDTO.swift (58%) rename iOS/{MSCoreKit/Sources/MSNetworking => MSData/Sources/MSData}/DTO/JourneyDTO.swift (73%) rename iOS/{MSCoreKit/Sources/MSNetworking => MSData/Sources/MSData}/DTO/PersonDTO.swift (72%) diff --git a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/CoordinateDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/CoordinateDTO.swift similarity index 79% rename from iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/CoordinateDTO.swift rename to iOS/MSData/Sources/MSData/DTO/Fragment/CoordinateDTO.swift index 2a90dad..cab443c 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/CoordinateDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/CoordinateDTO.swift @@ -5,7 +5,7 @@ // Created by 전민건 on 11/16/23. // -struct CoordinateDTO: Codable { +public struct CoordinateDTO: Codable { let latitude: Double 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 76% rename from iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/JourneyMetadataDTO.swift rename to iOS/MSData/Sources/MSData/DTO/Fragment/JourneyMetadataDTO.swift index 19e8ba3..20d44f9 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/JourneyMetadataDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/JourneyMetadataDTO.swift @@ -7,7 +7,7 @@ import Foundation -struct JourneyMetadataDTO: Codable { +public struct JourneyMetadataDTO: Codable { let date: Date diff --git a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SongDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift similarity index 68% rename from iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SongDTO.swift rename to iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift index 596e17b..4301e92 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SongDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift @@ -7,9 +7,9 @@ import Foundation -struct SongDTO: Codable { +public struct SongDTO: Codable, Identifiable { - let id: UUID + public let id: UUID let title: String let artwork: String diff --git a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SpotDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift similarity index 58% rename from iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SpotDTO.swift rename to iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift index 6ab1c68..1d9daf5 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/Fragment/SpotDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift @@ -7,11 +7,10 @@ import Foundation -struct SpotDTO: Codable { +public struct SpotDTO: Codable, Identifiable { - let spotIdentifier: UUID + public let id: UUID let coordinate: [Double] - let photo: Data? - let w3w: String + let photoURLs: [String] } diff --git a/iOS/MSCoreKit/Sources/MSNetworking/DTO/JourneyDTO.swift b/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift similarity index 73% rename from iOS/MSCoreKit/Sources/MSNetworking/DTO/JourneyDTO.swift rename to iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift index 55f90fa..9b8cb3a 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/JourneyDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift @@ -7,14 +7,14 @@ import Foundation -struct JourneyDTO: Codable { +public struct JourneyDTO: Codable, Identifiable { - let journeyIdentifier: UUID + public let id: UUID let title: String let metaData: JourneyMetadataDTO let spots: [SpotDTO] let coordinates: [CoordinateDTO] - let song: SongDTO? + let song: SongDTO let lineColor: String } diff --git a/iOS/MSCoreKit/Sources/MSNetworking/DTO/PersonDTO.swift b/iOS/MSData/Sources/MSData/DTO/PersonDTO.swift similarity index 72% rename from iOS/MSCoreKit/Sources/MSNetworking/DTO/PersonDTO.swift rename to iOS/MSData/Sources/MSData/DTO/PersonDTO.swift index 6735144..4cc9503 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/DTO/PersonDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/PersonDTO.swift @@ -7,9 +7,9 @@ import Foundation -struct PersonDTO: Codable { +public struct PersonDTO: Codable, Identifiable { - let personIdentifier: UUID + public let id: UUID let nickname: String let journeys: [JourneyDTO] let email: String From 25df647d9cc32c66442856ea4c109241a849dcd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 27 Nov 2023 18:15:30 +0900 Subject: [PATCH 09/39] =?UTF-8?q?:art:=20DTO=EC=97=90=20public=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 --- .../Sources/MSData/DTO/Fragment/CoordinateDTO.swift | 4 ++-- .../MSData/DTO/Fragment/JourneyMetadataDTO.swift | 2 +- iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift | 4 ++-- iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift | 4 ++-- iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift | 12 ++++++------ iOS/MSData/Sources/MSData/DTO/PersonDTO.swift | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/iOS/MSData/Sources/MSData/DTO/Fragment/CoordinateDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/CoordinateDTO.swift index cab443c..354ec8c 100644 --- a/iOS/MSData/Sources/MSData/DTO/Fragment/CoordinateDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/CoordinateDTO.swift @@ -7,7 +7,7 @@ public struct CoordinateDTO: Codable { - let latitude: Double - let longitude: Double + public let latitude: Double + public let longitude: Double } diff --git a/iOS/MSData/Sources/MSData/DTO/Fragment/JourneyMetadataDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/JourneyMetadataDTO.swift index 20d44f9..6d0e5f5 100644 --- a/iOS/MSData/Sources/MSData/DTO/Fragment/JourneyMetadataDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/JourneyMetadataDTO.swift @@ -9,6 +9,6 @@ import Foundation 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 index 4301e92..686f1af 100644 --- a/iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift @@ -10,7 +10,7 @@ import Foundation public struct SongDTO: Codable, Identifiable { public let id: UUID - let title: String - let artwork: String + public let title: String + public let artwork: String } diff --git a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift index 1d9daf5..3e00aa6 100644 --- a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift @@ -10,7 +10,7 @@ import Foundation public struct SpotDTO: Codable, Identifiable { public let id: UUID - let coordinate: [Double] - let photoURLs: [String] + 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 index 9b8cb3a..58947bf 100644 --- a/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift @@ -10,11 +10,11 @@ import Foundation public struct JourneyDTO: Codable, Identifiable { public let id: UUID - let title: String - let metaData: JourneyMetadataDTO - let spots: [SpotDTO] - let coordinates: [CoordinateDTO] - let song: SongDTO - let lineColor: String + public let title: 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 index 4cc9503..1319988 100644 --- a/iOS/MSData/Sources/MSData/DTO/PersonDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/PersonDTO.swift @@ -10,8 +10,8 @@ import Foundation public struct PersonDTO: Codable, Identifiable { public let id: UUID - let nickname: String - let journeys: [JourneyDTO] - let email: String + public let nickname: String + public let journeys: [JourneyDTO] + public let email: String } From 8d9f40f0a566a783f7d52bf5df0ecdc86ea33935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 27 Nov 2023 19:18:12 +0900 Subject: [PATCH 10/39] =?UTF-8?q?:sparkles:=20Journey=20Mock=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # iOS/Features/JourneyList/Package.swift # iOS/Features/JourneyList/Sources/JourneyList/File.swift # iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift # iOS/Features/JourneyList/Sources/JourneyList/Song.swift # iOS/Features/JourneyList/Sources/JourneyList/Spot.swift # iOS/MSData/Resources/MockJourney.json # iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift --- .../Sources/JourneyList/File.swift | 37 + .../Sources/JourneyList/Journey.swift | 38 +- .../JourneyListViewController.swift | 15 +- .../Presentation/JourneyListViewModel.swift | 1 + .../JourneyList/Presentation/View/File | 0 .../Sources/JourneyList/Song.swift | 15 + .../Sources/JourneyList/Spot.swift | 4 +- iOS/MSData/Resources/MockJourney.json | 653 ++++++++++++++++++ .../Sources/MSData/DTO/Fragment/SongDTO.swift | 1 + .../Sources/MSData/DTO/JourneyDTO.swift | 2 +- .../MSData/Repository/JourneyRepository.swift | 50 +- .../MSUIKit/Cells/JourneyCell/Journey.swift | 14 - .../Cells/JourneyCell/JourneyCell.swift | 21 +- .../Cells/JourneyCell/JourneyCellModel.swift | 51 ++ .../Cells/JourneyCell/JourneyInfoView.swift | 17 +- .../Cells/JourneyCell/MusicInfoView.swift | 24 +- .../MSUIKit/Cells/JourneyCell/Spot.swift | 12 - .../JourneyCell/SpotPhotoImageView.swift | 5 +- 18 files changed, 897 insertions(+), 63 deletions(-) create mode 100644 iOS/Features/JourneyList/Sources/JourneyList/File.swift delete mode 100644 iOS/Features/JourneyList/Sources/JourneyList/Presentation/View/File create mode 100644 iOS/Features/JourneyList/Sources/JourneyList/Song.swift create mode 100644 iOS/MSData/Resources/MockJourney.json delete mode 100644 iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/Journey.swift create mode 100644 iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCellModel.swift delete mode 100644 iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/Spot.swift diff --git a/iOS/Features/JourneyList/Sources/JourneyList/File.swift b/iOS/Features/JourneyList/Sources/JourneyList/File.swift new file mode 100644 index 0000000..8d5a3ee --- /dev/null +++ b/iOS/Features/JourneyList/Sources/JourneyList/File.swift @@ -0,0 +1,37 @@ +// +// File.swift +// +// +// 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/Journey.swift b/iOS/Features/JourneyList/Sources/JourneyList/Journey.swift index a5c1e7e..f7a64e1 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Journey.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Journey.swift @@ -7,8 +7,38 @@ import Foundation -struct Journey: Hashable { - let locatoin: String - let date: String - let spot: Spot +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/Presentation/JourneyListViewController.swift b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift index 08f9a96..ebcd1e5 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift @@ -144,7 +144,12 @@ private extension JourneyListViewController { func configureDataSource() -> JourneyListDataSource { let cellRegistration = JourneyCellRegistration { cell, _, itemIdentifier in - // TODO: Cell 데이터 바인딩 + let cellModel = JourneyCellModel(location: itemIdentifier.location, + date: itemIdentifier.date, + songTitle: itemIdentifier.song.title, + songArtist: itemIdentifier.song.artist) + cell.update(with: cellModel) +// cell.updateImages(images: itemIdentifier.spot.photoURLs) } let dataSource = JourneyListDataSource(collectionView: self.collectionView, @@ -176,8 +181,12 @@ private extension JourneyListViewController { self.titleStack.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor) ]) - self.titleStack.addArrangedSubview(self.titleLabel) - self.titleStack.addArrangedSubview(self.subtitleLabel) + [ + self.titleLabel, + self.subtitleLabel + ].forEach { + self.titleStack.addArrangedSubview($0) + } self.view.addSubview(self.collectionView) self.collectionView.translatesAutoresizingMaskIntoConstraints = false diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift index d37b097..6f899a0 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift @@ -6,6 +6,7 @@ // import Combine +import Foundation import MSData diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/View/File b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/View/File deleted file mode 100644 index e69de29..0000000 diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Song.swift b/iOS/Features/JourneyList/Sources/JourneyList/Song.swift new file mode 100644 index 0000000..cf990e9 --- /dev/null +++ b/iOS/Features/JourneyList/Sources/JourneyList/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/Spot.swift b/iOS/Features/JourneyList/Sources/JourneyList/Spot.swift index 218ba82..cd2eb00 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Spot.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Spot.swift @@ -7,8 +7,8 @@ import Foundation -struct Spot: Hashable { +struct Spot: Decodable { - let images: [String] + let photoURLs: [String] } diff --git a/iOS/MSData/Resources/MockJourney.json b/iOS/MSData/Resources/MockJourney.json new file mode 100644 index 0000000..6160ff9 --- /dev/null +++ b/iOS/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/DTO/Fragment/SongDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift index 686f1af..af9efcc 100644 --- a/iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SongDTO.swift @@ -12,5 +12,6 @@ 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/JourneyDTO.swift b/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift index 58947bf..a52949a 100644 --- a/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift @@ -10,7 +10,7 @@ import Foundation public struct JourneyDTO: Codable, Identifiable { public let id: UUID - public let title: String + public let location: String public let metaData: JourneyMetadataDTO public let spots: [SpotDTO] public let coordinates: [CoordinateDTO] diff --git a/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift b/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift index 12e6ea6..d0c4634 100644 --- a/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift +++ b/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift @@ -8,11 +8,57 @@ import Foundation public protocol JourneyRepository { - + func fetchJourneyList() async -> Result<[JourneyDTO], Error> } public struct JourneyRepositoryImpl: JourneyRepository { - public init() { } + // 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 + } } diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/Journey.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/Journey.swift deleted file mode 100644 index a5c1e7e..0000000 --- a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/Journey.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// 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/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift index 1d7577b..2803faa 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift @@ -40,12 +40,6 @@ public final class JourneyCell: UICollectionViewCell { super.init(frame: frame) self.configureStyles() self.configureLayout() - - (1...10).forEach { _ in - let imageView = SpotPhotoImageView() - imageView.backgroundColor = .systemBlue - self.spotImageStack.addArrangedSubview(imageView) - } } public required init?(coder: NSCoder) { @@ -54,8 +48,19 @@ public final class JourneyCell: UICollectionViewCell { // MARK: - Functions - public func update(with data: String) { - + public func update(with model: JourneyCellModel) { + self.infoView.update(location: model.location, + date: model.date, + title: model.song.title, + artist: model.song.artist) + } + + public func updateImages(images: [Data]) { + images.forEach { data in + let imageView = SpotPhotoImageView() + imageView.update(with: data) + self.spotImageStack.addArrangedSubview(imageView) + } } } 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 index 4b114cb..8c20d87 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyInfoView.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyInfoView.swift @@ -43,7 +43,7 @@ final class JourneyInfoView: UIView { return stackView }() - private let titleLabel: UILabel = { + private let locationTitleLabel: UILabel = { let label = UILabel() label.font = .msFont(.subtitle) label.textColor = .msColor(.primaryTypo) @@ -84,10 +84,15 @@ final class JourneyInfoView: UIView { // MARK: - Functions - func update(with journey: Journey) { - self.titleLabel.text = journey.locatoin - self.dateLabel.text = journey.date - self.w3wLabel.text = nil + 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) } } @@ -109,7 +114,7 @@ private extension JourneyInfoView { self.contentStack.addArrangedSubview(self.titleLabelStack) self.contentStack.addArrangedSubview(self.musicInfoView) - self.titleLabelStack.addArrangedSubview(self.titleLabel) + self.titleLabelStack.addArrangedSubview(self.locationTitleLabel) self.titleLabelStack.addArrangedSubview(self.subLabelStack) self.subLabelStack.addArrangedSubview(self.dateLabel) diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift index 3b5701a..0ca6770 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift @@ -27,14 +27,15 @@ final class MusicInfoView: UIView { private let iconImageView: UIImageView = { let imageView = UIImageView() imageView.image = .msIcon(.voice) + imageView.tintColor = .msColor(.primaryTypo) return imageView }() - private let musicTitleLabel: UILabel = { + private let titleLabel: UILabel = { let label = UILabel() - label.font = .msFont(.caption) + label.font = .msFont(.boldCaption) label.textColor = .msColor(.primaryTypo) - label.text = "Attention" + label.text = "Title" return label }() @@ -46,11 +47,11 @@ final class MusicInfoView: UIView { return label }() - private let musicArtistLabel: UILabel = { + private let artistLabel: UILabel = { let label = UILabel() - label.font = .msFont(.boldCaption) + label.font = .msFont(.caption) label.textColor = .msColor(.primaryTypo) - label.text = "New Jeans" + label.text = "Artist" return label }() @@ -65,6 +66,13 @@ final class MusicInfoView: UIView { fatalError("MusicSpot은 code-based로만 작업 중입니다.") } + // MARK: - Functions + + func update(artist: String, title: String) { + self.artistLabel.text = artist + self.titleLabel.text = title + } + } // MARK: - UI Configuration @@ -95,9 +103,9 @@ private extension MusicInfoView { ]) [ - self.musicArtistLabel, + self.titleLabel, self.dividerLabel, - self.musicTitleLabel + self.artistLabel ].forEach { self.musicInfoStackView.addArrangedSubview($0) } diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/Spot.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/Spot.swift deleted file mode 100644 index 76cd258..0000000 --- a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/Spot.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Spot.swift -// JourneyList -// -// Created by 이창준 on 11/23/23. -// - -import Foundation - -struct Spot: Hashable { - let images: [String] -} diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/SpotPhotoImageView.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/SpotPhotoImageView.swift index f3de0a5..ed041f7 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/SpotPhotoImageView.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/SpotPhotoImageView.swift @@ -22,7 +22,6 @@ final class SpotPhotoImageView: UIView { private let imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill - imageView.backgroundColor = .systemBlue return imageView }() @@ -40,8 +39,8 @@ final class SpotPhotoImageView: UIView { // MARK: - Functions - func update(with imageURL: String) { - + func update(with imageData: Data) { + self.imageView.image = UIImage(data: imageData) } } From 2be2a97f075da75b3b2e1d7714a70da5d258756f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Sun, 26 Nov 2023 16:09:53 +0900 Subject: [PATCH 11/39] =?UTF-8?q?:art:=20MSNetworking=20=EC=9A=94=EC=86=8C?= =?UTF-8?q?=EB=93=A4=EC=97=90=20public=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift --- iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift | 4 ++-- iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift | 4 ++-- iOS/MSCoreKit/Sources/MSNetworking/HTTPMethod.swift | 2 +- iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift | 6 +++++- iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift | 3 ++- iOS/MSCoreKit/Sources/MSNetworking/Session.swift | 2 +- 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift b/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift index 3c4d2b6..769f703 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift @@ -7,13 +7,13 @@ import Foundation -enum APIbaseURL: String { +public enum APIbaseURL: String { // base URL 추가 case none = "" } -enum APIpathURL: String { +public enum APIpathURL: String { // path URL 추가 case none = "" diff --git a/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift b/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift index 72d3761..1be9518 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift @@ -1,13 +1,13 @@ // // HTTPBody.swift -// +// MSCoreKit // // Created by 전민건 on 11/16/23. // import Foundation -struct HTTPBody { +public struct HTTPBody { private let encoder = JSONEncoder() var content: Encodable? diff --git a/iOS/MSCoreKit/Sources/MSNetworking/HTTPMethod.swift b/iOS/MSCoreKit/Sources/MSNetworking/HTTPMethod.swift index 23936eb..e870826 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/HTTPMethod.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/HTTPMethod.swift @@ -7,7 +7,7 @@ import Foundation -enum HTTPMethod: String { +public enum HTTPMethod: String { case get = "GET" case post = "POST" diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift index 98e0639..6f4c591 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift @@ -4,11 +4,14 @@ // // Created by 전민건 on 11/16/23. // + import Foundation import Combine public struct MSNetworking { + // MARK: - Properties + private let encoder = JSONEncoder() private let session: Session @@ -20,7 +23,7 @@ public struct MSNetworking { // MARK: - Functions - public func request(router: Router) -> AnyPublisher? { + public func request(router: Router, type: T.Type) -> AnyPublisher? { guard let request: URLRequest = router.asURLRequest() else { return nil } @@ -39,4 +42,5 @@ public struct MSNetworking { } .eraseToAnyPublisher() } + } diff --git a/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift b/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift index f55dbe6..7b1e351 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift @@ -7,7 +7,7 @@ import Foundation -protocol Router { +public protocol Router { var baseURL: APIbaseURL { get } var pathURL: APIpathURL { get } @@ -15,4 +15,5 @@ protocol Router { var body: HTTPBody { get } func asURLRequest() -> URLRequest? + } diff --git a/iOS/MSCoreKit/Sources/MSNetworking/Session.swift b/iOS/MSCoreKit/Sources/MSNetworking/Session.swift index af485bb..240a261 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/Session.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/Session.swift @@ -7,7 +7,7 @@ import Foundation -protocol Session { +public protocol Session { func dataTaskPublisher(for request: URLRequest) -> URLSession.DataTaskPublisher From 55bc49df29d0444fd5f72f1748647844be11bc07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Sun, 26 Nov 2023 21:36:03 +0900 Subject: [PATCH 12/39] =?UTF-8?q?:recycle:=20MSNetworking=20URL=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD=20&=20Header=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift --- .../Sources/MSNetworking/APIURL.swift | 20 ------------- .../Sources/MSNetworking/HTTPBody.swift | 16 ++++++++-- .../Sources/MSNetworking/HTTPHeader.swift | 11 +++++++ .../Sources/MSNetworking/HTTPMethod.swift | 2 -- .../Sources/MSNetworking/MSNetworking.swift | 6 ++-- .../MSNetworking/Protocol/Router.swift | 29 ++++++++++++++++--- .../Sources/MSNetworking/Session.swift | 4 +-- 7 files changed, 53 insertions(+), 35 deletions(-) delete mode 100644 iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift create mode 100644 iOS/MSCoreKit/Sources/MSNetworking/HTTPHeader.swift diff --git a/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift b/iOS/MSCoreKit/Sources/MSNetworking/APIURL.swift deleted file mode 100644 index 769f703..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 - -public enum APIbaseURL: String { - - // base URL 추가 - case none = "" -} - -public enum APIpathURL: String { - - // path URL 추가 - case none = "" -} diff --git a/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift b/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift index 1be9518..59470c8 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/HTTPBody.swift @@ -9,13 +9,25 @@ import Foundation 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 e870826..5afe787 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/HTTPMethod.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/HTTPMethod.swift @@ -5,8 +5,6 @@ // Created by 전민건 on 11/16/23. // -import Foundation - public enum HTTPMethod: String { case get = "GET" diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift index 6f4c591..b5a0bc3 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift @@ -23,10 +23,8 @@ public struct MSNetworking { // MARK: - Functions - public func request(router: Router, type: T.Type) -> AnyPublisher? { - guard let request: URLRequest = router.asURLRequest() else { - return nil - } + public func request(router: Router) -> AnyPublisher? { + guard let request = router.request else { return nil } return session .dataTaskPublisher(for: request) diff --git a/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift b/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift index 7b1e351..c1af560 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift @@ -9,11 +9,32 @@ import Foundation 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 } - func asURLRequest() -> URLRequest? + var request: URLRequest? { get } + +} + +extension Router { + + 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() + } + self.headers.forEach { key, value in + request.addValue(value, forHTTPHeaderField: key) + } + + return request + } } diff --git a/iOS/MSCoreKit/Sources/MSNetworking/Session.swift b/iOS/MSCoreKit/Sources/MSNetworking/Session.swift index 240a261..7ce10c3 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/Session.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/Session.swift @@ -13,6 +13,4 @@ public protocol Session { } -extension URLSession: Session { - -} +extension URLSession: Session { } From c87c0a52c21435613be0275daa4198ded1d06a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 27 Nov 2023 01:04:56 +0900 Subject: [PATCH 13/39] =?UTF-8?q?:art:=20MSNetworking=20Error=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD=20&=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/MSNetworking/MSNetworkError.swift | 5 +-- .../Sources/MSNetworking/MSNetworking.swift | 34 +++++++++++++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift index 966c709..909a3bf 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift @@ -9,7 +9,8 @@ import Foundation enum MSNetworkError: Error { - case noResponse - case responseCode + case invalidRouter + case unknownResponse + case invalidStatusCode(statusCode: Int, description: String) } diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift index b5a0bc3..b900521 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift @@ -12,7 +12,18 @@ public struct MSNetworking { // MARK: - Properties - private let encoder = JSONEncoder() + 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 // MARK: - Initializer @@ -23,21 +34,24 @@ public struct MSNetworking { // MARK: - Functions - public func request(router: Router) -> AnyPublisher? { - guard let request = router.request else { return nil } + public func request(_ type: T.Type, router: Router) -> 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 + .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() } From 17e7f20782ba1e4d3318f6ffbdb570abbc7e4afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 27 Nov 2023 01:18:34 +0900 Subject: [PATCH 14/39] =?UTF-8?q?:art:=20MSNetworking=EC=97=90=20timeout?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift | 3 ++- iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift index 909a3bf..0ac2e93 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift @@ -7,10 +7,11 @@ import Foundation -enum MSNetworkError: Error { +public enum MSNetworkError: Error { case invalidRouter case unknownResponse case invalidStatusCode(statusCode: Int, description: String) + case timeout } diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift index b900521..4c4214a 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift @@ -10,6 +10,8 @@ import Combine public struct MSNetworking { + public typealias TimeoutInterval = DispatchQueue.SchedulerTimeType.Stride + // MARK: - Properties private let encoder: JSONEncoder = { @@ -34,13 +36,16 @@ public struct MSNetworking { // MARK: - Functions - public func request(_ type: T.Type, router: Router) -> AnyPublisher { + 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) + .timeout(timeoutInterval, scheduler: DispatchQueue.global()) .tryMap { data, response -> Data in guard let response = response as? HTTPURLResponse else { throw MSNetworkError.unknownResponse From db1cd1b072f4cc7fffdac5c00aaea1b93a0e41c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Tue, 28 Nov 2023 00:13:00 +0900 Subject: [PATCH 15/39] =?UTF-8?q?:test=5Ftube:=20MSNetworking=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=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 --- .../SaveJourneyViewController.swift | 2 +- .../Sources/MSNetworking/MSNetworkError.swift | 19 ++++ .../Sources/MSNetworking/MSNetworking.swift | 8 +- .../MSNetworkingTests/MSNetworkingTests.swift | 96 +++++++++++++++---- .../Mock/MockMSNetworking.swift | 50 ---------- .../MSNetworkingTests/Mock/MockRouter.swift | 21 ++-- .../Mock/MockURLProtocol.swift | 54 +++++++++++ 7 files changed, 174 insertions(+), 76 deletions(-) delete mode 100644 iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockMSNetworking.swift create mode 100644 iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockURLProtocol.swift diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index 73a99d0..24c399c 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -201,7 +201,7 @@ private extension SaveJourneyViewController { } let journeyCellRegistration = JourneyCellRegistration { cell, _, itemIdentifier in - cell.update(with: "여정") + } let headerRegistration = HeaderRegistration(elementKind: SaveJourneyHeaderView.elementKind, diff --git a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift index 0ac2e93..ff5d183 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworkError.swift @@ -15,3 +15,22 @@ public enum MSNetworkError: Error { 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 4c4214a..43d027d 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/MSNetworking.swift @@ -12,6 +12,10 @@ public struct MSNetworking { public typealias TimeoutInterval = DispatchQueue.SchedulerTimeType.Stride + // MARK: - Constants + + public static let dispatchQueueLabel = "com.MSNetworking.MSCoreKit.MusicSpot" + // MARK: - Properties private let encoder: JSONEncoder = { @@ -27,11 +31,13 @@ public struct MSNetworking { }() private let session: Session + public let queue: DispatchQueue // MARK: - Initializer public init(session: Session) { self.session = session + self.queue = DispatchQueue(label: MSNetworking.dispatchQueueLabel, qos: .background) } // MARK: - Functions @@ -45,7 +51,7 @@ public struct MSNetworking { return session .dataTaskPublisher(for: request) - .timeout(timeoutInterval, scheduler: DispatchQueue.global()) + .timeout(timeoutInterval, scheduler: self.queue) .tryMap { data, response -> Data in guard let response = response as? HTTPURLResponse else { throw MSNetworkError.unknownResponse diff --git a/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift b/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift index 0f8e587..5acf5b6 100644 --- a/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift +++ b/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift @@ -5,33 +5,93 @@ // 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 { + 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 + } + +} + From 88cd0c03bbe13d0c74ffe0b295e0e2887c7879d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Sun, 26 Nov 2023 21:44:43 +0900 Subject: [PATCH 16/39] =?UTF-8?q?:sparkles:=20Journey=20Router=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 # Conflicts: # iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift --- .../Presentation/JourneyListViewModel.swift | 4 +- .../MSData/Repository/JourneyRepository.swift | 2 +- .../Router/Journey/JourneyRouter+Body.swift | 18 ++++++++ .../Router/Journey/JourneyRouter+Header.swift | 18 ++++++++ .../Router/Journey/JourneyRouter+Method.swift | 18 ++++++++ .../Router/Journey/JourneyRouter+URL.swift | 27 ++++++++++++ .../MSData/Router/Journey/JourneyRouter.swift | 14 +++++++ .../Sources/MSData/Router/JourneyRouter.swift | 42 ------------------- 8 files changed, 99 insertions(+), 44 deletions(-) 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 delete mode 100644 iOS/MSData/Sources/MSData/Router/JourneyRouter.swift diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift index 6f899a0..c826950 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift @@ -26,10 +26,12 @@ public final class JourneyListViewModel { public var state = State() + private let repository: JourneyRepository + // MARK: - Initializer public init(repository: JourneyRepository) { - + self.repository = repository } // MARK: - Functions diff --git a/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift b/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift index d0c4634..3e3b4a9 100644 --- a/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift +++ b/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift @@ -5,7 +5,7 @@ // Created by 이창준 on 11/26/23. // -import Foundation +import MSNetworking public protocol JourneyRepository { func fetchJourneyList() async -> Result<[JourneyDTO], Error> 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..5ad87fb --- /dev/null +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift @@ -0,0 +1,27 @@ +// +// JourneyRouter+BaseURL.swift +// MSData +// +// Created by 이창준 on 11/26/23. +// + +import Foundation + +import MSNetworking + +extension JourneyRouter { + + public var baseURL: String { + guard let urlString = Bundle.main.object(forInfoDictionaryKey: "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/Sources/MSData/Router/JourneyRouter.swift b/iOS/MSData/Sources/MSData/Router/JourneyRouter.swift deleted file mode 100644 index 6ea802f..0000000 --- a/iOS/MSData/Sources/MSData/Router/JourneyRouter.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// JourneyRouter.swift -// JourneyList -// -// Created by 이창준 on 11/26/23. -// - -import Foundation - -import MSNetworking - -enum JourneyRouter: Router { - - var baseURL: APIbaseURL { - switch self { - - } - } - - var pathURL: APIpathURL { - switch self { - - } - } - - var method: HTTPMethod { - switch self { - - } - } - - var body: HTTPBody { - switch self { - - } - } - - func asURLRequest() -> URLRequest? { - <#code#> - } - -} From ba0ed8de7baaaf48861783675922e2cd676c9258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Thu, 30 Nov 2023 17:09:38 +0900 Subject: [PATCH 17/39] =?UTF-8?q?:bug:=20=EC=97=AC=EC=A0=95=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20Demo=EC=95=B1=EC=9D=84=20=EC=8B=A4=ED=96=89=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=9C=20=EC=83=81=ED=83=9C=EB=A1=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 --- .../Presentation/SaveJourneyViewController.swift | 1 + .../Sources/MSNetworking/Protocol/Router.swift | 10 ++++++---- .../Tests/MSNetworkingTests/MSNetworkingTests.swift | 1 + .../MSNetworkingTests/Mock/MockURLProtocol.swift | 1 - iOS/MSData/Sources/MSData/MSData.swift | 2 -- .../MSData/Repository/JourneyRepository.swift | 13 +++++++++---- .../{ => Sources/MSData}/Resources/APIInfo.plist | 0 .../{ => Sources/MSData}/Resources/MockJourney.json | 0 .../MSData/Router/Journey/JourneyRouter+URL.swift | 6 +++++- 9 files changed, 22 insertions(+), 12 deletions(-) delete mode 100644 iOS/MSData/Sources/MSData/MSData.swift rename iOS/MSData/{ => Sources/MSData}/Resources/APIInfo.plist (100%) rename iOS/MSData/{ => Sources/MSData}/Resources/MockJourney.json (100%) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index 24c399c..36e5ba1 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -79,6 +79,7 @@ public final class SaveJourneyViewController: UIViewController { let stackView = UIStackView() stackView.axis = .horizontal stackView.spacing = Metric.buttonSpacing + stackView.alignment = .center return stackView }() diff --git a/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift b/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift index c1af560..48e802c 100644 --- a/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift +++ b/iOS/MSCoreKit/Sources/MSNetworking/Protocol/Router.swift @@ -13,7 +13,7 @@ public protocol Router { var pathURL: String { get } var method: HTTPMethod { get } var body: HTTPBody? { get } - var headers: HTTPHeaders { get } + var headers: HTTPHeaders? { get } var request: URLRequest? { get } @@ -21,7 +21,7 @@ public protocol Router { extension Router { - var request: URLRequest? { + public var request: URLRequest? { guard let baseURL = URL(string: self.baseURL) else { return nil } let url = baseURL.appendingPathComponent(self.pathURL) @@ -30,8 +30,10 @@ extension Router { if let body = self.body { request.httpBody = body.data() } - self.headers.forEach { key, value in - request.addValue(value, forHTTPHeaderField: key) + if let headers = self.headers { + headers.forEach { key, value in + request.addValue(value, forHTTPHeaderField: key) + } } return request diff --git a/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift b/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift index 5acf5b6..8fe0af0 100644 --- a/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift +++ b/iOS/MSCoreKit/Tests/MSNetworkingTests/MSNetworkingTests.swift @@ -80,6 +80,7 @@ final class MSNetworkingTests: XCTestCase { .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 에러를 발생시켜야 합니다.") diff --git a/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockURLProtocol.swift b/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockURLProtocol.swift index 74893b1..8fe7fb3 100644 --- a/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockURLProtocol.swift +++ b/iOS/MSCoreKit/Tests/MSNetworkingTests/Mock/MockURLProtocol.swift @@ -51,4 +51,3 @@ final class MockURLProtocol: URLProtocol { } } - diff --git a/iOS/MSData/Sources/MSData/MSData.swift b/iOS/MSData/Sources/MSData/MSData.swift deleted file mode 100644 index 08b22b8..0000000 --- a/iOS/MSData/Sources/MSData/MSData.swift +++ /dev/null @@ -1,2 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book diff --git a/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift b/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift index 3e3b4a9..0929a92 100644 --- a/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift +++ b/iOS/MSData/Sources/MSData/Repository/JourneyRepository.swift @@ -5,13 +5,16 @@ // Created by 이창준 on 11/26/23. // +import Combine +import Foundation + import MSNetworking public protocol JourneyRepository { func fetchJourneyList() async -> Result<[JourneyDTO], Error> } -public struct JourneyRepositoryImpl: JourneyRepository { +public struct JourneyRepositoryImplementation: JourneyRepository { // MARK: - Properties @@ -26,7 +29,7 @@ public struct JourneyRepositoryImpl: JourneyRepository { // MARK: - Functions public func fetchJourneyList() async -> Result<[JourneyDTO], Error> { - #if DEBUG +#if DEBUG guard let jsonURL = Bundle.module.url(forResource: "MockJourney", withExtension: "json") else { return .failure((MSNetworkError.invalidRouter)) } @@ -41,7 +44,7 @@ public struct JourneyRepositoryImpl: JourneyRepository { } catch { print(error) } - #else +#else return await withCheckedContinuation { continuation in var cancellable: AnyCancellable? cancellable = self.networking.request([JourneyDTO].self, router: JourneyRouter.journeyList) @@ -58,7 +61,9 @@ public struct JourneyRepositoryImpl: JourneyRepository { cancellable?.cancel() } } - #endif +#endif + + return .failure(MSNetworkError.unknownResponse) } } diff --git a/iOS/MSData/Resources/APIInfo.plist b/iOS/MSData/Sources/MSData/Resources/APIInfo.plist similarity index 100% rename from iOS/MSData/Resources/APIInfo.plist rename to iOS/MSData/Sources/MSData/Resources/APIInfo.plist diff --git a/iOS/MSData/Resources/MockJourney.json b/iOS/MSData/Sources/MSData/Resources/MockJourney.json similarity index 100% rename from iOS/MSData/Resources/MockJourney.json rename to iOS/MSData/Sources/MSData/Resources/MockJourney.json diff --git a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift index 5ad87fb..01fc237 100644 --- a/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift +++ b/iOS/MSData/Sources/MSData/Router/Journey/JourneyRouter+URL.swift @@ -12,9 +12,13 @@ import MSNetworking extension JourneyRouter { public var baseURL: String { - guard let urlString = Bundle.main.object(forInfoDictionaryKey: "BaseURL") as? String else { + 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 } From e90f3e9c28096e81c19d24fc7b327fe047882749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Thu, 30 Nov 2023 18:34:02 +0900 Subject: [PATCH 18/39] =?UTF-8?q?:art:=20=EC=97=AC=EC=A0=95=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=84=EC=8B=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JourneyListViewController.swift | 5 +++-- .../Presentation/JourneyListViewModel.swift | 18 ++++++++++++------ iOS/MusicSpot/MusicSpot/SceneDelegate.swift | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift index ebcd1e5..c2ee18b 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift @@ -206,12 +206,13 @@ private extension JourneyListViewController { // MARK: - Preview import MSDesignSystem +import MSData import MSNetworking @available(iOS 17, *) #Preview { MSFont.registerFonts() - let session = URLSession(configuration: .default) - let journeyListViewModel = JourneyListViewModel(networking: MSNetworking(session: session)) + let journeyListRepository = JourneyRepositoryImplementation() + let journeyListViewModel = JourneyListViewModel(repository: journeyListRepository) let journeyListViewController = JourneyListViewController(viewModel: journeyListViewModel) return journeyListViewController } diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift index c826950..e6064e6 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewModel.swift @@ -48,12 +48,18 @@ public final class JourneyListViewModel { private extension JourneyListViewModel { func fetchInitialJourneys() { - 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"]))]) + self.state.journeys.send([Journey(location: "여정 위치", + date: .now, + spots: [ + Spot(photoURLs: ["sdlkj", "sdklfj"]) + ], + song: Song(artist: "sdlkfj", title: "sdklfj")), + Journey(location: "여정 위치", + date: .now, + spots: [ + Spot(photoURLs: ["sdlkj", "sdklfj"]) + ], + song: Song(artist: "sdlkfj", title: "sdklfj"))]) } } diff --git a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift index cdbb377..2d228e1 100644 --- a/iOS/MusicSpot/MusicSpot/SceneDelegate.swift +++ b/iOS/MusicSpot/MusicSpot/SceneDelegate.swift @@ -28,7 +28,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { MSFont.registerFonts() - let journeyRepository = JourneyRepositoryImpl() + let journeyRepository = JourneyRepositoryImplementation() let testViewModel = JourneyListViewModel(repository: journeyRepository) let testViewController = JourneyListViewController(viewModel: testViewModel) window.rootViewController = testViewController From 5683ddb977173ca988aa483440adc1045c042fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Thu, 30 Nov 2023 20:44:14 +0900 Subject: [PATCH 19/39] =?UTF-8?q?:art:=20DTOConvertor=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/JourneyList/Package.swift | 5 +++++ .../JourneyList/Domain/JourneyListUseCase.swift | 12 ------------ .../Sources/JourneyList/JourneyList.swift | 8 -------- .../{File.swift => Model/DTOConvertor.swift} | 4 ++-- .../Sources/JourneyList/{ => Model}/Journey.swift | 0 .../Sources/JourneyList/{ => Model}/Song.swift | 0 .../Sources/JourneyList/{ => Model}/Spot.swift | 0 7 files changed, 7 insertions(+), 22 deletions(-) delete mode 100644 iOS/Features/JourneyList/Sources/JourneyList/Domain/JourneyListUseCase.swift delete mode 100644 iOS/Features/JourneyList/Sources/JourneyList/JourneyList.swift rename iOS/Features/JourneyList/Sources/JourneyList/{File.swift => Model/DTOConvertor.swift} (93%) rename iOS/Features/JourneyList/Sources/JourneyList/{ => Model}/Journey.swift (100%) rename iOS/Features/JourneyList/Sources/JourneyList/{ => Model}/Song.swift (100%) rename iOS/Features/JourneyList/Sources/JourneyList/{ => Model}/Spot.swift (100%) diff --git a/iOS/Features/JourneyList/Package.swift b/iOS/Features/JourneyList/Package.swift index 9b0093d..874d7e6 100644 --- a/iOS/Features/JourneyList/Package.swift +++ b/iOS/Features/JourneyList/Package.swift @@ -23,6 +23,7 @@ private enum Target { private enum Dependency { + static let msData = "MSData" static let msUIKit = "MSUIKit" } @@ -39,12 +40,16 @@ let package = Package( targets: [Target.journeyList]) ], dependencies: [ + .package(name: Dependency.msData, + path: Dependency.msData.fromRootPath), .package(name: Dependency.msUIKit, path: Dependency.msUIKit.fromRootPath) ], targets: [ .target(name: Target.journeyList, dependencies: [ + .product(name: Dependency.msData, + package: Dependency.msData), .product(name: Dependency.msUIKit, package: Dependency.msUIKit) ]) diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Domain/JourneyListUseCase.swift b/iOS/Features/JourneyList/Sources/JourneyList/Domain/JourneyListUseCase.swift deleted file mode 100644 index 70ce088..0000000 --- a/iOS/Features/JourneyList/Sources/JourneyList/Domain/JourneyListUseCase.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// FetchJourneyListUseCase.swift -// JourneyList -// -// Created by 이창준 on 11/26/23. -// - -import Foundation - -struct FetchJourneyListUseCase { - -} diff --git a/iOS/Features/JourneyList/Sources/JourneyList/JourneyList.swift b/iOS/Features/JourneyList/Sources/JourneyList/JourneyList.swift deleted file mode 100644 index fa1180d..0000000 --- a/iOS/Features/JourneyList/Sources/JourneyList/JourneyList.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// JourneyList.swift -// JourneyList -// -// Created by 이창준 on 2023.11.29. -// - -import Foundation diff --git a/iOS/Features/JourneyList/Sources/JourneyList/File.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift similarity index 93% rename from iOS/Features/JourneyList/Sources/JourneyList/File.swift rename to iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift index 8d5a3ee..7b411a9 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/File.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift @@ -1,6 +1,6 @@ // -// File.swift -// +// DTOConvertor.swift +// JourneyList // // Created by 이창준 on 2023.11.27. // diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Journey.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/Journey.swift similarity index 100% rename from iOS/Features/JourneyList/Sources/JourneyList/Journey.swift rename to iOS/Features/JourneyList/Sources/JourneyList/Model/Journey.swift diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Song.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/Song.swift similarity index 100% rename from iOS/Features/JourneyList/Sources/JourneyList/Song.swift rename to iOS/Features/JourneyList/Sources/JourneyList/Model/Song.swift diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Spot.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/Spot.swift similarity index 100% rename from iOS/Features/JourneyList/Sources/JourneyList/Spot.swift rename to iOS/Features/JourneyList/Sources/JourneyList/Model/Spot.swift From 8da2f9c538ce1878e15b13aea7de2d92b5ec7e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 00:09:20 +0900 Subject: [PATCH 20/39] =?UTF-8?q?:truck:=20=EC=97=AC=EC=A0=95=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EB=AA=A8=EB=8D=B8=20=ED=8C=8C=EC=9D=BC=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SaveJourney/Sources/SaveJourney/{ => Model}/Journey.swift | 0 .../SaveJourney/Sources/SaveJourney/{ => Model}/Spot.swift | 0 iOS/Features/SaveJourney/Sources/SaveJourney/SaveJourney.swift | 2 -- 3 files changed, 2 deletions(-) rename iOS/Features/SaveJourney/Sources/SaveJourney/{ => Model}/Journey.swift (100%) rename iOS/Features/SaveJourney/Sources/SaveJourney/{ => Model}/Spot.swift (100%) delete mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/SaveJourney.swift diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift similarity index 100% rename from iOS/Features/SaveJourney/Sources/SaveJourney/Journey.swift rename to iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Spot.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift similarity index 100% rename from iOS/Features/SaveJourney/Sources/SaveJourney/Spot.swift rename to iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/SaveJourney.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/SaveJourney.swift deleted file mode 100644 index 08b22b8..0000000 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/SaveJourney.swift +++ /dev/null @@ -1,2 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book From 681ed0c2a1bcae8f5d39445940d1a1c3c7b10084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 00:11:19 +0900 Subject: [PATCH 21/39] =?UTF-8?q?:art:=20=EB=9F=B0=EC=B9=98=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Base.lproj/LaunchScreen.storyboard | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Base.lproj/LaunchScreen.storyboard b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Base.lproj/LaunchScreen.storyboard index 865e932..ea13b37 100644 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Base.lproj/LaunchScreen.storyboard +++ b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,11 @@ - - + + + - + + + @@ -11,10 +14,22 @@ - + - + + + + + + + + @@ -22,4 +37,9 @@ + + + + + From 2954f3e108a0e4054c899b28b52ed447e773ac25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 00:26:15 +0900 Subject: [PATCH 22/39] =?UTF-8?q?:bug:=20=EB=B9=8C=EB=93=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/Home/Package.swift | 4 +- iOS/Features/JourneyList/Package.swift | 4 - .../MusicSpot.xcodeproj/project.pbxproj | 96 +++++++++---------- 3 files changed, 45 insertions(+), 59 deletions(-) diff --git a/iOS/Features/Home/Package.swift b/iOS/Features/Home/Package.swift index 012cb72..164214a 100644 --- a/iOS/Features/Home/Package.swift +++ b/iOS/Features/Home/Package.swift @@ -54,9 +54,7 @@ let package = Package( .package(name: Dependency.journeyList, path: Dependency.journeyList.fromCurrentPath), .package(name: Dependency.msUIKit, - path: Dependency.msUIKit.fromRootPath), - .package(name: Dependency.msCoreKit, - path: Dependency.msCoreKit.fromRootPath) + path: Dependency.msUIKit.fromRootPath) ], targets: [ .target(name: Target.home, diff --git a/iOS/Features/JourneyList/Package.swift b/iOS/Features/JourneyList/Package.swift index 5498537..ab3488b 100644 --- a/iOS/Features/JourneyList/Package.swift +++ b/iOS/Features/JourneyList/Package.swift @@ -13,10 +13,6 @@ private extension String { return "../../" + self } - var fromRootPath: String { - return "../../" + self - } - } private enum Target { diff --git a/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj b/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj index 8a33cd6..54a09fe 100644 --- a/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj +++ b/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj @@ -15,18 +15,18 @@ 08CBF87D2B18468E007D3797 /* SpotCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8752B18468E007D3797 /* SpotCoordinator.swift */; }; 08CBF87E2B18468E007D3797 /* SearchMusicCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8762B18468E007D3797 /* SearchMusicCoordinator.swift */; }; 08CBF87F2B18468E007D3797 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8772B18468E007D3797 /* Coordinator.swift */; }; + DD197AF82B1CD514001D4290 /* Home in Frameworks */ = {isa = PBXBuildFile; productRef = DD197AF72B1CD514001D4290 /* Home */; }; + DD197AFA2B1CD514001D4290 /* NavigateMap in Frameworks */ = {isa = PBXBuildFile; productRef = DD197AF92B1CD514001D4290 /* NavigateMap */; }; + DD197AFC2B1CD514001D4290 /* RecordJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DD197AFB2B1CD514001D4290 /* RecordJourney */; }; + DD197AFF2B1CD51B001D4290 /* JourneyList in Frameworks */ = {isa = PBXBuildFile; productRef = DD197AFE2B1CD51B001D4290 /* JourneyList */; }; + DD197B022B1CD521001D4290 /* RewindJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B012B1CD521001D4290 /* RewindJourney */; }; + DD197B052B1CD528001D4290 /* SaveJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B042B1CD528001D4290 /* SaveJourney */; }; + DD197B082B1CD52E001D4290 /* SelectSong in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B072B1CD52E001D4290 /* SelectSong */; }; + DD197B0B2B1CD534001D4290 /* Spot in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B0A2B1CD534001D4290 /* Spot */; }; 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 */; }; - DD9782712B1902B8003CB134 /* Home in Frameworks */ = {isa = PBXBuildFile; productRef = DD9782702B1902B8003CB134 /* Home */; }; - DDCDDA172B18F4B8008E66D1 /* NavigateMap in Frameworks */ = {isa = PBXBuildFile; productRef = DDCDDA162B18F4B8008E66D1 /* NavigateMap */; }; - DDCDDA192B18F4B8008E66D1 /* RecordJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DDCDDA182B18F4B8008E66D1 /* RecordJourney */; }; - DDCDDA1C2B18F4BE008E66D1 /* JourneyList in Frameworks */ = {isa = PBXBuildFile; productRef = DDCDDA1B2B18F4BE008E66D1 /* JourneyList */; }; - DDCDDA1F2B18F4C6008E66D1 /* RewindJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DDCDDA1E2B18F4C6008E66D1 /* RewindJourney */; }; - DDCDDA222B18F4CD008E66D1 /* SaveJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DDCDDA212B18F4CD008E66D1 /* SaveJourney */; }; - DDCDDA252B18F4D7008E66D1 /* SelectSong in Frameworks */ = {isa = PBXBuildFile; productRef = DDCDDA242B18F4D7008E66D1 /* SelectSong */; }; - DDCDDA282B18F4DE008E66D1 /* Spot in Frameworks */ = {isa = PBXBuildFile; productRef = DDCDDA272B18F4DE008E66D1 /* Spot */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -52,14 +52,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DDCDDA192B18F4B8008E66D1 /* RecordJourney in Frameworks */, - DDCDDA172B18F4B8008E66D1 /* NavigateMap in Frameworks */, - DDCDDA1F2B18F4C6008E66D1 /* RewindJourney in Frameworks */, - DDCDDA1C2B18F4BE008E66D1 /* JourneyList in Frameworks */, - DDCDDA282B18F4DE008E66D1 /* Spot in Frameworks */, - DD9782712B1902B8003CB134 /* Home in Frameworks */, - DDCDDA222B18F4CD008E66D1 /* SaveJourney in Frameworks */, - DDCDDA252B18F4D7008E66D1 /* SelectSong in Frameworks */, + DD197AFC2B1CD514001D4290 /* RecordJourney in Frameworks */, + DD197B082B1CD52E001D4290 /* SelectSong in Frameworks */, + DD197B0B2B1CD534001D4290 /* Spot in Frameworks */, + DD197AFA2B1CD514001D4290 /* NavigateMap in Frameworks */, + DD197AF82B1CD514001D4290 /* Home in Frameworks */, + DD197B022B1CD521001D4290 /* RewindJourney in Frameworks */, + DD197B052B1CD528001D4290 /* SaveJourney in Frameworks */, + DD197AFF2B1CD51B001D4290 /* JourneyList in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -126,13 +126,6 @@ path = MusicSpot; sourceTree = ""; }; - DDE77E1D2B1320B9005927B0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -150,14 +143,14 @@ ); name = MusicSpot; packageProductDependencies = ( - DDCDDA162B18F4B8008E66D1 /* NavigateMap */, - DDCDDA182B18F4B8008E66D1 /* RecordJourney */, - DDCDDA1B2B18F4BE008E66D1 /* JourneyList */, - DDCDDA1E2B18F4C6008E66D1 /* RewindJourney */, - DDCDDA212B18F4CD008E66D1 /* SaveJourney */, - DDCDDA242B18F4D7008E66D1 /* SelectSong */, - DDCDDA272B18F4DE008E66D1 /* Spot */, - DD9782702B1902B8003CB134 /* Home */, + DD197AF72B1CD514001D4290 /* Home */, + DD197AF92B1CD514001D4290 /* NavigateMap */, + DD197AFB2B1CD514001D4290 /* RecordJourney */, + DD197AFE2B1CD51B001D4290 /* JourneyList */, + DD197B012B1CD521001D4290 /* RewindJourney */, + DD197B042B1CD528001D4290 /* SaveJourney */, + DD197B072B1CD52E001D4290 /* SelectSong */, + DD197B0A2B1CD534001D4290 /* Spot */, ); productName = MusicSpot; productReference = DD73F8552B024C4900EE9BF2 /* MusicSpot.app */; @@ -188,12 +181,12 @@ ); mainGroup = DD73F84C2B024C4900EE9BF2; packageReferences = ( - DDCDDA152B18F4B8008E66D1 /* XCLocalSwiftPackageReference "../Features/Home" */, - DDCDDA1A2B18F4BE008E66D1 /* XCLocalSwiftPackageReference "../Features/JourneyList" */, - DDCDDA1D2B18F4C6008E66D1 /* XCLocalSwiftPackageReference "../Features/RewindJourney" */, - DDCDDA202B18F4CD008E66D1 /* XCLocalSwiftPackageReference "../Features/SaveJourney" */, - DDCDDA232B18F4D7008E66D1 /* XCLocalSwiftPackageReference "../Features/SelectSong" */, - DDCDDA262B18F4DE008E66D1 /* XCLocalSwiftPackageReference "../Features/Spot" */, + DD197AF62B1CD514001D4290 /* XCLocalSwiftPackageReference "../Features/Home" */, + DD197AFD2B1CD51B001D4290 /* XCLocalSwiftPackageReference "../Features/JourneyList" */, + DD197B002B1CD521001D4290 /* XCLocalSwiftPackageReference "../Features/RewindJourney" */, + DD197B032B1CD528001D4290 /* XCLocalSwiftPackageReference "../Features/SaveJourney" */, + DD197B062B1CD52E001D4290 /* XCLocalSwiftPackageReference "../Features/SelectSong" */, + DD197B092B1CD534001D4290 /* XCLocalSwiftPackageReference "../Features/Spot" */, ); productRefGroup = DD73F8562B024C4900EE9BF2 /* Products */; projectDirPath = ""; @@ -466,63 +459,62 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - DDCDDA152B18F4B8008E66D1 /* XCLocalSwiftPackageReference "../Features/Home" */ = { + DD197AF62B1CD514001D4290 /* XCLocalSwiftPackageReference "../Features/Home" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/Home; }; - DDCDDA1A2B18F4BE008E66D1 /* XCLocalSwiftPackageReference "../Features/JourneyList" */ = { + DD197AFD2B1CD51B001D4290 /* XCLocalSwiftPackageReference "../Features/JourneyList" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/JourneyList; }; - DDCDDA1D2B18F4C6008E66D1 /* XCLocalSwiftPackageReference "../Features/RewindJourney" */ = { + DD197B002B1CD521001D4290 /* XCLocalSwiftPackageReference "../Features/RewindJourney" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/RewindJourney; }; - DDCDDA202B18F4CD008E66D1 /* XCLocalSwiftPackageReference "../Features/SaveJourney" */ = { + DD197B032B1CD528001D4290 /* XCLocalSwiftPackageReference "../Features/SaveJourney" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/SaveJourney; }; - DDCDDA232B18F4D7008E66D1 /* XCLocalSwiftPackageReference "../Features/SelectSong" */ = { + DD197B062B1CD52E001D4290 /* XCLocalSwiftPackageReference "../Features/SelectSong" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/SelectSong; }; - DDCDDA262B18F4DE008E66D1 /* XCLocalSwiftPackageReference "../Features/Spot" */ = { + DD197B092B1CD534001D4290 /* XCLocalSwiftPackageReference "../Features/Spot" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/Spot; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - DD9782702B1902B8003CB134 /* Home */ = { + DD197AF72B1CD514001D4290 /* Home */ = { isa = XCSwiftPackageProductDependency; - package = DDCDDA152B18F4B8008E66D1 /* XCLocalSwiftPackageReference "../Features/Home" */; productName = Home; }; - DDCDDA162B18F4B8008E66D1 /* NavigateMap */ = { + DD197AF92B1CD514001D4290 /* NavigateMap */ = { isa = XCSwiftPackageProductDependency; productName = NavigateMap; }; - DDCDDA182B18F4B8008E66D1 /* RecordJourney */ = { + DD197AFB2B1CD514001D4290 /* RecordJourney */ = { isa = XCSwiftPackageProductDependency; productName = RecordJourney; }; - DDCDDA1B2B18F4BE008E66D1 /* JourneyList */ = { + DD197AFE2B1CD51B001D4290 /* JourneyList */ = { isa = XCSwiftPackageProductDependency; productName = JourneyList; }; - DDCDDA1E2B18F4C6008E66D1 /* RewindJourney */ = { + DD197B012B1CD521001D4290 /* RewindJourney */ = { isa = XCSwiftPackageProductDependency; productName = RewindJourney; }; - DDCDDA212B18F4CD008E66D1 /* SaveJourney */ = { + DD197B042B1CD528001D4290 /* SaveJourney */ = { isa = XCSwiftPackageProductDependency; productName = SaveJourney; }; - DDCDDA242B18F4D7008E66D1 /* SelectSong */ = { + DD197B072B1CD52E001D4290 /* SelectSong */ = { isa = XCSwiftPackageProductDependency; productName = SelectSong; }; - DDCDDA272B18F4DE008E66D1 /* Spot */ = { + DD197B0A2B1CD534001D4290 /* Spot */ = { isa = XCSwiftPackageProductDependency; productName = Spot; }; From d00a0b761586650b61416cc435ff8754d8a8eb8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 00:39:43 +0900 Subject: [PATCH 23/39] =?UTF-8?q?:sparkles:=20Model=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20Repository=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SaveJourneyDemo/SceneDelegate.swift | 4 ++- .../SaveJourney/Model/DTO+Mapping.swift | 28 ++++++++++++++++ .../Sources/SaveJourney/Model/Journey.swift | 4 +-- .../Sources/SaveJourney/Model/Spot.swift | 2 +- .../SaveJourneyViewController.swift | 8 ++++- .../Presentation/SaveJourneyViewModel.swift | 33 ++++++++++--------- 6 files changed, 58 insertions(+), 21 deletions(-) create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift index de9d0b0..dcd96a8 100644 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift +++ b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift @@ -7,6 +7,7 @@ import UIKit +import MSData import MSDesignSystem import SaveJourney @@ -27,7 +28,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { MSFont.registerFonts() - let saveJourneyViewModel = SaveJourneyViewModel() + let journeyRepository = JourneyRepositoryImplementation() + let saveJourneyViewModel = SaveJourneyViewModel(repository: journeyRepository) let saveJourneyViewController = SaveJourneyViewController(viewModel: saveJourneyViewModel) window.rootViewController = saveJourneyViewController diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift new file mode 100644 index 0000000..9669a20 --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift @@ -0,0 +1,28 @@ +// +// DTO+Mapping.swift +// SaveJourney +// +// Created by 이창준 on 2023.12.04. +// + +import Foundation + +import MSData + +extension Journey { + + init(dto: JourneyDTO) { + self.location = dto.location + self.spots = dto.spots.map { Spot(dto: $0) } + self.date = dto.metaData.date + } + +} + +extension Spot { + + init(dto: ResponsibleSpotDTO) { + self.photoURLs = dto.photoURLs + } + +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift index 9861c12..a450c87 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift @@ -10,7 +10,7 @@ import Foundation struct Journey: Hashable { let location: String - let date: String - let spot: Spot + let date: Date + let spots: [Spot] } diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift index 218ba82..b3c4cc1 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift @@ -9,6 +9,6 @@ import Foundation struct Spot: Hashable { - let images: [String] + let photoURLs: [String] } diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index 36e5ba1..db34a98 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -131,6 +131,7 @@ public final class SaveJourneyViewController: UIViewController { .store(in: &self.cancellables) self.viewModel.state.journeys + .receive(on: DispatchQueue.main) .sink { journeys in var snapshot = SpotSnapshot() snapshot.append(journeys.map { .spot($0) }) @@ -315,9 +316,14 @@ private extension SaveJourneyViewController { // MARK: - Preview +#if DEBUG +import MSData + @available(iOS 17, *) #Preview { - let saveJourneyViewModel = SaveJourneyViewModel() + let journeyRepository = JourneyRepositoryImplementation() + let saveJourneyViewModel = SaveJourneyViewModel(repository: journeyRepository) let saveJourneyViewController = SaveJourneyViewController(viewModel: saveJourneyViewModel) return saveJourneyViewController } +#endif diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift index 1b83c2a..1714eed 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift @@ -7,6 +7,8 @@ import Combine +import MSData + public final class SaveJourneyViewModel { public enum Action { @@ -22,18 +24,31 @@ public final class SaveJourneyViewModel { // MARK: - Properties + private let repository: JourneyRepository + public var state = State() // MARK: - Initializer - public init() { } + public init(repository: JourneyRepository) { + self.repository = repository + } // MARK: - Functions func trigger(_ action: Action) { switch action { case .viewNeedsLoaded: - self.fetchInitialJourneys() + Task { + let result = await self.repository.fetchJourneyList() + switch result { + case .success(let journeyDTOs): + let journeys = journeyDTOs.map { Journey(dto: $0) } + self.state.journeys.send(journeys) + case .failure(let error): + print(error) + } + } case .mediaControlButtonDidTap: print("Media Control Button Tap.") case .nextButtonDidTap: @@ -42,17 +57,3 @@ public final class SaveJourneyViewModel { } } - -private extension SaveJourneyViewModel { - - func fetchInitialJourneys() { - self.state.music.send("NewJeans") - self.state.journeys.send([Journey(location: "여정 위치", - date: "2023. 01. 01", - spot: Spot(images: ["sdlkj", "sdklfj"])), - Journey(location: "여정 위치", - date: "2023. 01. 02", - spot: Spot(images: ["slkjc", "llskl", "llskldf", "llskl5", "llskl12"]))]) - } - -} From 9735a9d0379147aa9475fadb569b164ed1f59d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 00:45:29 +0900 Subject: [PATCH 24/39] =?UTF-8?q?:sparkles:=20=EC=97=AC=EC=A0=95=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B0=94?= =?UTF-8?q?=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/SaveJourney/Model/DTO+Mapping.swift | 10 ++++++++++ .../Sources/SaveJourney/Model/Journey.swift | 1 + .../Sources/SaveJourney/Model/Song.swift | 15 +++++++++++++++ .../Presentation/SaveJourneyViewController.swift | 9 +++++++-- 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Model/Song.swift diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift index 9669a20..b1ab594 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift @@ -15,6 +15,7 @@ extension Journey { self.location = dto.location self.spots = dto.spots.map { Spot(dto: $0) } self.date = dto.metaData.date + self.song = Song(dto: dto.song) } } @@ -26,3 +27,12 @@ extension Spot { } } + +extension Song { + + init(dto: SongDTO) { + self.title = dto.title + self.artist = dto.artist + } + +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift index a450c87..9e852e0 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift @@ -12,5 +12,6 @@ struct Journey: Hashable { let location: String let date: Date let spots: [Spot] + let song: Song } diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Song.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Song.swift new file mode 100644 index 0000000..fb8aed6 --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Song.swift @@ -0,0 +1,15 @@ +// +// File.swift +// +// +// Created by 이창준 on 2023.12.04. +// + +import Foundation + +struct Song: Hashable { + + let title: String + let artist: String + +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index db34a98..dc6725c 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -202,8 +202,13 @@ private extension SaveJourneyViewController { cell.update(with: itemIdentifier) } - let journeyCellRegistration = JourneyCellRegistration { cell, _, itemIdentifier in - + let journeyCellRegistration = JourneyCellRegistration { cell, indexPath, itemIdentifier in + let cellModel = JourneyCellModel(location: itemIdentifier.location, + date: itemIdentifier.date, + songTitle: itemIdentifier.song.title, + songArtist: itemIdentifier.song.artist) + cell.update(with: cellModel) + // TODO: ImageFetcher 사용해 이미지 업데이트 } let headerRegistration = HeaderRegistration(elementKind: SaveJourneyHeaderView.elementKind, From 1b9af606cfdddf64bddf3dfb5f6028218c9a7576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 01:00:21 +0900 Subject: [PATCH 25/39] =?UTF-8?q?:truck:=20Modal=20Background=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/MSDesignSystem/MSColor.swift | 1 + .../Background Modal.colorset/Contents.json | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Background Modal.colorset/Contents.json diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift b/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift index 93222f7..8c5e896 100644 --- a/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift +++ b/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift @@ -12,6 +12,7 @@ public enum MSColor: String { case secondaryBackground = "Background Secondary" case primaryButtonBackground = "Button Background Primary" case secondaryButtonBackground = "Button Background Secondary" + case modalBackground = "Background Modal" case primaryTypo = "Typo Primary" case primaryButtonTypo = "Button Typo Primary" diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Background Modal.colorset/Contents.json b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Background Modal.colorset/Contents.json new file mode 100644 index 0000000..fc0088a --- /dev/null +++ b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/Background Modal.colorset/Contents.json @@ -0,0 +1,56 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "iphone" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.980", + "blue" : "0xFA", + "green" : "0xFA", + "red" : "0xFA" + } + }, + "idiom" : "iphone" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.980", + "blue" : "0x23", + "green" : "0x23", + "red" : "0x23" + } + }, + "idiom" : "iphone" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} From 9c6840295ebc7e9926fd2e84fbb944405025e883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 03:23:34 +0900 Subject: [PATCH 26/39] =?UTF-8?q?:sparkles:=20MSAlertViewController=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/MSDesignSystem/MSColor.swift | 3 + .../Contents.json | 56 +++ .../TextField Typo.colorset/Contents.json | 56 +++ .../MSUIKit/MSAlertViewController.swift | 325 ++++++++++++++++++ .../Sources/MSUIKit/MSButton/MSButton.swift | 1 + .../MSUIKit/MSTextField/MSTextField.swift | 24 +- 6 files changed, 458 insertions(+), 7 deletions(-) create mode 100644 iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/TextField Background.colorset/Contents.json create mode 100644 iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/TextField Typo.colorset/Contents.json create mode 100644 iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift b/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift index 8c5e896..10d8dfb 100644 --- a/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift +++ b/iOS/MSUIKit/Sources/MSDesignSystem/MSColor.swift @@ -22,6 +22,9 @@ public enum MSColor: String { case componentBackground = "Component Background" case componentTypo = "Component Typo" + case textFieldBackground = "TextField Background" + case textFieldTypo = "TextField Typo" + case musicSpot = "MusicSpot" } diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/TextField Background.colorset/Contents.json b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/TextField Background.colorset/Contents.json new file mode 100644 index 0000000..86ed0b0 --- /dev/null +++ b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/TextField Background.colorset/Contents.json @@ -0,0 +1,56 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "iphone" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.700", + "blue" : "0xF1", + "green" : "0xED", + "red" : "0xF0" + } + }, + "idiom" : "iphone" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.700", + "blue" : "0x3C", + "green" : "0x38", + "red" : "0x3E" + } + }, + "idiom" : "iphone" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/TextField Typo.colorset/Contents.json b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/TextField Typo.colorset/Contents.json new file mode 100644 index 0000000..269a60c --- /dev/null +++ b/iOS/MSUIKit/Sources/MSDesignSystem/Resources/MSColor.xcassets/TextField Typo.colorset/Contents.json @@ -0,0 +1,56 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "iphone" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.600", + "blue" : "0x43", + "green" : "0x3C", + "red" : "0x3C" + } + }, + "idiom" : "iphone" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.600", + "blue" : "0xF5", + "green" : "0xEB", + "red" : "0xEB" + } + }, + "idiom" : "iphone" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift new file mode 100644 index 0000000..5bae9b1 --- /dev/null +++ b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift @@ -0,0 +1,325 @@ +// +// MSAlertViewController.swift +// MSUIKit +// +// Created by 이창준 on 2023.12.04. +// + +import UIKit + +import MSDesignSystem + +open class MSAlertViewController: UIViewController { + + // MARK: - Constants + + private enum Typo { + + static let cancelButtonTitle = "취소" + static let doneButtonTitle = "여정 완료" + + } + + private enum Metric { + + static let bottomSheetHeight: CGFloat = 294.0 + static let dismissingHeightRatio: CGFloat = 0.6 + static let dimmedViewMaximumAlpha: CGFloat = 0.6 + + static let horizontalInset: CGFloat = 12.0 + static let verticalInset: CGFloat = 24.0 + static let containerViewCornerRadius: CGFloat = 12.0 + static let stackSpacing: CGFloat = 4.0 + + enum ResizeIndicator { + static let width: CGFloat = 36.0 + static let height: CGFloat = 5.0 + static let topSpacing: CGFloat = 5.0 + } + + static let gestureVelocity: CGFloat = 750.0 + + } + + // MARK: - UI Components + + // Base + public let containerView: UIView = { + let view = UIView() + view.backgroundColor = .msColor(.modalBackground) + view.layer.cornerRadius = Metric.containerViewCornerRadius + view.clipsToBounds = true + return view + }() + + private let dimmedView: UIView = { + let view = UIView() + view.backgroundColor = .black + view.alpha = .zero + return view + }() + + private let resizeIndicator: UIView = { + let view = UIView() + view.backgroundColor = .darkGray.withAlphaComponent(0.5) + view.layer.cornerRadius = 2.5 + view.clipsToBounds = true + return view + }() + + // Title + private let titleStack: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = Metric.stackSpacing + return stackView + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.headerTitle) + label.textColor = .msColor(.primaryTypo) + label.text = "Title" + return label + }() + + private let subtitleLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.caption) + label.textColor = .msColor(.secondaryTypo) + label.text = "Subtitle" + return label + }() + + // TextField + private let textField: MSTextField = { + let textField = MSTextField() + textField.imageStyle = .none + textField.clearButtonMode = .whileEditing + textField.placeholder = "Placeholder" + return textField + }() + + // Button + private let buttonStack: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = Metric.stackSpacing + return stackView + }() + + private let cancelButton: MSButton = { + let button = MSButton.secondary() + button.cornerStyle = .squared + button.title = Typo.cancelButtonTitle + return button + }() + + private let doneButton: MSButton = { + let button = MSButton.primary() + button.cornerStyle = .squared + button.title = Typo.doneButtonTitle + return button + }() + + // Gesture + private lazy var tapGesture: UITapGestureRecognizer = { + let tagGesture = UITapGestureRecognizer(target: self, + action: #selector(self.dismissBottomSheet)) + return tagGesture + }() + + private lazy var panGesture: UIPanGestureRecognizer = { + let panGesture = UIPanGestureRecognizer(target: self, + action: #selector(self.handlePanGesture(_:))) + panGesture.delaysTouchesBegan = false + panGesture.delaysTouchesEnded = false + return panGesture + }() + + private var containerViewHeight: NSLayoutConstraint? + private var containerViewBottomInset: NSLayoutConstraint? + + private var keyboardLayoutHeight: CGFloat { + self.view.keyboardLayoutGuide.layoutFrame.height + } + + // MARK: - Life Cycle + + open override func viewDidLoad() { + super.viewDidLoad() + self.configureStyles() + self.configureLayout() + } + + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.animatePresentView() + } + + // MARK: - SELECTORS + + @objc + open func dismissBottomSheet() { + self.textField.resignFirstResponder() + self.animateDismissView() + } + + @objc + private func handlePanGesture(_ sender: UIPanGestureRecognizer) { + let translation = sender.translation(in: self.view) + let velocity = sender.velocity(in: self.view) + let updatedBottomInset = Metric.verticalInset + translation.y + + switch sender.state { + case .changed: + if updatedBottomInset > Metric.verticalInset { + self.containerViewBottomInset?.constant = updatedBottomInset - self.keyboardLayoutHeight + self.view.layoutIfNeeded() + } + case .ended: + if translation.y > Metric.bottomSheetHeight * (1 - Metric.dismissingHeightRatio) + || velocity.y > Metric.gestureVelocity { + self.animateDismissView() + } else { + UIView.animate(withDuration: 0.4) { + self.containerViewBottomInset?.constant = Metric.verticalInset - self.keyboardLayoutHeight + self.view.layoutIfNeeded() + } + } + default: + break + } + } + + // MARK: - Helpers + + public func dismissAlert() { + self.animateDismissView() + } + + private func animatePresentView() { + UIView.animate(withDuration: 0.3) { + self.containerViewBottomInset?.constant = -Metric.verticalInset / 2 + self.view.layoutIfNeeded() + } + + self.dimmedView.alpha = .zero + UIView.animate(withDuration: 0.4) { + self.dimmedView.alpha = Metric.dimmedViewMaximumAlpha + } + } + + private func animateDismissView() { + UIView.animate(withDuration: 0.3) { + self.containerViewBottomInset?.constant = Metric.bottomSheetHeight + self.keyboardLayoutHeight + self.view.layoutIfNeeded() + } + + self.dimmedView.alpha = Metric.dimmedViewMaximumAlpha + UIView.animate(withDuration: 0.4) { + self.dimmedView.alpha = .zero + } completion: { _ in + self.dismiss(animated: false) + } + } + + // MARK: - Functions + + public func updateTitle(_ title: String) { + self.titleLabel.text = title + } +} + +// MARK: - UI Configuration + +private extension MSAlertViewController { + + func configureStyles() { + self.view.backgroundColor = .clear + self.dimmedView.addGestureRecognizer(self.tapGesture) + self.view.addGestureRecognizer(self.panGesture) + } + + func configureLayout() { + [ + self.dimmedView, + self.containerView, + ].forEach { + self.view.addSubview($0) + $0.translatesAutoresizingMaskIntoConstraints = false + } + let bottomInset = self.containerView.bottomAnchor.constraint(equalTo: self.view.keyboardLayoutGuide.topAnchor, + constant: Metric.bottomSheetHeight) + let heightConstraint = self.containerView.heightAnchor.constraint(equalToConstant: Metric.bottomSheetHeight) + NSLayoutConstraint.activate([ + self.dimmedView.topAnchor.constraint(equalTo: self.view.topAnchor), + self.dimmedView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + self.dimmedView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), + self.dimmedView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), + + self.containerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, + constant: Metric.horizontalInset), + bottomInset, + self.containerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, + constant: -Metric.horizontalInset), + heightConstraint + ]) + self.containerViewBottomInset = bottomInset + self.containerViewHeight = heightConstraint + + [ + self.resizeIndicator, + self.titleStack, + self.textField, + self.buttonStack + ].forEach { + self.containerView.addSubview($0) + $0.translatesAutoresizingMaskIntoConstraints = false + } + NSLayoutConstraint.activate([ + self.resizeIndicator.widthAnchor.constraint(equalToConstant: Metric.ResizeIndicator.width), + self.resizeIndicator.heightAnchor.constraint(equalToConstant: Metric.ResizeIndicator.height), + self.resizeIndicator.centerXAnchor.constraint(equalTo: self.containerView.centerXAnchor), + self.resizeIndicator.topAnchor.constraint(equalTo: self.containerView.topAnchor, + constant: Metric.ResizeIndicator.topSpacing), + + self.titleStack.topAnchor.constraint(equalTo: self.containerView.topAnchor, + constant: Metric.verticalInset), + self.titleStack.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, + constant: Metric.horizontalInset), + self.titleStack.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, + constant: -Metric.horizontalInset), + + self.textField.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, + constant: Metric.horizontalInset), + self.textField.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, + constant: -Metric.horizontalInset), + + self.buttonStack.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, + constant: Metric.horizontalInset), + self.buttonStack.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor, + constant: -Metric.verticalInset / 2), + self.buttonStack.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, + constant: -Metric.horizontalInset) + ]) + let textFieldYConstraint = self.textField.centerYAnchor.constraint(equalTo: self.titleStack.bottomAnchor) + textFieldYConstraint.constant += ((self.titleStack.frame.minY - self.buttonStack.frame.maxY) / 2) + textFieldYConstraint.isActive = true + + [ + self.titleLabel, + self.subtitleLabel + ].forEach { + self.titleStack.addArrangedSubview($0) + } + + [ + self.cancelButton, + self.doneButton + ].forEach { + self.buttonStack.addArrangedSubview($0) + } + } + +} diff --git a/iOS/MSUIKit/Sources/MSUIKit/MSButton/MSButton.swift b/iOS/MSUIKit/Sources/MSUIKit/MSButton/MSButton.swift index 26ddb72..fb5dbca 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/MSButton/MSButton.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/MSButton/MSButton.swift @@ -72,6 +72,7 @@ public class MSButton: UIButton { configuration.imagePlacement = .leading configuration.imagePadding = Metric.imagePadding configuration.titleAlignment = .center + configuration.titleLineBreakMode = .byTruncatingMiddle self.configuration = configuration } diff --git a/iOS/MSUIKit/Sources/MSUIKit/MSTextField/MSTextField.swift b/iOS/MSUIKit/Sources/MSUIKit/MSTextField/MSTextField.swift index aa9d5b2..b740835 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/MSTextField/MSTextField.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/MSTextField/MSTextField.swift @@ -70,16 +70,20 @@ public class MSTextField: UITextField { private var leftImage = UIImageView() private var rightImage = UIImageView() + public var imageStyle: ImageStyle = .none { didSet { self.configureImageStyle() self.configureLayout() } } + public override var text: String? { - didSet { - self.convertMode() - } + didSet { self.convertMode() } + } + + public override var placeholder: String? { + didSet { self.configurePlaceholder() } } // MARK: - Initializer @@ -97,11 +101,10 @@ public class MSTextField: UITextField { // MARK: - UI Configuration private func configureStyles() { - MSFont.registerFonts() self.font = .msFont(.caption) self.layer.cornerRadius = Metric.cornerRadius - self.backgroundColor = .msColor(.primaryBackground) + self.backgroundColor = .msColor(.textFieldBackground) self.configureImageStyle() } @@ -157,6 +160,13 @@ public class MSTextField: UITextField { ]) } + private func configurePlaceholder() { + var container = AttributeContainer() + container.font = .msFont(.caption) + let attributedString = AttributedString(self.placeholder ?? "", attributes: container) + self.attributedPlaceholder = NSAttributedString(attributedString) + } + } // MARK: - Edit/Non-Edit Mode Functions @@ -183,7 +193,7 @@ private extension MSTextField { if let leftImage = self.imageStyle.leftImage { self.leftImage.image = leftImage } - self.leftImage.tintColor = .msColor(.secondaryTypo) + self.leftImage.tintColor = .msColor(.textFieldTypo) } private func configureRightImageStyle() { @@ -194,7 +204,7 @@ private extension MSTextField { if self.hasText { self.rightImage.image = ImageBox.close } - self.rightImage.tintColor = .msColor(.secondaryTypo) + self.rightImage.tintColor = .msColor(.textFieldTypo) } private func configureImages(color: UIColor) { From 72dd4ec8abb0725b73489341123c48e9bcc76b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 03:31:51 +0900 Subject: [PATCH 27/39] =?UTF-8?q?:sparkles:=20=ED=85=8D=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=ED=95=84=EB=93=9C=20AlertViewController=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=ED=95=98=EA=B3=A0=20Feature=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=83=81=EC=86=8D=EB=B0=9B=EC=95=84=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 --- .../SaveJourneyDemo/SceneDelegate.swift | 3 +- .../ConfirmTitleAlertViewController.swift | 55 +++++++++++++++++++ .../SaveJourneyViewController.swift | 8 ++- .../MSUIKit/MSAlertViewController.swift | 47 +++++----------- 4 files changed, 79 insertions(+), 34 deletions(-) create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift index dcd96a8..0419722 100644 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift +++ b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift @@ -31,8 +31,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let journeyRepository = JourneyRepositoryImplementation() let saveJourneyViewModel = SaveJourneyViewModel(repository: journeyRepository) let saveJourneyViewController = SaveJourneyViewController(viewModel: saveJourneyViewModel) + let navigationViewController = UINavigationController(rootViewController: saveJourneyViewController) - window.rootViewController = saveJourneyViewController + window.rootViewController = navigationViewController window.makeKeyAndVisible() } diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift new file mode 100644 index 0000000..8698c95 --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift @@ -0,0 +1,55 @@ +// +// ConfirmTitleAlertViewController.swift +// SaveJourney +// +// Created by 이창준 on 2023.12.04. +// + +import UIKit + +import MSUIKit + +final class ConfirmTitleAlertViewController: MSAlertViewController { + + // MARK: - Constants + + private enum Metric { + + static let horizontalInset: CGFloat = 12.0 + + } + + // MARK: - UI Components + + private let textField: MSTextField = { + let textField = MSTextField() + textField.imageStyle = .none + textField.clearButtonMode = .whileEditing + textField.placeholder = "Placeholder" + return textField + }() + + // MARK: - Helpers + + override func dismissBottomSheet() { + super.dismissBottomSheet() + self.textField.resignFirstResponder() + } + + // MARK: - UI Configuration + + override func configureLayout() { + super.configureLayout() + + self.containerView.addSubview(self.textField) + self.textField.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.textField.centerYAnchor.constraint(equalTo: self.containerView.centerYAnchor), + self.textField.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, + constant: Metric.horizontalInset), + self.textField.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, + constant: -Metric.horizontalInset) + ]) + } + +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index dc6725c..e8b5b5a 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -89,9 +89,15 @@ public final class SaveJourneyViewController: UIViewController { return button }() - private let nextButton: MSButton = { + private lazy var nextButton: MSButton = { let button = MSButton.primary() button.configuration?.title = Typo.nextButtonTitle + let action = UIAction { [weak self] _ in + let alert = ConfirmTitleAlertViewController() + alert.modalPresentationStyle = .overCurrentContext + self?.present(alert, animated: false) + } + button.addAction(action, for: .touchUpInside) return button }() diff --git a/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift index 5bae9b1..e8b5c12 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift @@ -91,15 +91,6 @@ open class MSAlertViewController: UIViewController { return label }() - // TextField - private let textField: MSTextField = { - let textField = MSTextField() - textField.imageStyle = .none - textField.clearButtonMode = .whileEditing - textField.placeholder = "Placeholder" - return textField - }() - // Button private let buttonStack: UIStackView = { let stackView = UIStackView() @@ -161,7 +152,6 @@ open class MSAlertViewController: UIViewController { @objc open func dismissBottomSheet() { - self.textField.resignFirstResponder() self.animateDismissView() } @@ -224,24 +214,15 @@ open class MSAlertViewController: UIViewController { } } - // MARK: - Functions - - public func updateTitle(_ title: String) { - self.titleLabel.text = title - } -} - -// MARK: - UI Configuration - -private extension MSAlertViewController { + // MARK: - UI Configuration - func configureStyles() { + open func configureStyles() { self.view.backgroundColor = .clear self.dimmedView.addGestureRecognizer(self.tapGesture) self.view.addGestureRecognizer(self.panGesture) } - func configureLayout() { + open func configureLayout() { [ self.dimmedView, self.containerView, @@ -271,7 +252,6 @@ private extension MSAlertViewController { [ self.resizeIndicator, self.titleStack, - self.textField, self.buttonStack ].forEach { self.containerView.addSubview($0) @@ -289,12 +269,7 @@ private extension MSAlertViewController { self.titleStack.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, constant: Metric.horizontalInset), self.titleStack.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, - constant: -Metric.horizontalInset), - - self.textField.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, - constant: Metric.horizontalInset), - self.textField.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, - constant: -Metric.horizontalInset), + constant: -Metric.horizontalInset), self.buttonStack.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, constant: Metric.horizontalInset), @@ -303,9 +278,6 @@ private extension MSAlertViewController { self.buttonStack.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, constant: -Metric.horizontalInset) ]) - let textFieldYConstraint = self.textField.centerYAnchor.constraint(equalTo: self.titleStack.bottomAnchor) - textFieldYConstraint.constant += ((self.titleStack.frame.minY - self.buttonStack.frame.maxY) / 2) - textFieldYConstraint.isActive = true [ self.titleLabel, @@ -322,4 +294,15 @@ private extension MSAlertViewController { } } + // MARK: - Functions + + public func updateTitle(_ title: String) { + self.titleLabel.text = title + } +} + +// MARK: - UI Configuration + +extension MSAlertViewController { + } From da27de7db27057ae76caed32e1f16e966ee60a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 03:52:36 +0900 Subject: [PATCH 28/39] =?UTF-8?q?:art:=20Alert=EC=9D=98=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=97=90=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=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 --- .../ConfirmTitleAlertViewController.swift | 20 +++++++++ .../MSUIKit/MSAlertViewController.swift | 41 +++++++++++++------ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift index 8698c95..9273a01 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift @@ -25,10 +25,20 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { let textField = MSTextField() textField.imageStyle = .none textField.clearButtonMode = .whileEditing + textField.enablesReturnKeyAutomatically = true textField.placeholder = "Placeholder" return textField }() + // MARK: - Properties + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + self.configureButtonActions() + } + // MARK: - Helpers override func dismissBottomSheet() { @@ -36,6 +46,16 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { self.textField.resignFirstResponder() } + private func configureButtonActions() { + self.cancelButtonAction = UIAction { _ in + self.dismissBottomSheet() + } + + self.doneButtonAction = UIAction { _ in + print("TODO: 완료 로직 구현!!") + } + } + // MARK: - UI Configuration override func configureLayout() { diff --git a/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift index e8b5c12..68c5183 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift @@ -31,6 +31,9 @@ open class MSAlertViewController: UIViewController { static let containerViewCornerRadius: CGFloat = 12.0 static let stackSpacing: CGFloat = 4.0 + static let cancelButtonVerticalInset: CGFloat = 10.0 + static let cancelButtonHorizontalInset: CGFloat = 28.0 + enum ResizeIndicator { static let width: CGFloat = 36.0 static let height: CGFloat = 5.0 @@ -96,6 +99,7 @@ open class MSAlertViewController: UIViewController { let stackView = UIStackView() stackView.axis = .horizontal stackView.spacing = Metric.stackSpacing + stackView.distribution = .fillProportionally return stackView }() @@ -103,6 +107,10 @@ open class MSAlertViewController: UIViewController { let button = MSButton.secondary() button.cornerStyle = .squared button.title = Typo.cancelButtonTitle + button.configuration?.contentInsets = NSDirectionalEdgeInsets(top: Metric.cancelButtonVerticalInset, + leading: Metric.cancelButtonHorizontalInset, + bottom: Metric.cancelButtonVerticalInset, + trailing: Metric.cancelButtonHorizontalInset) return button }() @@ -135,6 +143,22 @@ open class MSAlertViewController: UIViewController { self.view.keyboardLayoutGuide.layoutFrame.height } + // MARK: - Properties + + open var cancelButtonAction: UIAction? { + didSet { + guard let action = self.cancelButtonAction else { return } + self.cancelButton.addAction(action, for: .touchUpInside) + } + } + + open var doneButtonAction: UIAction? { + didSet { + guard let action = self.doneButtonAction else { return } + self.doneButton.addAction(action, for: .touchUpInside) + } + } + // MARK: - Life Cycle open override func viewDidLoad() { @@ -148,7 +172,7 @@ open class MSAlertViewController: UIViewController { self.animatePresentView() } - // MARK: - SELECTORS + // MARK: - Helpers @objc open func dismissBottomSheet() { @@ -182,12 +206,6 @@ open class MSAlertViewController: UIViewController { } } - // MARK: - Helpers - - public func dismissAlert() { - self.animateDismissView() - } - private func animatePresentView() { UIView.animate(withDuration: 0.3) { self.containerViewBottomInset?.constant = -Metric.verticalInset / 2 @@ -299,10 +317,9 @@ open class MSAlertViewController: UIViewController { public func updateTitle(_ title: String) { self.titleLabel.text = title } -} - -// MARK: - UI Configuration - -extension MSAlertViewController { + + public func updateSubtitle(_ subtitle: String) { + self.subtitleLabel.text = subtitle + } } From 5cc59ceaef3dd140a917d0d8633eb92b069aea61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 03:59:02 +0900 Subject: [PATCH 29/39] =?UTF-8?q?:bug:=20AlertViewController=EC=9D=98=20?= =?UTF-8?q?=EB=B9=88=20=EA=B3=B5=EA=B0=84=EB=8F=84=20PanGesture=EA=B0=80?= =?UTF-8?q?=20=EB=8F=99=EC=9E=91=ED=95=98=EB=8A=94=20=EC=98=A4=EB=A5=98=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 --- .../ConfirmTitleAlertViewController.swift | 14 ++++++++++++++ .../Sources/MSUIKit/MSAlertViewController.swift | 10 ++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift index 9273a01..231a7f2 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift @@ -13,6 +13,13 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { // MARK: - Constants + private enum Typo { + + static let title = "여정 이름" + static let subtitle = "마지막으로 여정의 이름을 정해주세요." + + } + private enum Metric { static let horizontalInset: CGFloat = 12.0 @@ -58,6 +65,13 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { // MARK: - UI Configuration + override func configureStyles() { + super.configureStyles() + + self.updateTitle(Typo.title) + self.updateSubtitle(Typo.subtitle) + } + override func configureLayout() { super.configureLayout() diff --git a/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift index 68c5183..db59dd5 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift @@ -181,8 +181,8 @@ open class MSAlertViewController: UIViewController { @objc private func handlePanGesture(_ sender: UIPanGestureRecognizer) { - let translation = sender.translation(in: self.view) - let velocity = sender.velocity(in: self.view) + let translation = sender.translation(in: self.containerView) + let velocity = sender.velocity(in: self.containerView) let updatedBottomInset = Metric.verticalInset + translation.y switch sender.state { @@ -207,7 +207,9 @@ open class MSAlertViewController: UIViewController { } private func animatePresentView() { - UIView.animate(withDuration: 0.3) { + UIView.animate(withDuration: 0.3, + delay: .zero, + options: .curveEaseInOut) { self.containerViewBottomInset?.constant = -Metric.verticalInset / 2 self.view.layoutIfNeeded() } @@ -237,7 +239,7 @@ open class MSAlertViewController: UIViewController { open func configureStyles() { self.view.backgroundColor = .clear self.dimmedView.addGestureRecognizer(self.tapGesture) - self.view.addGestureRecognizer(self.panGesture) + self.containerView.addGestureRecognizer(self.panGesture) } open func configureLayout() { From baeea6eb07a2001d852b56ffb85eec99d2b8d32b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 09:46:10 +0900 Subject: [PATCH 30/39] =?UTF-8?q?:sparkles:=20=ED=97=A4=EB=8D=94=20?= =?UTF-8?q?=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SaveJourneyViewController.swift | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index e8b5b5a..e0b7f1e 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -34,16 +34,24 @@ public final class SaveJourneyViewController: UIViewController { // MARK: - Constants private enum Typo { + + static let songSectionTitle = "함께한 음악" + static func spotSectionTitle(_ numberOfSpots: Int) -> String { + return "\(numberOfSpots)개의 스팟" + } static let nextButtonTitle = "다음" + } 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 static let buttonSpacing: CGFloat = 4.0 static let buttonBottomInset: CGFloat = 24.0 + } // MARK: - Properties @@ -219,7 +227,15 @@ private extension SaveJourneyViewController { let headerRegistration = HeaderRegistration(elementKind: SaveJourneyHeaderView.elementKind, handler: { header, _, indexPath in - header.update(with: "헤더") + switch indexPath.section { + case .zero: + header.update(with: Typo.songSectionTitle) + case 1: + let spots = self.viewModel.state.journeys + header.update(with: Typo.spotSectionTitle(spots.value.count)) + default: + break + } }) let dataSource = SaveJourneyDataSource(collectionView: self.collectionView, From c83396dce66241ca1aa422097bb29a28e6fe0faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 09:57:50 +0900 Subject: [PATCH 31/39] =?UTF-8?q?:art:=20Button=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EC=A4=80=EB=B9=84=20&=20MediaPlayer=20?= =?UTF-8?q?=EC=A4=80=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SaveJourneyDemo/Info.plist | 2 + .../SaveJourneyViewController.swift | 49 ++++++++++++++++--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Info.plist b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Info.plist index 0eb786d..0185a10 100644 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Info.plist +++ b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/Info.plist @@ -2,6 +2,8 @@ + NSAppleMusicUsageDescription + 음악 좀 쓸게요 UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index e0b7f1e..c30aa69 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -10,6 +10,7 @@ import MapKit import UIKit import MSUIKit +import MediaPlayer enum SaveJourneySection { case music @@ -60,6 +61,8 @@ public final class SaveJourneyViewController: UIViewController { private var dataSource: SaveJourneyDataSource? + private let musicPlayer = MPMusicPlayerApplicationController.applicationMusicPlayer + private var cancellables: Set = [] // MARK: - UI Components @@ -100,12 +103,6 @@ public final class SaveJourneyViewController: UIViewController { private lazy var nextButton: MSButton = { let button = MSButton.primary() button.configuration?.title = Typo.nextButtonTitle - let action = UIAction { [weak self] _ in - let alert = ConfirmTitleAlertViewController() - alert.modalPresentationStyle = .overCurrentContext - self?.present(alert, animated: false) - } - button.addAction(action, for: .touchUpInside) return button }() @@ -128,7 +125,7 @@ public final class SaveJourneyViewController: UIViewController { super.viewDidLoad() self.configureStyles() self.configureLayout() - self.configureCollectionView() + self.configureComponents() self.bind() self.viewModel.trigger(.viewNeedsLoaded) } @@ -156,6 +153,38 @@ public final class SaveJourneyViewController: UIViewController { } +// MARK: - Buttons + +private extension SaveJourneyViewController { + + func configureButtons() { + let mediaControlAction = UIAction { [weak self] _ in + self?.viewModel.trigger(.mediaControlButtonDidTap) + } + self.mediaControlButton.addAction(mediaControlAction, for: .touchUpInside) + + let nextButtonAction = UIAction { [weak self] _ in + self?.viewModel.trigger(.nextButtonDidTap) + + let alert = ConfirmTitleAlertViewController() + alert.modalPresentationStyle = .overCurrentContext + self?.present(alert, animated: false) + } + self.nextButton.addAction(nextButtonAction, for: .touchUpInside) + } + +} + +// MARK: - Media Player + +private extension SaveJourneyViewController { + + func configureMusicPlayer() { + self.musicPlayer.setQueue(with: .songs()) + } + +} + // MARK: - Collection View private extension SaveJourneyViewController { @@ -339,6 +368,12 @@ private extension SaveJourneyViewController { } } + func configureComponents() { + self.configureCollectionView() + self.configureButtons() + self.configureMusicPlayer() + } + } // MARK: - Preview From 650b86faa4efa44fec6f783eef1357b418a27647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 17:24:00 +0900 Subject: [PATCH 32/39] =?UTF-8?q?:recycle:=20=EC=97=AC=EC=A0=95=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20SpotCell=20UI=20&=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/SaveJourney/Package.swift | 10 +- .../SaveJourneyDemo/SceneDelegate.swift | 4 +- .../Model/CellModel/SpotCellModel.swift | 24 ++++ .../SaveJourney/Model/Coordinate.swift | 15 +++ .../SaveJourney/Model/DTO+Mapping.swift | 15 ++- .../Sources/SaveJourney/Model/Spot.swift | 7 +- .../Presentation/SaveJourneySection.swift | 36 ++++++ .../SaveJourneyViewController.swift | 79 ++++++++----- .../Presentation/SaveJourneyViewModel.swift | 21 ++-- .../Presentation/View/SpotCell.swift | 111 ++++++++++++++++++ .../xcschemes/RepositoryTests.xcscheme | 53 +++++++++ iOS/MSData/Package.swift | 2 + .../Sources/MSData/DTO/Fragment/SpotDTO.swift | 26 +++- .../Sources/MSData/DTO/JourneyDTO.swift | 3 +- iOS/MSData/Sources/MSData/DTO/PersonDTO.swift | 2 +- .../MSData/Repository/SpotRepository.swift | 43 +++++++ .../Sources/MSData/Resources/MockSpot.json | 52 ++++++++ .../RepositoryTests/SpotRepositoryTests.swift | 36 ++++++ .../Sources/MSLogger/MSLogCategory.swift | 3 +- .../Cells/JourneyCell/JourneyCell.swift | 4 +- .../Cells/JourneyCell/JourneyCellModel.swift | 12 +- .../Cells/JourneyCell/JourneyInfoView.swift | 17 ++- .../Cells/JourneyCell/MusicInfoView.swift | 2 +- 23 files changed, 510 insertions(+), 67 deletions(-) create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Model/CellModel/SpotCellModel.swift create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Model/Coordinate.swift create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift create mode 100644 iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SpotCell.swift create mode 100644 iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/RepositoryTests.xcscheme create mode 100644 iOS/MSData/Sources/MSData/Repository/SpotRepository.swift create mode 100644 iOS/MSData/Sources/MSData/Resources/MockSpot.json create mode 100644 iOS/MSData/Tests/RepositoryTests/SpotRepositoryTests.swift diff --git a/iOS/Features/SaveJourney/Package.swift b/iOS/Features/SaveJourney/Package.swift index e1c3743..01bb06a 100644 --- a/iOS/Features/SaveJourney/Package.swift +++ b/iOS/Features/SaveJourney/Package.swift @@ -29,9 +29,9 @@ private enum Dependency { static let msData = "MSData" static let msUIKit = "MSUIKit" - static let msFoundation = "MSFoundation" static let msDesignsystem = "MSDesignSystem" static let msLogger = "MSLogger" + static let msFoundation = "MSFoundation" } @@ -50,7 +50,9 @@ let package = Package( .package(name: Dependency.msUIKit, path: Dependency.msUIKit.fromRootPath), .package(name: Dependency.msData, - path: Dependency.msData.fromRootPath) + path: Dependency.msData.fromRootPath), + .package(name: Dependency.msFoundation, + path: Dependency.msFoundation.fromRootPath) ], targets: [ .target(name: Target.saveJourney, @@ -58,7 +60,9 @@ let package = Package( .product(name: Dependency.msData, package: Dependency.msData), .product(name: Dependency.msUIKit, - package: Dependency.msUIKit) + package: Dependency.msUIKit), + .product(name: Dependency.msLogger, + package: Dependency.msFoundation) ]) ] ) diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift index 0419722..a9e5f44 100644 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift +++ b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift @@ -28,8 +28,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { MSFont.registerFonts() - let journeyRepository = JourneyRepositoryImplementation() - let saveJourneyViewModel = SaveJourneyViewModel(repository: journeyRepository) + let spotRepository = SpotRepositoryImplementation() + let saveJourneyViewModel = SaveJourneyViewModel(spotRepository: spotRepository) let saveJourneyViewController = SaveJourneyViewController(viewModel: saveJourneyViewModel) let navigationViewController = UINavigationController(rootViewController: saveJourneyViewController) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/CellModel/SpotCellModel.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/CellModel/SpotCellModel.swift new file mode 100644 index 0000000..e35b7e5 --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/CellModel/SpotCellModel.swift @@ -0,0 +1,24 @@ +// +// SpotCellModel.swift +// SaveJourney +// +// Created by 이창준 on 2023.12.04. +// + +import Foundation + +public struct SpotCellModel: Hashable { + + let location: String + let date: Date + let photoURL: URL + + public init(location: String, + date: Date, + photoURL: URL) { + self.location = location + self.date = date + self.photoURL = photoURL + } + +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Coordinate.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Coordinate.swift new file mode 100644 index 0000000..71dbcda --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Coordinate.swift @@ -0,0 +1,15 @@ +// +// Coordinate.swift +// SaveJourney +// +// Created by 이창준 on 2023.12.04. +// + +import Foundation + +struct Coordinate: Hashable { + + let latitude: Double + let longitude: Double + +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift index b1ab594..e57f443 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift @@ -6,6 +6,7 @@ // import Foundation +import CoreLocation import MSData @@ -23,7 +24,19 @@ extension Journey { extension Spot { init(dto: ResponsibleSpotDTO) { - self.photoURLs = dto.photoURLs + self.id = dto.id + self.location = Coordinate(dto: dto.coordinate) + self.date = .now + self.photoURL = dto.photoURL + } + +} + +extension Coordinate { + + init(dto: CoordinateDTO) { + self.latitude = dto.latitude + self.longitude = dto.longitude } } diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift index b3c4cc1..bbbc3e8 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift @@ -7,8 +7,11 @@ import Foundation -struct Spot: Hashable { +struct Spot: Hashable, Identifiable { - let photoURLs: [String] + let id: UUID + let location: Coordinate + let date: Date + let photoURL: URL } diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift new file mode 100644 index 0000000..aa8cf90 --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift @@ -0,0 +1,36 @@ +// +// SaveJourneySection.swift +// SaveJourney +// +// Created by 이창준 on 2023.12.04. +// + +import UIKit + +enum SaveJourneySection { + case music + case spot + + var itemSize: NSCollectionLayoutSize { + switch self { + case .music: + return NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: .fractionalHeight(1.0)) + case .spot: + return NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), + heightDimension: .fractionalWidth(0.5)) + } + } + + var groupSize: NSCollectionLayoutSize { + switch self { + case .music: + return NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: .estimated(SaveJourneyMusicCell.estimatedHeight)) + case .spot: + return NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: .fractionalWidth(0.5)) + } + } + +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index c30aa69..a1987e6 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -6,20 +6,16 @@ // import Combine +import CoreLocation import MapKit import UIKit import MSUIKit import MediaPlayer -enum SaveJourneySection { - case music - case spot -} - enum SaveJourneyItem: Hashable { case music(String) - case spot(Journey) + case spot(Spot) } public final class SaveJourneyViewController: UIViewController { @@ -27,7 +23,7 @@ public final class SaveJourneyViewController: UIViewController { typealias SaveJourneyDataSource = UICollectionViewDiffableDataSource typealias HeaderRegistration = UICollectionView.SupplementaryRegistration typealias MusicCellRegistration = UICollectionView.CellRegistration - typealias JourneyCellRegistration = UICollectionView.CellRegistration + typealias SpotCellRegistration = UICollectionView.CellRegistration typealias SaveJourneySnapshot = NSDiffableDataSourceSnapshot typealias MusicSnapshot = NSDiffableDataSourceSectionSnapshot typealias SpotSnapshot = NSDiffableDataSourceSectionSnapshot @@ -48,7 +44,7 @@ public final class SaveJourneyViewController: UIViewController { static let horizontalInset: CGFloat = 24.0 static let verticalInset: CGFloat = 12.0 - static let innerGroupSpacing: CGFloat = 12.0 + static let innerSpacing: CGFloat = 4.0 static let headerTopInset: CGFloat = 24.0 static let buttonSpacing: CGFloat = 4.0 static let buttonBottomInset: CGFloat = 24.0 @@ -141,11 +137,11 @@ public final class SaveJourneyViewController: UIViewController { } .store(in: &self.cancellables) - self.viewModel.state.journeys + self.viewModel.state.spots .receive(on: DispatchQueue.main) - .sink { journeys in + .sink { spots in var snapshot = SpotSnapshot() - snapshot.append(journeys.map { .spot($0) }) + snapshot.append(spots.map { .spot($0) }) self.dataSource?.apply(snapshot, to: .spot) } .store(in: &self.cancellables) @@ -202,21 +198,24 @@ private extension SaveJourneyViewController { } func configureSection(for section: SaveJourneySection) -> NSCollectionLayoutSection { - let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), - heightDimension: .fractionalHeight(1.0)) - let item = NSCollectionLayoutItem(layoutSize: itemSize) + let item = NSCollectionLayoutItem(layoutSize: section.itemSize) - let groupHeight: NSCollectionLayoutDimension = switch section { - case .music: .estimated(SaveJourneyMusicCell.estimatedHeight) - case .spot: .estimated(JourneyCell.estimatedHeight) + let group: NSCollectionLayoutGroup + let itemCount = section == .music ? 1 : 2 + let interItemSpacing: CGFloat = section == .music ? .zero : Metric.innerSpacing + if #available(iOS 16.0, *) { + group = NSCollectionLayoutGroup.horizontal(layoutSize: section.groupSize, + repeatingSubitem: item, + count: itemCount) + } else { + group = NSCollectionLayoutGroup.horizontal(layoutSize: section.groupSize, + subitem: item, + count: itemCount) } - let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), - heightDimension: groupHeight) - let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, - subitems: [item]) + group.interItemSpacing = .fixed(interItemSpacing) let section = NSCollectionLayoutSection(group: group) - section.interGroupSpacing = Metric.innerGroupSpacing + section.interGroupSpacing = Metric.innerSpacing section.contentInsets = NSDirectionalEdgeInsets(top: Metric.verticalInset, leading: Metric.horizontalInset, bottom: Metric.verticalInset, @@ -245,12 +244,30 @@ private extension SaveJourneyViewController { cell.update(with: itemIdentifier) } - let journeyCellRegistration = JourneyCellRegistration { cell, indexPath, itemIdentifier in - let cellModel = JourneyCellModel(location: itemIdentifier.location, - date: itemIdentifier.date, - songTitle: itemIdentifier.song.title, - songArtist: itemIdentifier.song.artist) - cell.update(with: cellModel) + let journeyCellRegistration = SpotCellRegistration { cell, indexPath, itemIdentifier in + // TODO: 별도의 모델 사용 + let geocoder = CLGeocoder() + let location = CLLocation(latitude: itemIdentifier.location.latitude, + longitude: itemIdentifier.location.longitude) + Task { + guard let placemark = try? await geocoder.reverseGeocodeLocation(location).first else { + return + } + var location = "" + + if let locality = placemark.locality { + location += locality + } + + if let subLocality = placemark.subLocality { + location += " \(subLocality)" + } + + let cellModel = SpotCellModel(location: location, + date: itemIdentifier.date, + photoURL: itemIdentifier.photoURL) + cell.update(with: cellModel) + } // TODO: ImageFetcher 사용해 이미지 업데이트 } @@ -260,7 +277,7 @@ private extension SaveJourneyViewController { case .zero: header.update(with: Typo.songSectionTitle) case 1: - let spots = self.viewModel.state.journeys + let spots = self.viewModel.state.spots header.update(with: Typo.spotSectionTitle(spots.value.count)) default: break @@ -383,8 +400,8 @@ import MSData @available(iOS 17, *) #Preview { - let journeyRepository = JourneyRepositoryImplementation() - let saveJourneyViewModel = SaveJourneyViewModel(repository: journeyRepository) + let spotRepository = SpotRepositoryImplementation() + let saveJourneyViewModel = SaveJourneyViewModel(spotRepository: spotRepository) 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 index 1714eed..be1c72a 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift @@ -8,6 +8,7 @@ import Combine import MSData +import MSLogger public final class SaveJourneyViewModel { @@ -19,19 +20,19 @@ public final class SaveJourneyViewModel { public struct State { var music = CurrentValueSubject("") - var journeys = CurrentValueSubject<[Journey], Never>([]) + var spots = CurrentValueSubject<[Spot], Never>([]) } // MARK: - Properties - private let repository: JourneyRepository + private let spotRepository: SpotRepository public var state = State() // MARK: - Initializer - public init(repository: JourneyRepository) { - self.repository = repository + public init(spotRepository: SpotRepository) { + self.spotRepository = spotRepository } // MARK: - Functions @@ -40,13 +41,15 @@ public final class SaveJourneyViewModel { switch action { case .viewNeedsLoaded: Task { - let result = await self.repository.fetchJourneyList() + let result = await self.spotRepository.fetchRecordingSpots() switch result { - case .success(let journeyDTOs): - let journeys = journeyDTOs.map { Journey(dto: $0) } - self.state.journeys.send(journeys) + case .success(let responseDTOs): + let spots = responseDTOs.map { Spot(dto: $0) } + self.state.spots.send(spots) case .failure(let error): - print(error) + #if DEBUG + MSLogger.make(category: .saveJourney).error("\(error)") + #endif } } case .mediaControlButtonDidTap: diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SpotCell.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SpotCell.swift new file mode 100644 index 0000000..1e55c7e --- /dev/null +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SpotCell.swift @@ -0,0 +1,111 @@ +// +// SpotCell.swift +// SaveJourney +// +// Created by 이창준 on 2023.12.04. +// + +import UIKit + +import MSDesignSystem + +final class SpotCell: UICollectionViewCell { + + // MARK: - Constants + + private enum Metric { + + static let cornerRadius: CGFloat = 5.0 + static let labelStackHorizontalSpacing: CGFloat = 8.0 + static let labelStackVerticalSpacing: CGFloat = 5.0 + + } + + // MARK: - UI Components + + private let imageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + imageView.backgroundColor = .msColor(.componentBackground) + return imageView + }() + + private let labelStack: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + return stackView + }() + + private let locationLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.boldCaption) + label.textColor = .msColor(.primaryTypo) + label.numberOfLines = 1 + return label + }() + + private let dateLabel: UILabel = { + let label = UILabel() + label.font = .msFont(.caption) + label.textColor = .msColor(.secondaryTypo) + label.numberOfLines = 1 + return label + }() + + // MARK: - Initializer + + 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 cellModel: SpotCellModel) { + self.locationLabel.text = cellModel.location + self.dateLabel.text = cellModel.date.formatted(date: .abbreviated, time: .omitted) + } + +} + +private extension SpotCell { + + func configureStyles() { + self.layer.cornerRadius = Metric.cornerRadius + self.clipsToBounds = true + } + + func configureLayout() { + 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) + ]) + + self.imageView.addSubview(self.labelStack) + self.labelStack.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.labelStack.leadingAnchor.constraint(equalTo: self.imageView.leadingAnchor, + constant: Metric.labelStackHorizontalSpacing), + self.labelStack.bottomAnchor.constraint(equalTo: self.imageView.bottomAnchor, + constant: -Metric.labelStackVerticalSpacing), + self.labelStack.trailingAnchor.constraint(lessThanOrEqualTo: self.imageView.trailingAnchor) + ]) + + [ + self.locationLabel, + self.dateLabel + ].forEach { + self.labelStack.addArrangedSubview($0) + } + } + +} diff --git a/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/RepositoryTests.xcscheme b/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/RepositoryTests.xcscheme new file mode 100644 index 0000000..869bbd9 --- /dev/null +++ b/iOS/MSData/.swiftpm/xcode/xcshareddata/xcschemes/RepositoryTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/MSData/Package.swift b/iOS/MSData/Package.swift index bffa772..3c1c96a 100644 --- a/iOS/MSData/Package.swift +++ b/iOS/MSData/Package.swift @@ -20,6 +20,8 @@ let package = Package( dependencies: ["MSNetworking"], resources: [.process("Resources")]), .testTarget(name: "MSDataTests", + dependencies: ["MSData"]), + .testTarget(name: "RepositoryTests", dependencies: ["MSData"]) ] ) diff --git a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift index a630f1a..096fb1f 100644 --- a/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/Fragment/SpotDTO.swift @@ -23,10 +23,30 @@ public struct RequestableSpotDTO: Encodable, Identifiable { } -public struct ResponsibleSpotDTO: Codable, Identifiable { +public struct ResponsibleSpotDTO: Identifiable { public let id: UUID - public let coordinate: [Double] - public let photoURLs: [String] + public let coordinate: CoordinateDTO + public let photoURL: URL + +} + +// MARK: - Decodable + +extension ResponsibleSpotDTO: Decodable { + + enum CodingKeys: String, CodingKey { + case id = "journeyId" + case coordinate + case photoURL = "photoUrl" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(UUID.self, forKey: .id) + let coordinate = try container.decode([Double].self, forKey: .coordinate) + self.coordinate = CoordinateDTO(latitude: coordinate[0], longitude: coordinate[1]) + self.photoURL = try container.decode(URL.self, forKey: .photoURL) + } } diff --git a/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift b/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift index 8fc7337..ae23a67 100644 --- a/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/JourneyDTO.swift @@ -7,7 +7,7 @@ import Foundation -public struct JourneyDTO: Codable, Identifiable { +public struct JourneyDTO: Decodable, Identifiable { public let id: UUID public let location: String @@ -18,3 +18,4 @@ public struct JourneyDTO: Codable, Identifiable { public let lineColor: String } + diff --git a/iOS/MSData/Sources/MSData/DTO/PersonDTO.swift b/iOS/MSData/Sources/MSData/DTO/PersonDTO.swift index 1319988..a90e78c 100644 --- a/iOS/MSData/Sources/MSData/DTO/PersonDTO.swift +++ b/iOS/MSData/Sources/MSData/DTO/PersonDTO.swift @@ -7,7 +7,7 @@ import Foundation -public struct PersonDTO: Codable, Identifiable { +public struct PersonDTO: Decodable, Identifiable { public let id: UUID public let nickname: String diff --git a/iOS/MSData/Sources/MSData/Repository/SpotRepository.swift b/iOS/MSData/Sources/MSData/Repository/SpotRepository.swift new file mode 100644 index 0000000..3f76afe --- /dev/null +++ b/iOS/MSData/Sources/MSData/Repository/SpotRepository.swift @@ -0,0 +1,43 @@ +// +// SpotRepository.swift +// MSData +// +// Created by 이창준 on 2023.12.04. +// + +import Foundation + +import MSNetworking + +public protocol SpotRepository { + func fetchRecordingSpots() async -> Result<[ResponsibleSpotDTO], Error> +} + +public struct SpotRepositoryImplementation: SpotRepository { + + // MARK: - Initializer + + public init() { } + + // MARK: - Functions + + public func fetchRecordingSpots() async -> Result<[ResponsibleSpotDTO], Error> { + + #if DEBUG + guard let jsonURL = Bundle.module.url(forResource: "MockSpot", withExtension: "json") else { + return .failure((MSNetworkError.invalidRouter)) + } + do { + let jsonData = try Data(contentsOf: jsonURL) + let decoder = JSONDecoder() + let spots = try decoder.decode([ResponsibleSpotDTO].self, from: jsonData) + return .success(spots) + } catch { + print(error) + } + #endif + + return .failure(MSNetworkError.unknownResponse) + } + +} diff --git a/iOS/MSData/Sources/MSData/Resources/MockSpot.json b/iOS/MSData/Sources/MSData/Resources/MockSpot.json new file mode 100644 index 0000000..991112c --- /dev/null +++ b/iOS/MSData/Sources/MSData/Resources/MockSpot.json @@ -0,0 +1,52 @@ +[ + { + "journeyId": "ab4068ef-95ed-40c3-be6d-3db35df866b9", + "coordinate": [37.46569, 126.44748], + "photoUrl": "https://source.unsplash.com/random/200x300" + }, + { + "journeyId": "2c5dc172-724f-4781-9669-2657fad175c8", + "coordinate": [37.37501, 126.63229], + "photoUrl": "https://source.unsplash.com/random/200x300" + }, + { + "journeyId": "bb058be2-c5d0-400c-8b18-140cc971c126", + "coordinate": [37.52593, 126.91611], + "photoUrl": "https://source.unsplash.com/random/200x300" + }, + { + "journeyId": "f263a338-5fff-4349-95af-feb5abacbf29", + "coordinate": [37.55676, 126.91247], + "photoUrl": "https://source.unsplash.com/random/200x300" + }, + { + "journeyId": "02a90f9d-ca78-49f5-893a-3f0e79ccb8e6", + "coordinate": [37.54440, 127.04031], + "photoUrl": "https://source.unsplash.com/random/200x300" + }, + { + "journeyId": "8b10e0bb-4387-4997-adaf-9a4a6369f91a", + "coordinate": [37.57774, 126.97258], + "photoUrl": "https://source.unsplash.com/random/200x300" + }, + { + "journeyId": "d6397ea4-582e-4228-a98d-4dc541f854bb", + "coordinate": [37.27979, 127.01506], + "photoUrl": "https://source.unsplash.com/random/200x300" + }, + { + "journeyId": "3cd9a630-edbd-4c45-bb94-5f8b549f7586", + "coordinate": [36.82055, 127.14046], + "photoUrl": "https://source.unsplash.com/random/200x300" + }, + { + "journeyId": "00eefb58-bc78-4058-a898-f54d07e4c467", + "coordinate": [33.27913, 126.30613], + "photoUrl": "https://source.unsplash.com/random/200x300" + }, + { + "journeyId": "26b68488-60fc-4d8f-ac88-ff54c3edd3a5", + "coordinate": [35.14139, 129.10911], + "photoUrl": "https://source.unsplash.com/random/200x300" + } +] diff --git a/iOS/MSData/Tests/RepositoryTests/SpotRepositoryTests.swift b/iOS/MSData/Tests/RepositoryTests/SpotRepositoryTests.swift new file mode 100644 index 0000000..60e3188 --- /dev/null +++ b/iOS/MSData/Tests/RepositoryTests/SpotRepositoryTests.swift @@ -0,0 +1,36 @@ +// +// SpotRepositoryTests.swift +// +// +// Created by 이창준 on 2023.12.04. +// + +import XCTest +@testable import MSData + +final class SpotRepositoryTests: XCTestCase { + + // MARK: - Properties + + private let sut = SpotRepositoryImplementation() + + // MARK: - Tests + + func test_SpotResponseDTO_디코딩_성공() throws { + + let expectation = XCTestExpectation() + + Task { + let result = await self.sut.fetchRecordingSpots() + switch result { + case .success: + expectation.fulfill() + case .failure(let error): + XCTFail("\(error)") + } + } + + wait(for: [expectation], timeout: 5.0) + } + +} diff --git a/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift b/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift index 848022b..4c17411 100644 --- a/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift +++ b/iOS/MSFoundation/Sources/MSLogger/MSLogCategory.swift @@ -11,8 +11,9 @@ public enum MSLogCategory: String { case network case userDefaults case dataCore + case recordingJourney - case checkJourney + case saveJourney case login case setting case camera diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift index 9323264..4296555 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCell.swift @@ -64,8 +64,8 @@ public final class JourneyCell: UICollectionViewCell { public func update(with model: JourneyCellModel) { self.infoView.update(location: model.location, date: model.date, - title: model.song.title, - artist: model.song.artist) + title: model.song?.title, + artist: model.song?.artist) } @MainActor diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCellModel.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCellModel.swift index c63d7e7..0bf86d5 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCellModel.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyCellModel.swift @@ -19,19 +19,23 @@ public struct JourneyCellModel: Hashable { let id: UUID let location: String let date: Date - let song: Song + let song: Song? // MARK: - Initializer public init(id: UUID = UUID(), location: String, date: Date, - songTitle: String, - songArtist: String) { + songTitle: String?, + songArtist: String?) { self.id = id self.location = location self.date = date - self.song = Song(artist: songArtist, title: songTitle) + if let songTitle, let songArtist { + self.song = Song(artist: songArtist, title: songTitle) + } else { + self.song = nil + } } } diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyInfoView.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyInfoView.swift index 8c20d87..5b38918 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyInfoView.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/JourneyInfoView.swift @@ -43,7 +43,7 @@ final class JourneyInfoView: UIView { return stackView }() - private let locationTitleLabel: UILabel = { + private let titleLabel: UILabel = { let label = UILabel() label.font = .msFont(.subtitle) label.textColor = .msColor(.primaryTypo) @@ -87,12 +87,17 @@ final class JourneyInfoView: UIView { func update(location: String, date: Date, w3w: String = "", - title: String, - artist: String) { - self.locationTitleLabel.text = location + title: String?, + artist: String?) { + self.titleLabel.text = location self.dateLabel.text = date.formatted(date: .abbreviated, time: .omitted) self.w3wLabel.text = w3w - self.musicInfoView.update(artist: artist, title: title) + if let title, let artist { + self.musicInfoView.update(artist: artist, title: title) + self.musicInfoView.isHidden = false + } else { + self.musicInfoView.isHidden = true + } } } @@ -114,7 +119,7 @@ private extension JourneyInfoView { self.contentStack.addArrangedSubview(self.titleLabelStack) self.contentStack.addArrangedSubview(self.musicInfoView) - self.titleLabelStack.addArrangedSubview(self.locationTitleLabel) + self.titleLabelStack.addArrangedSubview(self.titleLabel) self.titleLabelStack.addArrangedSubview(self.subLabelStack) self.subLabelStack.addArrangedSubview(self.dateLabel) diff --git a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift index c53caf6..eddccc9 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/Cells/JourneyCell/MusicInfoView.swift @@ -75,7 +75,7 @@ final class MusicInfoView: UIView { // MARK: - Functions - func update(artist: String, title: String) { + func update(artist: String?, title: String?) { self.artistLabel.text = artist self.titleLabel.text = title } From 7c3b8e3ca40f19ffc506bf31bbf7561de8825f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 17:28:13 +0900 Subject: [PATCH 33/39] =?UTF-8?q?:memo:=20TODO=20=EC=82=AD=EC=A0=9C=20&=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SaveJourney/Presentation/SaveJourneyViewController.swift | 1 - .../Sources/SaveJourney/Presentation/View/SpotCell.swift | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index a1987e6..d019f47 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -245,7 +245,6 @@ private extension SaveJourneyViewController { } let journeyCellRegistration = SpotCellRegistration { cell, indexPath, itemIdentifier in - // TODO: 별도의 모델 사용 let geocoder = CLGeocoder() let location = CLLocation(latitude: itemIdentifier.location.latitude, longitude: itemIdentifier.location.longitude) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SpotCell.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SpotCell.swift index 1e55c7e..7ae5d4c 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SpotCell.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SpotCell.swift @@ -69,6 +69,7 @@ final class SpotCell: UICollectionViewCell { func update(with cellModel: SpotCellModel) { self.locationLabel.text = cellModel.location self.dateLabel.text = cellModel.date.formatted(date: .abbreviated, time: .omitted) + // TODO: ImageFetcher 적용 } } From 0412a59a9c90c27b73c62fb9c8488243b8fc80d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 20:14:58 +0900 Subject: [PATCH 34/39] =?UTF-8?q?:bug:=20=EB=B9=8C=EB=93=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/JourneyList/Model/DTOConvertor.swift | 2 +- .../Sources/JourneyList/Model/Journey.swift | 2 +- .../Sources/JourneyList/Model/Spot.swift | 4 ++-- .../Presentation/JourneyListViewController.swift | 3 +-- .../Presentation/SaveJourneySection.swift | 9 +++++++++ .../Presentation/SaveJourneyViewController.swift | 15 ++++----------- .../Presentation/View/SaveJourneyMusicCell.swift | 2 +- .../MSCoordinator/SaveJourneyCoordinator.swift | 4 +++- 8 files changed, 22 insertions(+), 19 deletions(-) diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift index e5d9304..2b84551 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Model/DTOConvertor.swift @@ -22,7 +22,7 @@ extension Journey { extension Spot { init(dto: ResponsibleSpotDTO) { - self.photoURLs = dto.photoURLs + self.photoURL = dto.photoURL } } diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Model/Journey.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/Journey.swift index f7a64e1..5d31027 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Model/Journey.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Model/Journey.swift @@ -7,7 +7,7 @@ import Foundation -struct Journey: Hashable, Decodable { +struct Journey: Hashable { // MARK: - Properties diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Model/Spot.swift b/iOS/Features/JourneyList/Sources/JourneyList/Model/Spot.swift index cd2eb00..8d8f0aa 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Model/Spot.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Model/Spot.swift @@ -7,8 +7,8 @@ import Foundation -struct Spot: Decodable { +struct Spot { - let photoURLs: [String] + let photoURL: URL } diff --git a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift index d1f546a..ec9d981 100644 --- a/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift +++ b/iOS/Features/JourneyList/Sources/JourneyList/Presentation/JourneyListViewController.swift @@ -223,8 +223,7 @@ extension JourneyListViewController: UICollectionViewDelegate { songArtist: itemIdentifier.song.artist) cell.update(with: cellModel) let photoURLs = itemIdentifier.spots - .flatMap { $0.photoURLs } - .compactMap { URL(string: $0) } + .map { $0.photoURL } Task { cell.addImageView(count: photoURLs.count) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift index aa8cf90..296cfae 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift @@ -7,6 +7,8 @@ import UIKit +// MARK: - Section + enum SaveJourneySection { case music case spot @@ -34,3 +36,10 @@ enum SaveJourneySection { } } + +// MARK: - Item + +enum SaveJourneyItem: Hashable { + case music(String) + case spot(Spot) +} diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index d019f47..ab38fdb 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -12,12 +12,6 @@ import UIKit import MSUIKit import MediaPlayer - -enum SaveJourneyItem: Hashable { - case music(String) - case spot(Spot) -} - public final class SaveJourneyViewController: UIViewController { typealias SaveJourneyDataSource = UICollectionViewDiffableDataSource @@ -44,6 +38,7 @@ public final class SaveJourneyViewController: UIViewController { static let horizontalInset: CGFloat = 24.0 static let verticalInset: CGFloat = 12.0 + static let collectionViewBottomSpacing: CGFloat = 80.0 static let innerSpacing: CGFloat = 4.0 static let headerTopInset: CGFloat = 24.0 static let buttonSpacing: CGFloat = 4.0 @@ -74,7 +69,7 @@ public final class SaveJourneyViewController: UIViewController { collectionView.backgroundColor = .clear collectionView.contentInset = UIEdgeInsets(top: self.view.frame.width, left: .zero, - bottom: .zero, + bottom: Metric.collectionViewBottomSpacing, right: .zero) collectionView.delegate = self return collectionView @@ -317,20 +312,18 @@ extension SaveJourneyViewController: UICollectionViewDelegate { public func scrollViewDidScroll(_ scrollView: UIScrollView) { let offset = scrollView.contentOffset - self.updateProfileViewLayout(by: offset, name: "Change Me!") + self.updateProfileViewLayout(by: offset) } - func updateProfileViewLayout(by offset: CGPoint, name: String) { + func updateProfileViewLayout(by offset: CGPoint) { 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 diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift index fc21968..ad6ec82 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift @@ -32,7 +32,7 @@ final class SaveJourneyMusicCell: UICollectionViewCell { imageView.contentMode = .scaleAspectFill imageView.layer.cornerRadius = Metric.imageViewCornerRadius imageView.clipsToBounds = true - imageView.backgroundColor = .systemBlue + imageView.backgroundColor = .msColor(.musicSpot) return imageView }() diff --git a/iOS/MusicSpot/MusicSpot/MSCoordinator/SaveJourneyCoordinator.swift b/iOS/MusicSpot/MusicSpot/MSCoordinator/SaveJourneyCoordinator.swift index ead5ab6..56ab4c0 100644 --- a/iOS/MusicSpot/MusicSpot/MSCoordinator/SaveJourneyCoordinator.swift +++ b/iOS/MusicSpot/MusicSpot/MSCoordinator/SaveJourneyCoordinator.swift @@ -7,6 +7,7 @@ import UIKit +import MSData import SaveJourney final class SaveJourneyCoordinator: Coordinator { @@ -28,7 +29,8 @@ final class SaveJourneyCoordinator: Coordinator { // MARK: - Functions func start() { - let saveJourneyViewModel = SaveJourneyViewModel() + let spotRepository = SpotRepositoryImplementation() + let saveJourneyViewModel = SaveJourneyViewModel(spotRepository: spotRepository) let saveJourneyViewController = SaveJourneyViewController(viewModel: saveJourneyViewModel) // saveJourneyViewController.delegate = self self.navigationController.pushViewController(saveJourneyViewController, animated: true) From 676af6588b195012e93abfa62910a98381de6099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 20:30:28 +0900 Subject: [PATCH 35/39] =?UTF-8?q?:sparkles:=20Song=20=EC=A3=BC=EC=9E=85=20?= =?UTF-8?q?=EB=B0=9B=EC=9D=80=20=ED=9B=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SaveJourneyDemo/SceneDelegate.swift | 4 +++- .../SaveJourney/Model/DTO+Mapping.swift | 1 + .../Sources/SaveJourney/Model/Journey.swift | 2 +- .../Sources/SaveJourney/Model/Song.swift | 15 +++++++++--- .../Sources/SaveJourney/Model/Spot.swift | 2 +- .../Presentation/SaveJourneySection.swift | 8 +++---- .../SaveJourneyViewController.swift | 24 ++++++++++--------- .../Presentation/SaveJourneyViewModel.swift | 8 ++++--- .../View/SaveJourneyMusicCell.swift | 6 ++++- 9 files changed, 45 insertions(+), 25 deletions(-) diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift index a9e5f44..2f0b8f7 100644 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift +++ b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo/SceneDelegate.swift @@ -28,8 +28,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { MSFont.registerFonts() + let song = Song(title: "OMG", artist: "NewJeans", albumArtURL: URL(string: "https://naver.com")!) let spotRepository = SpotRepositoryImplementation() - let saveJourneyViewModel = SaveJourneyViewModel(spotRepository: spotRepository) + let saveJourneyViewModel = SaveJourneyViewModel(selectedSong: song, + spotRepository: spotRepository) let saveJourneyViewController = SaveJourneyViewController(viewModel: saveJourneyViewModel) let navigationViewController = UINavigationController(rootViewController: saveJourneyViewController) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift index e57f443..fa37b2d 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/DTO+Mapping.swift @@ -46,6 +46,7 @@ extension Song { init(dto: SongDTO) { self.title = dto.title self.artist = dto.artist + self.albumArtURL = URL(string: dto.artwork) } } diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift index 9e852e0..a1e9ce8 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Journey.swift @@ -1,6 +1,6 @@ // // Journey.swift -// JourneyList +// SaveJourney // // Created by 이창준 on 11/23/23. // diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Song.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Song.swift index fb8aed6..d95fbac 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Song.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Song.swift @@ -1,15 +1,24 @@ // -// File.swift -// +// Song.swift +// SaveJourney // // Created by 이창준 on 2023.12.04. // import Foundation -struct Song: Hashable { +public struct Song: Hashable { let title: String let artist: String + let albumArtURL: URL? + + public init(title: String, + artist: String, + albumArtURL: URL?) { + self.title = title + self.artist = artist + self.albumArtURL = albumArtURL + } } diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift index bbbc3e8..d61bf5c 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Model/Spot.swift @@ -1,6 +1,6 @@ // // Spot.swift -// JourneyList +// SaveJourney // // Created by 이창준 on 11/23/23. // diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift index 296cfae..91ab510 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneySection.swift @@ -10,12 +10,12 @@ import UIKit // MARK: - Section enum SaveJourneySection { - case music + case song case spot var itemSize: NSCollectionLayoutSize { switch self { - case .music: + case .song: return NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)) case .spot: @@ -26,7 +26,7 @@ enum SaveJourneySection { var groupSize: NSCollectionLayoutSize { switch self { - case .music: + case .song: return NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(SaveJourneyMusicCell.estimatedHeight)) case .spot: @@ -40,6 +40,6 @@ enum SaveJourneySection { // MARK: - Item enum SaveJourneyItem: Hashable { - case music(String) + case song(Song) case spot(Spot) } diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index ab38fdb..74f959d 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -16,7 +16,7 @@ public final class SaveJourneyViewController: UIViewController { typealias SaveJourneyDataSource = UICollectionViewDiffableDataSource typealias HeaderRegistration = UICollectionView.SupplementaryRegistration - typealias MusicCellRegistration = UICollectionView.CellRegistration + typealias MusicCellRegistration = UICollectionView.CellRegistration typealias SpotCellRegistration = UICollectionView.CellRegistration typealias SaveJourneySnapshot = NSDiffableDataSourceSnapshot typealias MusicSnapshot = NSDiffableDataSourceSectionSnapshot @@ -124,11 +124,12 @@ public final class SaveJourneyViewController: UIViewController { // MARK: - Combine Binding func bind() { - self.viewModel.state.music - .sink { music in + self.viewModel.state.song + .print() + .sink { song in var snapshot = MusicSnapshot() - snapshot.append([.music(music)]) - self.dataSource?.apply(snapshot, to: .music) + snapshot.append([.song(song)]) + self.dataSource?.apply(snapshot, to: .song) } .store(in: &self.cancellables) @@ -196,8 +197,8 @@ private extension SaveJourneyViewController { let item = NSCollectionLayoutItem(layoutSize: section.itemSize) let group: NSCollectionLayoutGroup - let itemCount = section == .music ? 1 : 2 - let interItemSpacing: CGFloat = section == .music ? .zero : Metric.innerSpacing + let itemCount = section == .song ? 1 : 2 + let interItemSpacing: CGFloat = section == .song ? .zero : Metric.innerSpacing if #available(iOS 16.0, *) { group = NSCollectionLayoutGroup.horizontal(layoutSize: section.groupSize, repeatingSubitem: item, @@ -281,10 +282,10 @@ private extension SaveJourneyViewController { let dataSource = SaveJourneyDataSource(collectionView: self.collectionView, cellProvider: { collectionView, indexPath, item in switch item { - case .music(let music): + case .song(let song): return collectionView.dequeueConfiguredReusableCell(using: musicCellRegistration, for: indexPath, - item: music) + item: song) case .spot(let journey): return collectionView.dequeueConfiguredReusableCell(using: journeyCellRegistration, for: indexPath, @@ -298,7 +299,7 @@ private extension SaveJourneyViewController { } var snapshot = SaveJourneySnapshot() - snapshot.appendSections([.music, .spot]) + snapshot.appendSections([.song, .spot]) dataSource.apply(snapshot) return dataSource @@ -392,8 +393,9 @@ import MSData @available(iOS 17, *) #Preview { + let song = Song(title: "OMG", artist: "NewJeans", albumArtURL: URL(string: "sdf")!) let spotRepository = SpotRepositoryImplementation() - let saveJourneyViewModel = SaveJourneyViewModel(spotRepository: spotRepository) + let saveJourneyViewModel = SaveJourneyViewModel(selectedSong: song, spotRepository: spotRepository) 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 index be1c72a..c9fe3e1 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift @@ -19,7 +19,7 @@ public final class SaveJourneyViewModel { } public struct State { - var music = CurrentValueSubject("") + var song: CurrentValueSubject var spots = CurrentValueSubject<[Spot], Never>([]) } @@ -27,12 +27,14 @@ public final class SaveJourneyViewModel { private let spotRepository: SpotRepository - public var state = State() + public var state: State // MARK: - Initializer - public init(spotRepository: SpotRepository) { + public init(selectedSong: Song, + spotRepository: SpotRepository) { self.spotRepository = spotRepository + self.state = State(song: CurrentValueSubject(selectedSong)) } // MARK: - Functions diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift index ad6ec82..cca2cdf 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/View/SaveJourneyMusicCell.swift @@ -47,6 +47,7 @@ final class SaveJourneyMusicCell: UICollectionViewCell { private let audioIconImageView: UIImageView = { let imageView = UIImageView() imageView.image = .msIcon(.voice) + imageView.tintColor = .msColor(.primaryTypo) return imageView }() @@ -80,8 +81,11 @@ final class SaveJourneyMusicCell: UICollectionViewCell { // MARK: - Functions - func update(with data: String) { + func update(with cellModel: Song) { + self.titleLabel.text = cellModel.title + self.artistLabel.text = cellModel.artist + // TODO: ImageFetcher Merge 후 구현 } } From ca83a52846a1b86fb56caacd80754313ba79fec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 20:57:46 +0900 Subject: [PATCH 36/39] =?UTF-8?q?:sparkles:=20=EC=97=AC=EC=A0=95=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=ED=99=94=EB=A9=B4=20=ED=82=A4=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EC=A1=B0=EC=A0=95=20&=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SaveJourneyDemo.xcodeproj/project.pbxproj | 12 ++++---- .../ConfirmTitleAlertViewController.swift | 24 ++++++++++++--- .../SaveJourneyViewController.swift | 29 ++++++++++++++----- .../Presentation/SaveJourneyViewModel.swift | 3 -- .../MSUIKit/MSAlertViewController.swift | 2 +- 5 files changed, 48 insertions(+), 22 deletions(-) diff --git a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo.xcodeproj/project.pbxproj b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo.xcodeproj/project.pbxproj index 062e332..bf6de32 100644 --- a/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo.xcodeproj/project.pbxproj +++ b/iOS/Features/SaveJourney/SaveJourneyDemo/SaveJourneyDemo.xcodeproj/project.pbxproj @@ -7,11 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + DD197B132B1DF223001D4290 /* SaveJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B122B1DF223001D4290 /* SaveJourney */; }; DDAA4DC92B17356D002F0748 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA4DC82B17356D002F0748 /* AppDelegate.swift */; }; DDAA4DCB2B17356D002F0748 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA4DCA2B17356D002F0748 /* SceneDelegate.swift */; }; DDAA4DD22B17356E002F0748 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDAA4DD12B17356E002F0748 /* Assets.xcassets */; }; DDAA4DD52B17356E002F0748 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DDAA4DD32B17356E002F0748 /* LaunchScreen.storyboard */; }; - DDAA4DDE2B17357F002F0748 /* SaveJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DDAA4DDD2B17357F002F0748 /* SaveJourney */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -28,7 +28,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DDAA4DDE2B17357F002F0748 /* SaveJourney in Frameworks */, + DD197B132B1DF223001D4290 /* SaveJourney in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -80,7 +80,7 @@ ); name = SaveJourneyDemo; packageProductDependencies = ( - DDAA4DDD2B17357F002F0748 /* SaveJourney */, + DD197B122B1DF223001D4290 /* SaveJourney */, ); productName = SaveJourneyDemo; productReference = DDAA4DC52B17356D002F0748 /* SaveJourneyDemo.app */; @@ -111,7 +111,7 @@ ); mainGroup = DDAA4DBC2B17356D002F0748; packageReferences = ( - DDAA4DDC2B17357F002F0748 /* XCLocalSwiftPackageReference ".." */, + DD197B112B1DF223001D4290 /* XCLocalSwiftPackageReference ".." */, ); productRefGroup = DDAA4DC62B17356D002F0748 /* Products */; projectDirPath = ""; @@ -361,14 +361,14 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - DDAA4DDC2B17357F002F0748 /* XCLocalSwiftPackageReference ".." */ = { + DD197B112B1DF223001D4290 /* XCLocalSwiftPackageReference ".." */ = { isa = XCLocalSwiftPackageReference; relativePath = ..; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - DDAA4DDD2B17357F002F0748 /* SaveJourney */ = { + DD197B122B1DF223001D4290 /* SaveJourney */ = { isa = XCSwiftPackageProductDependency; productName = SaveJourney; }; diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift index 231a7f2..6c10d43 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift @@ -5,10 +5,17 @@ // Created by 이창준 on 2023.12.04. // +import Combine import UIKit import MSUIKit +protocol AlertViewControllerDelegate: AnyObject { + + func titleDidConfirmed(_ title: String) + +} + final class ConfirmTitleAlertViewController: MSAlertViewController { // MARK: - Constants @@ -39,6 +46,8 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { // MARK: - Properties + weak var delegate: AlertViewControllerDelegate? + // MARK: - Life Cycle override func viewDidLoad() { @@ -46,6 +55,12 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { self.configureButtonActions() } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.textField.becomeFirstResponder() + } + // MARK: - Helpers override func dismissBottomSheet() { @@ -54,12 +69,13 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { } private func configureButtonActions() { - self.cancelButtonAction = UIAction { _ in - self.dismissBottomSheet() + self.cancelButtonAction = UIAction { [weak self] _ in + self?.dismissBottomSheet() } - self.doneButtonAction = UIAction { _ in - print("TODO: 완료 로직 구현!!") + self.doneButtonAction = UIAction { [weak self] _ in + guard let title = self?.textField.text else { return } + self?.delegate?.titleDidConfirmed(title) } } diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index 74f959d..7ab1a50 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -12,6 +12,7 @@ import UIKit import MSUIKit import MediaPlayer + public final class SaveJourneyViewController: UIViewController { typealias SaveJourneyDataSource = UICollectionViewDiffableDataSource @@ -87,13 +88,13 @@ public final class SaveJourneyViewController: UIViewController { private let mediaControlButton: MSRectButton = { let button = MSRectButton.small() - button.configuration?.image = .msIcon(.play) + button.image = .msIcon(.play) return button }() private lazy var nextButton: MSButton = { let button = MSButton.primary() - button.configuration?.title = Typo.nextButtonTitle + button.title = Typo.nextButtonTitle return button }() @@ -125,7 +126,6 @@ public final class SaveJourneyViewController: UIViewController { func bind() { self.viewModel.state.song - .print() .sink { song in var snapshot = MusicSnapshot() snapshot.append([.song(song)]) @@ -156,11 +156,7 @@ private extension SaveJourneyViewController { self.mediaControlButton.addAction(mediaControlAction, for: .touchUpInside) let nextButtonAction = UIAction { [weak self] _ in - self?.viewModel.trigger(.nextButtonDidTap) - - let alert = ConfirmTitleAlertViewController() - alert.modalPresentationStyle = .overCurrentContext - self?.present(alert, animated: false) + self?.presentSaveJourney() } self.nextButton.addAction(nextButtonAction, for: .touchUpInside) } @@ -333,6 +329,23 @@ extension SaveJourneyViewController: UICollectionViewDelegate { } +// MARK: - AlertViewController + +extension SaveJourneyViewController: AlertViewControllerDelegate { + + private func presentSaveJourney() { + let alert = ConfirmTitleAlertViewController() + alert.modalPresentationStyle = .overCurrentContext + alert.delegate = self + self.present(alert, animated: false) + } + + func titleDidConfirmed(_ title: String) { + print("Title: \(title)") + } + +} + // MARK: - UI Configuration private extension SaveJourneyViewController { diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift index c9fe3e1..4a7a68d 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewModel.swift @@ -15,7 +15,6 @@ public final class SaveJourneyViewModel { public enum Action { case viewNeedsLoaded case mediaControlButtonDidTap - case nextButtonDidTap } public struct State { @@ -56,8 +55,6 @@ public final class SaveJourneyViewModel { } case .mediaControlButtonDidTap: print("Media Control Button Tap.") - case .nextButtonDidTap: - print("Next Button Tap.") } } diff --git a/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift index db59dd5..81bb0b7 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift @@ -167,7 +167,7 @@ open class MSAlertViewController: UIViewController { self.configureLayout() } - public override func viewDidAppear(_ animated: Bool) { + open override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.animatePresentView() } From 2764b623ee5ca1dee4c11ca54513ad60b087bffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Sun, 3 Dec 2023 20:45:35 +0900 Subject: [PATCH 37/39] =?UTF-8?q?:sparkles:=20UITextField=EC=9D=98=20Combi?= =?UTF-8?q?neCocoa=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # iOS/Features/SelectSong/Package.swift --- iOS/Features/SelectSong/Package.swift | 13 ++++++-- iOS/MSUIKit/Package.swift | 33 ++++++++++++------- .../CombineCocoa/UITextField+Combine.swift | 23 +++++++++++++ 3 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 iOS/MSUIKit/Sources/CombineCocoa/UITextField+Combine.swift diff --git a/iOS/Features/SelectSong/Package.swift b/iOS/Features/SelectSong/Package.swift index 910e61f..c6768c9 100644 --- a/iOS/Features/SelectSong/Package.swift +++ b/iOS/Features/SelectSong/Package.swift @@ -27,10 +27,11 @@ private enum Target { private enum Dependency { - static let msUIKit = "MSUIKit" - static let msFoundation = "MSFoundation" static let msDesignsystem = "MSDesignSystem" + static let msUIKit = "MSUIKit" + static let combineCocoa = "CombineCocoa" static let msLogger = "MSLogger" + static let msFoundation = "MSFoundation" } @@ -46,6 +47,12 @@ let package = Package( targets: [Target.selectSong]) ], targets: [ - .target(name:Target.selectSong) + .target(name:Target.selectSong, + dependencies: [ + .product(name: Dependency.msUIKit, + package: Dependency.msUIKit), + .product(name: Dependency.combineCocoa, + package: Dependency.msUIKit) + ]) ] ) diff --git a/iOS/MSUIKit/Package.swift b/iOS/MSUIKit/Package.swift index db13db1..082fa41 100644 --- a/iOS/MSUIKit/Package.swift +++ b/iOS/MSUIKit/Package.swift @@ -8,8 +8,14 @@ import PackageDescription extension String { static let package = "MSUIKit" - static let designSystem = "MSDesignSystem" - static let uiKit = "MSUIKit" + +} + +private enum Target { + + static let msDesignSystem = "MSDesignSystem" + static let msUIKit = "MSUIKit" + static let combineCocoa = "CombineCocoa" } @@ -21,20 +27,23 @@ let package = Package( .iOS(.v15) ], products: [ - .library(name: .designSystem, - type: .static, - targets: [.designSystem]), - .library(name: .uiKit, - targets: [.uiKit]) + .library(name: Target.msDesignSystem, + targets: [Target.msDesignSystem]), + .library(name: Target.msUIKit, + targets: [Target.msUIKit]), + .library(name: Target.combineCocoa, + targets: [Target.combineCocoa]) ], targets: [ - // Codes - .target(name: .designSystem, + .target(name: Target.msDesignSystem, resources: [ - .process("../\(String.designSystem)/Resources") + .process("../\(Target.msDesignSystem)/Resources") + ]), + .target(name: Target.msUIKit, + dependencies: [ + .target(name: Target.msDesignSystem) ]), - .target(name: .uiKit, - dependencies: ["MSDesignSystem"]) + .target(name: Target.combineCocoa) ], swiftLanguageVersions: [.v5] ) diff --git a/iOS/MSUIKit/Sources/CombineCocoa/UITextField+Combine.swift b/iOS/MSUIKit/Sources/CombineCocoa/UITextField+Combine.swift new file mode 100644 index 0000000..8ab0f78 --- /dev/null +++ b/iOS/MSUIKit/Sources/CombineCocoa/UITextField+Combine.swift @@ -0,0 +1,23 @@ +// +// UITextField+Combine.swift +// MSUIKit +// +// Created by 이창준 on 2023.12.03. +// + +import Combine +import UIKit + +public extension UITextField { + + var textPublisher: AnyPublisher { + let publisher = NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, + object: self) + + return publisher + .compactMap { $0.object as? UITextField } + .map { $0.text ?? "" } + .eraseToAnyPublisher() + } + +} From 9a2b65f1440a1959e0fa54b6cd47aed9c656fa94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 22:55:25 +0900 Subject: [PATCH 38/39] =?UTF-8?q?:sparkles:=20=EC=97=AC=EC=A0=95=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20(=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=82=B9=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Features/SaveJourney/Package.swift | 3 +++ .../ConfirmTitleAlertViewController.swift | 26 ++++++++++++++++--- .../MSUIKit/MSAlertViewController.swift | 7 +++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/iOS/Features/SaveJourney/Package.swift b/iOS/Features/SaveJourney/Package.swift index 01bb06a..b6ce195 100644 --- a/iOS/Features/SaveJourney/Package.swift +++ b/iOS/Features/SaveJourney/Package.swift @@ -28,6 +28,7 @@ private enum Target { private enum Dependency { static let msData = "MSData" + static let combineCocoa = "CombineCocoa" static let msUIKit = "MSUIKit" static let msDesignsystem = "MSDesignSystem" static let msLogger = "MSLogger" @@ -61,6 +62,8 @@ let package = Package( package: Dependency.msData), .product(name: Dependency.msUIKit, package: Dependency.msUIKit), + .product(name: Dependency.combineCocoa, + package: Dependency.msUIKit), .product(name: Dependency.msLogger, package: Dependency.msFoundation) ]) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift index 6c10d43..71075f1 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/ConfirmTitleAlertViewController.swift @@ -8,6 +8,7 @@ import Combine import UIKit +import CombineCocoa import MSUIKit protocol AlertViewControllerDelegate: AnyObject { @@ -23,13 +24,14 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { private enum Typo { static let title = "여정 이름" - static let subtitle = "마지막으로 여정의 이름을 정해주세요." + static let placeholder = "마지막으로 여정의 이름을 정해주세요." } private enum Metric { static let horizontalInset: CGFloat = 12.0 + static let textFieldCenterOffset: CGFloat = 15.0 } @@ -40,7 +42,7 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { textField.imageStyle = .none textField.clearButtonMode = .whileEditing textField.enablesReturnKeyAutomatically = true - textField.placeholder = "Placeholder" + textField.placeholder = Typo.placeholder return textField }() @@ -48,11 +50,14 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { weak var delegate: AlertViewControllerDelegate? + private var cancellables: Set = [] + // MARK: - Life Cycle override func viewDidLoad() { super.viewDidLoad() self.configureButtonActions() + self.bind() } override func viewDidAppear(_ animated: Bool) { @@ -61,6 +66,19 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { self.textField.becomeFirstResponder() } + // MARK: - Combine Binding + + private func bind() { + self.textField.textPublisher + .receive(on: DispatchQueue.main) + .map { $0.isEmpty } + .removeDuplicates() + .sink { [weak self] isTextEmpty in + self?.updateDoneButton(isEnabled: !isTextEmpty) + } + .store(in: &self.cancellables) + } + // MARK: - Helpers override func dismissBottomSheet() { @@ -85,7 +103,6 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { super.configureStyles() self.updateTitle(Typo.title) - self.updateSubtitle(Typo.subtitle) } override func configureLayout() { @@ -94,7 +111,8 @@ final class ConfirmTitleAlertViewController: MSAlertViewController { self.containerView.addSubview(self.textField) self.textField.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ - self.textField.centerYAnchor.constraint(equalTo: self.containerView.centerYAnchor), + self.textField.centerYAnchor.constraint(equalTo: self.containerView.centerYAnchor, + constant: -Metric.textFieldCenterOffset), self.textField.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, constant: Metric.horizontalInset), self.textField.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, diff --git a/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift index 81bb0b7..bbd0d8d 100644 --- a/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift +++ b/iOS/MSUIKit/Sources/MSUIKit/MSAlertViewController.swift @@ -82,7 +82,6 @@ open class MSAlertViewController: UIViewController { let label = UILabel() label.font = .msFont(.headerTitle) label.textColor = .msColor(.primaryTypo) - label.text = "Title" return label }() @@ -90,7 +89,6 @@ open class MSAlertViewController: UIViewController { let label = UILabel() label.font = .msFont(.caption) label.textColor = .msColor(.secondaryTypo) - label.text = "Subtitle" return label }() @@ -118,6 +116,7 @@ open class MSAlertViewController: UIViewController { let button = MSButton.primary() button.cornerStyle = .squared button.title = Typo.doneButtonTitle + button.isEnabled = false return button }() @@ -324,4 +323,8 @@ open class MSAlertViewController: UIViewController { self.subtitleLabel.text = subtitle } + public func updateDoneButton(isEnabled: Bool) { + self.doneButton.isEnabled = isEnabled + } + } From 093ca3b641cda920ff53ec70e92cf8de801f5a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Junnos=20=EF=A3=BF?= Date: Mon, 4 Dec 2023 23:40:16 +0900 Subject: [PATCH 39/39] =?UTF-8?q?:bug:=20=EB=B9=8C=EB=93=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SaveJourneyViewController.swift | 7 +- iOS/Features/SelectSong/Package.swift | 4 + .../MusicSpot.xcodeproj/project.pbxproj | 88 +++++++++---------- .../SaveJourneyCoordinator.swift | 3 +- 4 files changed, 56 insertions(+), 46 deletions(-) diff --git a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift index 7ab1a50..bfa6670 100644 --- a/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift +++ b/iOS/Features/SaveJourney/Sources/SaveJourney/Presentation/SaveJourneyViewController.swift @@ -168,7 +168,12 @@ private extension SaveJourneyViewController { private extension SaveJourneyViewController { func configureMusicPlayer() { - self.musicPlayer.setQueue(with: .songs()) + let songTitleFilter = MPMediaPropertyPredicate(value: "ETA", + forProperty: MPMediaItemPropertyTitle, + comparisonType: .contains) + let filterSet = Set([songTitleFilter]) + let query = MPMediaQuery(filterPredicates: filterSet) + self.musicPlayer.setQueue(with: query) } } diff --git a/iOS/Features/SelectSong/Package.swift b/iOS/Features/SelectSong/Package.swift index c6768c9..b45073f 100644 --- a/iOS/Features/SelectSong/Package.swift +++ b/iOS/Features/SelectSong/Package.swift @@ -46,6 +46,10 @@ let package = Package( .library(name: Target.selectSong, targets: [Target.selectSong]) ], + dependencies: [ + .package(name: Dependency.msUIKit, + path: Dependency.msUIKit.fromRootPath) + ], targets: [ .target(name:Target.selectSong, dependencies: [ diff --git a/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj b/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj index 54a09fe..5539f65 100644 --- a/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj +++ b/iOS/MusicSpot/MusicSpot.xcodeproj/project.pbxproj @@ -15,14 +15,14 @@ 08CBF87D2B18468E007D3797 /* SpotCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8752B18468E007D3797 /* SpotCoordinator.swift */; }; 08CBF87E2B18468E007D3797 /* SearchMusicCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8762B18468E007D3797 /* SearchMusicCoordinator.swift */; }; 08CBF87F2B18468E007D3797 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBF8772B18468E007D3797 /* Coordinator.swift */; }; - DD197AF82B1CD514001D4290 /* Home in Frameworks */ = {isa = PBXBuildFile; productRef = DD197AF72B1CD514001D4290 /* Home */; }; - DD197AFA2B1CD514001D4290 /* NavigateMap in Frameworks */ = {isa = PBXBuildFile; productRef = DD197AF92B1CD514001D4290 /* NavigateMap */; }; - DD197AFC2B1CD514001D4290 /* RecordJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DD197AFB2B1CD514001D4290 /* RecordJourney */; }; - DD197AFF2B1CD51B001D4290 /* JourneyList in Frameworks */ = {isa = PBXBuildFile; productRef = DD197AFE2B1CD51B001D4290 /* JourneyList */; }; - DD197B022B1CD521001D4290 /* RewindJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B012B1CD521001D4290 /* RewindJourney */; }; - DD197B052B1CD528001D4290 /* SaveJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B042B1CD528001D4290 /* SaveJourney */; }; - DD197B082B1CD52E001D4290 /* SelectSong in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B072B1CD52E001D4290 /* SelectSong */; }; - DD197B0B2B1CD534001D4290 /* Spot in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B0A2B1CD534001D4290 /* Spot */; }; + DD197B162B1E1BB1001D4290 /* Home in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B152B1E1BB1001D4290 /* Home */; }; + DD197B182B1E1BB1001D4290 /* NavigateMap in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B172B1E1BB1001D4290 /* NavigateMap */; }; + DD197B1A2B1E1BB1001D4290 /* RecordJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B192B1E1BB1001D4290 /* RecordJourney */; }; + DD197B1D2B1E1BB9001D4290 /* JourneyList in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B1C2B1E1BB9001D4290 /* JourneyList */; }; + DD197B202B1E1BC0001D4290 /* RewindJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B1F2B1E1BC0001D4290 /* RewindJourney */; }; + DD197B232B1E1BCC001D4290 /* SaveJourney in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B222B1E1BCC001D4290 /* SaveJourney */; }; + DD197B262B1E1BD6001D4290 /* SelectSong in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B252B1E1BD6001D4290 /* SelectSong */; }; + DD197B292B1E1BEA001D4290 /* Spot in Frameworks */ = {isa = PBXBuildFile; productRef = DD197B282B1E1BEA001D4290 /* Spot */; }; 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 */; }; @@ -52,14 +52,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DD197AFC2B1CD514001D4290 /* RecordJourney in Frameworks */, - DD197B082B1CD52E001D4290 /* SelectSong in Frameworks */, - DD197B0B2B1CD534001D4290 /* Spot in Frameworks */, - DD197AFA2B1CD514001D4290 /* NavigateMap in Frameworks */, - DD197AF82B1CD514001D4290 /* Home in Frameworks */, - DD197B022B1CD521001D4290 /* RewindJourney in Frameworks */, - DD197B052B1CD528001D4290 /* SaveJourney in Frameworks */, - DD197AFF2B1CD51B001D4290 /* JourneyList in Frameworks */, + DD197B1A2B1E1BB1001D4290 /* RecordJourney in Frameworks */, + DD197B262B1E1BD6001D4290 /* SelectSong in Frameworks */, + DD197B292B1E1BEA001D4290 /* Spot in Frameworks */, + DD197B182B1E1BB1001D4290 /* NavigateMap in Frameworks */, + DD197B162B1E1BB1001D4290 /* Home in Frameworks */, + DD197B202B1E1BC0001D4290 /* RewindJourney in Frameworks */, + DD197B232B1E1BCC001D4290 /* SaveJourney in Frameworks */, + DD197B1D2B1E1BB9001D4290 /* JourneyList in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -143,14 +143,14 @@ ); name = MusicSpot; packageProductDependencies = ( - DD197AF72B1CD514001D4290 /* Home */, - DD197AF92B1CD514001D4290 /* NavigateMap */, - DD197AFB2B1CD514001D4290 /* RecordJourney */, - DD197AFE2B1CD51B001D4290 /* JourneyList */, - DD197B012B1CD521001D4290 /* RewindJourney */, - DD197B042B1CD528001D4290 /* SaveJourney */, - DD197B072B1CD52E001D4290 /* SelectSong */, - DD197B0A2B1CD534001D4290 /* Spot */, + DD197B152B1E1BB1001D4290 /* Home */, + DD197B172B1E1BB1001D4290 /* NavigateMap */, + DD197B192B1E1BB1001D4290 /* RecordJourney */, + DD197B1C2B1E1BB9001D4290 /* JourneyList */, + DD197B1F2B1E1BC0001D4290 /* RewindJourney */, + DD197B222B1E1BCC001D4290 /* SaveJourney */, + DD197B252B1E1BD6001D4290 /* SelectSong */, + DD197B282B1E1BEA001D4290 /* Spot */, ); productName = MusicSpot; productReference = DD73F8552B024C4900EE9BF2 /* MusicSpot.app */; @@ -181,12 +181,12 @@ ); mainGroup = DD73F84C2B024C4900EE9BF2; packageReferences = ( - DD197AF62B1CD514001D4290 /* XCLocalSwiftPackageReference "../Features/Home" */, - DD197AFD2B1CD51B001D4290 /* XCLocalSwiftPackageReference "../Features/JourneyList" */, - DD197B002B1CD521001D4290 /* XCLocalSwiftPackageReference "../Features/RewindJourney" */, - DD197B032B1CD528001D4290 /* XCLocalSwiftPackageReference "../Features/SaveJourney" */, - DD197B062B1CD52E001D4290 /* XCLocalSwiftPackageReference "../Features/SelectSong" */, - DD197B092B1CD534001D4290 /* XCLocalSwiftPackageReference "../Features/Spot" */, + DD197B142B1E1BB1001D4290 /* XCLocalSwiftPackageReference "../Features/Home" */, + DD197B1B2B1E1BB9001D4290 /* XCLocalSwiftPackageReference "../Features/JourneyList" */, + DD197B1E2B1E1BC0001D4290 /* XCLocalSwiftPackageReference "../Features/RewindJourney" */, + DD197B212B1E1BCC001D4290 /* XCLocalSwiftPackageReference "../Features/SaveJourney" */, + DD197B242B1E1BD6001D4290 /* XCLocalSwiftPackageReference "../Features/SelectSong" */, + DD197B272B1E1BEA001D4290 /* XCLocalSwiftPackageReference "../Features/Spot" */, ); productRefGroup = DD73F8562B024C4900EE9BF2 /* Products */; projectDirPath = ""; @@ -459,62 +459,62 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - DD197AF62B1CD514001D4290 /* XCLocalSwiftPackageReference "../Features/Home" */ = { + DD197B142B1E1BB1001D4290 /* XCLocalSwiftPackageReference "../Features/Home" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/Home; }; - DD197AFD2B1CD51B001D4290 /* XCLocalSwiftPackageReference "../Features/JourneyList" */ = { + DD197B1B2B1E1BB9001D4290 /* XCLocalSwiftPackageReference "../Features/JourneyList" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/JourneyList; }; - DD197B002B1CD521001D4290 /* XCLocalSwiftPackageReference "../Features/RewindJourney" */ = { + DD197B1E2B1E1BC0001D4290 /* XCLocalSwiftPackageReference "../Features/RewindJourney" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/RewindJourney; }; - DD197B032B1CD528001D4290 /* XCLocalSwiftPackageReference "../Features/SaveJourney" */ = { + DD197B212B1E1BCC001D4290 /* XCLocalSwiftPackageReference "../Features/SaveJourney" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/SaveJourney; }; - DD197B062B1CD52E001D4290 /* XCLocalSwiftPackageReference "../Features/SelectSong" */ = { + DD197B242B1E1BD6001D4290 /* XCLocalSwiftPackageReference "../Features/SelectSong" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/SelectSong; }; - DD197B092B1CD534001D4290 /* XCLocalSwiftPackageReference "../Features/Spot" */ = { + DD197B272B1E1BEA001D4290 /* XCLocalSwiftPackageReference "../Features/Spot" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../Features/Spot; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - DD197AF72B1CD514001D4290 /* Home */ = { + DD197B152B1E1BB1001D4290 /* Home */ = { isa = XCSwiftPackageProductDependency; productName = Home; }; - DD197AF92B1CD514001D4290 /* NavigateMap */ = { + DD197B172B1E1BB1001D4290 /* NavigateMap */ = { isa = XCSwiftPackageProductDependency; productName = NavigateMap; }; - DD197AFB2B1CD514001D4290 /* RecordJourney */ = { + DD197B192B1E1BB1001D4290 /* RecordJourney */ = { isa = XCSwiftPackageProductDependency; productName = RecordJourney; }; - DD197AFE2B1CD51B001D4290 /* JourneyList */ = { + DD197B1C2B1E1BB9001D4290 /* JourneyList */ = { isa = XCSwiftPackageProductDependency; productName = JourneyList; }; - DD197B012B1CD521001D4290 /* RewindJourney */ = { + DD197B1F2B1E1BC0001D4290 /* RewindJourney */ = { isa = XCSwiftPackageProductDependency; productName = RewindJourney; }; - DD197B042B1CD528001D4290 /* SaveJourney */ = { + DD197B222B1E1BCC001D4290 /* SaveJourney */ = { isa = XCSwiftPackageProductDependency; productName = SaveJourney; }; - DD197B072B1CD52E001D4290 /* SelectSong */ = { + DD197B252B1E1BD6001D4290 /* SelectSong */ = { isa = XCSwiftPackageProductDependency; productName = SelectSong; }; - DD197B0A2B1CD534001D4290 /* Spot */ = { + DD197B282B1E1BEA001D4290 /* Spot */ = { isa = XCSwiftPackageProductDependency; productName = Spot; }; diff --git a/iOS/MusicSpot/MusicSpot/MSCoordinator/SaveJourneyCoordinator.swift b/iOS/MusicSpot/MusicSpot/MSCoordinator/SaveJourneyCoordinator.swift index 56ab4c0..ef5f9df 100644 --- a/iOS/MusicSpot/MusicSpot/MSCoordinator/SaveJourneyCoordinator.swift +++ b/iOS/MusicSpot/MusicSpot/MSCoordinator/SaveJourneyCoordinator.swift @@ -29,8 +29,9 @@ final class SaveJourneyCoordinator: Coordinator { // MARK: - Functions func start() { + let song = Song(title: "OMG", artist: "New Jeans", albumArtURL: nil) let spotRepository = SpotRepositoryImplementation() - let saveJourneyViewModel = SaveJourneyViewModel(spotRepository: spotRepository) + let saveJourneyViewModel = SaveJourneyViewModel(selectedSong: song, spotRepository: spotRepository) let saveJourneyViewController = SaveJourneyViewController(viewModel: saveJourneyViewModel) // saveJourneyViewController.delegate = self self.navigationController.pushViewController(saveJourneyViewController, animated: true)