diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e7152a4..473cd61 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,5 @@ -issue number : [#02](../issues/02) +issue number : [#](../issues/) +close # ### 작업 사항 @@ -6,14 +7,5 @@ issue number : [#02](../issues/02) ### 참고 자료 -[참고자료1]() -[참고자료2]() +[참고자료1]() -### 체크리스트 - -- [ ] Merge 하는 브랜치가 올바른가? -- [ ] 코딩컨벤션을 준수하는가? -- [ ] PR과 관련없는 변경사항이 없는가? -- [ ] 내 코드에 대한 자기 검토가 되었는가? -- [ ] 변경사항이 효과적이거나 동작이 작동한다는 것을 보증하는 테스트를 추가하였는가? -- [ ] 새로운 테스트와 기존의 테스트가 변경사항에 대해 만족하는가? diff --git a/.gitignore b/.gitignore index a75bbb0..7a9f225 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ Pods/ .DS_Store .AppleDouble .LSOverride - +GoogleService-Info.plist # Icon must end with two \r Icon diff --git a/Escaper/Escaper.xcodeproj/project.pbxproj b/Escaper/Escaper.xcodeproj/project.pbxproj index 8522c96..94da877 100644 --- a/Escaper/Escaper.xcodeproj/project.pbxproj +++ b/Escaper/Escaper.xcodeproj/project.pbxproj @@ -8,46 +8,88 @@ /* Begin PBXBuildFile section */ 0C7F483927337D1C007FE109 /* District.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7F483827337D1C007FE109 /* District.swift */; }; + 0CAEE3A9273A3769002C1136 /* RoomDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CAEE3A8273A3769002C1136 /* RoomDetailViewController.swift */; }; + 0CAEE3AC273A61B3002C1136 /* RoomDetailInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CAEE3AB273A61B2002C1136 /* RoomDetailInfoView.swift */; }; + 0CAEE3B1273BA7C3002C1136 /* RoomDetailUserRankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CAEE3B0273BA7C3002C1136 /* RoomDetailUserRankView.swift */; }; 1F061DA8272FF896008FC519 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1F061DA7272FF896008FC519 /* .swiftlint.yml */; }; 1F06844F27315D6300E2C789 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1F06844E27315D6300E2C789 /* GoogleService-Info.plist */; }; 1F0CAED32733E00300389CFA /* RoomDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0CAED22733E00300389CFA /* RoomDTO.swift */; }; 1F0CAED52733E03600389CFA /* FirebaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0CAED42733E03600389CFA /* FirebaseService.swift */; }; 1F0CAED72733E04F00389CFA /* RoomListRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0CAED62733E04F00389CFA /* RoomListRepository.swift */; }; + 1F22E652273D14D700409A96 /* TimePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F22E651273D14D700409A96 /* TimePickerViewController.swift */; }; + 1F22E654273D619300409A96 /* AddRecordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F22E653273D619300409A96 /* AddRecordViewModel.swift */; }; 1F72C20E2734010700C59CC1 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F72C20D2734010700C59CC1 /* Observable.swift */; }; + 1F74F10E273CEEC700CE4D37 /* AddRecordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F74F10D273CEEC700CE4D37 /* AddRecordViewController.swift */; }; + 1F74F113273CEEFE00CE4D37 /* RecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F74F112273CEEFE00CE4D37 /* RecordView.swift */; }; + 1F884BC32740D4E500DE67D8 /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 1F884BC22740D4E500DE67D8 /* FirebaseFirestore */; }; + 1F884BC52740D4FE00DE67D8 /* FirebaseFirestoreSwift-Beta in Frameworks */ = {isa = PBXBuildFile; productRef = 1F884BC42740D4FE00DE67D8 /* FirebaseFirestoreSwift-Beta */; }; + 1F884BC72740D51100DE67D8 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 1F884BC62740D51100DE67D8 /* FirebaseStorage */; }; 1FCDDE6C272EE0F00027AAAD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FCDDE6B272EE0F00027AAAD /* AppDelegate.swift */; }; 1FCDDE6E272EE0F00027AAAD /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FCDDE6D272EE0F00027AAAD /* SceneDelegate.swift */; }; 1FCDDE70272EE0F00027AAAD /* RoomListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FCDDE6F272EE0F00027AAAD /* RoomListViewController.swift */; }; 1FCDDE75272EE0F10027AAAD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1FCDDE74272EE0F10027AAAD /* Assets.xcassets */; }; 1FCDDE78272EE0F10027AAAD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1FCDDE76272EE0F10027AAAD /* LaunchScreen.storyboard */; }; - 1FE6C8AC2732621800AC7408 /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 1FE6C8AB2732621800AC7408 /* FirebaseFirestore */; }; - 1FE6C8AE2732621800AC7408 /* FirebaseFirestoreSwift-Beta in Frameworks */ = {isa = PBXBuildFile; productRef = 1FE6C8AD2732621800AC7408 /* FirebaseFirestoreSwift-Beta */; }; + 1FE53402273F8ABE00867832 /* FindRoomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE53400273F8ABE00867832 /* FindRoomViewController.swift */; }; + 1FE53403273F8ABE00867832 /* FindRoomTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE53401273F8ABE00867832 /* FindRoomTableViewCell.swift */; }; + 1FE53406273FB0D300867832 /* UIView+RoundCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE53405273FB0D200867832 /* UIView+RoundCorners.swift */; }; + 1FE53408273FB14A00867832 /* UISearchBar+SetTextFieldColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE53407273FB14A00867832 /* UISearchBar+SetTextFieldColor.swift */; }; + 1FE5340A273FB25D00867832 /* FindRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE53409273FB25D00867832 /* FindRoomViewModel.swift */; }; 1FE6C8B0273273BD00AC7408 /* UserRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6C8AF273273BD00AC7408 /* UserRecord.swift */; }; 1FE6C8B22732741E00AC7408 /* GeoLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6C8B12732741E00AC7408 /* GeoLocation.swift */; }; + 25020598273A3300004B024E /* RecordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25020597273A3300004B024E /* RecordViewController.swift */; }; + 2502059A273A33A5004B024E /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25020599273A33A5004B024E /* MapViewController.swift */; }; + 2502059C273A33CB004B024E /* LeaderBoardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2502059B273A33CB004B024E /* LeaderBoardViewController.swift */; }; + 25020600273CE1D8004B024E /* RecordInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 250205FE273CE1D8004B024E /* RecordInfo.swift */; }; + 25020601273CE1D8004B024E /* Record.swift in Sources */ = {isa = PBXBuildFile; fileRef = 250205FF273CE1D8004B024E /* Record.swift */; }; + 25020603273CE1E1004B024E /* RecordUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25020602273CE1E1004B024E /* RecordUsecase.swift */; }; + 25020605273CE1FC004B024E /* RecordRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25020604273CE1FC004B024E /* RecordRepository.swift */; }; + 25020609273CE349004B024E /* RecordInfoDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25020608273CE349004B024E /* RecordInfoDTO.swift */; }; 251031BC27339C0200BD07BF /* RatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251031BB27339C0200BD07BF /* RatingView.swift */; }; 251031C02733A2AC00BD07BF /* Rating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251031BF2733A2AC00BD07BF /* Rating.swift */; }; 251031C22733A5B500BD07BF /* RatingImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251031C12733A5B500BD07BF /* RatingImage.swift */; }; 251031C42733AAF500BD07BF /* RoomOverviewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251031C32733AAF500BD07BF /* RoomOverviewTableViewCell.swift */; }; 251031C62733BCAC00BD07BF /* RatingContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251031C52733BCAC00BD07BF /* RatingContainerView.swift */; }; - 2510A1472732200700D3E513 /* ColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2510A1462732200700D3E513 /* ColorPalette.swift */; }; + 2513CACF2735192F00A574C7 /* EDSKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2513CACE2735192F00A574C7 /* EDSKit.swift */; }; 25513368273107A000A92CF3 /* Genre.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25513367273107A000A92CF3 /* Genre.swift */; }; 2551336A273108A800A92CF3 /* RoomListUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25513369273108A800A92CF3 /* RoomListUseCase.swift */; }; 2551336C2731093B00A92CF3 /* RoomListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2551336B2731093B00A92CF3 /* RoomListViewModel.swift */; }; 2551336E27310D5500A92CF3 /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2551336D27310D5500A92CF3 /* Room.swift */; }; 2551339027315B1900A92CF3 /* SortingOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2551338F27315B1900A92CF3 /* SortingOption.swift */; }; + 25772763273CE87F00881845 /* RoomListRepositoryInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25772761273CE87F00881845 /* RoomListRepositoryInterface.swift */; }; + 25772764273CE87F00881845 /* RecordRepositoryInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25772762273CE87F00881845 /* RecordRepositoryInterface.swift */; }; + 25772768273D0D0A00881845 /* ImageCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25772767273D0D0A00881845 /* ImageCacheManager.swift */; }; + 2577276D273D18EB00881845 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2577276C273D18EB00881845 /* NetworkService.swift */; }; + 2577276E273D4EBB00881845 /* DistrictSelectButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2502059D273A6405004B024E /* DistrictSelectButton.swift */; }; + 25772770273D525500881845 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2577276F273D525500881845 /* Helper.swift */; }; + 25F92C5F273A2AC3005E4A21 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F92C5E273A2AC3005E4A21 /* MainTabBarController.swift */; }; 3E2BDEA12732C2FB0081A749 /* Tagable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E2BDEA02732C2FB0081A749 /* Tagable.swift */; }; 3E4378C927325241001ED120 /* TagScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E4378C827325241001ED120 /* TagScrollView.swift */; }; 3E4378CB27329028001ED120 /* TagButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E4378CA27329028001ED120 /* TagButton.swift */; }; 3E4378CD2732914B001ED120 /* DefaultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E4378CC2732914B001ED120 /* DefaultViewController.swift */; }; + 3E5902A6273AA3190045ADDE /* RecordCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5902A5273AA3190045ADDE /* RecordCollectionViewCell.swift */; }; + 3E5902A8273B92F50045ADDE /* RecordUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5902A7273B92F50045ADDE /* RecordUserView.swift */; }; + 3E5902AA273B9C850045ADDE /* RecordStarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5902A9273B9C850045ADDE /* RecordStarView.swift */; }; + 3E5902AC273BA1DC0045ADDE /* RecordResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5902AB273BA1DC0045ADDE /* RecordResultView.swift */; }; + 3E5902AE273BF0630045ADDE /* RecordHeadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5902AD273BF0630045ADDE /* RecordHeadView.swift */; }; + 3EBBD12B273A5621005B65F1 /* UtilityImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EBBD12A273A5621005B65F1 /* UtilityImage.swift */; }; + 3EED222E273CDA150084D290 /* RecordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EED222D273CDA150084D290 /* RecordViewModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0C7F483827337D1C007FE109 /* District.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = District.swift; sourceTree = ""; }; + 0CAEE3A8273A3769002C1136 /* RoomDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailViewController.swift; sourceTree = ""; }; + 0CAEE3AB273A61B2002C1136 /* RoomDetailInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailInfoView.swift; sourceTree = ""; }; + 0CAEE3B0273BA7C3002C1136 /* RoomDetailUserRankView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailUserRankView.swift; sourceTree = ""; }; 1F061DA7272FF896008FC519 /* .swiftlint.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; 1F06844E27315D6300E2C789 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 1F0CAED22733E00300389CFA /* RoomDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDTO.swift; sourceTree = ""; }; 1F0CAED42733E03600389CFA /* FirebaseService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseService.swift; sourceTree = ""; }; 1F0CAED62733E04F00389CFA /* RoomListRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListRepository.swift; sourceTree = ""; }; + 1F22E651273D14D700409A96 /* TimePickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePickerViewController.swift; sourceTree = ""; }; + 1F22E653273D619300409A96 /* AddRecordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecordViewModel.swift; sourceTree = ""; }; 1F72C20D2734010700C59CC1 /* Observable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; + 1F74F10D273CEEC700CE4D37 /* AddRecordViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecordViewController.swift; sourceTree = ""; }; + 1F74F112273CEEFE00CE4D37 /* RecordView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordView.swift; sourceTree = ""; }; 1FCDDE68272EE0F00027AAAD /* Escaper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Escaper.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1FCDDE6B272EE0F00027AAAD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 1FCDDE6D272EE0F00027AAAD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -55,23 +97,51 @@ 1FCDDE74272EE0F10027AAAD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 1FCDDE77272EE0F10027AAAD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 1FCDDE79272EE0F10027AAAD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1FE53400273F8ABE00867832 /* FindRoomViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindRoomViewController.swift; sourceTree = ""; }; + 1FE53401273F8ABE00867832 /* FindRoomTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindRoomTableViewCell.swift; sourceTree = ""; }; + 1FE53405273FB0D200867832 /* UIView+RoundCorners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+RoundCorners.swift"; sourceTree = ""; }; + 1FE53407273FB14A00867832 /* UISearchBar+SetTextFieldColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchBar+SetTextFieldColor.swift"; sourceTree = ""; }; + 1FE53409273FB25D00867832 /* FindRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindRoomViewModel.swift; sourceTree = ""; }; 1FE6C8AF273273BD00AC7408 /* UserRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRecord.swift; sourceTree = ""; }; 1FE6C8B12732741E00AC7408 /* GeoLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoLocation.swift; sourceTree = ""; }; + 25020597273A3300004B024E /* RecordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordViewController.swift; sourceTree = ""; }; + 25020599273A33A5004B024E /* MapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = ""; }; + 2502059B273A33CB004B024E /* LeaderBoardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderBoardViewController.swift; sourceTree = ""; }; + 2502059D273A6405004B024E /* DistrictSelectButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistrictSelectButton.swift; sourceTree = ""; }; + 250205FE273CE1D8004B024E /* RecordInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordInfo.swift; sourceTree = ""; }; + 250205FF273CE1D8004B024E /* Record.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Record.swift; sourceTree = ""; }; + 25020602273CE1E1004B024E /* RecordUsecase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordUsecase.swift; sourceTree = ""; }; + 25020604273CE1FC004B024E /* RecordRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordRepository.swift; sourceTree = ""; }; + 25020608273CE349004B024E /* RecordInfoDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordInfoDTO.swift; sourceTree = ""; }; 251031BB27339C0200BD07BF /* RatingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingView.swift; sourceTree = ""; }; 251031BF2733A2AC00BD07BF /* Rating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rating.swift; sourceTree = ""; }; 251031C12733A5B500BD07BF /* RatingImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingImage.swift; sourceTree = ""; }; 251031C32733AAF500BD07BF /* RoomOverviewTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomOverviewTableViewCell.swift; sourceTree = ""; }; 251031C52733BCAC00BD07BF /* RatingContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingContainerView.swift; sourceTree = ""; }; - 2510A1462732200700D3E513 /* ColorPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPalette.swift; sourceTree = ""; }; + 2513CACE2735192F00A574C7 /* EDSKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EDSKit.swift; sourceTree = ""; }; 25513367273107A000A92CF3 /* Genre.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Genre.swift; sourceTree = ""; }; 25513369273108A800A92CF3 /* RoomListUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListUseCase.swift; sourceTree = ""; }; 2551336B2731093B00A92CF3 /* RoomListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListViewModel.swift; sourceTree = ""; }; 2551336D27310D5500A92CF3 /* Room.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = ""; }; 2551338F27315B1900A92CF3 /* SortingOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortingOption.swift; sourceTree = ""; }; + 25772761273CE87F00881845 /* RoomListRepositoryInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomListRepositoryInterface.swift; sourceTree = ""; }; + 25772762273CE87F00881845 /* RecordRepositoryInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordRepositoryInterface.swift; sourceTree = ""; }; + 25772765273CFBD100881845 /* DistrictSelectButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DistrictSelectButton.swift; sourceTree = ""; }; + 25772767273D0D0A00881845 /* ImageCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCacheManager.swift; sourceTree = ""; }; + 2577276C273D18EB00881845 /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = ""; }; + 2577276F273D525500881845 /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = ""; }; + 25F92C5E273A2AC3005E4A21 /* MainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; 3E2BDEA02732C2FB0081A749 /* Tagable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tagable.swift; sourceTree = ""; }; 3E4378C827325241001ED120 /* TagScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagScrollView.swift; sourceTree = ""; }; 3E4378CA27329028001ED120 /* TagButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagButton.swift; sourceTree = ""; }; 3E4378CC2732914B001ED120 /* DefaultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultViewController.swift; sourceTree = ""; }; + 3E5902A5273AA3190045ADDE /* RecordCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordCollectionViewCell.swift; sourceTree = ""; }; + 3E5902A7273B92F50045ADDE /* RecordUserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordUserView.swift; sourceTree = ""; }; + 3E5902A9273B9C850045ADDE /* RecordStarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordStarView.swift; sourceTree = ""; }; + 3E5902AB273BA1DC0045ADDE /* RecordResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordResultView.swift; sourceTree = ""; }; + 3E5902AD273BF0630045ADDE /* RecordHeadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordHeadView.swift; sourceTree = ""; }; + 3EBBD12A273A5621005B65F1 /* UtilityImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityImage.swift; sourceTree = ""; }; + 3EED222D273CDA150084D290 /* RecordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordViewModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -79,28 +149,48 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 1FE6C8AC2732621800AC7408 /* FirebaseFirestore in Frameworks */, - 1FE6C8AE2732621800AC7408 /* FirebaseFirestoreSwift-Beta in Frameworks */, + 1F884BC52740D4FE00DE67D8 /* FirebaseFirestoreSwift-Beta in Frameworks */, + 1F884BC32740D4E500DE67D8 /* FirebaseFirestore in Frameworks */, + 1F884BC72740D51100DE67D8 /* FirebaseStorage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0CAEE3AA273A618A002C1136 /* Views */ = { + isa = PBXGroup; + children = ( + 0CAEE3AB273A61B2002C1136 /* RoomDetailInfoView.swift */, + 0CAEE3B0273BA7C3002C1136 /* RoomDetailUserRankView.swift */, + ); + path = Views; + sourceTree = ""; + }; 1F0CAED12733DFF300389CFA /* DataMapping */ = { isa = PBXGroup; children = ( + 25020608273CE349004B024E /* RecordInfoDTO.swift */, 1F0CAED22733E00300389CFA /* RoomDTO.swift */, ); path = DataMapping; sourceTree = ""; }; + 1F74F111273CEEFE00CE4D37 /* View */ = { + isa = PBXGroup; + children = ( + 1F74F112273CEEFE00CE4D37 /* RecordView.swift */, + ); + path = View; + sourceTree = ""; + }; 1FCDDE5F272EE0F00027AAAD = { isa = PBXGroup; children = ( 1F061DA7272FF896008FC519 /* .swiftlint.yml */, 1FCDDE6A272EE0F00027AAAD /* Escaper */, 1FCDDE69272EE0F00027AAAD /* Products */, + 25772769273D124D00881845 /* Frameworks */, ); sourceTree = ""; }; @@ -127,11 +217,40 @@ path = Escaper; sourceTree = ""; }; + 1FE533FF273F8ABE00867832 /* FindRoom */ = { + isa = PBXGroup; + children = ( + 1FE53400273F8ABE00867832 /* FindRoomViewController.swift */, + 1FE53409273FB25D00867832 /* FindRoomViewModel.swift */, + 1FE5342B273FB9D500867832 /* Cell */, + ); + path = FindRoom; + sourceTree = ""; + }; + 1FE53404273F98A800867832 /* TimePicker */ = { + isa = PBXGroup; + children = ( + 1F22E651273D14D700409A96 /* TimePickerViewController.swift */, + ); + path = TimePicker; + sourceTree = ""; + }; + 1FE5342B273FB9D500867832 /* Cell */ = { + isa = PBXGroup; + children = ( + 1FE53401273F8ABE00867832 /* FindRoomTableViewCell.swift */, + ); + name = Cell; + sourceTree = ""; + }; 251031BA2733950900BD07BF /* Common */ = { isa = PBXGroup; children = ( + 25772765273CFBD100881845 /* DistrictSelectButton.swift */, + 25F92C5E273A2AC3005E4A21 /* MainTabBarController.swift */, 3E4378CC2732914B001ED120 /* DefaultViewController.swift */, 251031BB27339C0200BD07BF /* RatingView.swift */, + 2502059D273A6405004B024E /* DistrictSelectButton.swift */, ); path = Common; sourceTree = ""; @@ -153,9 +272,11 @@ 2551335B2730ED5500A92CF3 /* Home */, 2551335C2730EDA900A92CF3 /* RoomList */, 2551335D2730EEDB00A92CF3 /* RoomDetail */, - 2551335E2730EEE200A92CF3 /* Search */, + 2551335E2730EEE200A92CF3 /* Map */, 255133602730EF0300A92CF3 /* Record */, 255133612730EF4500A92CF3 /* AddRecord */, + 1FE53404273F98A800867832 /* TimePicker */, + 1FE533FF273F8ABE00867832 /* FindRoom */, 2551335F2730EEEF00A92CF3 /* LeaderBoard */, ); path = Presentation; @@ -164,6 +285,7 @@ 255133552730E8F300A92CF3 /* Domain */ = { isa = PBXGroup; children = ( + 2577275F273CE87F00881845 /* Interface */, 3E2BDE9F2732C2E60081A749 /* Protocol */, 255133662731072000A92CF3 /* DataStruct */, 255133652731070F00A92CF3 /* UseCase */, @@ -174,9 +296,12 @@ 255133562730E8FD00A92CF3 /* Infrastructure */ = { isa = PBXGroup; children = ( - 1F0CAED12733DFF300389CFA /* DataMapping */, 1F0CAED42733E03600389CFA /* FirebaseService.swift */, + 25020604273CE1FC004B024E /* RecordRepository.swift */, 1F0CAED62733E04F00389CFA /* RoomListRepository.swift */, + 25772767273D0D0A00881845 /* ImageCacheManager.swift */, + 2577276C273D18EB00881845 /* NetworkService.swift */, + 1F0CAED12733DFF300389CFA /* DataMapping */, ); path = Infrastructure; sourceTree = ""; @@ -193,6 +318,8 @@ 255133582730EC3300A92CF3 /* Extensions */ = { isa = PBXGroup; children = ( + 1FE53405273FB0D200867832 /* UIView+RoundCorners.swift */, + 1FE53407273FB14A00867832 /* UISearchBar+SetTextFieldColor.swift */, ); path = Extensions; sourceTree = ""; @@ -201,8 +328,10 @@ isa = PBXGroup; children = ( 1F72C20D2734010700C59CC1 /* Observable.swift */, - 2510A1462732200700D3E513 /* ColorPalette.swift */, 251031C12733A5B500BD07BF /* RatingImage.swift */, + 2513CACE2735192F00A574C7 /* EDSKit.swift */, + 3EBBD12A273A5621005B65F1 /* UtilityImage.swift */, + 2577276F273D525500881845 /* Helper.swift */, ); path = Library; sourceTree = ""; @@ -235,20 +364,24 @@ 2551335D2730EEDB00A92CF3 /* RoomDetail */ = { isa = PBXGroup; children = ( + 0CAEE3A8273A3769002C1136 /* RoomDetailViewController.swift */, + 0CAEE3AA273A618A002C1136 /* Views */, ); path = RoomDetail; sourceTree = ""; }; - 2551335E2730EEE200A92CF3 /* Search */ = { + 2551335E2730EEE200A92CF3 /* Map */ = { isa = PBXGroup; children = ( + 25020599273A33A5004B024E /* MapViewController.swift */, ); - path = Search; + path = Map; sourceTree = ""; }; 2551335F2730EEEF00A92CF3 /* LeaderBoard */ = { isa = PBXGroup; children = ( + 2502059B273A33CB004B024E /* LeaderBoardViewController.swift */, ); path = LeaderBoard; sourceTree = ""; @@ -256,6 +389,9 @@ 255133602730EF0300A92CF3 /* Record */ = { isa = PBXGroup; children = ( + 25020597273A3300004B024E /* RecordViewController.swift */, + 3EED222D273CDA150084D290 /* RecordViewModel.swift */, + 3E5902A1273AA1BB0045ADDE /* Views */, ); path = Record; sourceTree = ""; @@ -263,6 +399,9 @@ 255133612730EF4500A92CF3 /* AddRecord */ = { isa = PBXGroup; children = ( + 1F74F10D273CEEC700CE4D37 /* AddRecordViewController.swift */, + 1F22E653273D619300409A96 /* AddRecordViewModel.swift */, + 1F74F111273CEEFE00CE4D37 /* View */, ); path = AddRecord; sourceTree = ""; @@ -273,23 +412,24 @@ 3E4378C827325241001ED120 /* TagScrollView.swift */, 3E4378CA27329028001ED120 /* TagButton.swift */, 251031C52733BCAC00BD07BF /* RatingContainerView.swift */, - 255133642730F07000A92CF3 /* Cells */, + 255133642730F07000A92CF3 /* Cell */, ); path = Views; sourceTree = ""; }; - 255133642730F07000A92CF3 /* Cells */ = { + 255133642730F07000A92CF3 /* Cell */ = { isa = PBXGroup; children = ( 251031C32733AAF500BD07BF /* RoomOverviewTableViewCell.swift */, ); - path = Cells; + path = Cell; sourceTree = ""; }; 255133652731070F00A92CF3 /* UseCase */ = { isa = PBXGroup; children = ( 25513369273108A800A92CF3 /* RoomListUseCase.swift */, + 25020602273CE1E1004B024E /* RecordUsecase.swift */, ); path = UseCase; sourceTree = ""; @@ -297,6 +437,8 @@ 255133662731072000A92CF3 /* DataStruct */ = { isa = PBXGroup; children = ( + 250205FF273CE1D8004B024E /* Record.swift */, + 250205FE273CE1D8004B024E /* RecordInfo.swift */, 25513367273107A000A92CF3 /* Genre.swift */, 2551336D27310D5500A92CF3 /* Room.swift */, 1FE6C8B12732741E00AC7408 /* GeoLocation.swift */, @@ -308,6 +450,30 @@ path = DataStruct; sourceTree = ""; }; + 2577275F273CE87F00881845 /* Interface */ = { + isa = PBXGroup; + children = ( + 25772760273CE87F00881845 /* Repositories */, + ); + path = Interface; + sourceTree = ""; + }; + 25772760273CE87F00881845 /* Repositories */ = { + isa = PBXGroup; + children = ( + 25772761273CE87F00881845 /* RoomListRepositoryInterface.swift */, + 25772762273CE87F00881845 /* RecordRepositoryInterface.swift */, + ); + path = Repositories; + sourceTree = ""; + }; + 25772769273D124D00881845 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; 3E2BDE9F2732C2E60081A749 /* Protocol */ = { isa = PBXGroup; children = ( @@ -316,6 +482,26 @@ path = Protocol; sourceTree = ""; }; + 3E5902A1273AA1BB0045ADDE /* Views */ = { + isa = PBXGroup; + children = ( + 3E5902A4273AA2450045ADDE /* Cells */, + ); + path = Views; + sourceTree = ""; + }; + 3E5902A4273AA2450045ADDE /* Cells */ = { + isa = PBXGroup; + children = ( + 3E5902A5273AA3190045ADDE /* RecordCollectionViewCell.swift */, + 3E5902AD273BF0630045ADDE /* RecordHeadView.swift */, + 3E5902A7273B92F50045ADDE /* RecordUserView.swift */, + 3E5902A9273B9C850045ADDE /* RecordStarView.swift */, + 3E5902AB273BA1DC0045ADDE /* RecordResultView.swift */, + ); + path = Cells; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -334,8 +520,9 @@ ); name = Escaper; packageProductDependencies = ( - 1FE6C8AB2732621800AC7408 /* FirebaseFirestore */, - 1FE6C8AD2732621800AC7408 /* FirebaseFirestoreSwift-Beta */, + 1F884BC22740D4E500DE67D8 /* FirebaseFirestore */, + 1F884BC42740D4FE00DE67D8 /* FirebaseFirestoreSwift-Beta */, + 1F884BC62740D51100DE67D8 /* FirebaseStorage */, ); productName = Escaper; productReference = 1FCDDE68272EE0F00027AAAD /* Escaper.app */; @@ -407,7 +594,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nexport arch_name=\"$(uname -m)\"\n\nif [ \"${arch_name}\" = \"x86_64\" ]; then\n if which swiftlint >/dev/null; then\n swiftlint --fix \n else \n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n fi\nelif [ \"${arch_name}\" = \"arm64\" ]; then\n if which swiftlint; then\n swiftlint --fix \n else\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n fi\nfi\n\n"; + shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nexport arch_name=\"$(uname -m)\"\n\nif [ \"${arch_name}\" = \"x86_64\" ]; then\n if which swiftlint >/dev/null; then\n swiftlint\n else \n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n fi\nelif [ \"${arch_name}\" = \"arm64\" ]; then\n if which swiftlint; then\n swiftlint\n else\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n fi\nfi\n\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -416,31 +603,65 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2577276E273D4EBB00881845 /* DistrictSelectButton.swift in Sources */, + 1FE53406273FB0D300867832 /* UIView+RoundCorners.swift in Sources */, + 25020609273CE349004B024E /* RecordInfoDTO.swift in Sources */, 251031C42733AAF500BD07BF /* RoomOverviewTableViewCell.swift in Sources */, + 25772764273CE87F00881845 /* RecordRepositoryInterface.swift in Sources */, + 3E5902AA273B9C850045ADDE /* RecordStarView.swift in Sources */, 3E4378C927325241001ED120 /* TagScrollView.swift in Sources */, + 3EED222E273CDA150084D290 /* RecordViewModel.swift in Sources */, 1F0CAED32733E00300389CFA /* RoomDTO.swift in Sources */, + 25020598273A3300004B024E /* RecordViewController.swift in Sources */, + 25772763273CE87F00881845 /* RoomListRepositoryInterface.swift in Sources */, 1FCDDE70272EE0F00027AAAD /* RoomListViewController.swift in Sources */, 2551336C2731093B00A92CF3 /* RoomListViewModel.swift in Sources */, - 2510A1472732200700D3E513 /* ColorPalette.swift in Sources */, + 0CAEE3AC273A61B3002C1136 /* RoomDetailInfoView.swift in Sources */, + 25772770273D525500881845 /* Helper.swift in Sources */, 1FE6C8B22732741E00AC7408 /* GeoLocation.swift in Sources */, 2551336E27310D5500A92CF3 /* Room.swift in Sources */, + 1FE53402273F8ABE00867832 /* FindRoomViewController.swift in Sources */, + 3E5902A8273B92F50045ADDE /* RecordUserView.swift in Sources */, 251031C62733BCAC00BD07BF /* RatingContainerView.swift in Sources */, 251031C02733A2AC00BD07BF /* Rating.swift in Sources */, 1F0CAED52733E03600389CFA /* FirebaseService.swift in Sources */, 1F72C20E2734010700C59CC1 /* Observable.swift in Sources */, 3E2BDEA12732C2FB0081A749 /* Tagable.swift in Sources */, + 2502059C273A33CB004B024E /* LeaderBoardViewController.swift in Sources */, 1F0CAED72733E04F00389CFA /* RoomListRepository.swift in Sources */, + 1F22E654273D619300409A96 /* AddRecordViewModel.swift in Sources */, 1FCDDE6C272EE0F00027AAAD /* AppDelegate.swift in Sources */, + 2577276D273D18EB00881845 /* NetworkService.swift in Sources */, + 25020601273CE1D8004B024E /* Record.swift in Sources */, 3E4378CD2732914B001ED120 /* DefaultViewController.swift in Sources */, + 0CAEE3B1273BA7C3002C1136 /* RoomDetailUserRankView.swift in Sources */, + 3E5902AE273BF0630045ADDE /* RecordHeadView.swift in Sources */, 2551336A273108A800A92CF3 /* RoomListUseCase.swift in Sources */, 3E4378CB27329028001ED120 /* TagButton.swift in Sources */, + 1FE53403273F8ABE00867832 /* FindRoomTableViewCell.swift in Sources */, + 2513CACF2735192F00A574C7 /* EDSKit.swift in Sources */, + 25020603273CE1E1004B024E /* RecordUsecase.swift in Sources */, 251031BC27339C0200BD07BF /* RatingView.swift in Sources */, + 25020605273CE1FC004B024E /* RecordRepository.swift in Sources */, + 25020600273CE1D8004B024E /* RecordInfo.swift in Sources */, 0C7F483927337D1C007FE109 /* District.swift in Sources */, + 25F92C5F273A2AC3005E4A21 /* MainTabBarController.swift in Sources */, + 3EBBD12B273A5621005B65F1 /* UtilityImage.swift in Sources */, + 1F22E652273D14D700409A96 /* TimePickerViewController.swift in Sources */, + 0CAEE3A9273A3769002C1136 /* RoomDetailViewController.swift in Sources */, + 3E5902AC273BA1DC0045ADDE /* RecordResultView.swift in Sources */, + 1F74F113273CEEFE00CE4D37 /* RecordView.swift in Sources */, 2551339027315B1900A92CF3 /* SortingOption.swift in Sources */, 1FCDDE6E272EE0F00027AAAD /* SceneDelegate.swift in Sources */, 1FE6C8B0273273BD00AC7408 /* UserRecord.swift in Sources */, 251031C22733A5B500BD07BF /* RatingImage.swift in Sources */, + 1FE5340A273FB25D00867832 /* FindRoomViewModel.swift in Sources */, + 25772768273D0D0A00881845 /* ImageCacheManager.swift in Sources */, 25513368273107A000A92CF3 /* Genre.swift in Sources */, + 2502059A273A33A5004B024E /* MapViewController.swift in Sources */, + 3E5902A6273AA3190045ADDE /* RecordCollectionViewCell.swift in Sources */, + 1FE53408273FB14A00867832 /* UISearchBar+SetTextFieldColor.swift in Sources */, + 1F74F10E273CEEC700CE4D37 /* AddRecordViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -596,7 +817,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 0.2; PRODUCT_BUNDLE_IDENTIFIER = kr.boostcamp.Escaper; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -627,7 +848,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 0.2; PRODUCT_BUNDLE_IDENTIFIER = kr.boostcamp.Escaper; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -671,16 +892,21 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 1FE6C8AB2732621800AC7408 /* FirebaseFirestore */ = { + 1F884BC22740D4E500DE67D8 /* FirebaseFirestore */ = { isa = XCSwiftPackageProductDependency; package = 1FE6C8AA2732621800AC7408 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseFirestore; }; - 1FE6C8AD2732621800AC7408 /* FirebaseFirestoreSwift-Beta */ = { + 1F884BC42740D4FE00DE67D8 /* FirebaseFirestoreSwift-Beta */ = { isa = XCSwiftPackageProductDependency; package = 1FE6C8AA2732621800AC7408 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = "FirebaseFirestoreSwift-Beta"; }; + 1F884BC62740D51100DE67D8 /* FirebaseStorage */ = { + isa = XCSwiftPackageProductDependency; + package = 1FE6C8AA2732621800AC7408 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseStorage; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 1FCDDE60272EE0F00027AAAD /* Project object */; diff --git a/Escaper/Escaper/Application/AppDelegate.swift b/Escaper/Escaper/Application/AppDelegate.swift index 89104ed..f5b9647 100644 --- a/Escaper/Escaper/Application/AppDelegate.swift +++ b/Escaper/Escaper/Application/AppDelegate.swift @@ -6,11 +6,13 @@ // import UIKit +import Firebase @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + FirebaseApp.configure() return true } diff --git a/Escaper/Escaper/Application/Base.lproj/LaunchScreen.storyboard b/Escaper/Escaper/Application/Base.lproj/LaunchScreen.storyboard index 865e932..0c8ec42 100644 --- a/Escaper/Escaper/Application/Base.lproj/LaunchScreen.storyboard +++ b/Escaper/Escaper/Application/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,12 @@ - - + + + - + + + + @@ -11,10 +15,34 @@ - + - + + + + + + + + + + + + + + + + + + + + @@ -22,4 +50,13 @@ + + + + + + + + + diff --git a/Escaper/Escaper/Application/SceneDelegate.swift b/Escaper/Escaper/Application/SceneDelegate.swift index d33a223..4288992 100644 --- a/Escaper/Escaper/Application/SceneDelegate.swift +++ b/Escaper/Escaper/Application/SceneDelegate.swift @@ -13,8 +13,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } - let viewController = RoomListViewController() - viewController.create() + let viewController = MainTabBarController() window = UIWindow(frame: UIScreen.main.bounds) window?.windowScene = windowScene window?.rootViewController = viewController diff --git a/Escaper/Escaper/Common/Extensions/UISearchBar+SetTextFieldColor.swift b/Escaper/Escaper/Common/Extensions/UISearchBar+SetTextFieldColor.swift new file mode 100644 index 0000000..64361c9 --- /dev/null +++ b/Escaper/Escaper/Common/Extensions/UISearchBar+SetTextFieldColor.swift @@ -0,0 +1,23 @@ +// +// UISearchBar+SetTextFieldColor.swift +// Escaper +// +// Created by TakHyun Jung on 2021/11/13. +// + +import UIKit + +extension UISearchBar { + func setTextFieldColor(color: UIColor?) { + guard let textField = self.value(forKey: "searchField") as? UITextField else { return } + switch self.searchBarStyle { + case .minimal: + textField.layer.backgroundColor = color?.cgColor + textField.layer.cornerRadius = 6 + case .prominent, .default: + textField.backgroundColor = color + @unknown default: + break + } + } +} diff --git a/Escaper/Escaper/Common/Extensions/UIView+RoundCorners.swift b/Escaper/Escaper/Common/Extensions/UIView+RoundCorners.swift new file mode 100644 index 0000000..10e4d1c --- /dev/null +++ b/Escaper/Escaper/Common/Extensions/UIView+RoundCorners.swift @@ -0,0 +1,17 @@ +// +// UIView+ScaledSize.swift +// Escaper +// +// Created by TakHyun Jung on 2021/11/13. +// + +import UIKit + +extension UIView { + func roundCorners(corners: UIRectCorner, radius: CGFloat) { + let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) + let mask = CAShapeLayer() + mask.path = path.cgPath + self.layer.mask = mask + } +} diff --git a/Escaper/Escaper/Common/Library/ColorPalette.swift b/Escaper/Escaper/Common/Library/ColorPalette.swift deleted file mode 100644 index 9ae346c..0000000 --- a/Escaper/Escaper/Common/Library/ColorPalette.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// ColorPalette.swift -// Escaper -// -// Created by 최완식 on 2021/11/03. -// - -import Foundation - -enum ColorPalette { - case bloodyBlack - case bloodyBurgundy - case bloodyDarkBurgundy - case bloodyRed - case charcoal - case gloomyPink - case gloomyPurple - case gloomyRed - case gloomyBrown - case pumpkin - case skullLightWhite - case skullWhite - - var name: String { - return String(describing: self) - } -} diff --git a/Escaper/Escaper/Common/Library/EDSKit.swift b/Escaper/Escaper/Common/Library/EDSKit.swift new file mode 100644 index 0000000..7c6d43e --- /dev/null +++ b/Escaper/Escaper/Common/Library/EDSKit.swift @@ -0,0 +1,109 @@ +// +// DesignSystem.swift +// Escaper +// +// Created by 최완식 on 2021/11/05. +// + +import UIKit + +typealias EDSColor = EDSKit.Color +typealias EDSLabel = EDSKit.Label +typealias EDSImage = EDSKit.Image + +enum EDSKit { + enum Label { + static func h01B(text: String = "", color: Color) -> UILabel { + let label = Label.makeLabel(text: text, color: color) + label.font = UIFont.systemFont(ofSize: 24, weight: .bold) + return label + } + + static func h02B(text: String = "", color: Color) -> UILabel { + let label = Label.makeLabel(text: text, color: color) + label.font = UIFont.systemFont(ofSize: 22, weight: .bold) + return label + } + + static func h03B(text: String = "", color: Color) -> UILabel { + let label = Label.makeLabel(text: text, color: color) + label.font = UIFont.systemFont(ofSize: 20, weight: .bold) + return label + } + + static func b01B(text: String = "", color: Color) -> UILabel { + let label = Label.makeLabel(text: text, color: color) + label.font = UIFont.systemFont(ofSize: 14, weight: .bold) + return label + } + + static func b02B(text: String = "", color: Color) -> UILabel { + let label = Label.makeLabel(text: text, color: color) + label.font = UIFont.systemFont(ofSize: 12, weight: .bold) + return label + } + + static func b03B(text: String = "", color: Color) -> UILabel { + let label = Label.makeLabel(text: text, color: color) + label.font = UIFont.systemFont(ofSize: 10, weight: .bold) + return label + } + + static func b01R(text: String = "", color: Color) -> UILabel { + let label = Label.makeLabel(text: text, color: color) + label.font = UIFont.systemFont(ofSize: 14, weight: .regular) + return label + } + + static func b02R(text: String = "", color: Color) -> UILabel { + let label = Label.makeLabel(text: text, color: color) + label.font = UIFont.systemFont(ofSize: 12, weight: .regular) + return label + } + + static func b03R(text: String = "", color: Color) -> UILabel { + let label = Label.makeLabel(text: text, color: color) + label.font = UIFont.systemFont(ofSize: 10, weight: .regular) + return label + } + } + + enum Color { + case bloodyBlack + case bloodyBurgundy + case bloodyDarkBurgundy + case bloodyRed + case charcoal + case gloomyPink + case gloomyPurple + case gloomyRed + case gloomyBrown + case pumpkin + case skullLightWhite + case skullWhite + case skullGrey + + var value: UIColor? { + return UIColor(named: String(describing: self)) + } + } + + enum Image { + var value: UIImage? { + return UIImage(named: String(describing: self)) + } + + case chevronDown + case recordCard + case plus + } +} + +private extension EDSLabel { + static func makeLabel(text: String, color: EDSColor) -> UILabel { + let label = UILabel() + label.text = text + label.textColor = color.value + return label + } +} diff --git a/Escaper/Escaper/Common/Library/Helper.swift b/Escaper/Escaper/Common/Library/Helper.swift new file mode 100644 index 0000000..ea8ca68 --- /dev/null +++ b/Escaper/Escaper/Common/Library/Helper.swift @@ -0,0 +1,14 @@ +// +// Helper.swift +// Escaper +// +// Created by 최완식 on 2021/11/11. +// + +import Foundation + +enum Helper { + static func parseUsername(email: String) -> String? { + return email.components(separatedBy: "@").first + } +} diff --git a/Escaper/Escaper/Common/Library/UtilityImage.swift b/Escaper/Escaper/Common/Library/UtilityImage.swift new file mode 100644 index 0000000..d26827b --- /dev/null +++ b/Escaper/Escaper/Common/Library/UtilityImage.swift @@ -0,0 +1,16 @@ +// +// UtilityImage.swift +// Escaper +// +// Created by shinheeRo on 2021/11/09. +// + +import Foundation + +enum UtilityImage: String { + var name: String { + return self.rawValue + } + case chevronDown + case recordCard +} diff --git a/Escaper/Escaper/Common/Utility/Helper.swift b/Escaper/Escaper/Common/Utility/Helper.swift new file mode 100644 index 0000000..1adbdc1 --- /dev/null +++ b/Escaper/Escaper/Common/Utility/Helper.swift @@ -0,0 +1,14 @@ +// +// Helper.swift +// Escaper +// +// Created by 최완식 on 2021/11/10. +// + +import Foundation + +enum Helper { + static func parseUsername(email: String) -> String? { + return email.components(separatedBy: "@").first + } +} diff --git a/Escaper/Escaper/Domain/DataStruct/District.swift b/Escaper/Escaper/Domain/DataStruct/District.swift index 4711589..6819b94 100644 --- a/Escaper/Escaper/Domain/DataStruct/District.swift +++ b/Escaper/Escaper/Domain/DataStruct/District.swift @@ -7,7 +7,7 @@ import Foundation -enum District: String, Codable { +enum District: String, CaseIterable, Codable { var name: String { return self.rawValue } diff --git a/Escaper/Escaper/Domain/DataStruct/Record.swift b/Escaper/Escaper/Domain/DataStruct/Record.swift new file mode 100644 index 0000000..47080d8 --- /dev/null +++ b/Escaper/Escaper/Domain/DataStruct/Record.swift @@ -0,0 +1,43 @@ +// +// Record.swift +// Escaper +// +// Created by 최완식 on 2021/11/10. +// + +import Foundation + +struct Record: Hashable { + var username: String + var imageUrlString: String + var roomName: String + var storeName: String + var isSuccess: Bool + var satisfaction: Rating + var difficulty: Rating + var numberOfTotalPlayers: Int + var rank: Int + var time: Int + + init(recordInfo: RecordInfo, room: Room) { + self.username = Helper.parseUsername(email: recordInfo.userEmail) ?? "Unknown" + self.imageUrlString = recordInfo.imageUrlString + self.roomName = room.name + self.storeName = room.storeName + self.isSuccess = recordInfo.isSuccess + self.satisfaction = recordInfo.satisfaction + self.difficulty = room.level + self.numberOfTotalPlayers = room.userRecords.count + self.rank = Self.calculateRank(username: self.username, time: recordInfo.time, allRecords: room.userRecords) ?? 0 + self.time = recordInfo.time + } +} + +private extension Record { + static func calculateRank(username: String, time: Int, allRecords: [UserRecord]) -> Int? { + let playerRecord = UserRecord(nickname: username, satisfaction: .zero, playTime: time) + var allPlayerRecords = allRecords + [playerRecord] + allPlayerRecords.sort { $0.playTime < $1.playTime } + return allPlayerRecords.firstIndex { $0.nickname == username } + } +} diff --git a/Escaper/Escaper/Domain/DataStruct/RecordInfo.swift b/Escaper/Escaper/Domain/DataStruct/RecordInfo.swift new file mode 100644 index 0000000..04c0be3 --- /dev/null +++ b/Escaper/Escaper/Domain/DataStruct/RecordInfo.swift @@ -0,0 +1,28 @@ +// +// RecordInfo.swift +// Escaper +// +// Created by 최완식 on 2021/11/10. +// + +import Foundation + +struct RecordInfo { + static let defaultImageUrlString = "gs://escaper-67244.appspot.com/records/default" + + var imageUrlString: String + var userEmail: String + var roomId: String + var satisfaction: Rating + var isSuccess: Bool + var time: Int + + func toDTO() -> RecordInfoDTO { + return RecordInfoDTO(imageUrlString: self.imageUrlString, + userEmail: self.userEmail, + roomId: self.roomId, + satisfaction: Double(self.satisfaction.rawValue), + isSuccess: self.isSuccess, + time: self.time) + } +} diff --git a/Escaper/Escaper/Domain/DataStruct/Room.swift b/Escaper/Escaper/Domain/DataStruct/Room.swift index 089b12d..8ad73d3 100644 --- a/Escaper/Escaper/Domain/DataStruct/Room.swift +++ b/Escaper/Escaper/Domain/DataStruct/Room.swift @@ -9,6 +9,7 @@ import Foundation import CoreLocation struct Room: Hashable { + var identifier: String var name: String var storeName: String var level: Rating diff --git a/Escaper/Escaper/Domain/DataStruct/SortingOption.swift b/Escaper/Escaper/Domain/DataStruct/SortingOption.swift index 6cb14b6..63be34e 100644 --- a/Escaper/Escaper/Domain/DataStruct/SortingOption.swift +++ b/Escaper/Escaper/Domain/DataStruct/SortingOption.swift @@ -12,7 +12,7 @@ enum SortingOption: String, Tagable, CaseIterable { return self.rawValue } - case satisfaction = "만족도순" - case level = "난이도순" - case distance = "거리순" + case satisfaction = "만족도" + case level = "난이도" + case distance = "거리" } diff --git a/Escaper/Escaper/Domain/Interface/Repositories/RecordRepositoryInterface.swift b/Escaper/Escaper/Domain/Interface/Repositories/RecordRepositoryInterface.swift new file mode 100644 index 0000000..1c4cba9 --- /dev/null +++ b/Escaper/Escaper/Domain/Interface/Repositories/RecordRepositoryInterface.swift @@ -0,0 +1,13 @@ +// +// RecordRepositoryInterface.swift +// Escaper +// +// Created by 최완식 on 2021/11/10. +// + +import Foundation + +protocol RecordRepositoryInterface { + func query(userEmail: String, completion: @escaping (Result<[RecordInfo], Error>) -> Void) + func addRecord(recordInfo: RecordInfo) +} diff --git a/Escaper/Escaper/Domain/Interface/Repositories/RoomListRepositoryInterface.swift b/Escaper/Escaper/Domain/Interface/Repositories/RoomListRepositoryInterface.swift new file mode 100644 index 0000000..a7e1d05 --- /dev/null +++ b/Escaper/Escaper/Domain/Interface/Repositories/RoomListRepositoryInterface.swift @@ -0,0 +1,14 @@ +// +// RoomListRepositoryInterface.swift +// Escaper +// +// Created by 최완식 on 2021/11/10. +// + +import Foundation + +protocol RoomListRepositroyInterface { + func query(genre: Genre, district: District, completion: @escaping (Result<[Room], Error>) -> Void) + func fetch(roomId: String, completion: @escaping (Result) -> Void) + func fetch(name: String, completion: @escaping (Result<[Room], Error>) -> Void) +} diff --git a/Escaper/Escaper/Domain/UseCase/RecordUsecase.swift b/Escaper/Escaper/Domain/UseCase/RecordUsecase.swift new file mode 100644 index 0000000..e8478b8 --- /dev/null +++ b/Escaper/Escaper/Domain/UseCase/RecordUsecase.swift @@ -0,0 +1,53 @@ +// +// RecordUsecase.swift +// Escaper +// +// Created by 최완식 on 2021/11/11. +// + +import Foundation + +protocol RecordUsecaseInterface { + func fetchAllRecords(userEmail: String, completion: @escaping (Result) -> Void) + func addRecord(imageUrlString: String?, userEmail: String, roomId: String, satisfaction: Rating, isSuccess: Bool, time: Int) +} + +class RecordUsecase: RecordUsecaseInterface { + private let roomRepository: RoomListRepositroyInterface + private let recordRepository: RecordRepository + + init(roomRepository: RoomListRepositroyInterface, recordRepository: RecordRepository) { + self.roomRepository = roomRepository + self.recordRepository = recordRepository + } + + func fetchAllRecords(userEmail: String, completion: @escaping (Result) -> Void) { + self.recordRepository.query(userEmail: userEmail) { result in + switch result { + case .success(let recordInfos): + recordInfos.forEach { recordInfo in + self.roomRepository.fetch(roomId: recordInfo.roomId) { result in + switch result { + case .success(let room): + completion(.success(Record(recordInfo: recordInfo, room: room))) + case .failure(let error): + completion(.failure(error)) + } + } + } + case .failure(let error): + completion(.failure(error)) + } + } + } + + func addRecord(imageUrlString: String?, userEmail: String, roomId: String, satisfaction: Rating, isSuccess: Bool, time: Int) { + let recordInfo = RecordInfo(imageUrlString: imageUrlString ?? RecordInfo.defaultImageUrlString, + userEmail: userEmail, + roomId: roomId, + satisfaction: satisfaction, + isSuccess: isSuccess, + time: time) + self.recordRepository.addRecord(recordInfo: recordInfo) + } +} diff --git a/Escaper/Escaper/Domain/UseCase/RoomListUseCase.swift b/Escaper/Escaper/Domain/UseCase/RoomListUseCase.swift index b51489c..86e90d0 100644 --- a/Escaper/Escaper/Domain/UseCase/RoomListUseCase.swift +++ b/Escaper/Escaper/Domain/UseCase/RoomListUseCase.swift @@ -9,45 +9,41 @@ import Foundation import CoreLocation protocol RoomListUseCaseInterface { - func query(genre: Genre, completion: @escaping (Result<[Room], Error>) -> Void) + func query(district: District, genre: Genre, completion: @escaping (Result<[Room], Error>) -> Void) + func fetch(name: String, completion: @escaping (Result<[Room], Error>) -> Void) } class RoomListUseCase: RoomListUseCaseInterface { private let repository: RoomListRepositroyInterface - private let locationManager: CLLocationManager = { - let manager = CLLocationManager() - manager.desiredAccuracy = kCLLocationAccuracyBest - manager.requestWhenInUseAuthorization() - return manager - }() init(repository: RoomListRepositroyInterface) { self.repository = repository } - func query(genre: Genre, completion: @escaping (Result<[Room], Error>) -> Void) { - self.fetchCurrentDistrict(genre: genre, completion: completion) + func query(district: District, genre: Genre, completion: @escaping (Result<[Room], Error>) -> Void) { + self.repository.query(genre: genre, district: district) { result in + switch result { + case .success(var roomList): + guard let location = CLLocationManager().location else { return } + for index in 0..) -> Void) { - guard let location = self.locationManager.location else { return } - let geocoder = CLGeocoder() - let locale = Locale(identifier: "Ko-kr") - geocoder.reverseGeocodeLocation(location, preferredLocale: locale) { placeMarks, _ in - guard let address: [CLPlacemark] = placeMarks, - let locality = address.last?.locality, - let district = District.init(rawValue: locality) else { return } - self.repository.query(genre: genre, district: district) { result in - switch result { - case .success(var roomList): - for index in 0..) -> Void) { + self.repository.fetch(name: name) { result in + switch result { + case .success(let rooms): + completion(.success(rooms)) + case .failure(let error): + completion(.failure(error)) } } } + } diff --git a/Escaper/Escaper/GoogleService-Info.plist b/Escaper/Escaper/GoogleService-Info.plist deleted file mode 100644 index c9b4e2b..0000000 --- a/Escaper/Escaper/GoogleService-Info.plist +++ /dev/null @@ -1,34 +0,0 @@ - - - - - CLIENT_ID - 534296336472-9kv8k2u9sudmkh0a1u8l852db358lgk6.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.534296336472-9kv8k2u9sudmkh0a1u8l852db358lgk6 - API_KEY - AIzaSyC_odfHMeQo5wTWd3mBN0W9yRxXRBc3HS8 - GCM_SENDER_ID - 534296336472 - PLIST_VERSION - 1 - BUNDLE_ID - kr.boostcamp.Escaper - PROJECT_ID - escaper-67244 - STORAGE_BUCKET - escaper-67244.appspot.com - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:534296336472:ios:6115a897fe7418d450522d - - \ No newline at end of file diff --git a/Escaper/Escaper/Info.plist b/Escaper/Escaper/Info.plist index 761fdb3..956c6ce 100644 --- a/Escaper/Escaper/Info.plist +++ b/Escaper/Escaper/Info.plist @@ -2,12 +2,28 @@ + NSPhotoLibraryUsageDescription + 기록을 추가하기 위해 권한이 필요합니다. (필수권한) NSLocationWhenInUseUsageDescription - 현재위치를 확인하기 위해 권한이 필요합니다.(필수권한) - NSLocationAlwaysUsageDescription - 현재위치를 확인하기 위해 권한이 필요합니다.(필수권한) + 현재위치를 확인하기 위해 권한이 필요합니다. (필수권한) + UIViewControllerBasedStatusBarAppearance + + UIStatusBarStyle + UIStatusBarStyleLightContent UIUserInterfaceStyle Light + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~iphone + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/Escaper/Escaper/Infrastructure/DataMapping/RecordInfoDTO.swift b/Escaper/Escaper/Infrastructure/DataMapping/RecordInfoDTO.swift new file mode 100644 index 0000000..7f3dc26 --- /dev/null +++ b/Escaper/Escaper/Infrastructure/DataMapping/RecordInfoDTO.swift @@ -0,0 +1,38 @@ +// +// RecordInfoDTO.swift +// Escaper +// +// Created by 최완식 on 2021/11/10. +// + +import Foundation + +struct RecordInfoDTO: Codable { + var imageUrlString: String + var userEmail: String + var roomId: String + var satisfaction: Double + var isSuccess: Bool + var time: Int + + func toDictionary() -> [String: Any] { + let dictionary: [String: Any] = [ + "imageUrlString": self.imageUrlString, + "userEmail": self.userEmail, + "roomId": self.roomId, + "satisfaction": self.satisfaction, + "isSuccess": self.isSuccess, + "time": self.time + ] + return dictionary + } + + func toDomain() -> RecordInfo { + return RecordInfo(imageUrlString: self.imageUrlString, + userEmail: self.userEmail, + roomId: self.roomId, + satisfaction: Rating(rawValue: Int(self.satisfaction.rounded())) ?? Rating.zero, + isSuccess: self.isSuccess, + time: self.time) + } +} diff --git a/Escaper/Escaper/Infrastructure/DataMapping/RoomDTO.swift b/Escaper/Escaper/Infrastructure/DataMapping/RoomDTO.swift index bfc10a7..57747b5 100644 --- a/Escaper/Escaper/Infrastructure/DataMapping/RoomDTO.swift +++ b/Escaper/Escaper/Infrastructure/DataMapping/RoomDTO.swift @@ -8,6 +8,7 @@ import Foundation struct RoomDTO: Codable { + var identifier: String var name: String var storeName: String var level: Double @@ -20,6 +21,7 @@ struct RoomDTO: Codable { func toDictionary() -> [String: Any] { let dictionary: [String: Any] = [ + "identifier": self.identifier, "name": self.name, "storeName": self.storeName, "level": self.level, @@ -38,8 +40,9 @@ struct RoomDTO: Codable { let satisfactionSum = self.userRecords.reduce(0.0, { $0 + $1.satisfaction }) let satisfactionRawValue = self.userRecords.count == 0 ? 0 : (satisfactionSum / Double(self.userRecords.count)).rounded() let satisfaction = Rating(rawValue: Int(satisfactionRawValue)) ?? Rating.zero - let userRecords = self.userRecords.sorted { $0.playTime > $1.playTime }.prefix(3).map { $0 } - return Room(name: self.name, + let userRecords = self.userRecords.sorted { $0.playTime < $1.playTime }.prefix(3).map { $0 } + return Room(identifier: self.identifier, + name: self.name, storeName: self.storeName, level: level, satisfaction: satisfaction, diff --git a/Escaper/Escaper/Infrastructure/FirebaseService.swift b/Escaper/Escaper/Infrastructure/FirebaseService.swift index 34d6135..9da83c4 100644 --- a/Escaper/Escaper/Infrastructure/FirebaseService.swift +++ b/Escaper/Escaper/Infrastructure/FirebaseService.swift @@ -11,20 +11,35 @@ import FirebaseFirestoreSwift protocol RoomListNetwork: AnyObject { func query(genre: Genre, district: District, completion: @escaping (Result<[RoomDTO], Error>) -> Void) + func query(roomId: String, completion: @escaping (Result) -> Void) + func query(name: String, completion: @escaping (Result<[RoomDTO], Error>) -> Void) +} + +protocol RecordNetwork: AnyObject { + func query(userEmail: String, completion: @escaping (Result<[RecordInfoDTO], Error>) -> Void) + func addRecord(recordInfoDTO: RecordInfoDTO) } final class FirebaseService: RoomListNetwork { + enum Collection: String { + case rooms + case records + + var value: String { + return self.rawValue + } + } + static let shared = FirebaseService() - private let db: Firestore + private let database: Firestore private init() { - FirebaseApp.configure() - self.db = Firestore.firestore() + self.database = Firestore.firestore() } func query(genre: Genre, district: District, completion: @escaping (Result<[RoomDTO], Error>) -> Void) { - db.collection("rooms") + database.collection(Collection.rooms.value) .whereField("genres", arrayContains: genre.name) .whereField("district", isEqualTo: district.name) .getDocuments { snapshot, _ in @@ -41,19 +56,96 @@ final class FirebaseService: RoomListNetwork { } case .failure(let error): completion(.failure(error)) - break + return } } completion(Result.success(roomList)) } } - func addRoom(_ room: RoomDTO) { + func query(roomId: String, completion: @escaping (Result) -> Void) { + database.collection(Collection.rooms.value) + .document(roomId) + .getDocument { snapshot, error in + let result = Result { + try snapshot?.data(as: RoomDTO.self) + } + switch result { + case .success(let roomDTO): + guard let roomDTO = roomDTO else { return } + completion(.success(roomDTO)) + case .failure(let error): + completion(.failure(error)) + return + } + } + } + + func query(name: String, completion: @escaping (Result<[RoomDTO], Error>) -> Void) { + database.collection(Collection.rooms.value) + .getDocuments { snapshot, error in + guard let documents = snapshot?.documents else { return } + var roomList = [RoomDTO]() + for document in documents { + let result = Result { + try document.data(as: RoomDTO.self) + } + switch result { + case .success(let room): + if let room = room, + room.name.hasPrefix(name) { + roomList.append(room) + } + case .failure(let error): + completion(.failure(error)) + return + } + } + completion(Result.success(roomList.sorted(by: {$0.name < $1.name }))) + } + } + + func addRoom(identifier: Int, _ room: RoomDTO) { if FirebaseApp.app() == nil { FirebaseApp.configure() } - let db = Firestore.firestore() - let path = db.collection("rooms").document(room.name) + let database = Firestore.firestore() + let path = database.collection(Collection.rooms.value).document("\(identifier)") path.setData(room.toDictionary()) } } + +extension FirebaseService: RecordNetwork { + func query(userEmail: String, completion: @escaping (Result<[RecordInfoDTO], Error>) -> Void) { + database.collection(Collection.records.value) + .whereField("userEmail", isEqualTo: userEmail) + .getDocuments { snapshot, _ in + guard let documents = snapshot?.documents else { return } + var recordInfoDTOList = [RecordInfoDTO]() + for document in documents { + let result = Result { + try document.data(as: RecordInfoDTO.self) + } + switch result { + case .success(let recordInfoDTO): + if let recordInfoDTO = recordInfoDTO { + recordInfoDTOList.append(recordInfoDTO) + } + case .failure(let error): + completion(.failure(error)) + return + } + } + completion(Result.success(recordInfoDTOList)) + } + } + + func addRecord(recordInfoDTO: RecordInfoDTO) { + if FirebaseApp.app() == nil { + FirebaseApp.configure() + } + let database = Firestore.firestore() + let path = database.collection(Collection.records.value).document() + path.setData(recordInfoDTO.toDictionary()) + } +} diff --git a/Escaper/Escaper/Infrastructure/ImageCacheManager.swift b/Escaper/Escaper/Infrastructure/ImageCacheManager.swift new file mode 100644 index 0000000..57fe111 --- /dev/null +++ b/Escaper/Escaper/Infrastructure/ImageCacheManager.swift @@ -0,0 +1,94 @@ +// +// ImageCacheManager.swift +// Escaper +// +// Created by 최완식 on 2021/11/11. +// + +import Firebase +import FirebaseStorage +import UIKit + +class ImageCacheManager { + typealias UrlString = String + typealias URLCompletion = (Result) -> Void + typealias DataCompletion = (Result) -> Void + + enum ImageType: String { + case users + case records + + var name: String { + return String(describing: self) + } + } + + static let shared = ImageCacheManager() + + private let fileManager = FileManager.default + private let storage = Storage.storage() + private let cachePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + private let metadata: StorageMetadata = { + let metadata = StorageMetadata() + metadata.contentType = "image/png" + return metadata + }() + + private init() {} + + func uploadRecord(image: UIImage, userEmail: String, roomId: String, completion: @escaping URLCompletion) { + let filename = "\(userEmail)_\(roomId)" + self.upload(image: image, type: .records, filename: filename) { result in + completion(result) + } + } + + func uploadUser(image: UIImage, userEmail: String, completion: @escaping URLCompletion) { + let filename = "\(userEmail)" + self.upload(image: image, type: .users, filename: filename) { result in + completion(result) + } + } + + func download(urlString: String, completion: @escaping DataCompletion) { + var filePath = URL(fileURLWithPath: cachePath) + guard let fileName = urlString.components(separatedBy: "/").last else { return } + filePath.appendPathComponent(fileName) + if !fileManager.fileExists(atPath: filePath.path) { + self.storage.reference(forURL: urlString).downloadURL { url, error in + guard let url = url else { return } + NetworkService.shared.downloadImage(from: url) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let localURL): + try? self.fileManager.moveItem(at: localURL, to: filePath) + guard let data = try? Data(contentsOf: filePath) else { return } + completion(.success(data)) + case .failure(let error): + completion(.failure(error)) + } + } + } + } else { + guard let data = try? Data(contentsOf: filePath) else { return } + completion(.success(data)) + } + } +} + +private extension ImageCacheManager { + func upload(image: UIImage, type: ImageType, filename: String, completion: @escaping (Result) -> Void) { + let filePath = "\(type.name)/\(filename)" + guard let data = image.jpegData(compressionQuality: 0.2) else { return } + let storageReference = self.storage.reference() + storageReference.child(filePath).putData(data, metadata: self.metadata) { (metadata, error) in + if let error = error { + completion(.failure(error)) + } else { + guard let metadata = metadata, + let path = metadata.path else { return } + completion(.success("gs://\(metadata.bucket)/\(path)")) + } + } + } +} diff --git a/Escaper/Escaper/Infrastructure/NetworkService.swift b/Escaper/Escaper/Infrastructure/NetworkService.swift new file mode 100644 index 0000000..7135da8 --- /dev/null +++ b/Escaper/Escaper/Infrastructure/NetworkService.swift @@ -0,0 +1,62 @@ +// +// NetworkService.swift +// Escaper +// +// Created by 최완식 on 2021/11/11. +// + +import Foundation + +class NetworkService { + static let shared = NetworkService() + private init() {} + + func downloadImage(from url: URL, completion: @escaping (Result) -> Void) { + URLSession.shared.downloadTask(with: url) { filePath, response, error in + guard error == nil, + let response = response as? HTTPURLResponse else { + completion(.failure(.responseError)) + return + } + switch response.statusCode { + case (200..<300): + guard let filePath = filePath else { + completion(.failure(.filePathError)) + return + } + completion(.success(filePath)) + case (300..<400): + completion(.failure(.clientError)) + case (400..<500): + completion(.failure(.serverError)) + default: + break + } + }.resume() + } +} + +extension NetworkService { + enum NetworkError: Error, LocalizedError { + case clientError + case serverError + case decodeError + case filePathError + case responseError + + public var errorDescription: String? { + switch self { + case .clientError: + return NSLocalizedString("클라이언트 에러", comment: "Client Error") + case .serverError: + return NSLocalizedString("서버 에러", comment: "Server Error") + case .decodeError: + return NSLocalizedString("디코드 에러", comment: "Decode Error") + case .filePathError: + return NSLocalizedString("파일 경로 에러", comment: "File Path Error") + case .responseError: + return NSLocalizedString("응답 에러", comment: "Response Error") + } + } + } +} diff --git a/Escaper/Escaper/Infrastructure/RecordRepository.swift b/Escaper/Escaper/Infrastructure/RecordRepository.swift new file mode 100644 index 0000000..f979ba6 --- /dev/null +++ b/Escaper/Escaper/Infrastructure/RecordRepository.swift @@ -0,0 +1,31 @@ +// +// RecordRepository.swift +// Escaper +// +// Created by 최완식 on 2021/11/10. +// + +import Foundation + +final class RecordRepository: RecordRepositoryInterface { + private let service: RecordNetwork + + init(service: RecordNetwork) { + self.service = service + } + + func query(userEmail: String, completion: @escaping (Result<[RecordInfo], Error>) -> Void) { + self.service.query(userEmail: userEmail) { result in + switch result { + case .success(let recordResponseDTOs): + completion(.success(recordResponseDTOs.map({ $0.toDomain() }))) + case .failure(let error): + print(error) + } + } + } + + func addRecord(recordInfo: RecordInfo) { + self.service.addRecord(recordInfoDTO: recordInfo.toDTO()) + } +} diff --git a/Escaper/Escaper/Infrastructure/RoomListRepository.swift b/Escaper/Escaper/Infrastructure/RoomListRepository.swift index e0dff0d..d2004f7 100644 --- a/Escaper/Escaper/Infrastructure/RoomListRepository.swift +++ b/Escaper/Escaper/Infrastructure/RoomListRepository.swift @@ -7,10 +7,6 @@ import Foundation -protocol RoomListRepositroyInterface { - func query(genre: Genre, district: District, completion: @escaping (Result<[Room], Error>) -> Void) -} - final class RoomListRepository: RoomListRepositroyInterface { private let service: RoomListNetwork @@ -19,7 +15,7 @@ final class RoomListRepository: RoomListRepositroyInterface { } func query(genre: Genre, district: District, completion: @escaping (Result<[Room], Error>) -> Void) { - service.query(genre: genre, district: district) { result in + self.service.query(genre: genre, district: district) { result in switch result { case .success(let roomDTOList): completion(.success(roomDTOList.map { $0.toDomain()})) @@ -28,4 +24,26 @@ final class RoomListRepository: RoomListRepositroyInterface { } } } + + func fetch(roomId: String, completion: @escaping (Result) -> Void) { + self.service.query(roomId: roomId) { result in + switch result { + case .success(let roomDTO): + completion(.success(roomDTO.toDomain())) + case .failure(let error): + completion(.failure(error)) + } + } + } + + func fetch(name: String, completion: @escaping (Result<[Room], Error>) -> Void) { + self.service.query(name: name) { result in + switch result { + case .success(let roomDTOs): + completion(.success(roomDTOs.map({ $0.toDomain() }))) + case .failure(let error): + completion(.failure(error)) + } + } + } } diff --git a/Escaper/Escaper/Presentation/AddRecord/AddRecordViewController.swift b/Escaper/Escaper/Presentation/AddRecord/AddRecordViewController.swift new file mode 100644 index 0000000..ccfdc8a --- /dev/null +++ b/Escaper/Escaper/Presentation/AddRecord/AddRecordViewController.swift @@ -0,0 +1,195 @@ +// +// AddRecordViewController.swift +// Escaper +// +// Created by TakHyun Jung on 2021/11/09. +// + +import UIKit + +class AddRecordViewController: DefaultViewController { + enum Constant { + static let topVerticalSpace = CGFloat(18) + static let defaultVerticalSpace = CGFloat(30) + static let saveButtonHeight = CGFloat(35) + } + + private var viewModel: (AddRecordViewModelInput & AddRecordViewModelOutput)? + private let titleLabel: UILabel = EDSLabel.h02B(text: "기록 추가", color: .skullLightWhite) + private let backButton: UIButton = { + let button = UIButton() + button.setTitle("취소", for: .normal) + button.setTitleColor(EDSColor.bloodyRed.value, for: .normal) + return button + }() + private let recordView = RecordView() + private let saveRecordButton: UIButton = { + let button = UIButton() + button.layer.cornerRadius = 10 + button.setTitle("저장하기", for: .normal) + return button + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.configure() + self.configureLayout() + } + + func create() { + let recordRepository = RecordRepository(service: FirebaseService.shared) + let roomRepository = RoomListRepository(service: FirebaseService.shared) + let usecase = RecordUsecase(roomRepository: roomRepository, recordRepository: recordRepository) + let viewModel = AddRecordViewModel(usecase: usecase) + self.viewModel = viewModel + } + + @objc func backButtonTapped() { + self.dismiss(animated: true) + } + + @objc func saveRecordButtonTapped() { + let userEmail = "kessler.myah@hotmail.com" + guard let image = self.recordView.fetchSelectedImage(), + let roomId = self.viewModel?.roomId else { return } + self.dismiss(animated: true) { + ImageCacheManager.shared.uploadRecord(image: image, userEmail: userEmail, roomId: roomId) { [weak self] result in + switch result { + case .success(let url): + self?.viewModel?.post(email: userEmail, imageUrlString: url) + case .failure(let err): + print(err) + } + } + } + } +} + +extension AddRecordViewController: RecordViewDelegate { + func updateEscapingTime(time: Int) { + self.viewModel?.time = time + self.viewModel?.changeSaveState() + } + + func updateRoom(identifer: String) { + self.viewModel?.roomId = identifer + self.viewModel?.changeSaveState() + } + + func updateIsSuccess(_ isSuccess: Bool) { + self.viewModel?.isSuccess = isSuccess + } + + func findRoomTitleButtonTapped() { + let findRoomViewController = FindRoomViewController() + findRoomViewController.create() + findRoomViewController.modalPresentationStyle = .automatic + findRoomViewController.roomTransferDelegate = self + self.present(findRoomViewController, animated: true) + } + + func userImageViewTapped() { + let imagePickerController = UIImagePickerController() + imagePickerController.sourceType = .photoLibrary + imagePickerController.delegate = self + self.present(imagePickerController, animated: true) + } + + func escapingTimePickerButtonTapped() { + let timePickerController = TimePickerViewController() + timePickerController.delegate = self + self.present(timePickerController, animated: true) + } +} + +extension AddRecordViewController: TimePickerDelegate { + func updateTime(hour: Int, minutes: Int, seconds: Int) { + self.recordView.updateTimePicker(hour: hour, minutes: minutes, seconds: seconds) + } +} + +extension AddRecordViewController: RoomInformationTransferable { + func transfer(room: Room) { + self.recordView.updateRoomInformation(room) + } +} + +extension AddRecordViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { + picker.dismiss(animated: true) + guard let image = info[.originalImage] as? UIImage else { return } + self.recordView.updateUserSelectedImage(image) + } +} + +private extension AddRecordViewController { + func configure() { + self.configureDelegates() + self.configureButtonAction() + self.bindViewModel() + } + + func configureLayout() { + self.configureTitleLabelLayout() + self.configureRecordViewLayout() + self.configureBackButtonLayout() + self.configureSaveButtonLayout() + self.tabBarController?.tabBar.isHidden = true + } + + func configureTitleLabelLayout() { + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.titleLabel) + NSLayoutConstraint.activate([ + self.titleLabel.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: Constant.topVerticalSpace), + self.titleLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor) + ]) + } + + func configureRecordViewLayout() { + self.recordView.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.recordView) + NSLayoutConstraint.activate([ + self.recordView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.8), + self.recordView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + self.recordView.heightAnchor.constraint(equalTo: self.recordView.widthAnchor, multiplier: 1.5), + self.recordView.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: Constant.defaultVerticalSpace) + ]) + } + + func configureBackButtonLayout() { + self.backButton.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.backButton) + NSLayoutConstraint.activate([ + self.backButton.centerYAnchor.constraint(equalTo: self.titleLabel.centerYAnchor), + self.backButton.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: Constant.topVerticalSpace) + ]) + } + + func configureSaveButtonLayout() { + self.saveRecordButton.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.saveRecordButton) + NSLayoutConstraint.activate([ + self.saveRecordButton.centerXAnchor.constraint(equalTo: self.recordView.centerXAnchor), + self.saveRecordButton.topAnchor.constraint(equalTo: self.recordView.bottomAnchor, constant: Constant.defaultVerticalSpace), + self.saveRecordButton.widthAnchor.constraint(equalTo: self.recordView.widthAnchor), + self.saveRecordButton.heightAnchor.constraint(equalToConstant: Constant.saveButtonHeight) + ]) + } + + func configureDelegates() { + self.recordView.delegate = self + } + + func configureButtonAction() { + self.backButton.addTarget(self, action: #selector(self.backButtonTapped), for: .touchUpInside) + self.saveRecordButton.addTarget(self, action: #selector(self.saveRecordButtonTapped), for: .touchUpInside) + } + + func bindViewModel() { + self.viewModel?.state.observe(on: self) { [weak self] postableState in + self?.saveRecordButton.isEnabled = postableState + self?.saveRecordButton.backgroundColor = postableState ? EDSColor.pumpkin.value : EDSColor.skullGrey.value + } + } +} diff --git a/Escaper/Escaper/Presentation/AddRecord/AddRecordViewModel.swift b/Escaper/Escaper/Presentation/AddRecord/AddRecordViewModel.swift new file mode 100644 index 0000000..cf58917 --- /dev/null +++ b/Escaper/Escaper/Presentation/AddRecord/AddRecordViewModel.swift @@ -0,0 +1,53 @@ +// +// AddRecordViewModel.swift +// Escaper +// +// Created by TakHyun Jung on 2021/11/11. +// + +import Foundation + +protocol AddRecordViewModelInput { + func post(email: String, imageUrlString: String) +} + +protocol AddRecordViewModelOutput { + var roomId: String { get set } + var satisfaction: Rating { get set } + var isSuccess: Bool { get set } + var time: Int { get set } + var state: Observable { get } + + func changeSaveState() +} + +class AddRecordViewModel: AddRecordViewModelInput, AddRecordViewModelOutput { + private let usecase: RecordUsecaseInterface + var roomId: String + var satisfaction: Rating + var isSuccess: Bool + var time: Int + var state: Observable + + init(usecase: RecordUsecaseInterface) { + self.usecase = usecase + self.roomId = "" + self.satisfaction = .four + self.isSuccess = true + self.time = 0 + self.state = Observable(false) + } + + func changeSaveState() { + self.state.value = !self.roomId.isEmpty && self.time != 0 + } + + func post(email: String, imageUrlString: String) { + self.usecase.addRecord(imageUrlString: imageUrlString, + userEmail: email, + roomId: self.roomId, + satisfaction: self.satisfaction, + isSuccess: self.isSuccess, + time: self.time) + } +} diff --git a/Escaper/Escaper/Presentation/AddRecord/View/RecordView.swift b/Escaper/Escaper/Presentation/AddRecord/View/RecordView.swift new file mode 100644 index 0000000..4497895 --- /dev/null +++ b/Escaper/Escaper/Presentation/AddRecord/View/RecordView.swift @@ -0,0 +1,256 @@ +// +// RecordView.swift +// Escaper +// +// Created by TakHyun Jung on 2021/11/10. +// + +import UIKit + +protocol RecordViewDelegate: AnyObject { + func findRoomTitleButtonTapped() + func userImageViewTapped() + func escapingTimePickerButtonTapped() + func updateRoom(identifer: String) + func updateIsSuccess(_ isSuccess: Bool) + func updateEscapingTime(time: Int) +} + +class RecordView: UIView { + enum Constant { + static let stackViewSpacing = CGFloat(20) + } + + private let recordBackgroundImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = EDSImage.recordCard.value + imageView.contentMode = .scaleAspectFit + return imageView + }() + private let userSelectedImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = EDSImage.plus.value + imageView.backgroundColor = EDSColor.charcoal.value + imageView.layer.cornerRadius = 25 + imageView.layer.masksToBounds = true + return imageView + }() + private let findRoomTitleButton: UIButton = { + let button = UIButton() + let configuration = UIImage.SymbolConfiguration(pointSize: 20, weight: .bold) + button.setImage(UIImage(systemName: "magnifyingglass", withConfiguration: configuration), for: .normal) + button.setTitle("방이름을 찾아주세요", for: .normal) + button.setTitleColor(EDSColor.charcoal.value, for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 20, weight: .bold) + button.tintColor = .black + button.semanticContentAttribute = .forceRightToLeft + return button + }() + private let roomStoreTitleLabel = EDSLabel.b01R(text: "업체정보", color: .charcoal) + private let satisfactionLabel: UILabel = EDSLabel.b01B(text: "만족도", color: .charcoal) + private let escapingStatusLabel = EDSLabel.b01B(text: "탈출 여부", color: .charcoal) + private let escapingTimeLabel = EDSLabel.b01B(text: "탈출 시간", color: .charcoal) + private let satisFactionRatingView = RatingView() + private let escapingStatusSegmentControl: UISegmentedControl = { + let segmentControl = UISegmentedControl(items: ["Success", "Fail"]) + segmentControl.tintColor = .clear + segmentControl.layer.backgroundColor = EDSColor.skullLightWhite.value!.cgColor + segmentControl.selectedSegmentIndex = 0 + segmentControl.selectedSegmentTintColor = EDSColor.pumpkin.value + return segmentControl + }() + private let escapingTimePickerButton: UIButton = { + let button = UIButton() + button.setTitle("00 : 00 : 00", for: .normal) + button.setTitleColor(EDSColor.charcoal.value, for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 10, weight: .bold) + button.backgroundColor = EDSColor.skullWhite.value + button.layer.cornerRadius = 5 + button.layer.masksToBounds = true + return button + }() + private let roomInformationStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .center + stackView.distribution = .fill + stackView.spacing = 10 + return stackView + }() + private let escapingInformationStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .leading + stackView.distribution = .fillEqually + stackView.spacing = Constant.stackViewSpacing + return stackView + }() + private let escapingInputStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .leading + stackView.distribution = .fillEqually + stackView.spacing = Constant.stackViewSpacing + return stackView + }() + private let escapingStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.alignment = .fill + stackView.distribution = .fill + stackView.spacing = Constant.stackViewSpacing + return stackView + }() + + weak var delegate: RecordViewDelegate? + + override init(frame: CGRect) { + super.init(frame: frame) + self.configure() + self.configureLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configure() + self.configureLayout() + } + + @objc func findRoomButtonTouched() { + self.delegate?.findRoomTitleButtonTapped() + } + + @objc func escapingRoomButtonTouched() { + self.delegate?.escapingTimePickerButtonTapped() + } + + @objc func escapingStatusSegmentControlChanged() { + let currentColor = self.escapingStatusSegmentControl.selectedSegmentIndex == 0 ? EDSColor.pumpkin : EDSColor.bloodyRed + self.escapingStatusSegmentControl.selectedSegmentTintColor = currentColor.value + self.delegate?.updateIsSuccess(self.escapingStatusSegmentControl.selectedSegmentIndex == 0) + } + + @objc func userSelectedImageViewTapped() { + self.delegate?.userImageViewTapped() + } + + func updateRoomInformation(_ room: Room) { + self.findRoomTitleButton.setTitle(room.name, for: .normal) + self.findRoomTitleButton.setImage(nil, for: .normal) + self.roomStoreTitleLabel.text = room.storeName + self.delegate?.updateRoom(identifer: room.identifier) + } + + func updateTimePicker(hour: Int, minutes: Int, seconds: Int) { + let timeString = "\(String(format: "%02d", hour)) : \(String(format: "%02d", minutes)) : \(String(format: "%02d", seconds))" + self.escapingTimePickerButton.setTitle(timeString, for: .normal) + self.delegate?.updateEscapingTime(time: hour*3600 + minutes*60 + seconds) + } + + func updateUserSelectedImage(_ image: UIImage) { + self.userSelectedImageView.image = image + } + + func fetchSelectedImage() -> UIImage? { + return self.userSelectedImageView.image + } +} + +private extension RecordView { + func configure() { + self.configureImageViewEvent() + self.configureAddTarget() + } + + func configureLayout() { + self.configureRecordBackgroundImageViewLayout() + self.configureImagePickerButtonLayout() + self.configurArrrangeSubViews() + self.configureRatingViewLayout() + self.configureEscapingtimePickerButtonLayout() + self.configureRoomInformationStackViewLayout() + self.configureEscapingStackViewLayout() + } + + func configureRecordBackgroundImageViewLayout() { + self.recordBackgroundImageView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.recordBackgroundImageView) + NSLayoutConstraint.activate([ + self.recordBackgroundImageView.topAnchor.constraint(equalTo: self.topAnchor), + self.recordBackgroundImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.recordBackgroundImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.recordBackgroundImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor) + ]) + } + + func configureImagePickerButtonLayout() { + self.userSelectedImageView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.userSelectedImageView) + NSLayoutConstraint.activate([ + self.userSelectedImageView.heightAnchor.constraint(equalTo: self.recordBackgroundImageView.heightAnchor, multiplier: 0.2), + self.userSelectedImageView.widthAnchor.constraint(equalTo: self.userSelectedImageView.heightAnchor), + self.userSelectedImageView.topAnchor.constraint(equalTo: self.recordBackgroundImageView.topAnchor, constant: 15), + self.userSelectedImageView.centerXAnchor.constraint(equalTo: self.recordBackgroundImageView.centerXAnchor) + ]) + } + + func configurArrrangeSubViews() { + self.roomInformationStackView.addArrangedSubview(self.findRoomTitleButton) + self.roomInformationStackView.addArrangedSubview(self.roomStoreTitleLabel) + self.escapingInformationStackView.addArrangedSubview(self.satisfactionLabel) + self.escapingInformationStackView.addArrangedSubview(self.escapingStatusLabel) + self.escapingInformationStackView.addArrangedSubview(self.escapingTimeLabel) + self.escapingInputStackView.addArrangedSubview(self.satisFactionRatingView) + self.escapingInputStackView.addArrangedSubview(self.escapingStatusSegmentControl) + self.escapingInputStackView.addArrangedSubview(self.escapingTimePickerButton) + self.escapingStackView.addArrangedSubview(self.escapingInformationStackView) + self.escapingStackView.addArrangedSubview(self.escapingInputStackView) + } + + func configureRatingViewLayout() { + self.satisFactionRatingView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.satisFactionRatingView.heightAnchor.constraint(equalToConstant: 23), + self.satisFactionRatingView.widthAnchor.constraint(equalToConstant: 23 * CGFloat(Rating.maxRating) + RatingView.Constant.summationOfElementSpacing) + ]) + } + + func configureEscapingtimePickerButtonLayout() { + self.escapingTimePickerButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.escapingTimePickerButton.widthAnchor.constraint(equalTo: self.escapingInputStackView.widthAnchor, multiplier: 0.5) + ]) + } + + func configureRoomInformationStackViewLayout() { + self.roomInformationStackView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.roomInformationStackView) + NSLayoutConstraint.activate([ + self.roomInformationStackView.topAnchor.constraint(equalTo: self.userSelectedImageView.bottomAnchor, constant: 30), + self.roomInformationStackView.centerXAnchor.constraint(equalTo: self.recordBackgroundImageView.centerXAnchor), + self.roomInformationStackView.widthAnchor.constraint(lessThanOrEqualTo: self.recordBackgroundImageView.widthAnchor) + ]) + } + + func configureEscapingStackViewLayout() { + self.escapingStackView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.escapingStackView) + NSLayoutConstraint.activate([ + self.escapingStackView.centerXAnchor.constraint(equalTo: self.recordBackgroundImageView.centerXAnchor), + self.escapingStackView.topAnchor.constraint(equalTo: self.roomInformationStackView.bottomAnchor, constant: 30), + self.escapingStackView.widthAnchor.constraint(lessThanOrEqualTo: self.recordBackgroundImageView.widthAnchor) + ]) + } + + func configureImageViewEvent() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.userSelectedImageViewTapped)) + self.userSelectedImageView.addGestureRecognizer(tapGesture) + self.userSelectedImageView.isUserInteractionEnabled = true + } + + func configureAddTarget() { + self.findRoomTitleButton.addTarget(self, action: #selector(self.findRoomButtonTouched), for: .touchUpInside) + self.escapingTimePickerButton.addTarget(self, action: #selector(self.escapingRoomButtonTouched), for: .touchUpInside) + self.escapingStatusSegmentControl.addTarget(self, action: #selector(self.escapingStatusSegmentControlChanged), for: .valueChanged) + } +} diff --git a/Escaper/Escaper/Presentation/Common/DefaultViewController.swift b/Escaper/Escaper/Presentation/Common/DefaultViewController.swift index 4bd45e3..ce06114 100644 --- a/Escaper/Escaper/Presentation/Common/DefaultViewController.swift +++ b/Escaper/Escaper/Presentation/Common/DefaultViewController.swift @@ -10,7 +10,6 @@ import UIKit class DefaultViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - self.view.backgroundColor = UIColor(named: ColorPalette.bloodyBlack.name) + self.view.backgroundColor = EDSColor.bloodyBlack.value } - } diff --git a/Escaper/Escaper/Presentation/Common/DistrictSelectButton.swift b/Escaper/Escaper/Presentation/Common/DistrictSelectButton.swift new file mode 100644 index 0000000..21dc335 --- /dev/null +++ b/Escaper/Escaper/Presentation/Common/DistrictSelectButton.swift @@ -0,0 +1,118 @@ +// +// DistrictSelectButton.swift +// Escaper +// +// Created by 최완식 on 2021/11/09. +// + +import UIKit + +protocol DistrictSelectViewDelegate: AnyObject { + func districtDidSelected(district: District) +} + +class DistrictSelectButton: UIButton { + private enum Constant { + static let sideSpace = CGFloat(5) + static let districtLabelWidth = CGFloat(61) + static let infoLabelWidth = CGFloat(25) + static let imageLength = CGFloat(14) + } + + weak var delegate: DistrictSelectViewDelegate? + + private var districtLabel: UILabel = { + let label = EDSLabel.b01B(text: " 지역 ", color: .pumpkin) + label.textAlignment = .center + return label + }() + private var infoLabel: UILabel = { + let label = EDSLabel.b01B(text: "선택 ", color: .pumpkin) + label.textAlignment = .right + return label + }() + private var indicatorImageView = UIImageView(image: EDSImage.chevronDown.value) + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configure() + } + + override init(frame: CGRect) { + super.init(frame: frame) + self.configure() + } + + override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { + let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in + return self?.makeMenu() + } + return configuration + } + + func updateTitle(district: District) { + self.districtLabel.text = " \(district.name) " + self.infoLabel.text = "기준 " + } +} + +private extension DistrictSelectButton { + func configure() { + self.configureUI() + self.configureIndicatorImageViewLayout() + self.configureDistrictLayout() + self.configureInfoLabelLayout() + } + + func configureUI() { + self.backgroundColor = EDSColor.bloodyBlack.value + if #available(iOS 14.0, *) { + self.showsMenuAsPrimaryAction = true + self.menu = self.makeMenu() + } else { + let interaction = UIContextMenuInteraction(delegate: self) + self.addInteraction(interaction) + } + } + + func configureDistrictLayout() { + self.districtLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.districtLabel) + NSLayoutConstraint.activate([ + self.districtLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor), + self.districtLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.districtLabel.widthAnchor.constraint(equalToConstant: Constant.districtLabelWidth) + ]) + } + + func configureInfoLabelLayout() { + self.infoLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.infoLabel) + NSLayoutConstraint.activate([ + self.infoLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor), + self.infoLabel.trailingAnchor.constraint(equalTo: self.indicatorImageView.leadingAnchor, constant: -Constant.sideSpace), + self.infoLabel.widthAnchor.constraint(equalToConstant: Constant.infoLabelWidth) + ]) + } + + func configureIndicatorImageViewLayout() { + self.indicatorImageView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.indicatorImageView) + NSLayoutConstraint.activate([ + self.indicatorImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor), + self.indicatorImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor), + self.indicatorImageView.widthAnchor.constraint(equalToConstant: Constant.imageLength), + self.indicatorImageView.heightAnchor.constraint(equalToConstant: Constant.imageLength) + ]) + } + + func makeMenu() -> UIMenu { + let districtActions = District.allCases.map { district in + UIAction(title: district.name) { [weak self] _ in + self?.delegate?.districtDidSelected(district: district) + self?.updateTitle(district: district) + } + } + return UIMenu(title: "", children: districtActions) + } +} diff --git a/Escaper/Escaper/Presentation/Common/MainTabBarController.swift b/Escaper/Escaper/Presentation/Common/MainTabBarController.swift new file mode 100644 index 0000000..678f264 --- /dev/null +++ b/Escaper/Escaper/Presentation/Common/MainTabBarController.swift @@ -0,0 +1,115 @@ +// +// MainTabBarController.swift +// Escaper +// +// Created by 최완식 on 2021/11/09. +// + +import UIKit + +class MainTabBarController: UITabBarController { + enum Constant { + static let itemInset = CGFloat(10) + } + + override func viewDidLoad() { + super.viewDidLoad() + self.configureTabBar() + self.configureSubViewControllers() + } +} + +private extension MainTabBarController { + enum TabBarItemConfig: String { + var title: String { + return self.rawValue + } + var unselectedImage: UIImage? { + return UIImage(named: String(describing: self)) + } + var selectedImage: UIImage? { + return UIImage(named: String(describing: self) + "Selected") + } + + case home = "홈" + case record = "기록" + case map = "지도" + case leaderBoard = "리더보드" + } + + func configureTabBar() { + let homeBarItem = self.makeTabBarItem( + title: TabBarItemConfig.home.title, + unselected: TabBarItemConfig.home.unselectedImage, + selected: TabBarItemConfig.home.selectedImage + ) + let homeViewController = RoomListViewController() + homeViewController.tabBarItem = homeBarItem + homeViewController.create() + let homeNavigationController = UINavigationController(rootViewController: homeViewController) + let navigationAppearance: UINavigationBarAppearance = { + let appearance = UINavigationBarAppearance() + appearance.backgroundColor = EDSColor.gloomyBrown.value + return appearance + }() + homeViewController.navigationController?.navigationBar.standardAppearance = navigationAppearance + homeViewController.navigationController?.navigationBar.tintColor = EDSColor.skullLightWhite.value + homeViewController.navigationController?.navigationBar.barTintColor = EDSColor.skullLightWhite.value + if #available(iOS 15.0, *) { + homeViewController.navigationController?.navigationBar.compactScrollEdgeAppearance = navigationAppearance + homeViewController.navigationController?.navigationBar.scrollEdgeAppearance = navigationAppearance + } + let recordBarItem = self.makeTabBarItem( + title: TabBarItemConfig.record.title, + unselected: TabBarItemConfig.record.unselectedImage, + selected: TabBarItemConfig.record.selectedImage + ) + let recordViewController = RecordViewController() + recordViewController.tabBarItem = recordBarItem + recordViewController.create() + let mapBarItem = self.makeTabBarItem( + title: TabBarItemConfig.map.title, + unselected: TabBarItemConfig.map.unselectedImage, + selected: TabBarItemConfig.map.selectedImage + ) + let mapViewController = MapViewController() + mapViewController.tabBarItem = mapBarItem + mapViewController.create() + let leaderBoardBarItem = self.makeTabBarItem( + title: TabBarItemConfig.leaderBoard.title, + unselected: TabBarItemConfig.leaderBoard.unselectedImage, + selected: TabBarItemConfig.leaderBoard.selectedImage + ) + let leaderBoardViewController = LeaderBoardViewController() + leaderBoardViewController.tabBarItem = leaderBoardBarItem + leaderBoardViewController.create() + let viewControllers = [ + homeNavigationController, + recordViewController, + mapViewController, + leaderBoardViewController + ] + self.viewControllers = viewControllers + } + + func configureSubViewControllers() { + let tabBarAppearance: UITabBarAppearance = { + let appearance = UITabBarAppearance() + appearance.backgroundColor = EDSColor.gloomyBrown.value + return appearance + }() + self.tabBar.standardAppearance = tabBarAppearance + self.tabBar.tintColor = EDSColor.skullLightWhite.value + self.tabBar.barTintColor = EDSColor.skullLightWhite.value + } + + func makeTabBarItem(title: String, unselected: UIImage?, selected: UIImage?) -> UITabBarItem { + let item = UITabBarItem( + title: title, + image: unselected, + selectedImage: selected + ) + item.imageInsets = UIEdgeInsets(top: Constant.itemInset, left: Constant.itemInset, bottom: Constant.itemInset, right: Constant.itemInset) + return item + } +} diff --git a/Escaper/Escaper/Presentation/FindRoom/FindRoomTableViewCell.swift b/Escaper/Escaper/Presentation/FindRoom/FindRoomTableViewCell.swift new file mode 100644 index 0000000..355f610 --- /dev/null +++ b/Escaper/Escaper/Presentation/FindRoom/FindRoomTableViewCell.swift @@ -0,0 +1,64 @@ +// +// FindRoomTableViewCell.swift +// Escaper +// +// Created by TakHyun Jung on 2021/11/10. +// + +import UIKit + +class FindRoomTableViewCell: UITableViewCell { + static let identifier = String(describing: RoomOverviewTableViewCell.self) + + private let titleLabel = EDSLabel.b01B(color: .pumpkin) + private let storeNameLabel = EDSLabel.b03R(color: .skullLightWhite) + private let stackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .leading + stackView.distribution = .fillProportionally + return stackView + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.configure() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configure() + } + + func update(_ room: Room) { + self.titleLabel.text = room.name + self.storeNameLabel.text = room.storeName + } +} + +private extension FindRoomTableViewCell { + func configure() { + self.configureStackViewLayout() + self.clearBackgroundSelectedCell() + } + + func configureStackViewLayout() { + self.stackView.translatesAutoresizingMaskIntoConstraints = false + self.stackView.addArrangedSubview(self.titleLabel) + self.stackView.addArrangedSubview(self.storeNameLabel) + self.contentView.addSubview(self.stackView) + NSLayoutConstraint.activate([ + self.stackView.topAnchor.constraint(equalTo: self.contentView.topAnchor), + self.stackView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor), + self.stackView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor), + self.stackView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor) + ]) + } + + func clearBackgroundSelectedCell() { + let bgColorView = UIView() + bgColorView.backgroundColor = .clear + self.selectedBackgroundView = bgColorView + self.backgroundColor = .clear + } +} diff --git a/Escaper/Escaper/Presentation/FindRoom/FindRoomViewController.swift b/Escaper/Escaper/Presentation/FindRoom/FindRoomViewController.swift new file mode 100644 index 0000000..0d70ceb --- /dev/null +++ b/Escaper/Escaper/Presentation/FindRoom/FindRoomViewController.swift @@ -0,0 +1,164 @@ +// +// FindRoomViewController.swift +// Escaper +// +// Created by TakHyun Jung on 2021/11/10. +// + +import UIKit + +protocol RoomInformationTransferable: AnyObject { + func transfer(room: Room) +} + +class FindRoomViewController: UIViewController { + enum Constant { + static let verticalSpace = CGFloat(20) + } + + enum Section { + case main + } + + private var viewModel: (FindRoomViewModelInput & FindRoomViewModelOutput)? + private var searchRequestWorkItem: DispatchWorkItem? + weak var roomTransferDelegate: RoomInformationTransferable? + private let containerView: UIView = { + let view = UIView() + view.backgroundColor = EDSColor.gloomyBrown.value + return view + }() + private let searchBar: UISearchBar = { + let searchBar = UISearchBar() + searchBar.searchBarStyle = .minimal + return searchBar + }() + private let roomListTableView: UITableView = { + let tableView = UITableView() + tableView.backgroundColor = .clear + tableView.rowHeight = 50 + tableView.keyboardDismissMode = .onDrag + return tableView + }() + private var dataSource: UITableViewDiffableDataSource? + + override func viewDidLoad() { + super.viewDidLoad() + self.configure() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + self.containerView.roundCorners(corners: [.topLeft, .topRight], radius: 50) + } + + func create() { + let repository = RoomListRepository(service: FirebaseService.shared) + let usecase = RoomListUseCase(repository: repository) + let viewModel = FindRoomViewModel(usecase: usecase) + self.viewModel = viewModel + } + + @objc func clearViewTapped(_ sender: UITapGestureRecognizer) { + let tappedPoint = sender.location(in: self.containerView) + guard self.containerView.hitTest(tappedPoint, with: nil) == nil else { return } + self.dismiss(animated: true) + } +} + +extension FindRoomViewController: UISearchBarDelegate { + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + self.searchRequestWorkItem?.cancel() + let requestWorkItem = DispatchWorkItem { [weak self] in + self?.viewModel?.fetch(name: searchText) + } + self.searchRequestWorkItem = requestWorkItem + DispatchQueue.global().asyncAfter(deadline: .now() + 0.2, execute: requestWorkItem) + } +} + +extension FindRoomViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let selectedRoom = self.dataSource?.itemIdentifier(for: indexPath) else { return } + self.roomTransferDelegate?.transfer(room: selectedRoom) + self.dismiss(animated: true) + } +} + +private extension FindRoomViewController { + func configure() { + self.configureContainerViewLayout() + self.configureSearchBarLayout() + self.configureRoomListTableViewLayout() + self.configureRoomListTableView() + self.bindViewModel() + self.configureDelegates() + self.configureTapGesture() + self.view.backgroundColor = .clear + self.searchBar.becomeFirstResponder() + } + + func configureContainerViewLayout() { + self.containerView.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.containerView) + NSLayoutConstraint.activate([ + self.containerView.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.85), + self.containerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), + self.containerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + self.containerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) + ]) + } + + func configureSearchBarLayout() { + self.searchBar.setTextFieldColor(color: .white) + self.searchBar.translatesAutoresizingMaskIntoConstraints = false + self.containerView.addSubview(self.searchBar) + NSLayoutConstraint.activate([ + self.searchBar.topAnchor.constraint(equalTo: self.containerView.topAnchor, constant: Constant.verticalSpace), + self.searchBar.centerXAnchor.constraint(equalTo: self.containerView.centerXAnchor), + self.searchBar.widthAnchor.constraint(equalTo: self.containerView.widthAnchor, multiplier: 0.85) + ]) + } + + func configureRoomListTableViewLayout() { + self.roomListTableView.translatesAutoresizingMaskIntoConstraints = false + self.containerView.addSubview(self.roomListTableView) + NSLayoutConstraint.activate([ + self.roomListTableView.topAnchor.constraint(equalTo: self.searchBar.bottomAnchor, constant: Constant.verticalSpace), + self.roomListTableView.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor), + self.roomListTableView.leadingAnchor.constraint(equalTo: self.searchBar.leadingAnchor), + self.roomListTableView.trailingAnchor.constraint(equalTo: self.searchBar.trailingAnchor) + ]) + } + + func configureRoomListTableView() { + self.roomListTableView.register(FindRoomTableViewCell.self, forCellReuseIdentifier: FindRoomTableViewCell.identifier) + self.dataSource = UITableViewDiffableDataSource(tableView: self.roomListTableView) { (_: UITableView, _: IndexPath, room: Room) -> UITableViewCell? in + let cell = self.roomListTableView.dequeueReusableCell(withIdentifier: FindRoomTableViewCell.identifier) as? FindRoomTableViewCell + cell?.update(room) + return cell + } + } + + func bindViewModel() { + self.viewModel?.rooms.observe(on: self) { [weak self] roomList in + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([Section.main]) + snapshot.appendItems(roomList) + self?.dataSource?.apply(snapshot, animatingDifferences: true) + } + self.viewModel?.fetch(name: "") + } + + func configureDelegates() { + self.searchBar.delegate = self + self.roomListTableView.delegate = self + } + + func configureTapGesture() { + let clearViewTapGesture = UITapGestureRecognizer(target: self, action: #selector(self.clearViewTapped)) + clearViewTapGesture.cancelsTouchesInView = false + self.view.addGestureRecognizer(clearViewTapGesture) + self.view.isUserInteractionEnabled = true + } +} diff --git a/Escaper/Escaper/Presentation/FindRoom/FindRoomViewModel.swift b/Escaper/Escaper/Presentation/FindRoom/FindRoomViewModel.swift new file mode 100644 index 0000000..7dd1662 --- /dev/null +++ b/Escaper/Escaper/Presentation/FindRoom/FindRoomViewModel.swift @@ -0,0 +1,37 @@ +// +// FindRoomViewModel.swift +// Escaper +// +// Created by TakHyun Jung on 2021/11/13. +// + +import Foundation + +protocol FindRoomViewModelInput { + func fetch(name: String) +} + +protocol FindRoomViewModelOutput { + var rooms: Observable<[Room]> { get } +} + +class FindRoomViewModel: FindRoomViewModelInput, FindRoomViewModelOutput { + private let usecase: RoomListUseCaseInterface + private(set) var rooms: Observable<[Room]> + + init(usecase: RoomListUseCaseInterface) { + self.usecase = usecase + self.rooms = Observable([]) + } + + func fetch(name: String) { + self.usecase.fetch(name: name) { result in + switch result { + case .success(let rooms): + self.rooms.value = rooms + case .failure(let error): + print(error) + } + } + } +} diff --git a/Escaper/Escaper/Presentation/LeaderBoard/LeaderBoardViewController.swift b/Escaper/Escaper/Presentation/LeaderBoard/LeaderBoardViewController.swift new file mode 100644 index 0000000..23fd04e --- /dev/null +++ b/Escaper/Escaper/Presentation/LeaderBoard/LeaderBoardViewController.swift @@ -0,0 +1,18 @@ +// +// LeaderBoardViewController.swift +// Escaper +// +// Created by 최완식 on 2021/11/09. +// + +import UIKit + +class LeaderBoardViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + } + + func create() { + // 의존성 주입 + } +} diff --git a/Escaper/Escaper/Presentation/Map/MapViewController.swift b/Escaper/Escaper/Presentation/Map/MapViewController.swift new file mode 100644 index 0000000..5218e2e --- /dev/null +++ b/Escaper/Escaper/Presentation/Map/MapViewController.swift @@ -0,0 +1,18 @@ +// +// MapViewController.swift +// Escaper +// +// Created by 최완식 on 2021/11/09. +// + +import UIKit + +class MapViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + } + + func create() { + // 의존성 주입 + } +} diff --git a/Escaper/Escaper/Presentation/Record/RecordViewController.swift b/Escaper/Escaper/Presentation/Record/RecordViewController.swift new file mode 100644 index 0000000..758e998 --- /dev/null +++ b/Escaper/Escaper/Presentation/Record/RecordViewController.swift @@ -0,0 +1,286 @@ +// +// RecordViewController.swift +// Escaper +// +// Created by 최완식 on 2021/11/09. +// + +import UIKit + +class RecordViewController: DefaultViewController { + private enum Constant { + static let deviceHeight = UIScreen.main.bounds.size.height + static let topBottomVerticalSpace = deviceHeight * 0.04 + static let defaultVerticalSpace = deviceHeight * 0.015 + static let collectionViewHeight = deviceHeight * 0.55 + static let buttonHeight = deviceHeight * 0.06 + static let longHorizontalSpace = CGFloat(120) + static let shortHorizontalSpace = CGFloat(60) + } + + private enum GreetingMessage: String { + case level0 = "방탈출이 처음이시군요!" + case level1 = "방탈출 세계에 입문하신걸 축하드립니다!" + case level2 = "조금만 더 하면 나도 방탈출 고수!" + case level3 = "이정도면, 왠만한 방은 거의 탈출해보셨겠는걸요?" + + var value: String { + return self.rawValue + } + } + + private enum Section: Int { + case card = 0 + + var index: Int { + return self.rawValue + } + } + + private typealias Datasource = UICollectionViewDiffableDataSource + + private var viewModel: RecordViewModel? + private var datasource: Datasource? + private let titleLabel: UILabel = { + let label: UILabel = EDSLabel.h01B(text: "탈출 기록", color: .skullWhite) + label.textAlignment = .center + return label + }() + private let countingLabel: UILabel = { + let label = UILabel() + label.textColor = EDSColor.pumpkin.value + label.font = UIFont.systemFont(ofSize: 26, weight: .bold) + label.textAlignment = .center + return label + }() + private let greetingLabel: UILabel = { + let label: UILabel = EDSLabel.b02B(text: "", color: .skullWhite) + label.textAlignment = .center + return label + }() + private let recordCollectionView: UICollectionView = { + let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.estimatedItemSize = .zero + let collectionView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.contentInsetAdjustmentBehavior = .never + collectionView.backgroundColor = .clear + collectionView.contentInset = UIEdgeInsets(top: 0, left: UIScreen.main.bounds.size.width*0.1, bottom: 0, right: UIScreen.main.bounds.size.width*0.1) + collectionView.showsHorizontalScrollIndicator = false + collectionView.decelerationRate = .fast + return collectionView + }() + private let addButton: UIButton = { + let button: UIButton = UIButton() + button.setTitle("추가하기", for: .normal) + button.setTitleColor(EDSColor.bloodyBlack.value, for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 20, weight: .heavy) + button.titleLabel?.textAlignment = .center + button.backgroundColor = EDSColor.pumpkin.value + button.layer.cornerRadius = 10 + return button + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.configure() + self.configureLayout() + self.bindViewModel() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.viewModel?.records.value.removeAll() + self.viewModel?.fetch(userEmail: "kessler.myah@hotmail.com") + } + + func create() { + let roomRepository = RoomListRepository(service: FirebaseService.shared) + let recordRepository = RecordRepository(service: FirebaseService.shared) + let usecase = RecordUsecase(roomRepository: roomRepository, recordRepository: recordRepository) + let viewModel = DefaultRecordViewModel(useCase: usecase) + self.viewModel = viewModel + } + + func bindViewModel() { + self.viewModel?.records.observe(on: self) { [weak self] result in + self?.configureRecordCollectionViewData(records: result) + } + } + + @objc func addButtonTapped() { + let addRecordViewController = AddRecordViewController() + addRecordViewController.create() + addRecordViewController.modalPresentationStyle = .fullScreen + self.present(addRecordViewController, animated: true) + } +} + +extension RecordViewController: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return CGFloat(10) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: UIScreen.main.bounds.size.width*0.8, height: Constant.deviceHeight*0.55) + } + + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + let cellWidthIncludingSpacing = UIScreen.main.bounds.size.width*0.8 + CGFloat(10) + var offset = targetContentOffset.pointee + let index = (offset.x + scrollView.contentInset.left) / cellWidthIncludingSpacing + let roundedIndex = round(index) + offset = CGPoint(x: roundedIndex * cellWidthIncludingSpacing - scrollView.contentInset.left, y: scrollView.contentInset.top) + targetContentOffset.pointee = offset + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + let cellWidthIncludingSpacing = UIScreen.main.bounds.size.width*0.8 + CGFloat(10) + let offsetX = self.recordCollectionView.contentOffset.x + let index = (offsetX + self.recordCollectionView.contentInset.left) / cellWidthIncludingSpacing + let roundedIndex = round(index) + let indexPath = IndexPath(item: Int(roundedIndex), section: Section.card.index) + if let cell = self.recordCollectionView.cellForItem(at: indexPath) { + self.animateZoomforCell(zoomCell: cell) + } + if Int(roundedIndex) != 0 { + let index = Int(roundedIndex) - 1 + let indexPath = IndexPath(item: index, section: Section.card.index) + if let previousCell = self.recordCollectionView.cellForItem(at: indexPath) { + self.animateZoomforCellremove(zoomCell: previousCell) + } + } + if Int(roundedIndex) != self.recordCollectionView.numberOfItems(inSection: Section.card.index)-1 { + let index = Int(roundedIndex) + 1 + let indexPath = IndexPath(item: index, section: Section.card.index) + if let nextCell = self.recordCollectionView.cellForItem(at: indexPath) { + self.animateZoomforCellremove(zoomCell: nextCell) + } + } + } +} + +private extension RecordViewController { + func configure() { + self.configureRecordCollectionView() + } + + func configureLayout() { + self.configureTitleLabelLayout() + self.configureCountingLabelLayout() + self.configureGreetingLabelLayout() + self.configureRecordCollectionViewLayout() + self.configureAddButtonLayout() + } + + func configureRecordCollectionView() { + self.recordCollectionView.delegate = self + self.datasource = Datasource(collectionView: self.recordCollectionView) { (collectionView, indexPath, itemIdentifier) -> UICollectionViewCell in + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RecordCollectionViewCell.identifier, for: indexPath) as? RecordCollectionViewCell else { return UICollectionViewCell() } + cell.update(record: itemIdentifier) + return cell + } + self.recordCollectionView.register(RecordCollectionViewCell.self, forCellWithReuseIdentifier: RecordCollectionViewCell.identifier) + self.addButton.addTarget(self, action: #selector(self.addButtonTapped), for: .touchUpInside) + } + + func configureRecordCollectionViewData(records: [Record]) { + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([Section.card]) + snapshot.appendItems(records, toSection: Section.card) + self.datasource?.apply(snapshot, animatingDifferences: false) + if records.count > 1 { + let indexPath = IndexPath(item: 1, section: Section.card.index) + if let cell = self.recordCollectionView.cellForItem(at: indexPath) { + animateZoomforCellremove(zoomCell: cell) + } + } + configureRecordSummary(count: records.count) + } + + func configureRecordSummary(count: Int) { + self.countingLabel.text = String(count) + switch count { + case 0: + self.greetingLabel.text = GreetingMessage.level0.value + case 1..<10: + self.greetingLabel.text = GreetingMessage.level1.value + case 10..<25: + self.greetingLabel.text = GreetingMessage.level2.value + default: + self.greetingLabel.text = GreetingMessage.level3.value + } + } + + func configureTitleLabelLayout() { + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.titleLabel) + NSLayoutConstraint.activate([ + self.titleLabel.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: Constant.defaultVerticalSpace), + self.titleLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constant.longHorizontalSpace), + self.titleLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constant.longHorizontalSpace) + ]) + } + + func configureCountingLabelLayout() { + self.countingLabel.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.countingLabel) + NSLayoutConstraint.activate([ + self.countingLabel.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: Constant.defaultVerticalSpace), + self.countingLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constant.longHorizontalSpace), + self.countingLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constant.longHorizontalSpace) + ]) + } + + func configureGreetingLabelLayout() { + self.greetingLabel.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.greetingLabel) + NSLayoutConstraint.activate([ + self.greetingLabel.topAnchor.constraint(equalTo: self.countingLabel.bottomAnchor, constant: Constant.defaultVerticalSpace), + self.greetingLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constant.shortHorizontalSpace), + self.greetingLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constant.shortHorizontalSpace) + ]) + } + + func configureRecordCollectionViewLayout() { + self.recordCollectionView.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.recordCollectionView) + NSLayoutConstraint.activate([ + self.recordCollectionView.topAnchor.constraint(equalTo: self.greetingLabel.bottomAnchor, constant: Constant.defaultVerticalSpace), + self.recordCollectionView.heightAnchor.constraint(equalToConstant: Constant.collectionViewHeight), + self.recordCollectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + self.recordCollectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) + ]) + } + + func configureAddButtonLayout() { + self.addButton.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.addButton) + NSLayoutConstraint.activate([ + self.addButton.topAnchor.constraint(equalTo: self.recordCollectionView.bottomAnchor, constant: Constant.topBottomVerticalSpace), + self.addButton.heightAnchor.constraint(equalToConstant: Constant.buttonHeight), + self.addButton.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: Constant.shortHorizontalSpace), + self.addButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constant.shortHorizontalSpace) + ]) + } + + func animateZoomforCell(zoomCell: UICollectionViewCell) { + UIView.animate(withDuration: 0.2, + delay: 0, + options: .curveEaseOut, + animations: { + zoomCell.transform = CGAffineTransform(scaleX: 1.0, y: 1.0) + }, + completion: nil) + } + + func animateZoomforCellremove(zoomCell: UICollectionViewCell) { + UIView.animate(withDuration: 0, + delay: 0, + options: .curveEaseOut, + animations: { + zoomCell.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) + }, + completion: nil) + } +} diff --git a/Escaper/Escaper/Presentation/Record/RecordViewModel.swift b/Escaper/Escaper/Presentation/Record/RecordViewModel.swift new file mode 100644 index 0000000..e7e7951 --- /dev/null +++ b/Escaper/Escaper/Presentation/Record/RecordViewModel.swift @@ -0,0 +1,40 @@ +// +// RecordViewModel.swift +// Escaper +// +// Created by shinheeRo on 2021/11/11. +// + +import Foundation + + +protocol RecordViewModelInput { + func fetch(userEmail: String) +} + +protocol RecordViewModelOutput { + var records: Observable<[Record]> { get } +} + +protocol RecordViewModel: RecordViewModelInput, RecordViewModelOutput { } + +final class DefaultRecordViewModel: RecordViewModel { + internal var records: Observable<[Record]> + private let useCase: RecordUsecase? + + init(useCase: RecordUsecase) { + self.useCase = useCase + self.records = Observable([]) + } + + func fetch(userEmail: String) { + self.useCase?.fetchAllRecords(userEmail: userEmail) { result in + switch result { + case .success(let record): + self.records.value.append(record) + case .failure(let error): + print(error) + } + } + } +} diff --git a/Escaper/Escaper/Presentation/Record/Views/Cells/RecordCollectionViewCell.swift b/Escaper/Escaper/Presentation/Record/Views/Cells/RecordCollectionViewCell.swift new file mode 100644 index 0000000..ff382a3 --- /dev/null +++ b/Escaper/Escaper/Presentation/Record/Views/Cells/RecordCollectionViewCell.swift @@ -0,0 +1,142 @@ +// +// RecordCollectionViewCell.swift +// Escaper +// +// Created by shinheeRo on 2021/11/09. +// + +import UIKit + +final class RecordCollectionViewCell: UICollectionViewCell { + static let identifier = String(describing: RecordCollectionViewCell.self) + + enum Constant { + static var defaultHorizontalSpace = CGFloat(50) + static var longHorizontalSpace = CGFloat(180) + static var middleHorizontalSpace = CGFloat(120) + static var shortHorizontalSpace = CGFloat(50) + static let longVerticalSpace = CGFloat(30) + static let middleVerticalSpace = CGFloat(15) + static let shortVerticalSpace = CGFloat(10) + } + + private let backgroundImageView: UIImageView = UIImageView(image: UIImage(named: UtilityImage.recordCard.name)) + private let recordHeadView: RecordHeadView = RecordHeadView() + private let recordUserView: RecordUserView = RecordUserView() + private let recordStarView: RecordStarView = RecordStarView() + private let recordResultView: RecordResultView = RecordResultView() + private let shareButton: UIButton = { + let button = UIButton() + button.setTitle("Share", for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .heavy) + button.setTitleColor(EDSColor.skullWhite.value, for: .normal) + button.backgroundColor = EDSColor.gloomyPurple.value + button.layer.cornerRadius = 10 + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configureLayout() + } + + func update(record: Record) { + self.recordHeadView.update(imageURLString: record.imageUrlString, title: record.roomName, place: record.storeName) + self.recordUserView.update(nickname: record.username, result: record.isSuccess) + self.recordStarView.update(satisfaction: record.satisfaction, difficulty: record.difficulty) + self.recordResultView.update(playerRank: record.rank, numberOfPlayers: record.numberOfTotalPlayers, time: record.time) + } + + override func prepareForReuse() { + self.recordHeadView.prepareForReuse() + self.recordUserView.prepareForReuse() + self.recordStarView.prepareForReuse() + self.recordResultView.prepareForReuse() + } +} + +extension RecordCollectionViewCell { + func configureLayout() { + self.configureViewLayout() + self.configureBackgroundImageViewLayout() + self.configureRecordHeadViewLayout() + self.configureRecordUserViewLayout() + self.configureRecordStarViewLayout() + self.configureRecordResultViewLayout() + self.configureShareButtonLayout() + } + + func configureViewLayout() { + self.backgroundColor = EDSColor.bloodyBlack.value + } + + func configureBackgroundImageViewLayout() { + self.backgroundImageView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.backgroundImageView) + NSLayoutConstraint.activate([ + self.backgroundImageView.topAnchor.constraint(equalTo: self.topAnchor), + self.backgroundImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.backgroundImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor), + self.backgroundImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor) + ]) + } + + func configureRecordHeadViewLayout() { + self.recordHeadView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.recordHeadView) + NSLayoutConstraint.activate([ + self.recordHeadView.topAnchor.constraint(equalTo: self.topAnchor, constant: Constant.middleVerticalSpace), + self.recordHeadView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.25), + self.recordHeadView.centerXAnchor.constraint(equalTo: self.centerXAnchor) + ]) + } + + func configureRecordUserViewLayout() { + self.recordUserView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.recordUserView) + NSLayoutConstraint.activate([ + self.recordUserView.topAnchor.constraint(equalTo: self.recordHeadView.bottomAnchor, constant: Constant.longVerticalSpace), + self.recordUserView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 35), + self.recordUserView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -35), + self.recordUserView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.14) + ]) + } + + func configureRecordStarViewLayout() { + self.recordStarView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.recordStarView) + NSLayoutConstraint.activate([ + self.recordStarView.topAnchor.constraint(equalTo: self.recordUserView.bottomAnchor, constant: Constant.middleVerticalSpace), + self.recordStarView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constant.defaultHorizontalSpace), + self.recordStarView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -Constant.defaultHorizontalSpace), + self.recordStarView.heightAnchor.constraint(equalToConstant: CGFloat(40)) + ]) + } + + func configureRecordResultViewLayout() { + self.recordResultView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.recordResultView) + NSLayoutConstraint.activate([ + self.recordResultView.topAnchor.constraint(equalTo: self.recordStarView.bottomAnchor, constant: Constant.middleVerticalSpace), + self.recordResultView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constant.defaultHorizontalSpace), + self.recordResultView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -Constant.defaultHorizontalSpace), + self.recordResultView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.18) + ]) + } + + func configureShareButtonLayout() { + self.shareButton.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.shareButton) + NSLayoutConstraint.activate([ + self.shareButton.topAnchor.constraint(equalTo: self.recordResultView.bottomAnchor, constant: Constant.shortVerticalSpace), + self.shareButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constant.defaultHorizontalSpace), + self.shareButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -Constant.defaultHorizontalSpace), + self.shareButton.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.11) + ]) + } +} diff --git a/Escaper/Escaper/Presentation/Record/Views/Cells/RecordHeadView.swift b/Escaper/Escaper/Presentation/Record/Views/Cells/RecordHeadView.swift new file mode 100644 index 0000000..9392939 --- /dev/null +++ b/Escaper/Escaper/Presentation/Record/Views/Cells/RecordHeadView.swift @@ -0,0 +1,99 @@ +// +// RecordHeadView.swift +// Escaper +// +// Created by shinheeRo on 2021/11/10. +// + +import UIKit + +class RecordHeadView: UIView { + enum Constant { + static let defaultVerticalSpace = CGFloat(10) + } + + private var recordImageView: UIImageView = { + let imageView = UIImageView() + imageView.layer.cornerRadius = CGFloat(15) + imageView.layer.masksToBounds = true + return imageView + }() + private var titleLabel: UILabel = { + let label = EDSLabel.h02B(text: "", color: .bloodyBlack) + label.textAlignment = .center + return label + }() + private var placeLabel: UILabel = { + let label = EDSLabel.b01R(text: "", color: .gloomyPurple) + label.textAlignment = .center + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configureLayout() + } + + func update(imageURLString: String, title: String, place: String) { + self.titleLabel.text = title + self.placeLabel.text = place + ImageCacheManager.shared.download(urlString: imageURLString) { result in + switch result { + case .success(let data): + DispatchQueue.main.async { [weak self] in + self?.recordImageView.image = UIImage(data: data) + } + case .failure(let error): + print(error) + } + } + } + + func prepareForReuse() { + self.recordImageView = UIImageView() + self.titleLabel.text = "" + self.placeLabel.text = "" + } +} + +extension RecordHeadView { + func configureLayout() { + self.configureRecordImageViewLayout() + self.configureTitleLabelLayout() + self.configurePlaceLabelLayout() + } + + func configureRecordImageViewLayout() { + self.recordImageView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.recordImageView) + NSLayoutConstraint.activate([ + self.recordImageView.topAnchor.constraint(equalTo: self.topAnchor), + self.recordImageView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.5), + self.recordImageView.widthAnchor.constraint(equalTo: self.recordImageView.heightAnchor), + self.recordImageView.centerXAnchor.constraint(equalTo: self.centerXAnchor) + ]) + } + + func configureTitleLabelLayout() { + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.titleLabel) + NSLayoutConstraint.activate([ + self.titleLabel.topAnchor.constraint(equalTo: self.recordImageView.bottomAnchor, constant: Constant.defaultVerticalSpace), + self.titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor) + ]) + } + + func configurePlaceLabelLayout() { + self.placeLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.placeLabel) + NSLayoutConstraint.activate([ + self.placeLabel.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: Constant.defaultVerticalSpace), + self.placeLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor) + ]) + } +} diff --git a/Escaper/Escaper/Presentation/Record/Views/Cells/RecordResultView.swift b/Escaper/Escaper/Presentation/Record/Views/Cells/RecordResultView.swift new file mode 100644 index 0000000..6406a7c --- /dev/null +++ b/Escaper/Escaper/Presentation/Record/Views/Cells/RecordResultView.swift @@ -0,0 +1,102 @@ +// +// RecordResultView.swift +// Escaper +// +// Created by shinheeRo on 2021/11/10. +// + +import UIKit + +class RecordResultView: UIView { + private let rankLabel: UILabel = EDSLabel.h03B(text: "Rank", color: .gloomyPurple) + private var rankResultLabel: UILabel = { + let label = UILabel() + label.textColor = EDSColor.bloodyBlack.value + label.font = UIFont.systemFont(ofSize: 34, weight: .bold) + return label + }() + private let timeLabel: UILabel = EDSLabel.h03B(text: "Time", color: .gloomyPurple) + private var timeResultLabel: UILabel = { + let label = UILabel() + label.textColor = EDSColor.bloodyBlack.value + label.font = UIFont.systemFont(ofSize: 34, weight: .bold) + return label + }() + private let rankStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .fill + stackView.alignment = .center + stackView.distribution = .fill + return stackView + }() + private let timeStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .fill + stackView.alignment = .center + stackView.distribution = .fill + return stackView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + configureLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + configureLayout() + } + + func update(playerRank: Int, numberOfPlayers: Int, time: Int) { + self.rankResultLabel.text = String(playerRank) + "/" + String(numberOfPlayers) + self.timeResultLabel.text = String(time/60) + ":" + String(time%60) + } + + func prepareForReuse() { + self.rankResultLabel.text = "" + self.timeResultLabel.text = "" + } +} + +extension RecordResultView { + func configureLayout() { + configureRankStackViewLayout() + configureTimeStackViewLayout() + } + + func configureRankStackViewLayout() { + self.rankStackView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.rankStackView) + NSLayoutConstraint.activate([ + self.rankStackView.topAnchor.constraint(equalTo: self.topAnchor), + self.rankStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.rankStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.rankStackView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.5) + ]) + self.rankStackView.addArrangedSubview(self.rankLabel) + self.rankStackView.addArrangedSubview(self.rankResultLabel) + NSLayoutConstraint.activate([ + self.rankLabel.heightAnchor.constraint(equalTo: self.rankStackView.heightAnchor, multiplier: 0.3), + self.rankResultLabel.heightAnchor.constraint(equalTo: self.rankStackView.heightAnchor, multiplier: 0.7) + ]) + } + + func configureTimeStackViewLayout() { + self.timeStackView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.timeStackView) + NSLayoutConstraint.activate([ + self.timeStackView.topAnchor.constraint(equalTo: self.topAnchor), + self.timeStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor), + self.timeStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.timeStackView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.5) + ]) + self.timeStackView.addArrangedSubview(self.timeLabel) + self.timeStackView.addArrangedSubview(self.timeResultLabel) + NSLayoutConstraint.activate([ + self.timeLabel.heightAnchor.constraint(equalTo: self.timeStackView.heightAnchor, multiplier: 0.3), + self.timeResultLabel.heightAnchor.constraint(equalTo: self.timeStackView.heightAnchor, multiplier: 0.7) + ]) + } +} diff --git a/Escaper/Escaper/Presentation/Record/Views/Cells/RecordStarView.swift b/Escaper/Escaper/Presentation/Record/Views/Cells/RecordStarView.swift new file mode 100644 index 0000000..5a6ace7 --- /dev/null +++ b/Escaper/Escaper/Presentation/Record/Views/Cells/RecordStarView.swift @@ -0,0 +1,123 @@ +// +// RecordStarView.swift +// Escaper +// +// Created by shinheeRo on 2021/11/10. +// + +import UIKit + +class RecordStarView: UIView { + enum Constant { + static let defaultHeight = CGFloat(13) + static let ratingViewWidth = CGFloat(65) + } + + private let popularityLabel: UILabel = EDSLabel.b01B(text: "인기도", color: .gloomyPurple) + private var popularityRatingView: RatingView = RatingView() + private let difficultyLabel: UILabel = EDSLabel.b01B(text: "난이도", color: .gloomyPurple) + private var difficultyRatingView: RatingView = RatingView() + private let popularityHorizontalStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.distribution = .fillEqually + return stackView + }() + private let difficultyHorizontalStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.distribution = .fillEqually + return stackView + }() + private let verticalStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fillEqually + return stackView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configureLayout() + } + + func update(satisfaction: Rating, difficulty: Rating) { + self.popularityRatingView.fill(rating: satisfaction) + self.difficultyRatingView.fill(rating: difficulty) + } + + func prepareForReuse() { + self.popularityRatingView = RatingView() + self.difficultyRatingView = RatingView() + } +} + +extension RecordStarView { + func configureLayout() { + self.configurePopularityHorizontalStackView() + self.configureDifficultyHorizontalStackView() + self.configureVerticalStackView() + self.configurePopularityLabelLayout() + self.configureDifficultyLabelLayout() + self.configurePopularityRatingViewLayout() + self.configureDifficultyRatingViewLayout() + } + + func configurePopularityHorizontalStackView() { + self.popularityHorizontalStackView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.popularityHorizontalStackView) + self.popularityHorizontalStackView.addArrangedSubview(self.popularityLabel) + self.popularityHorizontalStackView.addArrangedSubview(self.popularityRatingView) + } + + func configureDifficultyHorizontalStackView() { + self.difficultyHorizontalStackView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.difficultyHorizontalStackView) + self.difficultyHorizontalStackView.addArrangedSubview(self.difficultyLabel) + self.difficultyHorizontalStackView.addArrangedSubview(self.difficultyRatingView) + } + + func configureVerticalStackView() { + self.verticalStackView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.verticalStackView) + self.verticalStackView.addArrangedSubview(self.popularityHorizontalStackView) + self.verticalStackView.addArrangedSubview(self.difficultyHorizontalStackView) + NSLayoutConstraint.activate([ + self.popularityHorizontalStackView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.5), + self.difficultyHorizontalStackView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.5) + ]) + } + + func configurePopularityLabelLayout() { + self.popularityLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.popularityLabel.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.55) + ]) + } + + func configureDifficultyLabelLayout() { + self.difficultyLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.difficultyLabel.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.55) + ]) + } + + func configurePopularityRatingViewLayout() { + self.popularityRatingView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.popularityRatingView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.45) + ]) + } + + func configureDifficultyRatingViewLayout() { + self.difficultyRatingView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.difficultyRatingView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.45) + ]) + } +} diff --git a/Escaper/Escaper/Presentation/Record/Views/Cells/RecordUserView.swift b/Escaper/Escaper/Presentation/Record/Views/Cells/RecordUserView.swift new file mode 100644 index 0000000..a303dbe --- /dev/null +++ b/Escaper/Escaper/Presentation/Record/Views/Cells/RecordUserView.swift @@ -0,0 +1,129 @@ +// +// RecordUserView.swift +// Escaper +// +// Created by shinheeRo on 2021/11/10. +// + +import UIKit + +class RecordUserView: UIView { + enum Constant { + static let verticalSpace = CGFloat(10) + static let horizontalSpace = CGFloat(5) + } + + enum Result: String { + case success + case fail + + var name: String { + return self.rawValue + } + } + + private let userImageView: UIImageView = { + let imageView = UIImageView() + imageView.layer.cornerRadius = CGFloat(20) + imageView.layer.masksToBounds = true + return imageView + }() + private let participantLabel: UILabel = EDSLabel.b03R(text: "참가자", color: .gloomyPurple) + private let nicknameLabel: UILabel = EDSLabel.b01B(text: "", color: .bloodyBlack) + private let resultLabel: UILabel = { + let label = EDSLabel.b03R(text: "", color: .bloodyBlack) + label.textAlignment = .center + label.layer.cornerRadius = CGFloat(12) + label.layer.masksToBounds = true + return label + }() + private let horizontalStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.distribution = .fillEqually + return stackView + }() + private let verticalStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fillEqually + return stackView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + configureLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + configureLayout() + } + + func update(nickname: String, result: Bool) { + self.nicknameLabel.text = nickname + self.resultLabel.text = result ? Result.success.name : Result.fail.name + switch result { + case true: + self.resultLabel.backgroundColor = EDSColor.pumpkin.value + case false: + self.resultLabel.backgroundColor = EDSColor.bloodyRed.value + } + } + + func prepareForReuse() { + self.nicknameLabel.text = "" + self.resultLabel.text = "" + } +} + +extension RecordUserView { + func configureLayout() { + configureUserImageViewLayout() + configureHorizontalStackViewLayout() + configureVerticalStackViewLayout() + configureNicknameLabeLayout() + configureResultLabelLayout() + } + + func configureUserImageViewLayout() { + self.userImageView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.userImageView) + NSLayoutConstraint.activate([ + self.userImageView.topAnchor.constraint(equalTo: self.topAnchor), + self.userImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.userImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.userImageView.widthAnchor.constraint(equalTo: self.userImageView.heightAnchor) + ]) + } + + func configureHorizontalStackViewLayout() { + self.horizontalStackView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.horizontalStackView) + self.horizontalStackView.addArrangedSubview(self.nicknameLabel) + self.horizontalStackView.addArrangedSubview(self.resultLabel) + } + + func configureVerticalStackViewLayout() { + self.verticalStackView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.verticalStackView) + self.verticalStackView.addArrangedSubview(self.participantLabel) + self.verticalStackView.addArrangedSubview(self.horizontalStackView) + NSLayoutConstraint.activate([ + self.verticalStackView.topAnchor.constraint(equalTo: self.topAnchor, constant: Constant.verticalSpace), + self.verticalStackView.leadingAnchor.constraint(equalTo: self.userImageView.trailingAnchor, constant: Constant.verticalSpace), + self.verticalStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -Constant.verticalSpace), + self.verticalStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -Constant.verticalSpace) + ]) + } + + func configureNicknameLabeLayout() { + self.nicknameLabel.translatesAutoresizingMaskIntoConstraints = false + self.nicknameLabel.widthAnchor.constraint(equalTo: self.verticalStackView.widthAnchor, multiplier: 0.7).isActive = true + } + + func configureResultLabelLayout() { + self.resultLabel.translatesAutoresizingMaskIntoConstraints = false + self.resultLabel.widthAnchor.constraint(equalTo: self.verticalStackView.widthAnchor, multiplier: 0.3).isActive = true + } +} diff --git a/Escaper/Escaper/Presentation/RoomDetail/RoomDetailViewController.swift b/Escaper/Escaper/Presentation/RoomDetail/RoomDetailViewController.swift new file mode 100644 index 0000000..1349234 --- /dev/null +++ b/Escaper/Escaper/Presentation/RoomDetail/RoomDetailViewController.swift @@ -0,0 +1,155 @@ +// +// RoomDetailViewController.swift +// Escaper +// +// Created by 박영광 on 2021/11/09. +// + +import UIKit + +final class RoomDetailViewController: DefaultViewController { + private enum Constant { + static let stackViewSpace: CGFloat = 10 + static let rankViewHeight: CGFloat = 60 + static let genreImageSize: CGFloat = 180 + static let shortVerticalSpace: CGFloat = 4 + static let longVerticalSpace: CGFloat = 24 + static let verticalSpace: CGFloat = 16 + static let horizontalSpace: CGFloat = 20 + static let DetailInfoHeight: CGFloat = 160 + static let DetailInfoSideSpace: CGFloat = 60 + } + + var room: Room? + private let scrollView = UIScrollView() + private let genreImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + return imageView + }() + private let titleLabel = EDSLabel.h01B(color: .skullLightWhite) + private let storeNameLabel = EDSLabel.b01R(color: .skullGrey) + private let roomDetailInfoVeiw = RoomDetailInfoView() + private let rankTitleLabel = EDSLabel.h01B(text: "이 방의 TOP3!", color: .skullLightWhite) + private let userRankStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = Constant.stackViewSpace + stackView.distribution = .fill + return stackView + }() + + override func viewDidLoad() { + super.viewDidLoad() + guard let room = room else { return } + self.configureLayout() + self.update(room: room) + self.roomDetailInfoVeiw.update(room: room) + self.updateStackView(userRecords: room.userRecords) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.navigationBar.isHidden = false + self.tabBarController?.tabBar.isHidden = true + } +} + +private extension RoomDetailViewController { + func configureLayout() { + self.configureScrollViewLayout() + self.configureGenreImageViewLayout() + self.configureTitleLabelLayout() + self.configureStoreNameLabelLayout() + self.configureRoomDetailInfoViewLayout() + self.configureRankTitleLabelLayout() + self.configureRankStackViewLayout() + } + + func configureScrollViewLayout() { + self.scrollView.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.scrollView) + NSLayoutConstraint.activate([ + self.scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor), + self.scrollView.frameLayoutGuide.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor), + self.scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), + self.scrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor) + ]) + } + + func configureGenreImageViewLayout() { + self.genreImageView.translatesAutoresizingMaskIntoConstraints = false + self.scrollView.addSubview(self.genreImageView) + NSLayoutConstraint.activate([ + self.genreImageView.centerXAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.centerXAnchor), + self.genreImageView.topAnchor.constraint(equalTo: self.scrollView.contentLayoutGuide.topAnchor, constant: 32), + self.genreImageView.widthAnchor.constraint(equalToConstant: Constant.genreImageSize), + self.genreImageView.heightAnchor.constraint(equalToConstant: Constant.genreImageSize) + ]) + } + + func configureTitleLabelLayout() { + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.scrollView.addSubview(self.titleLabel) + NSLayoutConstraint.activate([ + self.titleLabel.centerXAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.centerXAnchor), + self.titleLabel.topAnchor.constraint(equalTo: self.genreImageView.bottomAnchor, constant: Constant.longVerticalSpace) + ]) + } + + func configureStoreNameLabelLayout() { + self.storeNameLabel.translatesAutoresizingMaskIntoConstraints = false + self.scrollView.addSubview(self.storeNameLabel) + NSLayoutConstraint.activate([ + self.storeNameLabel.centerXAnchor.constraint(equalTo: self.titleLabel.centerXAnchor), + self.storeNameLabel.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: Constant.shortVerticalSpace) + ]) + } + + func configureRoomDetailInfoViewLayout() { + self.roomDetailInfoVeiw.translatesAutoresizingMaskIntoConstraints = false + self.scrollView.addSubview(self.roomDetailInfoVeiw) + NSLayoutConstraint.activate([ + self.roomDetailInfoVeiw.topAnchor.constraint(equalTo: self.storeNameLabel.bottomAnchor, constant: Constant.verticalSpace), + self.roomDetailInfoVeiw.leadingAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.leadingAnchor, constant: Constant.DetailInfoSideSpace), + self.roomDetailInfoVeiw.heightAnchor.constraint(equalToConstant: Constant.DetailInfoHeight) + ]) + } + + func configureRankTitleLabelLayout() { + self.rankTitleLabel.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.rankTitleLabel) + NSLayoutConstraint.activate([ + self.rankTitleLabel.topAnchor.constraint(equalTo: self.roomDetailInfoVeiw.bottomAnchor, constant: Constant.verticalSpace), + self.rankTitleLabel.centerXAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.centerXAnchor) + ]) + } + + func configureRankStackViewLayout() { + self.userRankStackView.translatesAutoresizingMaskIntoConstraints = false + self.scrollView.addSubview(self.userRankStackView) + NSLayoutConstraint.activate([ + self.userRankStackView.bottomAnchor.constraint(equalTo: self.scrollView.contentLayoutGuide.bottomAnchor, constant: -Constant.verticalSpace), + self.userRankStackView.leadingAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.leadingAnchor, constant: Constant.horizontalSpace), + self.userRankStackView.trailingAnchor.constraint(equalTo: self.scrollView.frameLayoutGuide.trailingAnchor, constant: -Constant.horizontalSpace), + self.userRankStackView.topAnchor.constraint(equalTo: self.rankTitleLabel.bottomAnchor, constant: Constant.verticalSpace) + ]) + } + + func update(room: Room) { + self.genreImageView.image = UIImage(named: room.genres.first?.detailImageAssetName ?? Genre.romance.detailImageAssetName) + self.titleLabel.text = room.name + self.storeNameLabel.text = room.storeName + } + + func updateStackView(userRecords: [UserRecord]) { + for (rank, userRecord) in userRecords.enumerated() { + let rankView = RoomDetailUserRankView() + rankView.translatesAutoresizingMaskIntoConstraints = false + rankView.heightAnchor.constraint(equalToConstant: Constant.rankViewHeight).isActive = true + rankView.layer.cornerRadius = Constant.rankViewHeight/2 + rankView.update(userRecord, rank: rank) + self.userRankStackView.addArrangedSubview(rankView) + } + } +} diff --git a/Escaper/Escaper/Presentation/RoomDetail/Views/RoomDetailInfoView.swift b/Escaper/Escaper/Presentation/RoomDetail/Views/RoomDetailInfoView.swift new file mode 100644 index 0000000..db21022 --- /dev/null +++ b/Escaper/Escaper/Presentation/RoomDetail/Views/RoomDetailInfoView.swift @@ -0,0 +1,140 @@ +// +// RoomDetailInfoView.swift +// Escaper +// +// Created by 박영광 on 2021/11/09. +// + +import UIKit + +class RoomDetailInfoView: UIView { + private enum Constant { + static let titleLabelSize: CGFloat = 150 + static let ratingViewHeight: CGFloat = 12 + static let ratingViewWidth: CGFloat = 72 + static let ratingViewAnchorY: CGFloat = 1 + static let sideSpace: CGFloat = 16 + static let gapSapce: CGFloat = 48 + } + + private let levelLabel = EDSLabel.b01B(text: "난이도", color: .skullWhite) + private let levelRatingView = RatingView() + private let satisfactionLabel = EDSLabel.b01B(text: "만족도", color: .skullLightWhite) + private let satisfactionRatingView = RatingView() + private let homepageTitleLabel = EDSLabel.b01B(text: "홈페이지", color: .skullLightWhite) + private let homepageLabel: UILabel = { + let label = EDSLabel.b02R(text: "홈페이지가 없습니다.", color: .skullLightWhite) + label.lineBreakMode = .byTruncatingTail + return label + }() + private let phoneNumberTitleLabel = EDSLabel.b01B(text: "전화번호", color: .skullLightWhite) + private let phoneNumberLabel = EDSLabel.b02R(text: "전화번호가 없습니다.", color: .skullLightWhite) + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configureLayout() + } + + func update(room: Room) { + self.levelRatingView.fill(rating: room.level) + self.satisfactionRatingView.fill(rating: room.satisfaction) + self.homepageLabel.text = room.homepage + self.phoneNumberLabel.text = room.telephone + } +} + +private extension RoomDetailInfoView { + func configureLayout() { + self.configureLevelLabelLayout() + self.configureSatisfactionLabelLayout() + self.configureHomepageTitleLabelLayout() + self.configurePhoneNumberTitleLabelLayout() + self.configureLevelRatingViewLayout() + self.configureSatisfactionRatingViewLayout() + self.configureHomepageLabelLayout() + self.configurePhoneNumberLabelLayout() + } + + func configureLevelLabelLayout() { + self.levelLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.levelLabel) + NSLayoutConstraint.activate([ + self.levelLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: Constant.sideSpace), + self.levelLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constant.sideSpace) + ]) + } + + func configureSatisfactionLabelLayout() { + self.satisfactionLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.satisfactionLabel) + NSLayoutConstraint.activate([ + self.satisfactionLabel.leadingAnchor.constraint(equalTo: self.levelLabel.leadingAnchor), + self.satisfactionLabel.topAnchor.constraint(equalTo: self.levelLabel.bottomAnchor, constant: Constant.sideSpace) + ]) + } + + func configureHomepageTitleLabelLayout() { + self.homepageTitleLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.homepageTitleLabel) + NSLayoutConstraint.activate([ + self.homepageTitleLabel.leadingAnchor.constraint(equalTo: self.satisfactionLabel.leadingAnchor), + self.homepageTitleLabel.topAnchor.constraint(equalTo: self.satisfactionLabel.bottomAnchor, constant: Constant.sideSpace) + ]) + } + + func configurePhoneNumberTitleLabelLayout() { + self.phoneNumberTitleLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.phoneNumberTitleLabel) + NSLayoutConstraint.activate([ + self.phoneNumberTitleLabel.leadingAnchor.constraint(equalTo: self.homepageTitleLabel.leadingAnchor), + self.phoneNumberTitleLabel.topAnchor.constraint(equalTo: self.homepageTitleLabel.bottomAnchor, constant: Constant.sideSpace) + ]) + } + + func configureLevelRatingViewLayout() { + self.levelRatingView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.levelRatingView) + NSLayoutConstraint.activate([ + self.levelRatingView.centerYAnchor.constraint(equalTo: self.levelLabel.centerYAnchor, constant: -Constant.ratingViewAnchorY), + self.levelRatingView.leadingAnchor.constraint(equalTo: levelLabel.trailingAnchor, constant: Constant.gapSapce), + self.levelRatingView.widthAnchor.constraint(equalToConstant: Constant.ratingViewWidth), + self.levelRatingView.heightAnchor.constraint(equalToConstant: Constant.ratingViewHeight) + ]) + } + + func configureSatisfactionRatingViewLayout() { + self.satisfactionRatingView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.satisfactionRatingView) + NSLayoutConstraint.activate([ + self.satisfactionRatingView.centerYAnchor.constraint(equalTo: self.satisfactionLabel.centerYAnchor, constant: Constant.ratingViewAnchorY), + self.satisfactionRatingView.leadingAnchor.constraint(equalTo: self.levelRatingView.leadingAnchor), + self.satisfactionRatingView.widthAnchor.constraint(equalToConstant: Constant.ratingViewWidth), + self.satisfactionRatingView.heightAnchor.constraint(equalToConstant: Constant.ratingViewHeight) + ]) + } + + func configureHomepageLabelLayout() { + self.homepageLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.homepageLabel) + NSLayoutConstraint.activate([ + self.homepageLabel.centerYAnchor.constraint(equalTo: self.homepageTitleLabel.centerYAnchor), + self.homepageLabel.leadingAnchor.constraint(equalTo: self.satisfactionRatingView.leadingAnchor), + self.homepageLabel.widthAnchor.constraint(equalToConstant: Constant.titleLabelSize) + ]) + } + + func configurePhoneNumberLabelLayout() { + self.phoneNumberLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.phoneNumberLabel) + NSLayoutConstraint.activate([ + self.phoneNumberLabel.centerYAnchor.constraint(equalTo: self.phoneNumberTitleLabel.centerYAnchor), + self.phoneNumberLabel.leadingAnchor.constraint(equalTo: self.homepageLabel.leadingAnchor), + self.phoneNumberLabel.widthAnchor.constraint(equalToConstant: Constant.titleLabelSize) + ]) + } +} diff --git a/Escaper/Escaper/Presentation/RoomDetail/Views/RoomDetailUserRankView.swift b/Escaper/Escaper/Presentation/RoomDetail/Views/RoomDetailUserRankView.swift new file mode 100644 index 0000000..01c5c54 --- /dev/null +++ b/Escaper/Escaper/Presentation/RoomDetail/Views/RoomDetailUserRankView.swift @@ -0,0 +1,122 @@ +// +// RoomDetailUserRankView.swift +// Escaper +// +// Created by 박영광 on 2021/11/10. +// + +import UIKit + +class RoomDetailUserRankView: UIView { + static let rankColor = [EDSColor.pumpkin.value, EDSColor.gloomyPink.value, EDSColor.gloomyRed.value] + + private enum Constant { + static let userImageSize: CGFloat = 50 + static let sideSpace: CGFloat = 32 + static let gapSpace: CGFloat = 10 + static let rankSize: CGFloat = 8 + static let titleLabelSize: CGFloat = 100 + } + + private let rankLabel: UILabel = { + let label = EDSLabel.b01B(color: .bloodyBlack) + label.textAlignment = .left + return label + }() + private let imageView: UIImageView = { + let imageView = UIImageView() + imageView.layer.masksToBounds = true + return imageView + }() + private let titleLabel = EDSLabel.b01B(color: .bloodyBlack) + private let timeLabel = EDSLabel.b01R(color: .bloodyBlack) + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configureLayout() + } + + func update(_ user: UserRecord, rank: Int) { + self.rankLabel.text = "\(rank + 1)" + self.imageView.image = UIImage(named: "romancePreview") + self.titleLabel.text = user.nickname + self.timeLabel.text = self.timeToString(time: user.playTime) + self.backgroundColor = .clear + self.backgroundColor = Self.rankColor[rank] + } +} + +private extension RoomDetailUserRankView { + func configureLayout() { + self.userRankLabelLayout() + self.userImageViewLayout() + self.userTitleLabelLayout() + self.userTimeLabelLayout() + } + + func userRankLabelLayout() { + self.rankLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.rankLabel) + NSLayoutConstraint.activate([ + self.rankLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor), + self.rankLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constant.sideSpace), + self.rankLabel.widthAnchor.constraint(equalToConstant: Constant.rankSize) + ]) + } + + func userImageViewLayout() { + self.imageView.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.imageView) + NSLayoutConstraint.activate([ + self.imageView.centerYAnchor.constraint(equalTo: self.rankLabel.centerYAnchor), + self.imageView.leadingAnchor.constraint(equalTo: self.rankLabel.trailingAnchor, constant: Constant.gapSpace), + self.imageView.widthAnchor.constraint(equalToConstant: Constant.userImageSize), + self.imageView.heightAnchor.constraint(equalToConstant: Constant.userImageSize) + ]) + self.imageView.layer.cornerRadius = Constant.userImageSize/2 + } + + func userTitleLabelLayout() { + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.titleLabel) + NSLayoutConstraint.activate([ + self.titleLabel.centerYAnchor.constraint(equalTo: self.imageView.centerYAnchor), + self.titleLabel.leadingAnchor.constraint(equalTo: self.imageView.trailingAnchor, constant: Constant.gapSpace), + self.titleLabel.widthAnchor.constraint(equalToConstant: Constant.titleLabelSize) + ]) + } + + func userTimeLabelLayout() { + self.timeLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(self.timeLabel) + NSLayoutConstraint.activate([ + self.timeLabel.centerYAnchor.constraint(equalTo: self.titleLabel.centerYAnchor), + self.timeLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -Constant.sideSpace) + ]) + } + + func timeToString(time: Int) -> String { + var ret = "" + var time = time + let second = time % 60 + if second > 0 { + ret = "\(second)s " + ret + } + time /= 60 + let min = time % 60 + if min > 0 { + ret = "\(min)m " + ret + } + time /= 60 + let hour = time % 60 + if hour > 0 { + ret = "\(hour)h " + ret + } + return ret + } +} diff --git a/Escaper/Escaper/Presentation/RoomList/RoomListViewController.swift b/Escaper/Escaper/Presentation/RoomList/RoomListViewController.swift index 41ed6d7..bcca443 100644 --- a/Escaper/Escaper/Presentation/RoomList/RoomListViewController.swift +++ b/Escaper/Escaper/Presentation/RoomList/RoomListViewController.swift @@ -6,20 +6,29 @@ // import UIKit +import CoreLocation final class RoomListViewController: DefaultViewController { enum Constant { - static let tagViewHeight = CGFloat(35) - static let cellHeight = CGFloat(65) - static let defaultVerticalSpace = CGFloat(10) - static let defaultOutlineSpace = CGFloat(20) + static let localeIdentifier = "Ko-kr" + static let tagViewHeight = CGFloat(30) + static let sideSpace = CGFloat(20) + static let cellHeight = CGFloat(96) + static let topVerticalSpace = CGFloat(18) + static let defaultVerticalSpace = CGFloat(13) + static let defaultOutlineSpace = CGFloat(14) + static let districtSelectButtonWidth = CGFloat(105) + static let sortingOptionWidth = CGFloat(230) } enum Section { case main } - private var viewModel: RoomListViewModelInterface! + private var viewModel: RoomListViewModelInterface? + private var locationManager: CLLocationManager? + private var dataSource: UITableViewDiffableDataSource? + private var districtSelectButton = DistrictSelectButton() private let genreTagScrollView: TagScrollView = { let tagScrollView: TagScrollView = TagScrollView() tagScrollView.showsHorizontalScrollIndicator = false @@ -33,20 +42,23 @@ final class RoomListViewController: DefaultViewController { private let roomOverviewTableView: UITableView = { let tableView = UITableView() tableView.backgroundColor = .clear + tableView.separatorStyle = .none tableView.rowHeight = Constant.cellHeight return tableView }() - private var dataSource: UITableViewDiffableDataSource! override func viewDidLoad() { super.viewDidLoad() - self.configureGenreTagScrollViewLayout() - self.configureSortingOptionTagScrollViewLayout() - self.configureRoomOverviewTableViewLayout() - self.configureDelegates() - self.configureDataSource() + self.configure() self.injectTagScrollViewElements() self.bindViewModel() + self.locationAuthorization() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.tabBarController?.tabBar.isHidden = false + self.navigationController?.navigationBar.isHidden = true } func create() { @@ -60,23 +72,70 @@ final class RoomListViewController: DefaultViewController { extension RoomListViewController: TagScrollViewDelegate { func tagSelected(element: Tagable) { switch element { - case let genre as Genre: - guard let sortingOption = self.sortingOptionTagScrollView.selectedButton?.element as? SortingOption else { return } - self.viewModel.fetch(genre: genre, sortingOption: sortingOption) + case is Genre: + self.fetchCurrentDistrict { [weak self] district in + self?.fetchWithCurrentSelectedOption(with: district) + } case let sortingOption as SortingOption: - self.viewModel.sort(option: sortingOption) + self.viewModel?.sort(option: sortingOption) + default: + break + } + } +} + +extension RoomListViewController: DistrictSelectViewDelegate { + func districtDidSelected(district: District) { + self.fetchWithCurrentSelectedOption(with: district) + } +} + +extension RoomListViewController: CLLocationManagerDelegate { + func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { + var status: CLAuthorizationStatus? + if #available(iOS 14.0, *) { + status = self.locationManager?.authorizationStatus + } else { + status = CLLocationManager.authorizationStatus() + } + guard let status = status else { return } + switch status { + case .authorizedAlways, .authorizedWhenInUse, .authorized: + self.fetchCurrentDistrict { [weak self] district in + self?.districtSelectButton.updateTitle(district: district) + self?.fetchWithCurrentSelectedOption(with: district) + } default: break } } } +extension RoomListViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let room = self.dataSource?.itemIdentifier(for: indexPath) else { return } + let detailViewController = RoomDetailViewController() + detailViewController.room = room + self.navigationController?.pushViewController(detailViewController, animated: true) + } +} + private extension RoomListViewController { + func configure() { + self.configureLocationManager() + self.configureDelegates() + self.configureRoomOverViewTableView() + self.configureGenreTagScrollViewLayout() + self.configureSortingOptionTagScrollViewLayout() + self.configureRoomOverviewTableViewLayout() + self.configureDistrictSelectButtonLayout() + } + func configureGenreTagScrollViewLayout() { self.genreTagScrollView.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(self.genreTagScrollView) NSLayoutConstraint.activate([ - self.genreTagScrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), + self.genreTagScrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: Constant.topVerticalSpace), self.genreTagScrollView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor), self.genreTagScrollView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor), self.genreTagScrollView.heightAnchor.constraint(equalToConstant: Constant.tagViewHeight) @@ -89,15 +148,13 @@ private extension RoomListViewController { NSLayoutConstraint.activate([ self.sortingOptionTagScrollView.topAnchor.constraint(equalTo: self.genreTagScrollView.bottomAnchor, constant: Constant.defaultVerticalSpace), self.sortingOptionTagScrollView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor), - self.sortingOptionTagScrollView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor), + self.sortingOptionTagScrollView.widthAnchor.constraint(equalToConstant: Constant.sortingOptionWidth), self.sortingOptionTagScrollView.heightAnchor.constraint(equalToConstant: Constant.tagViewHeight) ]) } func configureRoomOverviewTableViewLayout() { self.roomOverviewTableView.translatesAutoresizingMaskIntoConstraints = false - self.roomOverviewTableView.register(RoomOverviewTableViewCell.self, - forCellReuseIdentifier: RoomOverviewTableViewCell.identifier) self.view.addSubview(self.roomOverviewTableView) NSLayoutConstraint.activate([ self.roomOverviewTableView.topAnchor.constraint(equalTo: self.sortingOptionTagScrollView.bottomAnchor, constant: Constant.defaultVerticalSpace), @@ -107,7 +164,19 @@ private extension RoomListViewController { ]) } - func configureDataSource() { + func configureDistrictSelectButtonLayout() { + self.districtSelectButton.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.districtSelectButton) + NSLayoutConstraint.activate([ + self.districtSelectButton.centerYAnchor.constraint(equalTo: self.sortingOptionTagScrollView.centerYAnchor), + self.districtSelectButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -Constant.sideSpace), + self.districtSelectButton.widthAnchor.constraint(equalToConstant: Constant.districtSelectButtonWidth), + self.districtSelectButton.heightAnchor.constraint(equalToConstant: Constant.tagViewHeight) + ]) + } + + func configureRoomOverViewTableView() { + self.roomOverviewTableView.register(RoomOverviewTableViewCell.self, forCellReuseIdentifier: RoomOverviewTableViewCell.identifier) self.dataSource = UITableViewDiffableDataSource(tableView: roomOverviewTableView) { (tableView: UITableView, _: IndexPath, room: Room) -> UITableViewCell? in let cell = tableView.dequeueReusableCell(withIdentifier: RoomOverviewTableViewCell.identifier) as? RoomOverviewTableViewCell cell?.update(room) @@ -115,9 +184,18 @@ private extension RoomListViewController { } } + func configureLocationManager() { + self.locationManager = CLLocationManager() + self.locationManager?.delegate = self + self.locationManager?.desiredAccuracy = kCLLocationAccuracyBest + self.locationManager?.requestWhenInUseAuthorization() + } + func configureDelegates() { + self.roomOverviewTableView.delegate = self self.genreTagScrollView.tagDelegate = self self.sortingOptionTagScrollView.tagDelegate = self + self.districtSelectButton.delegate = self } func injectTagScrollViewElements() { @@ -126,11 +204,46 @@ private extension RoomListViewController { } func bindViewModel() { - self.viewModel.rooms.observe(on: self) { roomList in + self.viewModel?.rooms.observe(on: self) { roomList in var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([Section.main]) snapshot.appendItems(roomList) - self.dataSource.apply(snapshot, animatingDifferences: true) + self.dataSource?.apply(snapshot, animatingDifferences: true) + } + } + + func fetchCurrentDistrict(completion: @escaping (District) -> Void) { + guard let location = self.locationManager?.location else { return } + let geocoder = CLGeocoder() + let locale = Locale(identifier: Constant.localeIdentifier) + geocoder.reverseGeocodeLocation(location, preferredLocale: locale) { placeMarks, _ in + guard let address: [CLPlacemark] = placeMarks, + let locality = address.last?.locality, + let district = District.init(rawValue: locality) else { return } + completion(district) + } + } + + func fetchWithCurrentSelectedOption(with district: District) { + guard let genre = self.genreTagScrollView.selectedButton?.element as? Genre, + let sortingOption = self.sortingOptionTagScrollView.selectedButton?.element as? SortingOption else { return } + self.viewModel?.fetch(district: district, genre: genre, sortingOption: sortingOption) + } + + func locationAuthorization() { + if #available(iOS 14.0, *) { + return + } else { + let status = CLLocationManager.authorizationStatus() + switch status { + case .authorizedAlways, .authorizedWhenInUse, .authorized: + self.fetchCurrentDistrict { [weak self] district in + self?.districtSelectButton.updateTitle(district: district) + self?.fetchWithCurrentSelectedOption(with: district) + } + default: + break + } } } } diff --git a/Escaper/Escaper/Presentation/RoomList/RoomListViewModel.swift b/Escaper/Escaper/Presentation/RoomList/RoomListViewModel.swift index 61dc63f..cb4e446 100644 --- a/Escaper/Escaper/Presentation/RoomList/RoomListViewModel.swift +++ b/Escaper/Escaper/Presentation/RoomList/RoomListViewModel.swift @@ -10,7 +10,7 @@ import Foundation protocol RoomListViewModelInterface { var rooms: Observable<[Room]> { get } - func fetch(genre: Genre, sortingOption: SortingOption) + func fetch(district: District, genre: Genre, sortingOption: SortingOption) func sort(option: SortingOption) } @@ -23,8 +23,8 @@ class DefaultRoomListViewModel: RoomListViewModelInterface { self.rooms = Observable([]) } - func fetch(genre: Genre, sortingOption: SortingOption) { - self.usecase.query(genre: genre) { result in + func fetch(district: District, genre: Genre, sortingOption: SortingOption) { + self.usecase.query(district: district, genre: genre) { result in switch result { case .success(let rooms): self.rooms.value = self.sort(rooms: rooms, by: sortingOption) diff --git a/Escaper/Escaper/Presentation/RoomList/Views/Cells/RoomOverviewTableViewCell.swift b/Escaper/Escaper/Presentation/RoomList/Views/Cell/RoomOverviewTableViewCell.swift similarity index 59% rename from Escaper/Escaper/Presentation/RoomList/Views/Cells/RoomOverviewTableViewCell.swift rename to Escaper/Escaper/Presentation/RoomList/Views/Cell/RoomOverviewTableViewCell.swift index a5fad2b..c0d50b7 100644 --- a/Escaper/Escaper/Presentation/RoomList/Views/Cells/RoomOverviewTableViewCell.swift +++ b/Escaper/Escaper/Presentation/RoomList/Views/Cell/RoomOverviewTableViewCell.swift @@ -11,9 +11,18 @@ final class RoomOverviewTableViewCell: UITableViewCell { static let identifier = String(describing: RoomOverviewTableViewCell.self) enum Constant { - static let cornerRadius = CGFloat(20) + static let cornerRadius = CGFloat(15) + static let imageLength = CGFloat(50) static let verticalSpace = CGFloat(5) static let horizontalSpace = CGFloat(20) + static let contentSideSpace = CGFloat(24) + static let ratingContainerWidth = CGFloat(100) + static let ratingContainerHeight = CGFloat(30) + static let ratingVerticalSpace = CGFloat(8) + static let imageYAnchorSpace = CGFloat(8) + static let titleYAnchorSpace = CGFloat(20) + static let titleSpace = CGFloat(20) + static let distanceSpace = CGFloat(4) } private let genreImageView: UIImageView = { @@ -22,14 +31,12 @@ final class RoomOverviewTableViewCell: UITableViewCell { return imageView }() private let titleLabel: UILabel = { - let label = UILabel() - label.textColor = UIColor(named: ColorPalette.skullLightWhite.name) - label.font = UIFont.boldSystemFont(ofSize: 13) - label.lineBreakMode = .byCharWrapping + let label = EDSLabel.b02B(color: .skullLightWhite) + label.lineBreakMode = .byWordWrapping label.lineBreakMode = .byTruncatingTail - label.numberOfLines = 2 return label }() + private let distanceLabel = EDSLabel.b03R(color: .skullWhite) private let ratingContainerView = RatingContainerView() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { @@ -43,9 +50,11 @@ final class RoomOverviewTableViewCell: UITableViewCell { } func update(_ room: Room) { - self.genreImageView.image = UIImage(named: room.genres.first!.previewImageAssetName) + guard let genre = room.genres.first else { return } + self.genreImageView.image = UIImage(named: genre.previewImageAssetName) self.titleLabel.text = room.name self.ratingContainerView.update(level: room.level, satisfaction: room.satisfaction) + self.distanceLabel.text = room.distance < 1000 ? "\(Int(room.distance))m" : "\((room.distance / 100).rounded() / 10)km" } override func prepareForReuse() { @@ -72,6 +81,7 @@ private extension RoomOverviewTableViewCell { self.configureGenreImageViewLayout() self.configureTitleLabelLayout() self.configureRatingContainerViewLayout() + self.configureDistanceLabelLayout() } func configureCell() { @@ -80,17 +90,17 @@ private extension RoomOverviewTableViewCell { self.selectedBackgroundView = bgColorView self.backgroundColor = .clear self.contentView.layer.masksToBounds = true - self.contentView.backgroundColor = UIColor(named: ColorPalette.gloomyBrown.name) + self.contentView.backgroundColor = EDSColor.gloomyBrown.value } func configureGenreImageViewLayout() { self.genreImageView.translatesAutoresizingMaskIntoConstraints = false self.contentView.addSubview(self.genreImageView) NSLayoutConstraint.activate([ - self.genreImageView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor), - self.genreImageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 20), - self.genreImageView.heightAnchor.constraint(equalToConstant: 40), - self.genreImageView.widthAnchor.constraint(equalToConstant: 40) + self.genreImageView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor, constant: -Constant.imageYAnchorSpace), + self.genreImageView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -Constant.contentSideSpace), + self.genreImageView.heightAnchor.constraint(equalToConstant: Constant.imageLength), + self.genreImageView.widthAnchor.constraint(equalToConstant: Constant.imageLength) ]) } @@ -98,9 +108,9 @@ private extension RoomOverviewTableViewCell { self.titleLabel.translatesAutoresizingMaskIntoConstraints = false self.contentView.addSubview(self.titleLabel) NSLayoutConstraint.activate([ - self.titleLabel.leadingAnchor.constraint(equalTo: self.genreImageView.trailingAnchor, constant: 10), - self.titleLabel.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor), - self.titleLabel.widthAnchor.constraint(equalToConstant: 100) + self.titleLabel.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: Constant.contentSideSpace), + self.titleLabel.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor, constant: -Constant.titleYAnchorSpace), + self.titleLabel.trailingAnchor.constraint(equalTo: self.genreImageView.leadingAnchor, constant: -Constant.titleSpace) ]) } @@ -108,10 +118,19 @@ private extension RoomOverviewTableViewCell { self.ratingContainerView.translatesAutoresizingMaskIntoConstraints = false self.contentView.addSubview(self.ratingContainerView) NSLayoutConstraint.activate([ - self.ratingContainerView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor), - self.ratingContainerView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -20), - self.ratingContainerView.heightAnchor.constraint(equalToConstant: 30), - self.ratingContainerView.widthAnchor.constraint(equalToConstant: 100) + self.ratingContainerView.leadingAnchor.constraint(equalTo: self.titleLabel.leadingAnchor), + self.ratingContainerView.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: Constant.ratingVerticalSpace), + self.ratingContainerView.heightAnchor.constraint(equalToConstant: Constant.ratingContainerHeight), + self.ratingContainerView.widthAnchor.constraint(equalToConstant: Constant.ratingContainerWidth) + ]) + } + + func configureDistanceLabelLayout() { + self.distanceLabel.translatesAutoresizingMaskIntoConstraints = false + self.contentView.addSubview(self.distanceLabel) + NSLayoutConstraint.activate([ + self.distanceLabel.centerXAnchor.constraint(equalTo: self.genreImageView.centerXAnchor), + self.distanceLabel.topAnchor.constraint(equalTo: self.genreImageView.bottomAnchor, constant: Constant.distanceSpace) ]) } } diff --git a/Escaper/Escaper/Presentation/RoomList/Views/RatingContainerView.swift b/Escaper/Escaper/Presentation/RoomList/Views/RatingContainerView.swift index 96ad28d..ae68549 100644 --- a/Escaper/Escaper/Presentation/RoomList/Views/RatingContainerView.swift +++ b/Escaper/Escaper/Presentation/RoomList/Views/RatingContainerView.swift @@ -14,20 +14,8 @@ class RatingContainerView: UIView { static let ratingViewWidth = Constant.ratingViewHeight * CGFloat(Rating.maxRating) + RatingView.Constant.summationOfElementSpacing } - private let levelLabel: UILabel = { - let label = UILabel() - label.text = "난이도" - label.textColor = UIColor(named: ColorPalette.skullWhite.name) - label.font = UIFont.systemFont(ofSize: 10) - return label - }() - private let satisfactionLabel: UILabel = { - let label = UILabel() - label.text = "만족도" - label.textColor = UIColor(named: ColorPalette.skullWhite.name) - label.font = UIFont.systemFont(ofSize: 10) - return label - }() + private let levelLabel = EDSLabel.b03R(text: "난이도", color: .skullWhite) + private let satisfactionLabel = EDSLabel.b03R(text: "만족도", color: .skullWhite) private let levelRatingView = RatingView() private let satisfactionRatingView = RatingView() diff --git a/Escaper/Escaper/Presentation/RoomList/Views/TagButton.swift b/Escaper/Escaper/Presentation/RoomList/Views/TagButton.swift index 784e982..b54a98e 100644 --- a/Escaper/Escaper/Presentation/RoomList/Views/TagButton.swift +++ b/Escaper/Escaper/Presentation/RoomList/Views/TagButton.swift @@ -8,7 +8,17 @@ import UIKit final class TagButton: UIButton { + enum Constant { + static let cornerRadius = CGFloat(8) + static let borderWidth = CGFloat(0.25) + } + private(set) var element: Tagable? + private var elementLabel: UILabel = { + let label = EDSLabel.b01R(color: .skullWhite) + label.textAlignment = .center + return label + }() required init?(coder: NSCoder) { super.init(coder: coder) @@ -23,22 +33,37 @@ final class TagButton: UIButton { convenience init(element: Tagable) { self.init(frame: .zero) self.element = element - self.setTitle(element.name, for: .normal) + self.elementLabel.text = element.name } func touched() { - self.backgroundColor = UIColor(named: ColorPalette.pumpkin.name) + self.backgroundColor = EDSColor.bloodyDarkBurgundy.value } func untouched() { - self.backgroundColor = UIColor(named: ColorPalette.bloodyDarkBurgundy.name) + self.backgroundColor = EDSColor.bloodyBlack.value } } private extension TagButton { func configure() { - self.layer.cornerRadius = 5 - self.titleLabel?.textColor = UIColor(named: ColorPalette.skullWhite.name) + self.configureButtonUI() + self.configureElementLabelLayout() + } + + func configureButtonUI() { + self.layer.cornerRadius = Constant.cornerRadius + self.layer.borderWidth = Constant.borderWidth + self.layer.borderColor = EDSColor.bloodyDarkBurgundy.value?.cgColor self.untouched() } + + func configureElementLabelLayout() { + self.elementLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(elementLabel) + NSLayoutConstraint.activate([ + self.elementLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor), + self.elementLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor) + ]) + } } diff --git a/Escaper/Escaper/Presentation/RoomList/Views/TagScrollView.swift b/Escaper/Escaper/Presentation/RoomList/Views/TagScrollView.swift index 9c85061..3a209df 100644 --- a/Escaper/Escaper/Presentation/RoomList/Views/TagScrollView.swift +++ b/Escaper/Escaper/Presentation/RoomList/Views/TagScrollView.swift @@ -12,13 +12,19 @@ protocol TagScrollViewDelegate: AnyObject { } final class TagScrollView: UIScrollView { + enum Constant { + static let tagSpace = CGFloat(8) + static let tagElementExtraSpace = CGFloat(16) + static let extraViewSpace = CGFloat(12) + } + weak var tagDelegate: TagScrollViewDelegate? private(set) var selectedButton: TagButton? private let stackView: UIStackView = { let stackView = UIStackView() stackView.axis = .horizontal - stackView.spacing = 10 + stackView.spacing = Constant.tagSpace stackView.distribution = .fill return stackView }() @@ -36,17 +42,17 @@ final class TagScrollView: UIScrollView { func inject(elements: [Tagable]) { elements.forEach { element in let button = TagButton(element: element) - let width = self.calculateStringWidth(text: element.name)+20 + let width = self.calculateStringWidth(text: element.name) + Constant.tagElementExtraSpace button.widthAnchor.constraint(equalToConstant: width).isActive = true button.addTarget(self, action: #selector(buttonTouched(sender:)), for: .touchUpInside) self.stackView.addArrangedSubview(button) } - if let first = self.stackView.arrangedSubviews.first as? TagButton, - let element = first.element { + if let first = self.stackView.arrangedSubviews.first as? TagButton { self.selectedButton = first self.selectedButton?.touched() - self.tagDelegate?.tagSelected(element: element) } + injectExtraView(index: 0) + injectExtraView(index: elements.count+1) } } @@ -76,4 +82,10 @@ private extension TagScrollView { temporaryLabel.text = text return CGFloat(temporaryLabel.intrinsicContentSize.width) } + + func injectExtraView(index: Int) { + let extraView = UIView() + extraView.widthAnchor.constraint(equalToConstant: Constant.extraViewSpace).isActive = true + self.stackView.insertArrangedSubview(extraView, at: index) + } } diff --git a/Escaper/Escaper/Presentation/TimePicker/TimePickerViewController.swift b/Escaper/Escaper/Presentation/TimePicker/TimePickerViewController.swift new file mode 100644 index 0000000..e8d09c9 --- /dev/null +++ b/Escaper/Escaper/Presentation/TimePicker/TimePickerViewController.swift @@ -0,0 +1,170 @@ +// +// TimePickerViewController.swift +// Escaper +// +// Created by TakHyun Jung on 2021/11/11. +// + +import UIKit + +protocol TimePickerDelegate: AnyObject { + func updateTime(hour: Int, minutes: Int, seconds: Int) +} + +class TimePickerViewController: UIViewController { + private let containerView: UIView = { + let view = UIView() + view.backgroundColor = EDSColor.bloodyBlack.value + return view + }() + private let toolBar: UIToolbar = { + let toolBar = UIToolbar() + toolBar.barTintColor = EDSColor.gloomyBrown.value + return toolBar + }() + private let pickerView: UIPickerView = { + let pickerView = UIPickerView() + pickerView.backgroundColor = EDSColor.gloomyBrown.value + pickerView.setValue(EDSColor.pumpkin.value, forKeyPath: "textColor") + return pickerView + }() + + weak var delegate: TimePickerDelegate? + var hour: Int = 0 + var minutes: Int = 0 + var seconds: Int = 0 + + override func viewDidLoad() { + super.viewDidLoad() + self.configure() + self.view.backgroundColor = .clear + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + self.containerView.roundCorners(corners: [.topLeft, .topRight], radius: 25) + } + + @objc func cancelBarButtonTapped() { + self.dismiss(animated: true) + } + + @objc func confirmBarButtonTapped() { + self.delegate?.updateTime(hour: self.hour, minutes: self.minutes, seconds: self.seconds) + self.dismiss(animated: true) + } + +@objc func clearViewTapped(_ sender: UITapGestureRecognizer) { + let tappedPoint = sender.location(in: self.containerView) + guard self.containerView.hitTest(tappedPoint, with: nil) == nil else { return } + self.delegate?.updateTime(hour: self.hour, minutes: self.minutes, seconds: self.seconds) + self.dismiss(animated: true) +} +} + +private extension TimePickerViewController { + func configure() { + self.configureContainerViewLayout() + self.configureToolBarLayout() + self.configurePickerViewLayout() + self.configureToolBarItems() + self.configureTapGesture() + self.pickerView.delegate = self + } + + func configureContainerViewLayout() { + self.containerView.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(self.containerView) + NSLayoutConstraint.activate([ + self.containerView.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.3), + self.containerView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor), + self.containerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + self.containerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) + ]) + } + + func configureToolBarLayout() { + self.toolBar.translatesAutoresizingMaskIntoConstraints = false + self.containerView.addSubview(self.toolBar) + NSLayoutConstraint.activate([ + self.toolBar.topAnchor.constraint(equalTo: self.containerView.topAnchor), + self.toolBar.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor), + self.toolBar.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor), + self.toolBar.heightAnchor.constraint(equalTo: self.containerView.heightAnchor, multiplier: 0.25) + ]) + } + + func configurePickerViewLayout() { + self.pickerView.translatesAutoresizingMaskIntoConstraints = false + self.containerView.addSubview(self.pickerView) + NSLayoutConstraint.activate([ + self.pickerView.topAnchor.constraint(equalTo: self.toolBar.bottomAnchor), + self.pickerView.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor), + self.pickerView.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor), + self.pickerView.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor) + ]) + } + + func configureToolBarItems() { + let cancel = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(self.cancelBarButtonTapped)) + let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil) + let save = UIBarButtonItem(title: "확인", style: .done, target: self, action: #selector(self.confirmBarButtonTapped)) + cancel.tintColor = EDSColor.bloodyRed.value + save.tintColor = EDSColor.skullWhite.value + self.toolBar.items = [cancel, spacer, save] + } + + func configureTapGesture() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.clearViewTapped)) + self.view.addGestureRecognizer(tapGesture) + self.view.isUserInteractionEnabled = true + } +} + +extension TimePickerViewController: UIPickerViewDelegate, UIPickerViewDataSource { + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 3 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + switch component { + case 0: + return 3 + case 1, 2: + return 60 + default: + return 0 + } + } + + func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { + return pickerView.frame.size.width / 3 + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + switch component { + case 0: + hour = row + case 1: + minutes = row + case 2: + seconds = row + default: + break + } + } + + func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { + guard let color = EDSColor.skullWhite.value else { return NSAttributedString() } + switch component { + case 0: + return NSAttributedString(string: "\(row) 시간", attributes: [NSAttributedString.Key.foregroundColor: color, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .bold)]) + case 1: + return NSAttributedString(string: "\(row) 분", attributes: [NSAttributedString.Key.foregroundColor: color, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .bold)]) + case 2: + return NSAttributedString(string: "\(row) 초", attributes: [NSAttributedString.Key.foregroundColor: color, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .bold)]) + default: + return NSAttributedString() + } + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/1024.png b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100755 index 0000000..b607eec Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/114.png b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100755 index 0000000..6ba88b4 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/120.png b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100755 index 0000000..fb0603a Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/180.png b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100755 index 0000000..d29c04f Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/29.png b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100755 index 0000000..72784a6 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/40.png b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100755 index 0000000..fbb3316 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/57.png b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100755 index 0000000..5bf03cb Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/58.png b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100755 index 0000000..82a8e18 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/60.png b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100755 index 0000000..78ce200 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/80.png b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100755 index 0000000..657d23c Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/87.png b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100755 index 0000000..47d5d73 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json old mode 100644 new mode 100755 index 9221b9b..73d3b7f --- a/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,98 +1 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} +{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"}]} \ No newline at end of file diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/actionDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/actionDetail.imageset/Contents.json index a6ff88c..b503e8b 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/actionDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/actionDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "actionDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "actionDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/actionDetail.imageset/actionDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/actionDetail.imageset/actionDetail@2x.png new file mode 100644 index 0000000..34cf7bd Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/actionDetail.imageset/actionDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/actionDetail.imageset/actionDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/actionDetail.imageset/actionDetail@3x.png new file mode 100644 index 0000000..5543475 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/actionDetail.imageset/actionDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/adventureDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/adventureDetail.imageset/Contents.json index 4758a02..e87eda1 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/adventureDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/adventureDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "adventureDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "adventureDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/adventureDetail.imageset/adventureDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/adventureDetail.imageset/adventureDetail@2x.png new file mode 100644 index 0000000..58fea3c Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/adventureDetail.imageset/adventureDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/adventureDetail.imageset/adventureDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/adventureDetail.imageset/adventureDetail@3x.png new file mode 100644 index 0000000..76cfd99 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/adventureDetail.imageset/adventureDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/comedyDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/comedyDetail.imageset/Contents.json index 347172a..cfeeda7 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/comedyDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/comedyDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "comedyDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "comedyDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/comedyDetail.imageset/comedyDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/comedyDetail.imageset/comedyDetail@2x.png new file mode 100644 index 0000000..0e81194 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/comedyDetail.imageset/comedyDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/comedyDetail.imageset/comedyDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/comedyDetail.imageset/comedyDetail@3x.png new file mode 100644 index 0000000..67f49a5 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/comedyDetail.imageset/comedyDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/crimeDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/crimeDetail.imageset/Contents.json index bc2cbd6..6b74cee 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/crimeDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/crimeDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "crimeDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "crimeDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/crimeDetail.imageset/crimeDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/crimeDetail.imageset/crimeDetail@2x.png new file mode 100644 index 0000000..a09b874 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/crimeDetail.imageset/crimeDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/crimeDetail.imageset/crimeDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/crimeDetail.imageset/crimeDetail@3x.png new file mode 100644 index 0000000..c15d6ab Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/crimeDetail.imageset/crimeDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/dramaDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/dramaDetail.imageset/Contents.json index 620527f..ea9d23f 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/dramaDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/dramaDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "dramaDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "dramaDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/dramaDetail.imageset/dramaDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/dramaDetail.imageset/dramaDetail@2x.png new file mode 100644 index 0000000..99ad8ed Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/dramaDetail.imageset/dramaDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/dramaDetail.imageset/dramaDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/dramaDetail.imageset/dramaDetail@3x.png new file mode 100644 index 0000000..55fe01f Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/dramaDetail.imageset/dramaDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fantasyDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fantasyDetail.imageset/Contents.json index e6fc970..c36fdb1 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fantasyDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fantasyDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "fantasyDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "fantasyDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fantasyDetail.imageset/fantasyDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fantasyDetail.imageset/fantasyDetail@2x.png new file mode 100644 index 0000000..24bedf6 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fantasyDetail.imageset/fantasyDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fantasyDetail.imageset/fantasyDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fantasyDetail.imageset/fantasyDetail@3x.png new file mode 100644 index 0000000..5d10c35 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fantasyDetail.imageset/fantasyDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fearDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fearDetail.imageset/Contents.json index 925752e..75f5e60 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fearDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fearDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "fearDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "fearDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fearDetail.imageset/fearDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fearDetail.imageset/fearDetail@2x.png new file mode 100644 index 0000000..b75a3d3 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fearDetail.imageset/fearDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fearDetail.imageset/fearDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fearDetail.imageset/fearDetail@3x.png new file mode 100644 index 0000000..11d1580 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/fearDetail.imageset/fearDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/historyDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/historyDetail.imageset/Contents.json index 4e5caa2..93e152e 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/historyDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/historyDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "historyDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "historyDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/historyDetail.imageset/historyDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/historyDetail.imageset/historyDetail@2x.png new file mode 100644 index 0000000..c8ac9a5 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/historyDetail.imageset/historyDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/historyDetail.imageset/historyDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/historyDetail.imageset/historyDetail@3x.png new file mode 100644 index 0000000..3680c13 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/historyDetail.imageset/historyDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/mysteryDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/mysteryDetail.imageset/Contents.json index a72dfb6..edb6ab1 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/mysteryDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/mysteryDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "mysteryDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "mysteryDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/mysteryDetail.imageset/mysteryDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/mysteryDetail.imageset/mysteryDetail@2x.png new file mode 100644 index 0000000..579b521 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/mysteryDetail.imageset/mysteryDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/mysteryDetail.imageset/mysteryDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/mysteryDetail.imageset/mysteryDetail@3x.png new file mode 100644 index 0000000..7e9347d Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/mysteryDetail.imageset/mysteryDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/outdoorDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/outdoorDetail.imageset/Contents.json index bf4dea8..60c6f3b 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/outdoorDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/outdoorDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "outdoorDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "outdoorDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/outdoorDetail.imageset/outdoorDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/outdoorDetail.imageset/outdoorDetail@2x.png new file mode 100644 index 0000000..efe3e2d Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/outdoorDetail.imageset/outdoorDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/outdoorDetail.imageset/outdoorDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/outdoorDetail.imageset/outdoorDetail@3x.png new file mode 100644 index 0000000..bbf3ab8 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/outdoorDetail.imageset/outdoorDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/romanceDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/romanceDetail.imageset/Contents.json index ec218e5..aca10b9 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/romanceDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/romanceDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "romanceDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "romanceDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/romanceDetail.imageset/romanceDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/romanceDetail.imageset/romanceDetail@2x.png new file mode 100644 index 0000000..b56e867 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/romanceDetail.imageset/romanceDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/romanceDetail.imageset/romanceDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/romanceDetail.imageset/romanceDetail@3x.png new file mode 100644 index 0000000..c1c21f7 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/romanceDetail.imageset/romanceDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/sensitivityDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/sensitivityDetail.imageset/Contents.json index 8802d7a..b16a2d9 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/sensitivityDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/sensitivityDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "sensitivityDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "sensitivityDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/sensitivityDetail.imageset/sensitivityDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/sensitivityDetail.imageset/sensitivityDetail@2x.png new file mode 100644 index 0000000..e953d72 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/sensitivityDetail.imageset/sensitivityDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/sensitivityDetail.imageset/sensitivityDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/sensitivityDetail.imageset/sensitivityDetail@3x.png new file mode 100644 index 0000000..b8a0a3c Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/sensitivityDetail.imageset/sensitivityDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/thrillerDetail.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/thrillerDetail.imageset/Contents.json index 50c8c2b..7b86845 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/thrillerDetail.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/thrillerDetail.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "thrillerDetail@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "thrillerDetail@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/thrillerDetail.imageset/thrillerDetail@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/thrillerDetail.imageset/thrillerDetail@2x.png new file mode 100644 index 0000000..0d8faff Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/thrillerDetail.imageset/thrillerDetail@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/thrillerDetail.imageset/thrillerDetail@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/thrillerDetail.imageset/thrillerDetail@3x.png new file mode 100644 index 0000000..6f09dc3 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenreDetail/thrillerDetail.imageset/thrillerDetail@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/actionPreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/actionPreview.imageset/Contents.json index 0d9e22b..9d6fae6 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/actionPreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/actionPreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "actionPreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "actionPreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/actionPreview.imageset/actionPreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/actionPreview.imageset/actionPreview@2x.png new file mode 100644 index 0000000..d9fd390 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/actionPreview.imageset/actionPreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/actionPreview.imageset/actionPreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/actionPreview.imageset/actionPreview@3x.png new file mode 100644 index 0000000..13f7168 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/actionPreview.imageset/actionPreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/adventurePreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/adventurePreview.imageset/Contents.json index cc9f076..2dab2b7 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/adventurePreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/adventurePreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "adventurePreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "adventurePreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/adventurePreview.imageset/adventurePreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/adventurePreview.imageset/adventurePreview@2x.png new file mode 100644 index 0000000..bfe5c24 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/adventurePreview.imageset/adventurePreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/adventurePreview.imageset/adventurePreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/adventurePreview.imageset/adventurePreview@3x.png new file mode 100644 index 0000000..70c7b0d Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/adventurePreview.imageset/adventurePreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/comedyPreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/comedyPreview.imageset/Contents.json index c6bbf3e..0893d44 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/comedyPreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/comedyPreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "comedyPreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "comedyPreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/comedyPreview.imageset/comedyPreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/comedyPreview.imageset/comedyPreview@2x.png new file mode 100644 index 0000000..37353b2 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/comedyPreview.imageset/comedyPreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/comedyPreview.imageset/comedyPreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/comedyPreview.imageset/comedyPreview@3x.png new file mode 100644 index 0000000..d37a3cb Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/comedyPreview.imageset/comedyPreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/crimePreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/crimePreview.imageset/Contents.json index 632acbf..089e49b 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/crimePreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/crimePreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "crimePreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "crimePreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/crimePreview.imageset/crimePreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/crimePreview.imageset/crimePreview@2x.png new file mode 100644 index 0000000..6536406 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/crimePreview.imageset/crimePreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/crimePreview.imageset/crimePreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/crimePreview.imageset/crimePreview@3x.png new file mode 100644 index 0000000..684843e Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/crimePreview.imageset/crimePreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/dramaPreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/dramaPreview.imageset/Contents.json index 5259864..5e8fbc6 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/dramaPreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/dramaPreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "dramaPreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "dramaPreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/dramaPreview.imageset/dramaPreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/dramaPreview.imageset/dramaPreview@2x.png new file mode 100644 index 0000000..53d212d Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/dramaPreview.imageset/dramaPreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/dramaPreview.imageset/dramaPreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/dramaPreview.imageset/dramaPreview@3x.png new file mode 100644 index 0000000..e3a3765 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/dramaPreview.imageset/dramaPreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fantasyPreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fantasyPreview.imageset/Contents.json index cb78451..8f8ee9a 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fantasyPreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fantasyPreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "fantasyPreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "fantasyPreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fantasyPreview.imageset/fantasyPreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fantasyPreview.imageset/fantasyPreview@2x.png new file mode 100644 index 0000000..22095b4 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fantasyPreview.imageset/fantasyPreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fantasyPreview.imageset/fantasyPreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fantasyPreview.imageset/fantasyPreview@3x.png new file mode 100644 index 0000000..fbeefe9 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fantasyPreview.imageset/fantasyPreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fearPreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fearPreview.imageset/Contents.json index 0aec0b4..f7bce3e 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fearPreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fearPreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "fearPreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "fearPreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fearPreview.imageset/fearPreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fearPreview.imageset/fearPreview@2x.png new file mode 100644 index 0000000..2d1751d Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fearPreview.imageset/fearPreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fearPreview.imageset/fearPreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fearPreview.imageset/fearPreview@3x.png new file mode 100644 index 0000000..0522db7 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/fearPreview.imageset/fearPreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/historyPreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/historyPreview.imageset/Contents.json index ee07cb7..ecc6b09 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/historyPreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/historyPreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "historyPreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "historyPreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/historyPreview.imageset/historyPreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/historyPreview.imageset/historyPreview@2x.png new file mode 100644 index 0000000..65bfb47 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/historyPreview.imageset/historyPreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/historyPreview.imageset/historyPreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/historyPreview.imageset/historyPreview@3x.png new file mode 100644 index 0000000..77e7dba Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/historyPreview.imageset/historyPreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/mysteryPreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/mysteryPreview.imageset/Contents.json index 05a463b..95a6cf0 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/mysteryPreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/mysteryPreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "mysteryPreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "mysteryPreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/mysteryPreview.imageset/mysteryPreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/mysteryPreview.imageset/mysteryPreview@2x.png new file mode 100644 index 0000000..cc803ac Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/mysteryPreview.imageset/mysteryPreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/mysteryPreview.imageset/mysteryPreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/mysteryPreview.imageset/mysteryPreview@3x.png new file mode 100644 index 0000000..cf52a05 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/mysteryPreview.imageset/mysteryPreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/outdoorPreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/outdoorPreview.imageset/Contents.json index 5f42d2f..228f70d 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/outdoorPreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/outdoorPreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "outdoorPreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "outdoorPreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/outdoorPreview.imageset/outdoorPreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/outdoorPreview.imageset/outdoorPreview@2x.png new file mode 100644 index 0000000..89aad4f Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/outdoorPreview.imageset/outdoorPreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/outdoorPreview.imageset/outdoorPreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/outdoorPreview.imageset/outdoorPreview@3x.png new file mode 100644 index 0000000..3358c1b Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/outdoorPreview.imageset/outdoorPreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/romancePreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/romancePreview.imageset/Contents.json index 01d7364..9280e43 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/romancePreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/romancePreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "romancePreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "romancePreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/romancePreview.imageset/romancePreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/romancePreview.imageset/romancePreview@2x.png new file mode 100644 index 0000000..4692db7 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/romancePreview.imageset/romancePreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/romancePreview.imageset/romancePreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/romancePreview.imageset/romancePreview@3x.png new file mode 100644 index 0000000..a7e531d Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/romancePreview.imageset/romancePreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/sensitivityPreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/sensitivityPreview.imageset/Contents.json index 55ed5b6..a36b8e3 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/sensitivityPreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/sensitivityPreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "sensitivityPreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "sensitivityPreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/sensitivityPreview.imageset/sensitivityPreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/sensitivityPreview.imageset/sensitivityPreview@2x.png new file mode 100644 index 0000000..877d606 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/sensitivityPreview.imageset/sensitivityPreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/sensitivityPreview.imageset/sensitivityPreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/sensitivityPreview.imageset/sensitivityPreview@3x.png new file mode 100644 index 0000000..f862cc5 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/sensitivityPreview.imageset/sensitivityPreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/thrillerPreview.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/thrillerPreview.imageset/Contents.json index e01a226..e4639eb 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/thrillerPreview.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/thrillerPreview.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "thrillerPreview@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "thrillerPreview@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/thrillerPreview.imageset/thrillerPreview@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/thrillerPreview.imageset/thrillerPreview@2x.png new file mode 100644 index 0000000..bcb3f78 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/thrillerPreview.imageset/thrillerPreview@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/thrillerPreview.imageset/thrillerPreview@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/thrillerPreview.imageset/thrillerPreview@3x.png new file mode 100644 index 0000000..68215f5 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/GenrePreview/thrillerPreview.imageset/thrillerPreview@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Palette/bloodyBurgundy.colorset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Palette/bloodyBurgundy.colorset/Contents.json index 21b6efe..02a787b 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/Palette/bloodyBurgundy.colorset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/Palette/bloodyBurgundy.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.059", - "green" : "0.059", - "red" : "0.600" + "blue" : "0x0F", + "green" : "0x0F", + "red" : "0x99" } }, "idiom" : "universal" diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Palette/bloodyDarkBurgundy.colorset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Palette/bloodyDarkBurgundy.colorset/Contents.json index 3ede9bc..b94b603 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/Palette/bloodyDarkBurgundy.colorset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/Palette/bloodyDarkBurgundy.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.031", - "green" : "0.031", - "red" : "0.357" + "blue" : "0x07", + "green" : "0x07", + "red" : "0x5B" } }, "idiom" : "universal" diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Palette/charcoal.colorset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Palette/charcoal.colorset/Contents.json index 936fe1d..dd1316f 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/Palette/charcoal.colorset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/Palette/charcoal.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.114", - "green" : "0.114", - "red" : "0.114" + "blue" : "0x1D", + "green" : "0x1D", + "red" : "0x1D" } }, "idiom" : "universal" diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyBrown.colorset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyBrown.colorset/Contents.json index 8ca0bbf..91aed53 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyBrown.colorset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyBrown.colorset/Contents.json @@ -16,5 +16,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "localizable" : true } } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyPink.colorset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyPink.colorset/Contents.json index 71b65a7..383bc51 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyPink.colorset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyPink.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.608", - "green" : "0.580", - "red" : "0.675" + "blue" : "0x9B", + "green" : "0x93", + "red" : "0xAC" } }, "idiom" : "universal" diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyRed.colorset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyRed.colorset/Contents.json index e21ad57..6cb6113 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyRed.colorset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/Palette/gloomyRed.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.255", - "green" : "0.255", - "red" : "0.659" + "blue" : "0x41", + "green" : "0x41", + "red" : "0xA8" } }, "idiom" : "universal" diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Palette/pumpkin.colorset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Palette/pumpkin.colorset/Contents.json index 4f83019..2ebac23 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/Palette/pumpkin.colorset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/Palette/pumpkin.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.267", - "green" : "0.651", - "red" : "1.000" + "blue" : "0x44", + "green" : "0xA6", + "red" : "0xFF" } }, "idiom" : "universal" diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Palette/skullGrey.colorset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Palette/skullGrey.colorset/Contents.json new file mode 100644 index 0000000..c603020 --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/Palette/skullGrey.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x61", + "green" : "0x5D", + "red" : "0x6C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Palette/skullLightWhite.colorset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Palette/skullLightWhite.colorset/Contents.json index af88f26..3a50d2b 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/Palette/skullLightWhite.colorset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/Palette/skullLightWhite.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.918", - "green" : "0.918", - "red" : "0.918" + "blue" : "0xEA", + "green" : "0xEA", + "red" : "0xEA" } }, "idiom" : "universal" diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Palette/skullWhite.colorset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Palette/skullWhite.colorset/Contents.json index af42719..2ed2bc4 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/Palette/skullWhite.colorset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/Palette/skullWhite.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.847", - "green" : "0.847", - "red" : "0.847" + "blue" : "0xD7", + "green" : "0xD7", + "red" : "0xD7" } }, "idiom" : "universal" diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.filled.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.filled.imageset/Contents.json index 2d456bc..74c1d1b 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.filled.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.filled.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "star.filled@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "star.filled@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.filled.imageset/star.filled@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.filled.imageset/star.filled@2x.png new file mode 100644 index 0000000..61cd166 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.filled.imageset/star.filled@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.filled.imageset/star.filled@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.filled.imageset/star.filled@3x.png new file mode 100644 index 0000000..dd473e3 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.filled.imageset/star.filled@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/Contents.json index bb7488d..19f5282 100644 --- a/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/Contents.json +++ b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/Contents.json @@ -1,15 +1,17 @@ { "images" : [ { - "filename" : "star.unfilled-2.png", + "filename" : "star.unfilled.png", "idiom" : "universal", "scale" : "1x" }, { + "filename" : "star.unfilled@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "star.unfilled@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled-2.png b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled-2.png deleted file mode 100644 index 47ef481..0000000 Binary files a/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled-2.png and /dev/null differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled.png b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled.png new file mode 100644 index 0000000..4287959 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled@2x.png new file mode 100644 index 0000000..052fba4 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled@3x.png new file mode 100644 index 0000000..fd7513b Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Ratings/star.unfilled.imageset/star.unfilled@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/home.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/home.imageset/Contents.json new file mode 100644 index 0000000..7f2c0a7 --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/home.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "home.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/home.imageset/home.png b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/home.imageset/home.png new file mode 100644 index 0000000..93823a8 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/home.imageset/home.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/homeSelected.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/homeSelected.imageset/Contents.json new file mode 100644 index 0000000..dcda21e --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/homeSelected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "homeSelected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/homeSelected.imageset/homeSelected.png b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/homeSelected.imageset/homeSelected.png new file mode 100644 index 0000000..ea92bfe Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/homeSelected.imageset/homeSelected.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoard.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoard.imageset/Contents.json new file mode 100644 index 0000000..d1ae2b8 --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoard.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "leaderBoard.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoard.imageset/leaderBoard.png b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoard.imageset/leaderBoard.png new file mode 100644 index 0000000..c0716d3 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoard.imageset/leaderBoard.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoardSelected.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoardSelected.imageset/Contents.json new file mode 100644 index 0000000..6d3833d --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoardSelected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "leaderBoardSelected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoardSelected.imageset/leaderBoardSelected.png b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoardSelected.imageset/leaderBoardSelected.png new file mode 100644 index 0000000..9a4bff6 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/leaderBoardSelected.imageset/leaderBoardSelected.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/map.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/map.imageset/Contents.json new file mode 100644 index 0000000..c78b381 --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/map.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "map.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/map.imageset/map.png b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/map.imageset/map.png new file mode 100644 index 0000000..31557a7 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/map.imageset/map.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/mapSelected.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/mapSelected.imageset/Contents.json new file mode 100644 index 0000000..8c4c35f --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/mapSelected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "mapSelected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/mapSelected.imageset/mapSelected.png b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/mapSelected.imageset/mapSelected.png new file mode 100644 index 0000000..d6fc7e8 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/mapSelected.imageset/mapSelected.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/record.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/record.imageset/Contents.json new file mode 100644 index 0000000..3c948a1 --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/record.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "record.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/record.imageset/record.png b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/record.imageset/record.png new file mode 100644 index 0000000..68e82d3 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/record.imageset/record.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/recordSelected.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/recordSelected.imageset/Contents.json new file mode 100644 index 0000000..471ae0c --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/recordSelected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "recordSelected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/recordSelected.imageset/recordSelected.png b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/recordSelected.imageset/recordSelected.png new file mode 100644 index 0000000..5d1fe23 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/TabBarItems/recordSelected.imageset/recordSelected.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Utility/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Utility/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/Utility/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Utility/chevronDown.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Utility/chevronDown.imageset/Contents.json new file mode 100644 index 0000000..2691877 --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/Utility/chevronDown.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "chevron_down.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Utility/chevronDown.imageset/chevron_down.png b/Escaper/Escaper/Resources/Assets.xcassets/Utility/chevronDown.imageset/chevron_down.png new file mode 100644 index 0000000..a0dbd9e Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Utility/chevronDown.imageset/chevron_down.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/Contents.json new file mode 100644 index 0000000..337868b --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "plus.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "plus@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "plus@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/plus.png b/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/plus.png new file mode 100644 index 0000000..611b3ab Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/plus.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/plus@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/plus@2x.png new file mode 100644 index 0000000..59dc6cb Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/plus@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/plus@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/plus@3x.png new file mode 100644 index 0000000..b27534a Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Utility/plus.imageset/plus@3x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Contents.json b/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Contents.json new file mode 100644 index 0000000..66d74e3 --- /dev/null +++ b/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Record Card.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Record Card@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Record Card@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Record Card.png b/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Record Card.png new file mode 100644 index 0000000..1e52431 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Record Card.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Record Card@2x.png b/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Record Card@2x.png new file mode 100644 index 0000000..7656645 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Record Card@2x.png differ diff --git a/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Record Card@3x.png b/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Record Card@3x.png new file mode 100644 index 0000000..a94e742 Binary files /dev/null and b/Escaper/Escaper/Resources/Assets.xcassets/Utility/recordCard.imageset/Record Card@3x.png differ