├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── 아차-이슈.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── Acha ├── .swiftlint.yml ├── Acha.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── Acha.xcscheme ├── Acha │ ├── Acha.entitlements │ ├── App │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── AppIcon.png │ │ │ │ └── Contents.json │ │ │ ├── Color │ │ │ │ ├── CommentBoxColor.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── GameRoomColor.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── PointDarkColor.colorset │ │ │ │ │ └── Contents.json │ │ │ │ └── PointLightColor.colorset │ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── Image │ │ │ │ ├── Contents.json │ │ │ │ ├── commentImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── bubble 1.png │ │ │ │ ├── crazy.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── crazy.png │ │ │ │ ├── email.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── email.png │ │ │ │ ├── firstAnnotation.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── penguin.png │ │ │ │ ├── fourthAnnotation.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── bunny.png │ │ │ │ ├── invalidate.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── exclamation-mark.png │ │ │ │ ├── map_0.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── map_0.png │ │ │ │ ├── map_1.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── map_1.png │ │ │ │ ├── map_2.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── map_2.png │ │ │ │ ├── map_3.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── map_3.png │ │ │ │ ├── nickname.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── name.png │ │ │ │ ├── noBadge.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── noBadge.png │ │ │ │ ├── password.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── padlock.png │ │ │ │ ├── rank0.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── medal.png │ │ │ │ ├── rank1.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── medal (1).png │ │ │ │ ├── rank2.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── medal (2).png │ │ │ │ ├── secondAnnotation.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── pet.png │ │ │ │ ├── thirdAnnotation.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── cat.png │ │ │ │ └── x.circle.fill.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── x.circle.fill.png │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── Coordinator │ │ │ ├── AppCoordinator.swift │ │ │ └── Coordinator.swift │ │ ├── GoogleService-Info.plist │ │ ├── Info.plist │ │ └── SceneDelegate.swift │ ├── Data │ │ ├── DTO │ │ │ ├── BadgeDTO.swift │ │ │ ├── ChatDTO.swift │ │ │ ├── CommentDTO.swift │ │ │ ├── CommunityDTO.swift │ │ │ ├── CoordinateDTO.swift │ │ │ ├── InGameUserDataDTO.swift │ │ │ ├── MapDTO.swift │ │ │ ├── MultiGamePlayerDTO.swift │ │ │ ├── PostDTO.swift │ │ │ ├── RecordDTO.swift │ │ │ ├── RoomDTO.swift │ │ │ └── UserDTO.swift │ │ └── Repository │ │ │ ├── DefaultBadgeRepository.swift │ │ │ ├── DefaultCommunityRepository.swift │ │ │ ├── DefaultGameRoomRepository.swift │ │ │ ├── DefaultLocationRepository.swift │ │ │ ├── DefaultMapRepository.swift │ │ │ ├── DefaultRecordRepository.swift │ │ │ ├── DefaultTempRepository.swift │ │ │ ├── DefaultTimeRepository.swift │ │ │ ├── DefaultUserRepository.swift │ │ │ └── Protocol │ │ │ ├── BadgeRepository.swift │ │ │ ├── CommunityRepository.swift │ │ │ ├── GameRoomRepository.swift │ │ │ ├── LocationRepository.swift │ │ │ ├── MapRepository.swift │ │ │ ├── RecordRepository.swift │ │ │ ├── TempRepository.swift │ │ │ ├── TimeRepository.swift │ │ │ └── UserRepository.swift │ ├── Domain │ │ ├── Entity │ │ │ ├── Badge.swift │ │ │ ├── Chat.swift │ │ │ ├── Comment.swift │ │ │ ├── Coordinate.swift │ │ │ ├── HealthKitReadData.swift │ │ │ ├── HealthKitWriteData.swift │ │ │ ├── Image.swift │ │ │ ├── InGameRanking.swift │ │ │ ├── InGameRecord.swift │ │ │ ├── LogInData.swift │ │ │ ├── Map.swift │ │ │ ├── MapRegion.swift │ │ │ ├── MultiGamePlayerData.swift │ │ │ ├── Post.swift │ │ │ ├── Record.swift │ │ │ ├── RecordViewChartData.swift │ │ │ ├── RecordViewDayTotalRecord.swift │ │ │ ├── RecordViewHeaderRecord.swift │ │ │ ├── RoomUser.swift │ │ │ ├── SignUpData.swift │ │ │ └── User.swift │ │ └── UseCase │ │ │ ├── Auth │ │ │ ├── DefatulSignUpUsecase.swift │ │ │ ├── DefaultLoginUsecase.swift │ │ │ └── Protocol │ │ │ │ ├── AuthUseCaseProtocol.swift │ │ │ │ ├── LoginUseCase.swift │ │ │ │ └── SignUpUseCase.swift │ │ │ ├── Community │ │ │ ├── DefaultCommunityDetailUseCase.swift │ │ │ ├── DefaultCommunityMainUseCase.swift │ │ │ ├── DefaultCommunityPostWriteUseCase.swift │ │ │ └── Protocol │ │ │ │ ├── CommunityDetailUseCase.swift │ │ │ │ ├── CommunityMainUseCase.swift │ │ │ │ └── CommunityPostWriteUseCase.swift │ │ │ ├── Game │ │ │ ├── DefaultInGameUseCase.swift │ │ │ ├── DefaultMapBaseUseCase.swift │ │ │ ├── DefaultMultiGameChatUseCase.swift │ │ │ ├── DefaultMultiGameRoomUseCase.swift │ │ │ ├── DefaultMultiGameUseCase.swift │ │ │ ├── DefaultSelectMapUseCase.swift │ │ │ ├── DefaultSingleGameUseCase.swift │ │ │ └── Protocol │ │ │ │ ├── InGameUseCase.swift │ │ │ │ ├── MapBaseUseCase.swift │ │ │ │ ├── MultiGameChatUseCase.swift │ │ │ │ ├── MultiGameRoomUseCase.swift │ │ │ │ ├── MultiGameUseCase.swift │ │ │ │ ├── SelectMapUseCase.swift │ │ │ │ └── SingleGameUseCase.swift │ │ │ ├── Home │ │ │ └── DefaultHomeUseCase.swift │ │ │ ├── MyPage │ │ │ ├── DefaultMyInfoEditUseCase.swift │ │ │ ├── DefaultMyPageUseCase.swift │ │ │ └── Protocol │ │ │ │ ├── MyInfoEditUseCase.swift │ │ │ │ └── MyPageUseCase.swift │ │ │ └── Record │ │ │ ├── DefaultRecordMainViewUseCase.swift │ │ │ ├── DefaultRecordMapViewUseCase.swift │ │ │ └── Protocol │ │ │ ├── RecordMainViewUseCase.swift │ │ │ └── RecordMapViewUseCase.swift │ ├── Presentation │ │ ├── Auth │ │ │ ├── Common │ │ │ │ ├── AuthButton.swift │ │ │ │ ├── AuthInputTextField.swift │ │ │ │ ├── AuthTitleView.swift │ │ │ │ └── ScrollAbleViewController.swift │ │ │ ├── Coordinator │ │ │ │ ├── AuthCoordinator.swift │ │ │ │ ├── LoginCoordinator.swift │ │ │ │ └── SignupCoordinator .swift │ │ │ ├── ViewController │ │ │ │ ├── LoginViewController.swift │ │ │ │ └── SignupViewController.swift │ │ │ └── ViewModel │ │ │ │ ├── LoginViewModel.swift │ │ │ │ └── SignUpViewModel.swift │ │ ├── Base │ │ │ ├── BaseViewModel.swift │ │ │ └── MapBase │ │ │ │ ├── MapBaseViewController.swift │ │ │ │ └── MapBaseViewModel.swift │ │ └── TabBar │ │ │ ├── Community │ │ │ ├── Cell │ │ │ │ ├── CommunityDetailCommentCell.swift │ │ │ │ ├── CommunityDetailCommentHeaderView.swift │ │ │ │ ├── CommunityDetailPostCell.swift │ │ │ │ ├── CommunityIndicatorCell.swift │ │ │ │ └── CommunityMainCell.swift │ │ │ ├── Common │ │ │ │ └── CommentView.swift │ │ │ ├── ViewController │ │ │ │ ├── CommunityDetailViewController.swift │ │ │ │ ├── CommunityMainViewController.swift │ │ │ │ └── CommunityPostWriteViewController.swift │ │ │ └── ViewModel │ │ │ │ ├── CommunityDetailViewModel.swift │ │ │ │ ├── CommunityMainViewModel.swift │ │ │ │ └── CommunityPostWriteViewModel.swift │ │ │ ├── Coordinator │ │ │ ├── CommunityCoordinator.swift │ │ │ ├── HomeCoordinator.swift │ │ │ ├── MultiGameCoordinator.swift │ │ │ ├── MyPageCoordinator.swift │ │ │ ├── RecordCoordinator.swift │ │ │ ├── SingleGameCoordinator.swift │ │ │ └── TabBarCoordinator.swift │ │ │ ├── Home │ │ │ ├── Common │ │ │ │ ├── DistanceAndTimeBar.swift │ │ │ │ └── PaddingLabel.swift │ │ │ ├── HomeView │ │ │ │ ├── ViewController │ │ │ │ │ ├── HomeViewController.swift │ │ │ │ │ ├── MultiGameEnterViewController.swift │ │ │ │ │ └── QRReaderViewController.swift │ │ │ │ └── ViewModel │ │ │ │ │ └── HomeViewModel.swift │ │ │ ├── InGameMenus │ │ │ │ ├── View │ │ │ │ │ └── InGameMenuCell.swift │ │ │ │ ├── ViewController │ │ │ │ │ ├── InGamePlayMenuViewController.swift │ │ │ │ │ ├── InGameRankingViewController.swift │ │ │ │ │ └── InGameRecordViewController.swift │ │ │ │ └── ViewModel │ │ │ │ │ ├── InGameRankingViewModel.swift │ │ │ │ │ └── InGameRecordViewModel.swift │ │ │ ├── MultiGame │ │ │ │ ├── View │ │ │ │ │ ├── ChatCell.swift │ │ │ │ │ ├── GameRankCell.swift │ │ │ │ │ ├── GameRoomCell.swift │ │ │ │ │ ├── GameRoomHeader.swift │ │ │ │ │ ├── PlayerAnnotation.swift │ │ │ │ │ ├── PlayerAnnotationView.swift │ │ │ │ │ └── PlayerCircle.swift │ │ │ │ ├── ViewController │ │ │ │ │ ├── MultiGameChatViewController.swift │ │ │ │ │ ├── MultiGameRoomViewController.swift │ │ │ │ │ └── MultiGameViewController.swift │ │ │ │ └── ViewModel │ │ │ │ │ ├── MultiGameChatViewModel.swift │ │ │ │ │ ├── MultiGameRoomViewModel.swift │ │ │ │ │ └── MultiGameViewModel.swift │ │ │ ├── SelectMap │ │ │ │ ├── View │ │ │ │ │ ├── SelectMapRankingHeaderView.swift │ │ │ │ │ └── SelectMapRecordCell.swift │ │ │ │ ├── ViewController │ │ │ │ │ └── SelectMapViewController.swift │ │ │ │ └── ViewModel │ │ │ │ │ └── SelectMapViewModel.swift │ │ │ └── SingleGame │ │ │ │ ├── View │ │ │ │ └── GameOverView.swift │ │ │ │ ├── ViewController │ │ │ │ └── SingleGameViewController.swift │ │ │ │ └── ViewModel │ │ │ │ └── SingleGameViewModel.swift │ │ │ ├── MyPage │ │ │ ├── View │ │ │ │ ├── BadgeCell.swift │ │ │ │ ├── InfoTextFieldView.swift │ │ │ │ ├── MyPageHeaderView.swift │ │ │ │ └── SettingCell.swift │ │ │ ├── ViewController │ │ │ │ ├── BadgeViewController.swift │ │ │ │ ├── CharacterSelectViewController.swift │ │ │ │ ├── MyInfoEditViewController.swift │ │ │ │ └── MyPageViewController.swift │ │ │ └── ViewModel │ │ │ │ ├── BadgeViewModel.swift │ │ │ │ ├── CharacterSelectViewModel.swift │ │ │ │ ├── MyInfoEditViewModel.swift │ │ │ │ └── MyPageViewModel.swift │ │ │ └── Record │ │ │ ├── View │ │ │ ├── LineGraphView.swift │ │ │ ├── RecordMainCell.swift │ │ │ ├── RecordMainChartCell.swift │ │ │ ├── RecordMainHeaderView.swift │ │ │ ├── RecordMapCategoryCell.swift │ │ │ ├── RecordMapHeaderView.swift │ │ │ ├── RecordMapImageCell.swift │ │ │ └── RecordMapRankingCell.swift │ │ │ ├── ViewController │ │ │ ├── RecordMainViewController.swift │ │ │ ├── RecordMapViewController.swift │ │ │ └── RecordPageViewController.swift │ │ │ └── ViewModel │ │ │ ├── RecordMainViewModel.swift │ │ │ └── RecordMapViewModel.swift │ └── Util │ │ ├── Dependency │ │ ├── DIContainer.swift │ │ ├── DependenciesContainer.swift │ │ ├── DependenciesDefinition.swift │ │ └── DependencyKey.swift │ │ ├── Enums │ │ ├── AnimationKeyPath.swift │ │ ├── Errors.swift │ │ ├── FirebaseRealtimeType.swift │ │ ├── FirebaseStorageType.swift │ │ ├── Locations.swift │ │ ├── PinCharacter.swift │ │ ├── SystemImageNameSpace.swift │ │ └── TabBarType.swift │ │ ├── Error │ │ └── FirebaseServiceError.swift │ │ ├── Extension │ │ ├── CALayer+.swift │ │ ├── CLLocationCoordinate2D+.swift │ │ ├── Collection+.swift │ │ ├── Date+.swift │ │ ├── Double+.swift │ │ ├── Encodable+.swift │ │ ├── Int+.swift │ │ ├── MKCircle+.swift │ │ ├── Reactive+ │ │ │ ├── MKMapView+Rx.swift │ │ │ ├── UIApplication+Rx.swift │ │ │ ├── UIView+Rx.swift │ │ │ └── UIViewCotnroller+Rx.swift │ │ ├── String+.swift │ │ ├── UIButton+.swift │ │ ├── UIColor+.swift │ │ ├── UIFont+.swift │ │ ├── UIImage+.swift │ │ ├── UITextField+.swift │ │ └── UIViewController+.swift │ │ ├── Manager │ │ ├── AchaKeyboardManager.swift │ │ └── KeyChainManager.swift │ │ ├── Map │ │ └── MapAnnotation.swift │ │ └── Service │ │ ├── DefaultAuthService.swift │ │ ├── DefaultFirebaseStorageNetworkService.swift │ │ ├── DefaultHealthKitService.swift │ │ ├── DefaultImageCacheService.swift │ │ ├── DefaultKeychainService.swift │ │ ├── DefaultLocationService.swift │ │ ├── DefaultRandomService.swift │ │ ├── DefaultRealtimeDatabaseNetworkService.swift │ │ ├── DefaultTimerService.swift │ │ └── Protocol │ │ ├── AuthService.swift │ │ ├── FirebaseStorageNetworkService.swift │ │ ├── HealthKitService.swift │ │ ├── ImageCacheService.swift │ │ ├── KeychainService.swift │ │ ├── LocationService.swift │ │ ├── RandomService.swift │ │ ├── RealtimeDatabaseNetworkService.swift │ │ └── TimerService.swift ├── AchaTests │ └── AchaTests.swift ├── AchaUITests │ ├── AchaUITests.swift │ └── AchaUITestsLaunchTests.swift └── GPX │ ├── WorkSpace by 25km:h.gpx │ ├── WorkSpace by 6km:h.gpx │ ├── 국민대 12KMH.gpx │ ├── 국민대 30KMH.gpx │ ├── 부원여중 12KMH.gpx │ ├── 부원여중 5MPH.gpx │ ├── 부평동초등학교 20MPH.gpx │ ├── 부평동초등학교 4.2MPH.gpx │ ├── 부평동초등학교 4MPH.gpx │ └── 부평동초등학교 5MPH.gpx ├── AchaLibrary ├── .gitignore ├── .swiftpm │ └── xcode │ │ ├── package.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ └── xcschemes │ │ └── AchaLibrary.xcscheme ├── Package.resolved ├── Package.swift ├── README.md ├── Sources │ └── AchaLibrary │ │ └── AchaLibrary.swift └── Tests │ └── AchaLibraryTests │ └── AchaLibraryTests.swift └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj binary merge=union 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/아차-이슈.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 아차 이슈 3 | about: 템플릿 4 | title: '' 5 | labels: '' 6 | assignees: seungki-cho 7 | 8 | --- 9 | 10 | ## 📌 할 일 11 | - [ ] (1) 12 | - [ ] (2) 13 | ## 😫 관련 문제 및 해결 14 | 15 | ## 📖 참고 자료 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ### 작업 내용: 6 | 7 | 10 | - ex. 주문상품 리스트 구현 or 이미지 뷰어 구현 11 | 12 | ### 이번에 공들였던 부분: 13 | 14 | 18 | 19 | ### 질문: 20 | 21 | 25 | 26 | ### 제출 전 필수 확인 사항: 27 | 28 | - [ ] 빌드가 되는 코드인가요? 29 | - [ ] 버그가 발생하지 않는지 충분히 테스트 해봤나요? 30 | -------------------------------------------------------------------------------- /Acha/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - trailing_whitespace 3 | - large_tuple 4 | - function_body_length 5 | - type_body_length 6 | excluded: 7 | - Acha/AppDelegate.swift 8 | - Acha/SceneDelegate.swift 9 | - AchaUITests/AchaUITests.swift 10 | - AchaTests/AchaTests.swift 11 | -------------------------------------------------------------------------------- /Acha/Acha.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Acha/Acha.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Acha/Acha/Acha.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.healthkit 8 | 9 | com.apple.developer.healthkit.access 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Acha/Acha/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/11. 6 | // 7 | 8 | import UIKit 9 | import FirebaseCore 10 | import RxSwift 11 | 12 | @main 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | func application(_ application: UIApplication, 16 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | FirebaseApp.configure() 18 | DependenciesDefinition().inject() 19 | 20 | let appearance = UITabBarAppearance() 21 | appearance.configureWithOpaqueBackground() 22 | appearance.backgroundColor = .pointLight 23 | UITabBar.appearance().tintColor = .white 24 | UITabBar.appearance().standardAppearance = appearance 25 | 26 | if #available(iOS 15, *) { 27 | UITabBar.appearance().scrollEdgeAppearance = appearance 28 | } 29 | 30 | return true 31 | } 32 | 33 | // MARK: UISceneSession Lifecycle 34 | 35 | func application(_ application: UIApplication, 36 | configurationForConnecting connectingSceneSession: UISceneSession, 37 | options: UIScene.ConnectionOptions) -> UISceneConfiguration { 38 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 39 | } 40 | 41 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 42 | 43 | } 44 | 45 | func applicationWillTerminate(_ application: UIApplication) { 46 | guard let roomID = UserDefaults.standard.value(forKey: "roomID") as? String else {return} 47 | UserDefaults.standard.removeObject(forKey: "roomID") 48 | print(roomID) 49 | let service = DefaultRealtimeDatabaseNetworkService() 50 | service.terminate(type: .room(id: roomID)) 51 | sleep(5) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/AppIcon.appiconset/AppIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/AppIcon.appiconset/AppIcon.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Color/CommentBoxColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.851", 9 | "green" : "0.851", 10 | "red" : "0.851" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Color/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Color/GameRoomColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.753", 9 | "green" : "0.753", 10 | "red" : "0.753" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Color/PointDarkColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.345", 9 | "green" : "0.259", 10 | "red" : "0.251" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.557", 27 | "green" : "0.443", 28 | "red" : "0.420" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Color/PointLightColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.557", 9 | "green" : "0.443", 10 | "red" : "0.420" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.345", 27 | "green" : "0.259", 28 | "red" : "0.251" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/commentImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "bubble 1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/commentImage.imageset/bubble 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/commentImage.imageset/bubble 1.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/crazy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "crazy.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/crazy.imageset/crazy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/crazy.imageset/crazy.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/email.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "email.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/email.imageset/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/email.imageset/email.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/firstAnnotation.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "penguin.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/firstAnnotation.imageset/penguin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/firstAnnotation.imageset/penguin.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/fourthAnnotation.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "bunny.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/fourthAnnotation.imageset/bunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/fourthAnnotation.imageset/bunny.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/invalidate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "exclamation-mark.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/invalidate.imageset/exclamation-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/invalidate.imageset/exclamation-mark.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/map_0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "map_0.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/map_0.imageset/map_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/map_0.imageset/map_0.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/map_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "map_1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/map_1.imageset/map_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/map_1.imageset/map_1.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/map_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "map_2.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/map_2.imageset/map_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/map_2.imageset/map_2.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/map_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "map_3.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/map_3.imageset/map_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/map_3.imageset/map_3.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/nickname.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "name.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/nickname.imageset/name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/nickname.imageset/name.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/noBadge.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "noBadge.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/noBadge.imageset/noBadge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/noBadge.imageset/noBadge.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/password.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "padlock.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/password.imageset/padlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/password.imageset/padlock.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/rank0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "medal.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/rank0.imageset/medal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/rank0.imageset/medal.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/rank1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "medal (1).png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/rank1.imageset/medal (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/rank1.imageset/medal (1).png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/rank2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "medal (2).png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/rank2.imageset/medal (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/rank2.imageset/medal (2).png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/secondAnnotation.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pet.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/secondAnnotation.imageset/pet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/secondAnnotation.imageset/pet.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/thirdAnnotation.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "cat.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/thirdAnnotation.imageset/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/thirdAnnotation.imageset/cat.png -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/x.circle.fill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "x.circle.fill.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/App/Assets.xcassets/Image/x.circle.fill.imageset/x.circle.fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/iOS08_Acha/7fb18961ab68c0e9e76d939ef61f2da6e29a0a7a/Acha/Acha/App/Assets.xcassets/Image/x.circle.fill.imageset/x.circle.fill.png -------------------------------------------------------------------------------- /Acha/Acha/App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Acha/Acha/App/Coordinator/AppCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppCoordinator.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/14. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol AppCoordinatorProtocol: Coordinator { 11 | func connectAuth() 12 | func connectTabBar() 13 | } 14 | 15 | final class AppCoordinator: AppCoordinatorProtocol { 16 | 17 | var navigationController: UINavigationController 18 | var childCoordinators = [Coordinator]() 19 | weak var delegate: CoordinatorDelegate? 20 | 21 | required init(navigationController: UINavigationController) { 22 | self.navigationController = navigationController 23 | } 24 | 25 | func start() { 26 | if (try? KeyChainManager.get()) == nil { 27 | connectAuth() 28 | } else { 29 | connectTabBar() 30 | } 31 | } 32 | 33 | func connectAuth() { 34 | let coordinator = AuthCoordinator(navigationController: navigationController) 35 | coordinator.start() 36 | coordinator.delegate = self 37 | appendChildCoordinator(coordinator: coordinator) 38 | } 39 | 40 | func connectTabBar() { 41 | let coordinator = TabBarCoordinator(navigationController: navigationController) 42 | coordinator.start() 43 | coordinator.delegate = self 44 | appendChildCoordinator(coordinator: coordinator) 45 | } 46 | } 47 | 48 | extension AppCoordinator: CoordinatorDelegate { 49 | func didFinished(childCoordinator: Coordinator) { 50 | removeAllChildCoordinator() 51 | switch childCoordinator { 52 | case is AuthCoordinator: 53 | connectTabBar() 54 | case is TabBarCoordinator: 55 | connectAuth() 56 | default: 57 | break 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Acha/Acha/App/Coordinator/Coordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coordinator.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/14. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol Coordinator: AnyObject { 11 | var navigationController: UINavigationController {get set} 12 | var childCoordinators: [Coordinator] {get set} 13 | var delegate: CoordinatorDelegate? {get set} 14 | 15 | init(navigationController: UINavigationController) 16 | 17 | func start() 18 | /// 특정 자식 코디네이터 삭제 19 | func removeChildCoordinator(coordinator: Coordinator) 20 | /// 자식 코디네이터 추가 21 | func appendChildCoordinator(coordinator: Coordinator) 22 | /// 자식 코디네이터 전부 제거 23 | func removeAllChildCoordinator() 24 | /// 자기 자신 컨트롤러를 네비게이션 컨트롤러에서 제거 25 | func popSelfFromNavigatonController() 26 | } 27 | 28 | extension Coordinator { 29 | func removeChildCoordinator(coordinator: Coordinator) { 30 | childCoordinators = childCoordinators.filter { $0 !== coordinator } 31 | } 32 | 33 | func appendChildCoordinator(coordinator: Coordinator) { 34 | childCoordinators.append(coordinator) 35 | } 36 | 37 | func removeAllChildCoordinator() { 38 | childCoordinators = [] 39 | } 40 | 41 | func popSelfFromNavigatonController() { 42 | navigationController.viewControllers.removeLast() 43 | } 44 | } 45 | 46 | protocol CoordinatorDelegate: AnyObject { 47 | func didFinished(childCoordinator: Coordinator) 48 | } 49 | -------------------------------------------------------------------------------- /Acha/Acha/App/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 357663288042-3bj767jdrqsie6vr6hhlq0ud0suifcvp.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.357663288042-3bj767jdrqsie6vr6hhlq0ud0suifcvp 9 | API_KEY 10 | AIzaSyDrpL6fKqQ1Wpep35USMRwzCaRodBJEHZM 11 | GCM_SENDER_ID 12 | 357663288042 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | com.boostcamp.Acha 17 | PROJECT_ID 18 | acha-75e27 19 | STORAGE_BUCKET 20 | acha-75e27.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:357663288042:ios:7d1e8ada78ce7368d80593 33 | 34 | -------------------------------------------------------------------------------- /Acha/Acha/App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSLocationAlwaysAndWhenInUseUsageDescription 6 | 앱을 사용하는 동안 사용자의 실시간 위치를 포함하기 위해 위치 권한을 요청합니다 7 | NSLocationWhenInUseUsageDescription 8 | 앱을 사용하는 동안 사용자의 실시간 위치를 포함하기 위해 위치 권한을 요청합니다 9 | NSCameraUsageDescription 10 | QR 코드 인식을 위해 카메라 사용 권한을 요청합니다 11 | UIApplicationSceneManifest 12 | 13 | UIApplicationSupportsMultipleScenes 14 | 15 | UISceneConfigurations 16 | 17 | UIWindowSceneSessionRoleApplication 18 | 19 | 20 | UISceneConfigurationName 21 | Default Configuration 22 | UISceneDelegateClassName 23 | $(PRODUCT_MODULE_NAME).SceneDelegate 24 | 25 | 26 | 27 | 28 | UIBackgroundModes 29 | 30 | location 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Acha/Acha/App/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/11. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | var coordinator: AppCoordinator? 14 | 15 | func scene(_ scene: UIScene, 16 | willConnectTo session: UISceneSession, 17 | options connectionOptions: UIScene.ConnectionOptions) { 18 | guard let scene = (scene as? UIWindowScene) else { return } 19 | self.window = UIWindow(windowScene: scene) 20 | 21 | let navigationController = UINavigationController() 22 | coordinator = AppCoordinator(navigationController: navigationController) 23 | self.window?.rootViewController = navigationController 24 | self.window?.makeKeyAndVisible() 25 | coordinator?.start() 26 | } 27 | 28 | func sceneDidDisconnect(_ scene: UIScene) { 29 | 30 | } 31 | 32 | func sceneDidBecomeActive(_ scene: UIScene) { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | func sceneWillResignActive(_ scene: UIScene) { 38 | // Called when the scene will move from an active state to an inactive state. 39 | // This may occur due to temporary interruptions (ex. an incoming phone call). 40 | } 41 | 42 | func sceneWillEnterForeground(_ scene: UIScene) { 43 | // Called as the scene transitions from the background to the foreground. 44 | // Use this method to undo the changes made on entering the background. 45 | } 46 | 47 | func sceneDidEnterBackground(_ scene: UIScene) { 48 | // Called as the scene transitions from the foreground to the background. 49 | // Use this method to save data, release shared resources, and store enough scene-specific state information 50 | // to restore the scene back to its current state. 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/BadgeDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgeDTO.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct BadgeDTO: Codable { 11 | let id: Int 12 | let name: String 13 | let imageURL: String 14 | let isHidden: Bool 15 | 16 | enum CodingKeys: String, CodingKey { 17 | case id 18 | case name 19 | case imageURL = "image" 20 | case isHidden 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/ChatDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatDTO.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/08. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ChatDTO: Codable { 11 | let id: String 12 | let nickName: String 13 | let created: Date 14 | let text: String 15 | var read: [String] 16 | 17 | func toChat() -> Chat { 18 | return Chat(id: id, nickName: nickName, created: created, text: text) 19 | } 20 | 21 | func toRead() -> [String] { 22 | return read 23 | } 24 | 25 | init(data: Chat) { 26 | self.id = data.id 27 | self.nickName = data.nickName 28 | self.created = data.created 29 | self.text = data.text 30 | self.read = [data.id] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/CommentDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommentDTO.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/28. 6 | // 7 | 8 | import Foundation 9 | 10 | struct CommentDTO: Codable { 11 | let id: Int 12 | let postId: Int 13 | let nickName: String 14 | let userId: String 15 | let text: String 16 | let createdAt: Date 17 | 18 | func toDomain() -> Comment { 19 | return Comment( 20 | id: id, 21 | postId: postId, 22 | userId: userId, 23 | nickName: nickName, 24 | text: text, 25 | createdAt: createdAt 26 | ) 27 | } 28 | 29 | init(data: Comment) { 30 | self.id = data.id 31 | self.postId = data.postId 32 | self.nickName = data.nickName 33 | self.userId = data.userId 34 | self.text = data.text 35 | self.createdAt = data.createdAt 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/CommunityDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommunityDTO.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/03. 6 | // 7 | 8 | import Foundation 9 | 10 | struct CommunityDTO: Codable { 11 | var postList: [PostDTO]? 12 | 13 | mutating func addPost(post: PostDTO) { 14 | postList?.append(post) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/CoordinateDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoordinateDTO.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/27. 6 | // 7 | 8 | import Foundation 9 | 10 | struct CoordinateDTO: Codable { 11 | let latitude, longitude: Double 12 | 13 | func distance(from: Coordinate) -> Double { 14 | let theta = self.longitude - from.longitude 15 | let dist = sin(self.latitude.degreeToRadian()) * 16 | sin(from.latitude.degreeToRadian()) + 17 | cos(self.latitude.degreeToRadian()) * 18 | cos(from.latitude.degreeToRadian()) * 19 | cos(theta.degreeToRadian()) 20 | 21 | return acos(dist).radianToDegree() * 60 * 1.853159616 * 1000 22 | } 23 | 24 | func toDomain() -> Coordinate { 25 | return Coordinate(latitude: latitude, longitude: longitude) 26 | } 27 | 28 | init(data: Coordinate) { 29 | self.latitude = data.latitude 30 | self.longitude = data.longitude 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/InGameUserDataDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InGameUserData.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/27. 6 | // 7 | 8 | import Foundation 9 | 10 | struct InGameUserDataDTO: Codable, Comparable { 11 | static func < (lhs: InGameUserDataDTO, rhs: InGameUserDataDTO) -> Bool { 12 | return lhs.eatenMapID.count < rhs.eatenMapID.count 13 | } 14 | 15 | static func == (lhs: InGameUserDataDTO, rhs: InGameUserDataDTO) -> Bool { 16 | return lhs.eatenMapID.count == rhs.eatenMapID.count 17 | } 18 | 19 | let userID: String 20 | let eatenMapID: [Int] 21 | var routes: [CoordinateDTO] 22 | } 23 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/MapDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapDTO.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/27. 6 | // 7 | 8 | import Foundation 9 | 10 | struct MapDTO: Codable { 11 | let mapID: Int 12 | let name: String 13 | let centerCoordinate: CoordinateDTO 14 | let coordinates: [CoordinateDTO] 15 | let location: String 16 | let image: String 17 | 18 | enum CodingKeys: String, CodingKey { 19 | case mapID = "mapId" 20 | case name, centerCoordinate, coordinates, location, image 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/MultiGamePlayerDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiGamePlayerDTO.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | 10 | struct MultiGamePlayerDTO: Codable { 11 | let id: String 12 | let nickName: String 13 | let currentLocation: CoordinateDTO? 14 | let point: Int 15 | let locationHistory: [CoordinateDTO]? 16 | 17 | func toDamin() -> MultiGamePlayerData { 18 | return MultiGamePlayerData( 19 | id: id, 20 | nickName: nickName, 21 | currentLocation: currentLocation == nil ? nil : currentLocation?.toDomain(), 22 | point: point 23 | ) 24 | } 25 | 26 | init(data: MultiGamePlayerData, history: [Coordinate]) { 27 | self.id = data.id 28 | self.nickName = data.nickName 29 | self.currentLocation = data.currentLocation == nil ? nil : CoordinateDTO(data: data.currentLocation!) 30 | self.point = data.point 31 | self.locationHistory = history.map { CoordinateDTO(data: $0) } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/PostDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostDTO.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/29. 6 | // 7 | import Foundation 8 | 9 | struct PostDTO: Codable { 10 | let id: Int 11 | let userId: String 12 | let nickName: String 13 | let text: String 14 | let image: String? 15 | let createdAt: Date 16 | let comments: [CommentDTO]? 17 | 18 | func toDomain() -> Post { 19 | let comments = comments == nil ? [] : comments?.map { $0.toDomain() } 20 | 21 | return Post( 22 | id: id, 23 | userId: userId, 24 | nickName: nickName, 25 | text: text, 26 | createdAt: createdAt, 27 | comments: comments 28 | ) 29 | } 30 | 31 | init(data: Post, image: String? = nil) { 32 | self.id = data.id 33 | self.userId = data.userId 34 | self.nickName = data.nickName 35 | self.text = data.text 36 | self.image = image 37 | self.createdAt = data.createdAt 38 | self.comments = data.comments?.compactMap { CommentDTO(data: $0) } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/RecordDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordDTO.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct RecordDTO: Codable { 11 | let id: Int 12 | let mapID: Int 13 | let userID: String 14 | let calorie: Int 15 | let distance: Int 16 | let time: Int 17 | let isSingleMode: Bool 18 | let isWin: Bool? 19 | let isCompleted: Bool 20 | let createdAt: String 21 | 22 | enum CodingKeys: String, CodingKey { 23 | case id 24 | case mapID = "map_id" 25 | case userID = "user_id" 26 | case calorie 27 | case distance 28 | case time 29 | case isSingleMode 30 | case isWin 31 | case isCompleted 32 | case createdAt = "created_at" 33 | } 34 | 35 | func toDomain() -> Record { 36 | return Record(id: id, 37 | mapID: mapID, 38 | userID: userID, 39 | calorie: calorie, 40 | distance: distance, 41 | time: time, 42 | isSingleMode: isSingleMode, 43 | isWin: isWin, 44 | isCompleted: isCompleted, 45 | createdAt: createdAt) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/RoomDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoomDTO.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct RoomDTO: Codable { 11 | let id: String 12 | let createdAt: Date 13 | var user: [UserDTO] 14 | let mapID: [Int]? 15 | var inGameUserDatas: [InGameUserDataDTO]? 16 | var gameInformation: [MultiGamePlayerDTO]? 17 | var chats: [ChatDTO]? 18 | 19 | func toRoomUsers() -> [RoomUser] { 20 | return user.map { $0.toRoomUser() } 21 | } 22 | 23 | mutating func leaveFromRoom(userID: String) { 24 | self.user = user.filter { $0.id != userID } 25 | } 26 | 27 | mutating func enterRoom(user: UserDTO) { 28 | self.user.append(user) 29 | } 30 | 31 | mutating func appendChat(chat: ChatDTO) { 32 | if self.chats == nil { 33 | self.chats = [chat] 34 | } else { 35 | self.chats?.append(chat) 36 | } 37 | } 38 | 39 | init(id: String, user: [UserDTO]) { 40 | self.id = id 41 | self.user = user 42 | self.createdAt = Date() 43 | self.mapID = nil 44 | self.inGameUserDatas = nil 45 | self.gameInformation = nil 46 | self.chats = nil 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Acha/Acha/Data/DTO/UserDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDTO.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserDTO: Codable { 11 | let id: String 12 | let nickname: String 13 | let badges: [Int]? 14 | let records: [Int]? 15 | let pinCharacter: String? 16 | let friends: [Int]? 17 | 18 | func toRoomUser() -> RoomUser { 19 | return RoomUser(id: id, nickName: nickname) 20 | } 21 | 22 | func toDomain() -> User { 23 | return User(id: id, 24 | nickName: nickname, 25 | badges: badges ?? [], 26 | records: records ?? [], 27 | pinCharacter: pinCharacter ?? "firstAnnotation", 28 | friends: friends ?? []) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/DefaultLocationRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultLocationRepository.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import CoreLocation 11 | 12 | struct DefaultLocationRepository: LocationRepository { 13 | 14 | private let locationService: LocationService 15 | 16 | init(locationService: LocationService) { 17 | self.locationService = locationService 18 | } 19 | 20 | func getCurrentLocation() -> Observable { 21 | locationService.start() 22 | return locationService.userLocation 23 | .skip(1) 24 | .map { return Coordinate( 25 | latitude: $0.coordinate.latitude, 26 | longitude: $0.coordinate.longitude 27 | ) } 28 | } 29 | 30 | func stopObservingLocation() { 31 | locationService.stop() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/DefaultTempRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultRecordMapViewRepository.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/23. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | final class DefaultTempRepository: TempRepository { 12 | var tempDBNetwork: TempDBNetwork 13 | 14 | init(tempDBNetwork: TempDBNetwork) { 15 | self.tempDBNetwork = tempDBNetwork 16 | } 17 | 18 | func fetchMapData() -> Observable<[MapDTO]> { 19 | return tempDBNetwork.fetchData(path: "mapList").map { data in 20 | guard let mapDTO = try? JSONDecoder().decode([MapDTO].self, from: data) else { return [] } 21 | return mapDTO 22 | } 23 | } 24 | 25 | func fetchRecordData() -> Observable<[Record]> { 26 | return tempDBNetwork.fetchData(path: "record").map { data in 27 | guard let records = try? JSONDecoder().decode([Record].self, from: data) else { return [] } 28 | return records 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/DefaultTimeRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultTimeRepository.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | struct DefaultTimeRepository: TimeRepository { 12 | 13 | private let timerService: TimerService 14 | 15 | init(timeService: TimerService) { 16 | self.timerService = timeService 17 | } 18 | 19 | func startTimer() -> Observable { 20 | return timerService.start() 21 | } 22 | 23 | func setTimer(time: Int) -> Observable { 24 | return startTimer() 25 | .map { 26 | let remainTime = time - $0 27 | if remainTime <= 0 { stopTimer() } 28 | return remainTime 29 | } 30 | } 31 | 32 | func stopTimer() { 33 | return timerService.stop() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/Protocol/BadgeRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgeRepository.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/12/01. 6 | // 7 | 8 | import RxSwift 9 | 10 | protocol BadgeRepository { 11 | func fetchAllBadges() -> Observable<[Badge]> 12 | func fetchSomeBadges(ids: [Int]) -> Observable<[Badge]> 13 | } 14 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/Protocol/CommunityRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommunityRepository.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/05. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol CommunityRepository { 12 | var uploadCommentSuccess: PublishSubject { get set } 13 | 14 | func loadPost(count: Int) -> Observable<[Post]> 15 | func fetchPost(postID: Int) -> Observable 16 | func uploadPost(post: Post, image: Image?) -> Single 17 | func updatePost(post: Post, image: Image?) -> Single 18 | func deletePost(id: Int) -> Single 19 | func uploadComment(comment: Comment) -> Single 20 | } 21 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/Protocol/GameRoomRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameRoomRepository.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/05. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol GameRoomRepository { 12 | /// RoomDTO 전부를 끌고 오는 메서드 입니다 13 | func fetchRoomData(id: String) -> Single 14 | 15 | /// RoomDTO 를 RoomUser 로 변환 하고 리턴하는 메서드 입니다 16 | func fetchRoomUserData(id: String) -> Single<[RoomUser]> 17 | 18 | /// 방에 들어가는 메서드 입니다 19 | func enterRoom(id: String) -> Single<[RoomUser]> 20 | 21 | /// 방을 만드는 메서드입니다. ( 입장 포함 ) ... 방 번호 리턴 22 | func makeRoom() -> Observable 23 | 24 | /// 방을 떠나는 메서드입니다. 25 | func leaveRoom(id: String) 26 | 27 | /// 방을 삭제하는 메서드입니다. 28 | func deleteRoom(id: String) 29 | 30 | /// 원하는 방의 상황을 옵저빙 할 수 있는 메서드입니다. 31 | func observingRoom(id: String) -> Observable 32 | 33 | /// [RoomUser] 형태로 변경 해주는 메서드입니다. 34 | func observingRoomUser(id: String) -> Observable<[RoomUser]> 35 | 36 | /// observer 해제 ( observing 하고 안 할때 풀어줘야 합니다 ) 37 | func removeObserverRoom(id: String) 38 | 39 | /// 게임 유저 데이터 불러 오기 ... observing 사용 ... 해제 필요 40 | func observingMultiGamePlayers(id: String) -> Observable<[MultiGamePlayerData]> 41 | 42 | /// 게임 룸 업데이트 메서드 43 | func updateMultiGamePlayer( 44 | roomId: String, 45 | data: MultiGamePlayerData, 46 | histroy: [Coordinate] 47 | ) 48 | 49 | /// 게임 시작시 인 게임 데이터 만들기 50 | func startGame(roomId: String) 51 | 52 | /// 룸 챗 옵저빙 53 | func observingChats(id: String) -> Observable<[ChatDTO]> 54 | 55 | /// 읽은 정보 업데이트 56 | func updateReads(roomID: String, reads: [[String]]) 57 | 58 | /// 쳇 업데이트 59 | func updateChats(roomID: String, chat: Chat) 60 | 61 | /// 메시지들 읽은 사람들 리턴해주는 메서드 62 | func observingReads(id: String) -> Observable<[[String]]> 63 | } 64 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/Protocol/LocationRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationRepository.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import CoreLocation 11 | 12 | protocol LocationRepository { 13 | 14 | /// location 얻는 레포지토리 15 | func getCurrentLocation() -> Observable 16 | /// CLLocation 스탑 17 | func stopObservingLocation() 18 | } 19 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/Protocol/MapRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapRepository.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/27. 6 | // 7 | 8 | import RxSwift 9 | 10 | protocol MapRepository { 11 | func fetchAllMaps() -> Observable<[Map]> 12 | func fetchMapsAtLocation(location: String) -> Observable<[Map]> 13 | } 14 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/Protocol/RecordRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordRepository.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/27. 6 | // 7 | 8 | import RxSwift 9 | 10 | protocol RecordRepository { 11 | func fetchAllRecords() -> Single<[Record]> 12 | func fetchRecordDataAtMapID(mapID: Int) -> Single<[Record]> 13 | func uploadNewRecord(record: Record) 14 | func recordCount() -> Single 15 | func healthKitAuthorization() -> Observable 16 | func healthKitWrite(_ data: HealthKitWriteData) -> Observable 17 | func healthKitRead() -> Observable 18 | } 19 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/Protocol/TempRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordMainViewRepository.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/23. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol TempRepository { 12 | var tempDBNetwork: TempDBNetwork { get set } 13 | func fetchMapData() -> Observable<[MapDTO]> 14 | func fetchRecordData() -> Observable<[Record]> 15 | } 16 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/Protocol/TimeRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeRepository.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol TimeRepository { 12 | 13 | /// 타이머 시작 ( 1 -> 2 -> 3 ... ) 14 | func startTimer() -> Observable 15 | 16 | /// 특정 시간 타이머 맞춤 ( 0초 되면 종료 ) 17 | func setTimer(time: Int) -> Observable 18 | 19 | /// 타이머 종료 20 | func stopTimer() 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/Data/Repository/Protocol/UserRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserRepository.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/02. 6 | // 7 | import Foundation 8 | import RxSwift 9 | 10 | protocol UserRepository { 11 | func getUUID() -> String? 12 | func fetchUserData() -> Single 13 | func signUp(data: SignUpData) -> Single 14 | func logIn(data: LoginData) -> Single 15 | func signOut() -> Observable 16 | func delete() -> Single 17 | func updateUserData(user: User) -> Single 18 | func updateUserEmail(email: String, password: String) -> Single 19 | } 20 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/Badge.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Badge.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/30. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Badge: Hashable { 11 | let id: Int 12 | let name: String 13 | let image: Data 14 | let isHidden: Bool 15 | var isOwn: Bool = false 16 | } 17 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/Chat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Chat.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/08. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Chat: Hashable { 11 | let id: String 12 | let nickName: String 13 | let created: Date 14 | let text: String 15 | } 16 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/Comment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Comment.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/29. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Comment: Hashable { 11 | var id: Int 12 | var postId: Int 13 | var userId: String 14 | var nickName: String 15 | let text: String 16 | let createdAt: Date 17 | 18 | init( 19 | id: Int = -1, 20 | postId: Int = -1, 21 | userId: String, 22 | nickName: String, 23 | text: String, 24 | createdAt: Date = Date() 25 | ) { 26 | self.id = id 27 | self.postId = postId 28 | self.userId = userId 29 | self.nickName = nickName 30 | self.text = text 31 | self.createdAt = createdAt 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/Coordinate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coordinate.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/21. 6 | // 7 | 8 | import Foundation 9 | import MapKit 10 | 11 | // MARK: - Coordinate 12 | struct Coordinate: Decodable, Hashable { 13 | let latitude, longitude: Double 14 | 15 | func distance(from: Coordinate) -> Double { 16 | let theta = self.longitude - from.longitude 17 | let dist = sin(self.latitude.degreeToRadian()) * 18 | sin(from.latitude.degreeToRadian()) + 19 | cos(self.latitude.degreeToRadian()) * 20 | cos(from.latitude.degreeToRadian()) * 21 | cos(theta.degreeToRadian()) 22 | 23 | return acos(dist).radianToDegree() * 60 * 1.853159616 * 1000 24 | } 25 | 26 | func toCLLocationCoordinate2D() -> CLLocationCoordinate2D { 27 | return CLLocationCoordinate2D( 28 | latitude: latitude, 29 | longitude: longitude 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/HealthKitReadData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthKitReadData.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/01. 6 | // 7 | 8 | import Foundation 9 | 10 | struct HealthKitReadData { 11 | let calorie: Double? 12 | let distance: Double? 13 | } 14 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/HealthKitWriteData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthKitWriteData.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/01. 6 | // 7 | 8 | import Foundation 9 | 10 | struct HealthKitWriteData { 11 | let distance: Int 12 | let diatanceTime: Int64 13 | 14 | let calorie: Int 15 | let calorieTime: Int64 16 | 17 | init(distance: Double, diatanceTime: Int, calorie: Int, calorieTime: Int) { 18 | self.distance = Int(distance) 19 | self.diatanceTime = Int64(diatanceTime) 20 | self.calorie = calorie 21 | self.calorieTime = Int64(calorieTime) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/07. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Image: Hashable { 11 | let name: String 12 | let data: Data 13 | } 14 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/InGameRanking.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InGameRanking.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/15. 6 | // 7 | 8 | import Foundation 9 | 10 | struct InGameRanking: Hashable { 11 | var id: Int 12 | var time: Int 13 | var userName: String 14 | var date: Date 15 | } 16 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/InGameRecord.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InGameRecord.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/15. 6 | // 7 | 8 | import Foundation 9 | 10 | struct InGameRecord: Hashable { 11 | var id: Int 12 | var time: Int 13 | var userName: String 14 | var date: Date 15 | } 16 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/LogInData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogInData.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/27. 6 | // 7 | 8 | import Foundation 9 | 10 | struct LoginData: Equatable { 11 | let email: String 12 | let password: String 13 | } 14 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/Map.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Map.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/14. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - Map 11 | struct Map: Hashable { 12 | let mapID: Int 13 | let name: String 14 | let centerCoordinate: Coordinate 15 | let coordinates: [Coordinate] 16 | let location: String 17 | let image: Data 18 | } 19 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/MapRegion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapRegion.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/21. 6 | // 7 | 8 | import Foundation 9 | 10 | struct MapRegion { 11 | let center: Coordinate 12 | let span: CoordinateSpan 13 | } 14 | 15 | struct CoordinateSpan { 16 | let latitudeDelta: Double 17 | let longitudeDelta: Double 18 | } 19 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/MultiGamePlayerData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiGamePlayerData.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | 10 | struct MultiGamePlayerData: Hashable { 11 | let id: String 12 | let nickName: String 13 | let currentLocation: Coordinate? 14 | let point: Int 15 | } 16 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/Post.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Post.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/28. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Post: Hashable { 11 | var id: Int 12 | var userId: String 13 | var nickName: String 14 | var text: String 15 | var image: Data? 16 | let createdAt: Date 17 | var comments: [Comment]? 18 | 19 | init() { 20 | self.id = -1 21 | self.userId = "" 22 | self.nickName = "" 23 | self.text = "" 24 | self.image = nil 25 | self.createdAt = Date() 26 | self.comments = nil 27 | } 28 | 29 | init( 30 | id: Int = -1, 31 | userId: String, 32 | nickName: String, 33 | text: String, 34 | image: Data? = nil, 35 | createdAt: Date = Date(), 36 | comments: [Comment]? = nil 37 | ) { 38 | self.id = id 39 | self.userId = userId 40 | self.nickName = nickName 41 | self.text = text 42 | self.image = image 43 | self.createdAt = createdAt 44 | self.comments = comments 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/Record.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AchaRecord.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/17. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Record: Hashable, Codable { 11 | var id: Int 12 | var mapID: Int 13 | var userID: String 14 | var calorie: Int 15 | var distance: Int 16 | var time: Int 17 | var isSingleMode: Bool 18 | var isWin: Bool? 19 | var isCompleted: Bool 20 | var createdAt: String 21 | 22 | enum CodingKeys: String, CodingKey { 23 | case id 24 | case mapID = "map_id" 25 | case userID = "user_id" 26 | case calorie 27 | case distance 28 | case time 29 | case isSingleMode 30 | case isWin 31 | case isCompleted 32 | case createdAt = "created_at" 33 | } 34 | 35 | init(id: Int = -1, 36 | mapID: Int = -1, 37 | userID: String = "", 38 | calorie: Int = -1, 39 | distance: Int = -1, 40 | time: Int = Int.max, 41 | isSingleMode: Bool = true, 42 | isWin: Bool? = nil, 43 | isCompleted: Bool = false, 44 | createdAt: String = "") { 45 | self.id = id 46 | self.mapID = mapID 47 | self.userID = userID 48 | self.calorie = calorie 49 | self.distance = distance 50 | self.time = time 51 | self.isSingleMode = isSingleMode 52 | self.isWin = isWin 53 | self.isCompleted = isCompleted 54 | self.createdAt = createdAt 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/RecordViewChartData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordViewChartData.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/17. 6 | // 7 | 8 | import Foundation 9 | 10 | struct RecordViewChartData: Hashable { 11 | var number: Int 12 | var distance: Int 13 | } 14 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/RecordViewDayTotalRecord.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordViewDayTotalRecord.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/18. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DayTotalRecord: Hashable { 11 | var distance: Int 12 | var calorie: Int 13 | } 14 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/RecordViewHeaderRecord.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordViewHeaderRecord.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/17. 6 | // 7 | 8 | import Foundation 9 | 10 | struct RecordViewHeaderRecord: Hashable { 11 | var date: String 12 | var distance: Int 13 | var kcal: Int 14 | } 15 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/RoomUser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoomUser.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct RoomUser: Hashable { 11 | let id: String 12 | let nickName: String 13 | } 14 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/SignUpData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignUpData.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SignUpData: Equatable { 11 | let email: String 12 | let password: String 13 | let nickName: String 14 | 15 | func toUserDTO(id: String) -> UserDTO { 16 | return UserDTO(id: id, 17 | nickname: nickName, 18 | badges: [], 19 | records: [], 20 | pinCharacter: nil, 21 | friends: [] 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/Entity/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/05. 6 | // 7 | 8 | import Foundation 9 | 10 | struct User { 11 | let id: String 12 | var nickName: String 13 | let badges: [Int] 14 | let records: [Int] 15 | var pinCharacter: String 16 | let friends: [Int] 17 | 18 | init(id: String = "", 19 | nickName: String = "", 20 | badges: [Int] = [], 21 | records: [Int] = [], 22 | pinCharacter: String = "firstAnnotation", 23 | friends: [Int] = []) { 24 | self.id = id 25 | self.nickName = nickName 26 | self.badges = badges 27 | self.records = records 28 | self.pinCharacter = pinCharacter 29 | self.friends = friends 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Auth/DefatulSignUpUsecase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignUpUsecase.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/27. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxRelay 11 | 12 | final class DefaultSignUpUsecase: SignUpUsecase { 13 | 14 | var emailValidation: Bool = false 15 | var passwordValidation: Bool = false 16 | var nickNameValidation: Bool = false 17 | 18 | var emailRelay: RxRelay.BehaviorRelay = .init(value: "") 19 | var passwordRelay: RxRelay.BehaviorRelay = .init(value: "") 20 | var nickNameRelay: RxRelay.BehaviorRelay = .init(value: "") 21 | 22 | private let repository: UserRepository 23 | 24 | init(repository: UserRepository) { 25 | self.repository = repository 26 | } 27 | 28 | @discardableResult 29 | public func emailValidate(text: String) -> Bool { 30 | let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" 31 | emailValidation = text.stringCheck(pattern: pattern) 32 | if emailValidation { emailRelay.accept(text) } 33 | return emailValidation 34 | } 35 | 36 | @discardableResult 37 | public func passwordValidate(text: String) -> Bool { 38 | passwordValidation = text.count >= 6 ? true : false 39 | if passwordValidation { passwordRelay.accept(text) } 40 | return passwordValidation 41 | } 42 | 43 | @discardableResult 44 | public func nickNameValidate(text: String) -> Bool { 45 | nickNameValidation = text.count >= 3 ? true : false 46 | if nickNameValidation { nickNameRelay.accept(text) } 47 | return nickNameValidation 48 | } 49 | 50 | func isSignAble() -> Bool { 51 | return emailValidation && passwordValidation && nickNameValidation 52 | } 53 | 54 | func signUp() -> Observable { 55 | let signUpData = SignUpData( 56 | email: emailRelay.value, 57 | password: passwordRelay.value, 58 | nickName: nickNameRelay.value 59 | ) 60 | return repository.signUp(data: signUpData) 61 | .map { $0.id } 62 | .asObservable() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Auth/DefaultLoginUsecase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginUsecase.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/27. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxRelay 11 | 12 | final class DefaultLoginUseCase: LoginUseCase { 13 | 14 | var emailValidation: Bool = false 15 | var passwordValidation: Bool = false 16 | 17 | var emailRelay: RxRelay.BehaviorRelay = .init(value: "") 18 | var passwordRelay: RxRelay.BehaviorRelay = .init(value: "") 19 | private let repository: UserRepository 20 | 21 | init(repository: UserRepository) { 22 | self.repository = repository 23 | } 24 | 25 | @discardableResult 26 | public func emailValidate(text: String) -> Bool { 27 | let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" 28 | emailValidation = text.stringCheck(pattern: pattern) 29 | if emailValidation { emailRelay.accept(text) } 30 | return emailValidation 31 | } 32 | 33 | @discardableResult 34 | public func passwordValidate(text: String) -> Bool { 35 | passwordValidation = text.count >= 6 ? true : false 36 | if passwordValidation { passwordRelay.accept(text) } 37 | return passwordValidation 38 | } 39 | 40 | func isLogInAble() -> Bool { 41 | return emailValidation && passwordValidation 42 | } 43 | 44 | func logIn() -> Observable { 45 | let loginData = LoginData(email: emailRelay.value, password: passwordRelay.value) 46 | return repository.logIn(data: loginData).asObservable() 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Auth/Protocol/AuthUseCaseProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthUseCase.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/21. 6 | // 7 | 8 | import Foundation 9 | import FirebaseAuth 10 | import RxSwift 11 | import FirebaseDatabase 12 | 13 | protocol EmailValidatable { 14 | func emailValidate(text: String) -> Bool 15 | } 16 | 17 | protocol PasswordValidatable { 18 | func passwordValidate(text: String) -> Bool 19 | } 20 | 21 | protocol NickNameValidatable { 22 | func nickNameValidate(text: String) -> Bool 23 | } 24 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Auth/Protocol/LoginUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginUseCase.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/12. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxCocoa 11 | 12 | protocol LoginUseCase: EmailValidatable, PasswordValidatable { 13 | var emailRelay: BehaviorRelay {get set} 14 | var passwordRelay: BehaviorRelay {get set} 15 | 16 | var emailValidation: Bool {get set} 17 | var passwordValidation: Bool {get set} 18 | 19 | func isLogInAble() -> Bool 20 | func logIn() -> Observable 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Auth/Protocol/SignUpUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignUpUseCase.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/12. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxCocoa 11 | 12 | protocol SignUpUsecase: EmailValidatable, PasswordValidatable, NickNameValidatable { 13 | var emailRelay: BehaviorRelay {get set} 14 | var passwordRelay: BehaviorRelay {get set} 15 | var nickNameRelay: BehaviorRelay {get set} 16 | 17 | var emailValidation: Bool {get set} 18 | var passwordValidation: Bool {get set} 19 | var nickNameValidation: Bool {get set} 20 | 21 | func isSignAble() -> Bool 22 | func signUp() -> Observable 23 | } 24 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Community/DefaultCommunityMainUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommunityMainUseCase.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/30. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxRelay 11 | 12 | final class DefaultCommunityMainUseCase: CommunityMainUseCase { 13 | private let communityRepository: DefaultCommunityRepository 14 | private let disposeBag = DisposeBag() 15 | 16 | var posts = PublishSubject<[Post]>() 17 | 18 | init(communityRepository: DefaultCommunityRepository) { 19 | self.communityRepository = communityRepository 20 | } 21 | 22 | func loadPostData(count: Int) { 23 | communityRepository.loadPost(count: count) 24 | .subscribe(onNext: { [weak self] posts in 25 | guard let self else { return } 26 | self.posts.onNext(posts) 27 | }, onError: { [weak self] _ in 28 | guard let self else { return } 29 | self.posts.onNext([]) 30 | }) 31 | .disposed(by: disposeBag) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Community/DefaultCommunityPostWriteUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultCommunityPostWriteUseCase.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxRelay 11 | 12 | final class DefaultCommunityPostWriteUseCase: CommunityPostWriteUseCase { 13 | private let communityRepository: CommunityRepository 14 | private let userRepository: UserRepository 15 | private let disposeBag = DisposeBag() 16 | let post: Post! 17 | 18 | init(post: Post? = nil, 19 | communityRepository: CommunityRepository, 20 | userRepository: UserRepository 21 | ) { 22 | self.communityRepository = communityRepository 23 | self.post = post 24 | self.userRepository = userRepository 25 | } 26 | 27 | func confirmHavePost() -> Single { 28 | return Single.create { [weak self] single in 29 | guard let self else { return Disposables.create()} 30 | if let post = self.post { 31 | single(.success(post)) 32 | } 33 | return Disposables.create() 34 | } 35 | } 36 | 37 | func uploadPost(postContent: String, image: Image?) -> Single { 38 | return Single.create { [weak self] single in 39 | guard let self else { return Disposables.create()} 40 | self.userRepository.fetchUserData() 41 | .subscribe(onSuccess: { user in 42 | if let selfPost = self.post { 43 | var uploadPost = selfPost 44 | uploadPost.text = postContent 45 | uploadPost.userId = user.id 46 | uploadPost.nickName = user.nickName 47 | self.communityRepository.updatePost(post: uploadPost, image: image) 48 | .subscribe(onSuccess: { 49 | single(.success(())) 50 | }) 51 | .disposed(by: self.disposeBag) 52 | } else { 53 | let post = Post(userId: user.id, nickName: user.nickName, text: postContent) 54 | self.communityRepository.uploadPost(post: post, image: image) 55 | .subscribe(onSuccess: { 56 | single(.success(())) 57 | }) 58 | .disposed(by: self.disposeBag) 59 | } 60 | }).disposed(by: self.disposeBag) 61 | return Disposables.create() 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Community/Protocol/CommunityDetailUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommunityDetailUseCase.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/05. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol CommunityDetailUseCase { 12 | func fetchPost() 13 | func uploadComment(commentMessage: String) -> Single 14 | func deletePost() -> Single 15 | 16 | var post: PublishSubject<(post: Post, isMine: Bool)> { get set } 17 | var user: BehaviorSubject { get set } 18 | var fetchFailure: PublishSubject { get set } 19 | } 20 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Community/Protocol/CommunityMainUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommunityMainUseCase.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/30. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol CommunityMainUseCase { 12 | var posts: PublishSubject<[Post]> { get set } 13 | func loadPostData(count: Int) 14 | } 15 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Community/Protocol/CommunityPostWriteUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommunityPostWriteUseCase.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxRelay 11 | 12 | protocol CommunityPostWriteUseCase { 13 | func confirmHavePost() -> Single 14 | func uploadPost(postContent: String, image: Image?) -> Single 15 | } 16 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Game/DefaultInGameUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultInGameUseCase.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/29. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | class DefaultInGameUseCase: InGameUseCase { 12 | private let recordRepository: RecordRepository 13 | private let userRepository: UserRepository 14 | private let mapID: Int 15 | 16 | private var disposeBag = DisposeBag() 17 | var inGameRecord = PublishSubject<[InGameRecord]>() 18 | 19 | init(mapID: Int, 20 | recordRepository: RecordRepository, 21 | userRepository: UserRepository 22 | ) { 23 | self.mapID = mapID 24 | self.recordRepository = recordRepository 25 | self.userRepository = userRepository 26 | } 27 | 28 | func fetchRecord() { 29 | userRepository 30 | .fetchUserData() 31 | .asObservable() 32 | .subscribe(onNext: { [weak self] (user: User) in 33 | guard let self else { return } 34 | self.recordRepository.fetchAllRecords() 35 | .map { $0.filter { $0.mapID == self.mapID && $0.isCompleted && user.records.contains($0.id) } } 36 | .map { records in 37 | records.map { InGameRecord(id: $0.id, 38 | time: $0.time, 39 | userName: $0.userID, 40 | date: $0.createdAt.convertToDateFormat(format: "yyyy-MM-dd")) 41 | }.sorted { $0.date > $1.date} 42 | } 43 | .asObservable() 44 | .bind(to: self.inGameRecord) 45 | .disposed(by: self.disposeBag) 46 | }).disposed(by: disposeBag) 47 | } 48 | 49 | func fetchRanking() -> Single<[InGameRanking]> { 50 | recordRepository 51 | .fetchAllRecords() 52 | .map { $0.filter { $0.mapID == self.mapID && $0.isCompleted } } 53 | .map { records in 54 | records.map { InGameRanking(id: $0.id, 55 | time: $0.time, 56 | userName: $0.userID, 57 | date: $0.createdAt.convertToDateFormat(format: "yyyy-MM-dd")) 58 | }.sorted { $0.time < $1.time } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Game/DefaultMapBaseUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultMapBaseUseCase.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/28. 6 | // 7 | 8 | import RxSwift 9 | 10 | class DefaultMapBaseUseCase: MapBaseUseCase { 11 | 12 | private let locationService: LocationService 13 | let userRepository: UserRepository 14 | 15 | var user = BehaviorSubject(value: User()) 16 | var userLocation = BehaviorSubject(value: Coordinate(latitude: 37.0, longitude: 126.0)) 17 | private var disposeBag = DisposeBag() 18 | 19 | init(locationService: LocationService, 20 | userRepository: UserRepository) { 21 | self.locationService = locationService 22 | self.userRepository = userRepository 23 | } 24 | 25 | func start() { 26 | locationService.start() 27 | locationService.userLocation 28 | .map { Coordinate(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude) } 29 | .bind(to: self.userLocation) 30 | .disposed(by: disposeBag) 31 | 32 | userRepository.fetchUserData() 33 | .subscribe(onSuccess: { [weak self] user in 34 | guard let self else { return } 35 | self.user.onNext(user) 36 | }).disposed(by: disposeBag) 37 | } 38 | 39 | func stop() { 40 | locationService.stop() 41 | } 42 | 43 | func isAvailableLocationAuthorization() -> Observable<(Bool, Coordinate?)> { 44 | locationService.authorizationStatus 45 | .delay(.microseconds(1), scheduler: MainScheduler.instance) 46 | .map { [weak self] status in 47 | if let self, 48 | status, 49 | let userLocation = try? self.userLocation.value() { 50 | return (status, userLocation) 51 | } else { 52 | return (status, nil) 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Game/DefaultMultiGameRoomUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiGameRoomUseCase.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/23. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | struct DefaultMultiGameRoomUseCase: MultiGameRoomUseCase { 12 | 13 | enum GameRoomError: Error, LocalizedError { 14 | case notAvailiableUserNumber 15 | 16 | var errorDescription: String? { 17 | switch self { 18 | case .notAvailiableUserNumber: 19 | return "2명 이상이여야 게임을 시작할 수 있습니다 ✌️✌️✌️" 20 | } 21 | } 22 | } 23 | private let disposebag = DisposeBag() 24 | 25 | private let repository: GameRoomRepository 26 | init(repository: GameRoomRepository) { 27 | self.repository = repository 28 | } 29 | 30 | func observing(roomID: String) -> Observable<[RoomUser]?> { 31 | return repository.observingRoom(id: roomID) 32 | .map { 33 | return $0.gameInformation != nil ? nil : $0.toRoomUsers() 34 | } 35 | } 36 | 37 | func get(roomID: String) -> Single<[RoomUser]> { 38 | return repository.fetchRoomUserData(id: roomID) 39 | } 40 | 41 | func leave(roomID: String) { 42 | repository.leaveRoom(id: roomID) 43 | } 44 | 45 | func removeObserver(roomID: String) { 46 | repository.removeObserverRoom(id: roomID) 47 | } 48 | 49 | func isGameAvailable(roomID: String) -> Observable { 50 | return Observable.create { observer in 51 | get(roomID: roomID) 52 | .map { roomUsers in 53 | if 2...4 ~= roomUsers.count { 54 | observer.onNext(()) 55 | } else { 56 | observer.onError(GameRoomError.notAvailiableUserNumber) 57 | } 58 | } 59 | .subscribe() 60 | .disposed(by: disposebag) 61 | return Disposables.create() 62 | } 63 | } 64 | 65 | func startGame(roomID: String) { 66 | repository.startGame(roomId: roomID) 67 | removeObserver(roomID: roomID) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Game/Protocol/InGameUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InGameUseCase.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/29. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol InGameUseCase { 12 | var inGameRecord: PublishSubject<[InGameRecord]> { get set } 13 | 14 | func fetchRecord() 15 | func fetchRanking() -> Single<[InGameRanking]> 16 | } 17 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Game/Protocol/MapBaseUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapBaseUseCase.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/28. 6 | // 7 | 8 | import RxSwift 9 | 10 | protocol MapBaseUseCase { 11 | var user: BehaviorSubject { get set } 12 | var userLocation: BehaviorSubject { get set } 13 | 14 | func start() 15 | func stop() 16 | func isAvailableLocationAuthorization() -> Observable<(Bool, Coordinate?)> 17 | } 18 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Game/Protocol/MultiGameChatUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiGameChatUseCase.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/08. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol MultiGameChatUseCase { 12 | func chatWrite(text: String) 13 | func chatUpdate(roomID: String) 14 | func observeChats(roomID: String) -> Observable<[Chat]> 15 | func leave(roomID: String) 16 | } 17 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Game/Protocol/MultiGameRoomUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiGameRoomUseCase.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol MultiGameRoomUseCase { 12 | func observing(roomID: String) -> Observable<[RoomUser]?> 13 | func get(roomID: String) -> Single<[RoomUser]> 14 | func leave(roomID: String) 15 | func removeObserver(roomID: String) 16 | func isGameAvailable(roomID: String) -> Observable 17 | func startGame(roomID: String) 18 | } 19 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Game/Protocol/MultiGameUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiGameUseCase.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol MultiGameUseCase { 12 | 13 | func timerStart() -> Observable 14 | func timerStop() 15 | 16 | func getLocation() -> Observable 17 | func point() -> Observable 18 | var visitedLocation: Set {get set} 19 | var movedDistance: Double {get set} 20 | 21 | func healthKitStore(time: Int) 22 | func healthKitAuthorization() -> Observable 23 | func updateData(roomId: String) 24 | 25 | func initVisitedLocation() 26 | 27 | func stopObserveLocation() 28 | 29 | func observing(roomID: String) -> Observable<[MultiGamePlayerData]> 30 | func watchOthersLocation(roomID: String) -> Single 31 | 32 | func leave(roomID: String) 33 | 34 | func unReadChatting(roomID: String) -> Observable 35 | func stopOberservingRoom(id: String) 36 | 37 | func gameOver(roomID: String) -> Observable 38 | } 39 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Game/Protocol/SelectMapUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectMapUseCase.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/27. 6 | // 7 | 8 | import RxSwift 9 | 10 | protocol SelectMapUseCase: MapBaseUseCase { 11 | var selectedMap: Map? { get set } 12 | 13 | func start() 14 | func getMapsInUpdatedRegion(region: MapRegion) -> [Map] 15 | func mapSelected(_ selectedMap: Map) -> Single<(String, [Record])> 16 | func isStartable() -> Bool 17 | } 18 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Game/Protocol/SingleGameUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleGameUseCase.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/23. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol SingleGameUseCase: MapBaseUseCase { 12 | var tapTimer: TimerService { get } 13 | var runningTimer: TimerService { get } 14 | 15 | var ishideGameOverButton: BehaviorSubject { get } 16 | var previousCoordinate: Coordinate? { get set } 17 | var runningTime: BehaviorSubject { get set } 18 | var runningDistance: BehaviorSubject { get set } 19 | var wentLocations: PublishSubject<[Coordinate]> { get set } 20 | var visitLocations: PublishSubject<[Coordinate]> { get set } 21 | var tooFarFromLocaiton: BehaviorSubject { get set } 22 | var visitedMapIndex: BehaviorSubject> { get set } 23 | var gameOverInformation: PublishSubject<(Record, String, Badge?)> { get set } 24 | 25 | func start() 26 | func startGameOverTimer() 27 | func startRunningTimer() 28 | func stop() 29 | func gameOverButtonTapped() 30 | 31 | func healthKitWrite() -> Observable 32 | func healthKitAuthorization() -> Observable 33 | } 34 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Home/DefaultHomeUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeUseCase.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/23. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol HomeUseCase { 12 | func getUUID() -> Observable 13 | func makeRoom() -> Observable 14 | func enterRoom(id: String) -> Single<[RoomUser]> 15 | } 16 | 17 | struct DefaultHomeUseCase: HomeUseCase { 18 | 19 | enum HomUseCaseError: Error { 20 | case uuidNotFound 21 | } 22 | 23 | private let gameRoomRepository: GameRoomRepository 24 | private let userRepository: UserRepository 25 | init( 26 | gameRoomRepository: GameRoomRepository, 27 | userRepository: UserRepository 28 | ) { 29 | self.gameRoomRepository = gameRoomRepository 30 | self.userRepository = userRepository 31 | } 32 | 33 | func getUUID() -> Observable { 34 | return Observable.create { observer in 35 | guard let uuid = userRepository.getUUID() else { 36 | observer.onError(DefaultHomeUseCase.HomUseCaseError.uuidNotFound) 37 | return Disposables.create() 38 | } 39 | observer.onNext(uuid) 40 | observer.onCompleted() 41 | return Disposables.create() 42 | } 43 | } 44 | 45 | func makeRoom() -> Observable { 46 | return gameRoomRepository.makeRoom() 47 | } 48 | 49 | func enterRoom(id: String) -> Single<[RoomUser]> { 50 | return gameRoomRepository.enterRoom(id: id) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/MyPage/DefaultMyInfoEditUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultMyInfoEditUseCase.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/12/10. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | final class DefaultMyInfoEditUseCase: MyInfoEditUseCase { 12 | 13 | // MARK: - Properties 14 | private let userRepository: UserRepository 15 | private var disposeBag = DisposeBag() 16 | 17 | // MARK: - Lifecycles 18 | init(userRepository: UserRepository) { 19 | self.userRepository = userRepository 20 | } 21 | 22 | // MARK: - Helpers 23 | func emailValidate(text: String) -> Bool { 24 | let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" 25 | return text.stringCheck(pattern: pattern) 26 | } 27 | 28 | func updateUserInfo(user: User, email: String, password: String) -> Single { 29 | Single.create { [weak self] single in 30 | guard let self else { return Disposables.create() } 31 | self.userRepository.updateUserEmail(email: email, password: password) 32 | .subscribe(onSuccess: { [weak self] _ in 33 | guard let self else { return } 34 | self.userRepository.updateUserData(user: user) 35 | .subscribe(onSuccess: { 36 | single(.success(())) 37 | }, onFailure: { error in 38 | single(.failure(error)) 39 | }) 40 | .disposed(by: self.disposeBag) 41 | }, onFailure: { 42 | single(.failure($0)) 43 | }) 44 | .disposed(by: self.disposeBag) 45 | return Disposables.create() 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/MyPage/Protocol/MyInfoEditUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyInfoEditUseCase.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/12/10. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol MyInfoEditUseCase: EmailValidatable { 12 | func updateUserInfo(user: User, email: String, password: String) -> Single 13 | } 14 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/MyPage/Protocol/MyPageUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyPageUseCase.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/12/01. 6 | // 7 | 8 | import RxSwift 9 | 10 | protocol MyPageUseCase { 11 | var userInfo: User? { get set } 12 | var nickName: PublishSubject { get set } 13 | var ownedBadges: BehaviorSubject<[Badge]> { get set } 14 | var recentlyOwnedBadges: BehaviorSubject<[Badge]> { get set } 15 | var allBadges: BehaviorSubject<[Badge]> { get set } 16 | func fetchMyPageData() 17 | func logout() -> Observable 18 | func deleteUser() -> Single 19 | } 20 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Record/Protocol/RecordMainViewUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordViewUseCase.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/21. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | // swiftlint:disable: large_tuple 11 | protocol RecordMainViewUseCase { 12 | var mapAtMapId: BehaviorSubject<[Int: Map]> { get set } 13 | var weekDatas: PublishSubject<[RecordViewChartData]> { get set } 14 | var recordSectionDatas: PublishSubject<(allDates: [String], 15 | totalRecordAtDate: [String: DayTotalRecord], 16 | recordsAtDate: [String: [Record]], 17 | mapAtMapId: [Int: Map])> { get set } 18 | 19 | func loadMapData() -> Single 20 | func loadRecordData() 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/Domain/UseCase/Record/Protocol/RecordMapViewUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordMapViewUseCase.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/24. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol RecordMapViewUseCase { 12 | var dropDownMenus: BehaviorSubject<[Map]> { get set } 13 | var mapDataAtMapName: BehaviorSubject<[String: Map]> { get set } 14 | var mapDataAtCategory: BehaviorSubject<[String: [Map]]> { get set } 15 | var mapNameAndRecordDatas: PublishSubject<(mapImage: Data?, 16 | mapName: String, 17 | recordDatas: [Record])> { get set } 18 | 19 | func loadMapData() 20 | func loadRecordData() -> Observable<[Record]> 21 | func getDropDownMenus(mapName: String) 22 | func getMapNameAndRecordsAtLocation(location: String) 23 | func getMapNameAndRecordDatasAtMapName(mapName: String) 24 | } 25 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/Auth/Common/AuthButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthButton.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/21. 6 | // 7 | 8 | import UIKit 9 | 10 | final class AuthButton: UIButton { 11 | 12 | init(color: UIColor, text: String) { 13 | super.init(frame: .zero) 14 | 15 | configure(color: color, text: text) 16 | } 17 | 18 | required init?(coder: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | } 23 | 24 | extension AuthButton { 25 | private func configure(color: UIColor, text: String) { 26 | backgroundColor = color 27 | setTitle(text, for: .normal) 28 | layer.cornerRadius = 10 29 | titleLabel?.font = .boldBody 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/Auth/Common/AuthTitleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthTitleView.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/21. 6 | // 7 | 8 | import UIKit 9 | 10 | final class AuthTitleView: UIStackView { 11 | 12 | lazy var label = UILabel().then { 13 | $0.font = .largeTitle 14 | $0.textAlignment = .center 15 | } 16 | 17 | init(image: UIImage?, text: String) { 18 | super.init(frame: .zero) 19 | label.text = text 20 | configure() 21 | } 22 | 23 | required init(coder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | } 28 | 29 | extension AuthTitleView { 30 | private func configure() { 31 | axis = .horizontal 32 | layout() 33 | } 34 | 35 | private func layout() { 36 | addViews() 37 | addConstraints() 38 | } 39 | 40 | private func addViews() { 41 | addArrangedSubview(label) 42 | } 43 | 44 | private func addConstraints() { 45 | label.snp.makeConstraints { make in 46 | make.edges.equalTo(self) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/Auth/Common/ScrollAbleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollAbleViewController.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class ScrollAbleViewController: UIViewController { 11 | 12 | lazy var scrollView = UIScrollView() 13 | lazy var contentView = UIStackView() 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | configure() 18 | hideKeyboardWhenTapped() 19 | } 20 | 21 | } 22 | 23 | extension ScrollAbleViewController { 24 | func addView() { 25 | view.addSubview(scrollView) 26 | scrollView.addSubview(contentView) 27 | } 28 | 29 | func addConstraint() { 30 | scrollView.snp.makeConstraints { 31 | $0.top.bottom.equalToSuperview() 32 | $0.leading.trailing.equalToSuperview().inset(80) 33 | } 34 | 35 | contentView.snp.makeConstraints { 36 | $0.width.equalToSuperview() 37 | $0.centerX.top.bottom.equalToSuperview() 38 | } 39 | 40 | } 41 | 42 | func configure() { 43 | contentView.axis = .vertical 44 | contentView.spacing = 50 45 | contentView.backgroundColor = .white 46 | contentView.distribution = .fillProportionally 47 | addView() 48 | addConstraint() 49 | } 50 | 51 | } 52 | 53 | extension ScrollAbleViewController { 54 | 55 | private func hideKeyboardWhenTapped() { 56 | let tapGestureRecognizer = UITapGestureRecognizer( 57 | target: self, 58 | action: #selector(dismissKeyboard) 59 | ) 60 | tapGestureRecognizer.cancelsTouchesInView = false 61 | view.addGestureRecognizer(tapGestureRecognizer) 62 | } 63 | 64 | @objc private func dismissKeyboard() { 65 | view.endEditing(true) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/Auth/Coordinator/AuthCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthCoordinator.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/14. 6 | // 7 | 8 | import UIKit 9 | import FirebaseAuth 10 | import Firebase 11 | 12 | protocol AuthCoordinatorProtocol: Coordinator { 13 | func connectLoginCoordinator() 14 | func connectSignupCoordinator() 15 | } 16 | 17 | final class AuthCoordinator: AuthCoordinatorProtocol { 18 | 19 | var navigationController: UINavigationController 20 | var childCoordinators: [Coordinator] = [] 21 | weak var delegate: CoordinatorDelegate? 22 | 23 | required init(navigationController: UINavigationController) { 24 | self.navigationController = navigationController 25 | self.navigationController.isNavigationBarHidden = true 26 | } 27 | 28 | func start() { 29 | connectLoginCoordinator() 30 | } 31 | 32 | func connectLoginCoordinator() { 33 | let coordinator = LoginCoordinator(navigationController: navigationController) 34 | appendChildCoordinator(coordinator: coordinator) 35 | coordinator.delegate = self 36 | coordinator.loginDelegate = self 37 | coordinator.start() 38 | } 39 | 40 | func connectSignupCoordinator() { 41 | let coordinator = SignupCoordinator(navigationController: navigationController) 42 | appendChildCoordinator(coordinator: coordinator) 43 | coordinator.delegate = self 44 | coordinator.signupDelegate = self 45 | coordinator.start() 46 | } 47 | } 48 | 49 | extension AuthCoordinator: CoordinatorDelegate { 50 | func didFinished(childCoordinator: Coordinator) { 51 | removeChildCoordinator(coordinator: childCoordinator) 52 | navigationController.viewControllers = [] 53 | delegate?.didFinished(childCoordinator: self) 54 | } 55 | } 56 | 57 | extension AuthCoordinator: LoginCoordinatorDelegate, SginupCoordinatorDelegate { 58 | func switchToSignup() { 59 | resetViews() 60 | connectSignupCoordinator() 61 | } 62 | 63 | func switchToLogin() { 64 | resetViews() 65 | connectLoginCoordinator() 66 | } 67 | 68 | private func resetViews() { 69 | navigationController.viewControllers = [] 70 | removeAllChildCoordinator() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/Auth/Coordinator/LoginCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginCoordinator.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/14. 6 | // 7 | 8 | import UIKit 9 | import RxSwift 10 | 11 | protocol LoginCoordinatorProtocol: Coordinator { 12 | func showLoginViewController() 13 | func showSignupViewController() 14 | } 15 | 16 | protocol LoginCoordinatorDelegate: AnyObject { 17 | func switchToSignup() 18 | } 19 | 20 | final class LoginCoordinator: LoginCoordinatorProtocol { 21 | 22 | weak var delegate: CoordinatorDelegate? 23 | weak var loginDelegate: LoginCoordinatorDelegate? 24 | 25 | var navigationController: UINavigationController 26 | 27 | var childCoordinators: [Coordinator] = [] 28 | 29 | required init(navigationController: UINavigationController) { 30 | self.navigationController = navigationController 31 | } 32 | 33 | func start() { 34 | showLoginViewController() 35 | } 36 | func showLoginViewController() { 37 | @DIContainer.Resolve(LoginUseCase.self) 38 | var useCase: LoginUseCase 39 | let viewModel = LoginViewModel( 40 | coordinator: self, 41 | useCase: useCase 42 | ) 43 | let viewController = LoginViewController(viewModel: viewModel) 44 | let transiton = CATransition() 45 | transiton.type = .moveIn 46 | transiton.subtype = .fromLeft 47 | transiton.duration = 0.3 48 | navigationController.view.layer.add(transiton, forKey: "login") 49 | navigationController.pushViewController(viewController, animated: true) 50 | self.navigationController.isNavigationBarHidden = true 51 | } 52 | 53 | func showSignupViewController() { 54 | loginDelegate?.switchToSignup() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/Auth/Coordinator/SignupCoordinator .swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignupCoordinator .swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/14. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol SignupCoordinatorProtocol: Coordinator { 11 | func showSignupViewController() 12 | func showLoginViewController() 13 | } 14 | 15 | protocol SginupCoordinatorDelegate: AnyObject { 16 | func switchToLogin() 17 | } 18 | 19 | final class SignupCoordinator: SignupCoordinatorProtocol { 20 | 21 | weak var delegate: CoordinatorDelegate? 22 | weak var signupDelegate: SginupCoordinatorDelegate? 23 | 24 | var navigationController: UINavigationController 25 | 26 | var childCoordinators: [Coordinator] = [] 27 | 28 | required init(navigationController: UINavigationController) { 29 | self.navigationController = navigationController 30 | } 31 | 32 | func start() { 33 | showSignupViewController() 34 | } 35 | 36 | func showSignupViewController() { 37 | @DIContainer.Resolve(SignUpUsecase.self) 38 | var useCase: SignUpUsecase 39 | let viewModel = SignUpViewModel( 40 | coordinator: self, 41 | useCase: useCase 42 | ) 43 | let transiton = CATransition() 44 | transiton.type = .moveIn 45 | transiton.subtype = .fromRight 46 | transiton.duration = 0.3 47 | navigationController.view.layer.add(transiton, forKey: "signup") 48 | let viewController = SignupViewController(viewModel: viewModel) 49 | navigationController.pushViewController(viewController, animated: true) 50 | } 51 | 52 | func showLoginViewController() { 53 | signupDelegate?.switchToLogin() 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/Base/BaseViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewModel.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/16. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol BaseViewModel { 12 | associatedtype Input 13 | associatedtype Output 14 | 15 | var disposeBag: DisposeBag { get } 16 | 17 | func transform(input: Input) -> Output 18 | } 19 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/Base/MapBase/MapBaseViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapBaseViewModel.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/28. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | class MapBaseViewModel: BaseViewModel { 12 | 13 | // MARK: - Input 14 | struct Input { 15 | let viewWillDisappearEvent: Observable 16 | let focusButtonTapped: Observable 17 | } 18 | // MARK: - Output 19 | struct Output { 20 | var user = PublishSubject() 21 | var isAvailableLocationAuthorization = PublishSubject<(Bool, Coordinate?)>() 22 | var focusUserEvent = PublishSubject() 23 | } 24 | // MARK: - Properties 25 | var disposeBag = DisposeBag() 26 | 27 | // MARK: - Dependency 28 | let mapBaseUseCase: MapBaseUseCase 29 | 30 | // MARK: - Lifecycles 31 | init(useCase: MapBaseUseCase) { 32 | self.mapBaseUseCase = useCase 33 | } 34 | 35 | // MARK: - Helpers 36 | func transform(input: Input) -> Output { 37 | let output = Output() 38 | 39 | mapBaseUseCase.user 40 | .bind(to: output.user) 41 | .disposed(by: disposeBag) 42 | 43 | input.viewWillDisappearEvent 44 | .subscribe(onNext: { [weak self] in 45 | guard let self else { return } 46 | self.mapBaseUseCase.stop() 47 | }) 48 | .disposed(by: disposeBag) 49 | 50 | input.focusButtonTapped 51 | .subscribe(onNext: { [weak self] in 52 | guard let self, 53 | let userLocation = try? self.mapBaseUseCase.userLocation.value() else { return } 54 | output.focusUserEvent.onNext(userLocation) 55 | }) 56 | .disposed(by: disposeBag) 57 | 58 | mapBaseUseCase.isAvailableLocationAuthorization() 59 | .bind(to: output.isAvailableLocationAuthorization) 60 | .disposed(by: disposeBag) 61 | 62 | return output 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Community/Cell/CommunityDetailCommentHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommunityDetailCommentHeaderView.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/05. 6 | // 7 | 8 | import UIKit 9 | 10 | final class CommunityDetailCommentHeaderView: UICollectionReusableView { 11 | // MARK: - UI properties 12 | private lazy var titleLabel: UILabel = UILabel().then { 13 | $0.text = "댓글" 14 | $0.font = .largeTitle 15 | $0.textColor = .pointLight 16 | } 17 | 18 | // MARK: - Properties 19 | static let identifier = "CommunityDetailCommentHeaderView" 20 | 21 | // MARK: - Lifecycles 22 | override init(frame: CGRect) { 23 | super.init(frame: frame) 24 | setupViews() 25 | configureUI() 26 | } 27 | 28 | required init?(coder: NSCoder) { 29 | fatalError("init(coder:) has not been implemented") 30 | } 31 | 32 | // MARK: - Helpers 33 | private func setupViews() { 34 | addSubview(titleLabel) 35 | } 36 | 37 | private func configureUI() { 38 | titleLabel.snp.makeConstraints { 39 | $0.leading.trailing.bottom.equalToSuperview() 40 | $0.top.equalToSuperview().offset(20) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Community/Cell/CommunityIndicatorCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommunityIndicatorCell.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/12. 6 | // 7 | 8 | import UIKit 9 | 10 | final class CommunityIndicatorCell: UICollectionViewCell { 11 | // MARK: - UI properties 12 | var indicatorView: UIActivityIndicatorView = UIActivityIndicatorView().then { 13 | $0.style = .large 14 | } 15 | 16 | // MARK: - Properties 17 | static let identifier = "CommunityIndicatorCell" 18 | 19 | // MARK: - Lifecycles 20 | override init(frame: CGRect) { 21 | super.init(frame: frame) 22 | setupViews() 23 | configureUI() 24 | } 25 | 26 | required init?(coder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | // MARK: - Helpers 31 | private func setupViews() { 32 | contentView.addSubview(indicatorView) 33 | } 34 | 35 | private func configureUI() { 36 | indicatorView.snp.makeConstraints { 37 | $0.center.equalToSuperview() 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Community/ViewModel/CommunityPostWriteViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommunityPostWriteViewModel.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxRelay 11 | 12 | final class CommunityPostWriteViewModel: BaseViewModel { 13 | struct Input { 14 | var viewWillAppearEvent: Observable 15 | var rightButtonTapped: Observable<(String, Image?)> 16 | } 17 | 18 | struct Output { 19 | var post = PublishRelay() 20 | } 21 | 22 | // MARK: - Dependency 23 | var disposeBag = DisposeBag() 24 | private let useCase: CommunityPostWriteUseCase 25 | private let coordinator: CommunityCoordinator 26 | 27 | // MARK: - Lifecycles 28 | init(useCase: CommunityPostWriteUseCase, coordinator: CommunityCoordinator) { 29 | self.useCase = useCase 30 | self.coordinator = coordinator 31 | } 32 | 33 | // MARK: - Helpers 34 | func transform(input: Input) -> Output { 35 | let output = Output() 36 | 37 | input.viewWillAppearEvent 38 | .subscribe(onNext: { [weak self] _ in 39 | guard let self else { return } 40 | self.useCase.confirmHavePost() 41 | .subscribe(onSuccess: { post in 42 | if let post { 43 | output.post.accept(post) 44 | } 45 | }).disposed(by: self.disposeBag) 46 | }).disposed(by: disposeBag) 47 | 48 | input.rightButtonTapped 49 | .subscribe(onNext: { [weak self] (postContent, image) in 50 | guard let self else { return } 51 | self.useCase.uploadPost(postContent: postContent, image: image) 52 | .subscribe(onSuccess: { _ in 53 | self.coordinator.popLastViewController() 54 | }) 55 | .disposed(by: self.disposeBag) 56 | }).disposed(by: disposeBag) 57 | 58 | return output 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Coordinator/RecordCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordCoordinator.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/14. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol RecordCoordinatorProtocol: Coordinator { 11 | func showRecordPageViewController() 12 | } 13 | 14 | final class RecordCoordinator: RecordCoordinatorProtocol { 15 | var navigationController: UINavigationController 16 | var childCoordinators: [Coordinator] = [] 17 | weak var delegate: CoordinatorDelegate? 18 | 19 | init(navigationController: UINavigationController) { 20 | self.navigationController = navigationController 21 | self.navigationController.isNavigationBarHidden = false 22 | } 23 | 24 | func start() { 25 | showRecordPageViewController() 26 | } 27 | 28 | func showRecordPageViewController() { 29 | @DIContainer.Resolve(RecordMainViewUseCase.self) 30 | var mainUseCase: RecordMainViewUseCase 31 | let mainViewModel = RecordMainViewModel(useCase: mainUseCase) 32 | 33 | @DIContainer.Resolve(RecordMapViewUseCase.self) 34 | var mapUseCase: RecordMapViewUseCase 35 | let mapViewModel = RecordMapViewModel(useCase: mapUseCase) 36 | 37 | let viewController = RecordPageViewController( 38 | recordMainViewController: RecordMainViewController(viewModel: mainViewModel), 39 | recordMapViewController: RecordMapViewController(viewModel: mapViewModel) 40 | ) 41 | navigationController.pushViewController(viewController, animated: true) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/Common/PaddingLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UILabel+Extension.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/15. 6 | // 7 | 8 | import UIKit 9 | 10 | final class PaddingLabel: UILabel { 11 | private let topInset: CGFloat 12 | private let bottomInset: CGFloat 13 | private let leftInset: CGFloat 14 | private let rightInset: CGFloat 15 | 16 | init( 17 | topInset: CGFloat?, 18 | bottomInset: CGFloat?, 19 | leftInset: CGFloat?, 20 | rightInset: CGFloat? 21 | ) { 22 | self.topInset = topInset ?? 0 23 | self.bottomInset = bottomInset ?? 0 24 | self.leftInset = leftInset ?? 0 25 | self.rightInset = rightInset ?? 0 26 | super.init(frame: .zero) 27 | } 28 | 29 | required init?(coder: NSCoder) { 30 | fatalError("init(coder:) has not been implemented") 31 | } 32 | 33 | override func drawText(in rect: CGRect) { 34 | let insets = UIEdgeInsets( 35 | top: topInset, 36 | left: leftInset, 37 | bottom: bottomInset, 38 | right: rightInset 39 | ) 40 | super.drawText(in: rect.inset(by: insets)) 41 | } 42 | 43 | override var intrinsicContentSize: CGSize { 44 | let size = super.intrinsicContentSize 45 | let newWidth = size.width + leftInset + rightInset 46 | let newHeight = size.height + topInset + bottomInset 47 | return CGSize(width: newWidth, height: newHeight) 48 | } 49 | 50 | override var bounds: CGRect { 51 | didSet { 52 | preferredMaxLayoutWidth = bounds.width - (leftInset + rightInset) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/InGameMenus/ViewController/InGamePlayMenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingGamePlayRankingView.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/15. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | import Then 11 | 12 | class InGamePlayMenuViewController: UIViewController { 13 | 14 | // MARK: - UI properties 15 | lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: createBasicListLayout()) 16 | 17 | let titleLabel = UILabel().then { 18 | $0.font = .largeTitle 19 | $0.textColor = .white 20 | $0.backgroundColor = .pointLight 21 | $0.textAlignment = .center 22 | $0.translatesAutoresizingMaskIntoConstraints = false 23 | } 24 | 25 | // MARK: - Properties 26 | // MARK: - Lifecycles 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | layout() 31 | } 32 | 33 | // MARK: - Helpers 34 | private func createBasicListLayout() -> UICollectionViewLayout { 35 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 36 | heightDimension: .fractionalHeight(1.0)) 37 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 38 | 39 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 40 | heightDimension: .absolute(44)) 41 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, 42 | subitems: [item]) 43 | 44 | let section = NSCollectionLayoutSection(group: group) 45 | 46 | let layout = UICollectionViewCompositionalLayout(section: section) 47 | return layout 48 | } 49 | 50 | } 51 | 52 | extension InGamePlayMenuViewController { 53 | private func layout() { 54 | addViews() 55 | layoutViews() 56 | } 57 | 58 | private func addViews() { 59 | view.addSubview(titleLabel) 60 | view.addSubview(collectionView) 61 | } 62 | 63 | private func layoutViews() { 64 | titleLabel.snp.makeConstraints { 65 | $0.top.leading.trailing.equalToSuperview() 66 | $0.height.equalTo(100) 67 | 68 | } 69 | 70 | collectionView.snp.makeConstraints { 71 | $0.top.equalTo(titleLabel.snp.bottom) 72 | $0.leading.trailing.bottom.equalToSuperview() 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/InGameMenus/ViewModel/InGameRankingViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InGameRankingViewModel.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/21. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import Firebase 11 | 12 | class InGameRankingViewModel: BaseViewModel { 13 | struct Input { 14 | 15 | } 16 | struct Output { 17 | var rankings: Single<[InGameRanking]> 18 | } 19 | private let inGameUseCase: InGameUseCase 20 | var disposeBag = DisposeBag() 21 | 22 | init(inGameUseCase: InGameUseCase) { 23 | self.inGameUseCase = inGameUseCase 24 | } 25 | 26 | func transform(input: Input) -> Output { 27 | Output(rankings: inGameUseCase.fetchRanking()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/InGameMenus/ViewModel/InGameRecordViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InGameRecordViewModel.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/21. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxRelay 11 | import Firebase 12 | 13 | final class InGameRecordViewModel: BaseViewModel { 14 | 15 | struct Input { 16 | 17 | } 18 | 19 | struct Output { 20 | var inGameRecord: PublishSubject<[InGameRecord]> 21 | } 22 | 23 | private let inGameUseCase: InGameUseCase 24 | var disposeBag = DisposeBag() 25 | 26 | init(inGameUseCase: InGameUseCase) { 27 | self.inGameUseCase = inGameUseCase 28 | } 29 | 30 | func transform(input: Input) -> Output { 31 | inGameUseCase.fetchRecord() 32 | return Output(inGameRecord: inGameUseCase.inGameRecord) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/MultiGame/View/ChatCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatCell.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/08. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | import Then 11 | 12 | final class ChatCell: UICollectionViewCell { 13 | static let identifier = String(describing: ChatCell.self) 14 | 15 | private lazy var textLabel = PaddingLabel(topInset: 10, bottomInset: 10, leftInset: 20, rightInset: 20).then { 16 | $0.font = .commentBody 17 | $0.textColor = .black 18 | $0.numberOfLines = 0 19 | } 20 | 21 | override init(frame: CGRect) { 22 | super.init(frame: frame) 23 | configureUI() 24 | } 25 | 26 | required init?(coder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | private func configureUI() { 31 | addSubview(textLabel) 32 | textLabel.snp.makeConstraints { 33 | $0.edges.equalToSuperview() 34 | } 35 | } 36 | 37 | func bind(data: Chat) { 38 | textLabel.text = "\(data.nickName) : \(data.text)" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/MultiGame/View/GameRankCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameRankCollectionViewCell.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/07. 6 | // 7 | 8 | import UIKit 9 | import Then 10 | import SnapKit 11 | 12 | final class GameRankCell: UICollectionViewCell { 13 | static let identifier = String(describing: GameRankCell.self) 14 | private let rankingLabel = PaddingLabel(topInset: 10, bottomInset: 10, leftInset: 10, rightInset: 10).then { 15 | $0.font = .smallTitle 16 | } 17 | 18 | override init(frame: CGRect) { 19 | super.init(frame: frame) 20 | layout() 21 | } 22 | 23 | required init?(coder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | private func layout() { 28 | addSubview(rankingLabel) 29 | rankingLabel.snp.makeConstraints { 30 | $0.edges.equalToSuperview() 31 | } 32 | } 33 | 34 | override func prepareForReuse() { 35 | super.prepareForReuse() 36 | rankingLabel.text = "" 37 | } 38 | 39 | func bind(data: MultiGamePlayerData, rank: Int) { 40 | let nickName = data.nickName.stringLimit(6) 41 | rankingLabel.text = "\(rank) : \(nickName) \(data.point) 점" 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/MultiGame/View/GameRoomCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameRoomCollectionViewCell.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/24. 6 | // 7 | 8 | import UIKit 9 | import Then 10 | import SnapKit 11 | 12 | final class GameRoomCell: UICollectionViewCell { 13 | static let identifier = "gameRoomCollectionViewCell" 14 | private lazy var nameLabel = PaddingLabel( 15 | topInset: 0, 16 | bottomInset: 0, 17 | leftInset: 12, 18 | rightInset: 12 19 | ).then { 20 | $0.numberOfLines = 0 21 | $0.font = .commentBody 22 | $0.textColor = .pointDark 23 | } 24 | 25 | override init(frame: CGRect) { 26 | super.init(frame: .zero) 27 | layout() 28 | } 29 | 30 | required init?(coder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | override func prepareForReuse() { 35 | super.prepareForReuse() 36 | nameLabel.text = "" 37 | } 38 | } 39 | 40 | extension GameRoomCell { 41 | func layout() { 42 | addViews() 43 | addConstratins() 44 | } 45 | 46 | func addViews() { 47 | addSubview(nameLabel) 48 | } 49 | 50 | func addConstratins() { 51 | nameLabel.snp.makeConstraints { 52 | $0.edges.equalToSuperview() 53 | } 54 | } 55 | } 56 | 57 | extension GameRoomCell { 58 | func bind(data: RoomUser) { 59 | nameLabel.text = data.nickName 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/MultiGame/View/GameRoomHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameRoomCollectionViewHeader.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/24. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | import Then 11 | 12 | final class GameRoomHeader: UICollectionReusableView { 13 | static let identifier = "GameRoomCollectionViewHeader" 14 | private let headerLabel: PaddingLabel = PaddingLabel( 15 | topInset: 0, 16 | bottomInset: 0, 17 | leftInset: 10, 18 | rightInset: 0 19 | ).then { 20 | $0.font = .title 21 | $0.textColor = .white 22 | $0.textAlignment = .left 23 | } 24 | override init(frame: CGRect) { 25 | super.init(frame: frame) 26 | backgroundColor = .pointDark 27 | layout() 28 | } 29 | 30 | required init?(coder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | override func prepareForReuse() { 35 | super.prepareForReuse() 36 | headerLabel.text = "" 37 | } 38 | } 39 | 40 | extension GameRoomHeader { 41 | private func layout() { 42 | addViews() 43 | addConstraints() 44 | } 45 | 46 | private func addViews() { 47 | addSubview(headerLabel) 48 | } 49 | 50 | private func addConstraints() { 51 | headerLabel.snp.makeConstraints { 52 | $0.edges.equalToSuperview() 53 | } 54 | } 55 | 56 | func bind(playerNumber: Int) { 57 | headerLabel.text = "플레이어 \(playerNumber) 명" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/MultiGame/View/PlayerAnnotation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerAnnotation.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/06. 6 | // 7 | 8 | import Foundation 9 | import MapKit 10 | 11 | final class PlayerAnnotation: NSObject, MKAnnotation { 12 | 13 | enum PlayerType: Int { 14 | case first = 0 15 | case second = 1 16 | case third = 2 17 | case fourth = 3 18 | } 19 | 20 | var coordinate: CLLocationCoordinate2D 21 | let nickName: String 22 | let point: Int 23 | private let type: PlayerType 24 | let title: String? 25 | let subtitle: String? 26 | 27 | init(player: MultiGamePlayerData, type: PlayerType) { 28 | self.coordinate = player.currentLocation == nil ? CLLocationCoordinate2D( 29 | latitude: 0, 30 | longitude: 0 31 | ) : player.currentLocation!.toCLLocationCoordinate2D() 32 | self.nickName = player.nickName 33 | self.point = player.point 34 | self.type = type 35 | self.title = player.nickName 36 | self.subtitle = "\(player.point) 점" 37 | super.init() 38 | } 39 | 40 | func image() -> UIImage? { 41 | switch type { 42 | case .first: 43 | return .firstAnnotation 44 | case .second: 45 | return .secondAnnotation 46 | case .third: 47 | return .thirdAnnotation 48 | case .fourth: 49 | return .fourthAnnotation 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/MultiGame/View/PlayerAnnotationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerAnnotationView.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/07. 6 | // 7 | 8 | import Foundation 9 | import MapKit 10 | 11 | final class PlayerAnnotationView: MKAnnotationView { 12 | 13 | static let identifier = String(describing: PlayerAnnotationView.self) 14 | 15 | override init(annotation: MKAnnotation?, reuseIdentifier: String?) { 16 | super.init(annotation: annotation, reuseIdentifier: reuseIdentifier) 17 | guard let playerAnnotation = self.annotation as? PlayerAnnotation else {return} 18 | image = playerAnnotation.image()?.imageWith(newSize: CGSize(width: 30, height: 30)) 19 | } 20 | 21 | required init?(coder aDecoder: NSCoder) { 22 | fatalError("init(coder:) has not been implemented") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/MultiGame/View/PlayerCircle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerCircle.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/07. 6 | // 7 | 8 | import Foundation 9 | import MapKit 10 | 11 | final class PlayerCircle: MKCircle { 12 | 13 | var type: CircleType = .first 14 | 15 | func overlayColor() -> UIColor? { 16 | switch type { 17 | case .first: 18 | return .red 19 | case .second: 20 | return .purple 21 | case .third: 22 | return .blue 23 | case .fourth: 24 | return .black 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Home/SelectMap/View/SelectMapRankingHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectMapRankingHeaderView.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/22. 6 | // 7 | 8 | import UIKit 9 | import Then 10 | import SnapKit 11 | 12 | final class SelectMapRankingHeaderView: UICollectionReusableView { 13 | // MARK: - UI properties 14 | private let titleLabel: UILabel = UILabel().then { 15 | $0.font = .boldBody 16 | $0.textColor = .white 17 | $0.text = "땅 이름 랭킹" 18 | $0.clipsToBounds = true 19 | } 20 | 21 | private let closeButton: UIButton = UIButton().then { 22 | $0.setImage(SystemImageNameSpace.xmark.uiImage, for: .normal) 23 | $0.tintColor = .white 24 | let imageConfig = UIImage.SymbolConfiguration(pointSize: 24) 25 | $0.setPreferredSymbolConfiguration(imageConfig, forImageIn: .normal) 26 | } 27 | 28 | // MARK: - Properties 29 | static let identifier = "SelectMapRankingHeaderView" 30 | private var closeButtonHandler: (() -> Void)? 31 | 32 | // MARK: - Lifecycles 33 | override init(frame: CGRect) { 34 | super.init(frame: frame) 35 | configureUI() 36 | closeButton.addTarget(self, 37 | action: #selector(closeButtonDidClick), 38 | for: .touchUpInside) 39 | } 40 | 41 | required init?(coder: NSCoder) { 42 | fatalError("init(coder:) has not been implemented") 43 | } 44 | 45 | // MARK: - Helpers 46 | @objc func closeButtonDidClick() { 47 | closeButtonHandler?() 48 | } 49 | 50 | private func configureUI() { 51 | backgroundColor = .pointLight 52 | 53 | addSubview(closeButton) 54 | closeButton.snp.makeConstraints { 55 | $0.top.trailing.bottom.equalToSuperview().inset(15) 56 | $0.width.height.equalTo(30) 57 | } 58 | 59 | addSubview(titleLabel) 60 | titleLabel.snp.makeConstraints { 61 | $0.top.leading.bottom.equalToSuperview().inset(15) 62 | $0.trailing.equalTo(closeButton.snp.leading).inset(15) 63 | } 64 | } 65 | 66 | func setData(mapName: String, closeButtonHandler: (() -> Void)?) { 67 | titleLabel.text = mapName + " 랭킹" 68 | self.closeButtonHandler = closeButtonHandler 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/MyPage/View/BadgeCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgeCell.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/30. 6 | // 7 | 8 | import UIKit 9 | import Then 10 | import SnapKit 11 | import RxSwift 12 | 13 | final class BadgeCell: UICollectionViewCell { 14 | // MARK: - UI properties 15 | private lazy var badgeLabel: UILabel = UILabel().then { 16 | $0.font = .subBody 17 | $0.textAlignment = .center 18 | } 19 | private lazy var badgeImage: UIImageView = UIImageView().then { 20 | $0.contentMode = .scaleAspectFit 21 | $0.layer.cornerRadius = 50 22 | $0.layer.borderWidth = 3 23 | $0.layer.borderColor = UIColor.pointLight.cgColor 24 | $0.clipsToBounds = true 25 | } 26 | // MARK: - Properties 27 | static let identifer = "BadgeCell" 28 | 29 | // MARK: - Lifecycles 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | setupSubviews() 33 | } 34 | 35 | required init?(coder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | // MARK: - Helpers 39 | private func setupSubviews() { 40 | [badgeLabel, badgeImage].forEach { contentView.addSubview($0) } 41 | 42 | badgeImage.snp.makeConstraints { 43 | $0.top.centerX.equalToSuperview() 44 | $0.width.height.equalTo(100) 45 | } 46 | 47 | badgeLabel.snp.makeConstraints { 48 | $0.top.equalTo(badgeImage.snp.bottom).offset(5) 49 | $0.leading.trailing.bottom.equalToSuperview() 50 | $0.height.equalTo(20) 51 | } 52 | } 53 | 54 | func bind(badge: Badge) { 55 | badgeImage.image = UIImage(data: badge.image) 56 | badgeLabel.text = badge.name 57 | } 58 | 59 | func bind(pinCharacter: PinCharacter) { 60 | badgeImage.image = pinCharacter.image 61 | badgeLabel.text = pinCharacter.name 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/MyPage/View/InfoTextFieldView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoTextFieldView.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/12/08. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | import Then 11 | 12 | final class InfoTextFieldView: UIView { 13 | 14 | // MARK: - UI properties 15 | private var label: UILabel = UILabel().then { 16 | $0.textColor = .pointLight 17 | $0.font = .title 18 | } 19 | 20 | var textField: UITextField = UITextField().then { 21 | $0.textColor = .black 22 | $0.layer.cornerRadius = 10 23 | $0.layer.borderColor = UIColor.pointLight.cgColor 24 | $0.layer.borderWidth = 2 25 | $0.addLeftRightPadding() 26 | } 27 | 28 | // MARK: - Properties 29 | 30 | // MARK: - Lifecycles 31 | init(frame: CGRect, title: String) { 32 | label.text = title 33 | super.init(frame: frame) 34 | configureUI() 35 | } 36 | 37 | required init?(coder: NSCoder) { 38 | fatalError("init(coder:) has not been implemented") 39 | } 40 | } 41 | 42 | extension InfoTextFieldView { 43 | // MARK: - Helpers 44 | private func configureUI() { 45 | addSubview(label) 46 | addSubview(textField) 47 | 48 | label.snp.makeConstraints { 49 | $0.centerY.leading.equalToSuperview() 50 | $0.width.equalTo(60) 51 | } 52 | textField.snp.makeConstraints { 53 | $0.leading.equalTo(label.snp.trailing).offset(5) 54 | $0.trailing.centerY.equalToSuperview() 55 | $0.height.equalTo(40) 56 | } 57 | } 58 | 59 | func setContent(_ content: String) { 60 | textField.text = content 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/MyPage/View/MyPageHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyPageHeaderView.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/30. 6 | // 7 | 8 | import UIKit 9 | import Then 10 | import SnapKit 11 | 12 | final class MyPageHeaderView: UICollectionReusableView { 13 | // MARK: - UI properties 14 | private lazy var titleLabel: UILabel = UILabel().then { 15 | $0.font = .smallTitle 16 | $0.textColor = .white 17 | } 18 | private lazy var moreButton: UIButton = UIButton().then { 19 | $0.setTitle("더보기 >", for: .normal) 20 | $0.setTitleColor(.white, for: .normal) 21 | $0.titleLabel?.font = .tinyTitle 22 | $0.isHidden = true 23 | } 24 | 25 | // MARK: - Properties 26 | static let identifer = "MyPageHeaderView" 27 | private var moreButtonHandler: () -> Void = {} 28 | 29 | // MARK: - Lifecycles 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | configureUI() 33 | setUpMoreButton() 34 | } 35 | 36 | required init?(coder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | // MARK: - Helpers 41 | private func configureUI() { 42 | backgroundColor = .pointLight 43 | layer.cornerRadius = 10 44 | 45 | [titleLabel, moreButton].forEach { addSubview($0) } 46 | titleLabel.snp.makeConstraints { 47 | $0.leading.equalToSuperview().offset(15) 48 | $0.top.bottom.equalToSuperview().inset(10) 49 | } 50 | 51 | moreButton.snp.makeConstraints { 52 | $0.trailing.equalToSuperview().offset(-15) 53 | $0.top.bottom.equalTo(titleLabel) 54 | } 55 | } 56 | 57 | private func setUpMoreButton() { 58 | moreButton.addTarget(self, 59 | action: #selector(moreButtonDidClick), 60 | for: .touchUpInside) 61 | } 62 | 63 | @objc func moreButtonDidClick() { 64 | moreButtonHandler() 65 | } 66 | 67 | func bind(title: String, moreButtonHandler: (() -> Void)?) { 68 | titleLabel.text = title 69 | if let handler = moreButtonHandler { 70 | moreButton.isHidden = false 71 | self.moreButtonHandler = handler 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/MyPage/View/SettingCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingCell.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/12/01. 6 | // 7 | 8 | import UIKit 9 | import Then 10 | import SnapKit 11 | 12 | final class SettingCell: UICollectionViewCell { 13 | // MARK: - UI properties 14 | private lazy var button: UIButton = UIButton().then { 15 | $0.titleLabel?.font = .commentBody 16 | $0.setTitleColor(.black, for: .normal) 17 | $0.contentHorizontalAlignment = .left 18 | } 19 | 20 | // MARK: - Properties 21 | static let identifer = "SettingCell" 22 | var buttonHandler: (() -> Void)? 23 | 24 | // MARK: - Lifecycles 25 | override init(frame: CGRect) { 26 | super.init(frame: frame) 27 | setupSubviews() 28 | setupButton() 29 | } 30 | 31 | required init?(coder: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | 35 | // MARK: - Helpers 36 | private func setupSubviews() { 37 | addSubview(button) 38 | 39 | button.snp.makeConstraints { 40 | $0.top.bottom.equalToSuperview() 41 | $0.leading.trailing.equalToSuperview().inset(10) 42 | } 43 | } 44 | 45 | private func setupButton() { 46 | button.addTarget(self, 47 | action: #selector(buttonDidClick), 48 | for: .touchUpInside) 49 | } 50 | 51 | @objc func buttonDidClick() { 52 | buttonHandler?() 53 | } 54 | 55 | func bind(title: String, handler: (() -> Void)?) { 56 | button.setTitle(title, for: .normal) 57 | self.buttonHandler = handler 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/MyPage/ViewModel/BadgeViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgeViewModel.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/30. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | class BadgeViewModel: BaseViewModel { 12 | struct Input { 13 | 14 | } 15 | 16 | struct Output { 17 | var brandNewBadges = BehaviorSubject<[Badge]>(value: []) 18 | var aquiredBadges = BehaviorSubject<[Badge]>(value: []) 19 | var inaquiredBadges = BehaviorSubject<[Badge]>(value: []) 20 | } 21 | var disposeBag = DisposeBag() 22 | let allBadges: [Badge] 23 | let ownedBadges: [Badge] 24 | 25 | init(allBadges: [Badge], 26 | ownedBadges: [Badge] 27 | ) { 28 | self.allBadges = allBadges 29 | self.ownedBadges = ownedBadges 30 | } 31 | 32 | func transform(input: Input) -> Output { 33 | let output = Output() 34 | output.brandNewBadges 35 | .onNext(ownedBadges) 36 | output.aquiredBadges 37 | .onNext(allBadges.filter { $0.isOwn }) 38 | output.inaquiredBadges 39 | .onNext(allBadges.filter { !$0.isOwn }) 40 | return output 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Record/View/RecordMainHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordHeaderView.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/15. 6 | // 7 | 8 | import UIKit 9 | import RxSwift 10 | import RxRelay 11 | 12 | class RecordMainHeaderView: UICollectionReusableView { 13 | // MARK: - UI properties 14 | private lazy var dateLabel = UILabel().then { 15 | $0.font = .largeTitle 16 | $0.textColor = .pointLight 17 | $0.textAlignment = .center 18 | } 19 | 20 | private lazy var distanceLabel = UILabel().then { 21 | $0.font = .smallTitle 22 | $0.textColor = .pointLight 23 | $0.textAlignment = .center 24 | } 25 | 26 | private lazy var kcalLabel = UILabel().then { 27 | $0.font = .smallTitle 28 | $0.textColor = .pointLight 29 | $0.textAlignment = .center 30 | } 31 | 32 | // MARK: - Properties 33 | static let identifier = "RecordHeaderView" 34 | var bindEvent = PublishRelay() 35 | 36 | // MARK: - Lifecycles 37 | override init(frame: CGRect) { 38 | super.init(frame: frame) 39 | setUpSubviews() 40 | } 41 | 42 | required init?(coder: NSCoder) { 43 | fatalError("init(coder:) has not been implemented") 44 | } 45 | 46 | // MARK: - Helpers 47 | 48 | private func setUpSubviews() { 49 | [dateLabel, distanceLabel, kcalLabel].forEach { 50 | addSubview($0) 51 | } 52 | 53 | configureUI() 54 | } 55 | 56 | private func configureUI() { 57 | dateLabel.snp.makeConstraints { 58 | $0.top.leading.trailing.equalToSuperview() 59 | $0.height.equalTo(50) 60 | } 61 | 62 | distanceLabel.snp.makeConstraints { 63 | $0.top.equalTo(dateLabel.snp.bottom) 64 | $0.leading.trailing.equalToSuperview() 65 | $0.height.equalTo(25) 66 | } 67 | 68 | kcalLabel.snp.makeConstraints { 69 | $0.top.equalTo(distanceLabel.snp.bottom) 70 | $0.leading.trailing.bottom.equalToSuperview() 71 | } 72 | } 73 | 74 | func bind(headerRecord: RecordViewHeaderRecord) { 75 | dateLabel.text = headerRecord.date 76 | distanceLabel.text = "누적 거리: \(headerRecord.distance.convertToDecimal) m" 77 | kcalLabel.text = "누적 칼로리: \(headerRecord.kcal.convertToDecimal) kcal" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Record/View/RecordMapCategoryCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordMapCategoryCell.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class RecordMapCategoryCell: UICollectionViewCell { 11 | // MARK: - UI properties 12 | private lazy var locationNameLabel = UILabel().then { 13 | $0.layer.backgroundColor = UIColor.pointLight.cgColor 14 | $0.layer.cornerRadius = 15 15 | $0.font = UIFont.boldBody 16 | $0.textColor = .white 17 | $0.textAlignment = .center 18 | } 19 | // MARK: - Properties 20 | static let identifier = "RecordMapCategoryCell" 21 | 22 | // MARK: - Lifecycles 23 | override init(frame: CGRect) { 24 | super.init(frame: frame) 25 | setUpSubviews() 26 | configureUI() 27 | } 28 | 29 | required init?(coder: NSCoder) { 30 | fatalError("init(coder:) has not been implemented") 31 | } 32 | 33 | // MARK: - Helpers 34 | 35 | private func setUpSubviews() { 36 | contentView.addSubview(locationNameLabel) 37 | } 38 | 39 | private func configureUI() { 40 | locationNameLabel.snp.makeConstraints { 41 | $0.top.bottom.leading.trailing.equalToSuperview() 42 | } 43 | } 44 | 45 | func setLocationName(name: String) { 46 | locationNameLabel.text = name 47 | } 48 | 49 | func getLocationName() -> String { 50 | guard let name = locationNameLabel.text else { return "" } 51 | return name 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Record/View/RecordMapHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordMapHeaderView.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/22. 6 | // 7 | 8 | import UIKit 9 | import RxSwift 10 | import RxRelay 11 | 12 | class RecordMapHeaderView: UICollectionReusableView { 13 | // MARK: - UI properties 14 | private lazy var mapName = UILabel().then { 15 | $0.textColor = .pointLight 16 | $0.font = .largeTitle 17 | $0.textAlignment = .right 18 | } 19 | 20 | private lazy var downButton = UIButton().then { 21 | $0.tintColor = .pointLight 22 | $0.setImage(SystemImageNameSpace.chevronDown.uiImage, for: .normal) 23 | } 24 | 25 | // MARK: - Properties 26 | static let identifier = "RecordMapHeaderView" 27 | var dropDownMenuTapEvent = PublishRelay() 28 | 29 | // MARK: - Lifecycles 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | setUpSubviews() 33 | configureUI() 34 | } 35 | 36 | required init?(coder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | // MARK: - Helpers 41 | private func setUpSubviews() { 42 | [mapName, downButton].forEach { 43 | addSubview($0) 44 | } 45 | } 46 | 47 | private func configureUI() { 48 | mapName.snp.makeConstraints { 49 | $0.top.bottom.equalToSuperview() 50 | $0.centerX.equalToSuperview() 51 | $0.width.greaterThanOrEqualTo(30) 52 | } 53 | 54 | downButton.snp.makeConstraints { 55 | $0.top.bottom.equalToSuperview() 56 | $0.leading.equalTo(mapName.snp.trailing).offset(5) 57 | $0.width.greaterThanOrEqualTo(30) 58 | } 59 | } 60 | 61 | func setMapName(mapName: String) { 62 | self.mapName.text = mapName 63 | } 64 | 65 | func setDropDownMenus(maps: [Map]) { 66 | var menuItems: [UIAction] = [] 67 | maps.forEach { 68 | menuItems.append( 69 | UIAction(title: $0.name) { action in 70 | self.dropDownMenuTapEvent.accept(action.title) 71 | }) 72 | } 73 | downButton.menu = UIMenu(children: menuItems) 74 | downButton.showsMenuAsPrimaryAction = true 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Record/View/RecordMapImageCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordMapImageCell.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/13. 6 | // 7 | 8 | import UIKit 9 | 10 | final class RecordMapImageCell: UICollectionViewCell { 11 | private lazy var imageView: UIImageView = UIImageView().then { 12 | $0.clipsToBounds = true 13 | $0.layer.cornerRadius = 10 14 | } 15 | 16 | static let identifier: String = "RecordMapImageCell" 17 | 18 | override init(frame: CGRect) { 19 | super.init(frame: frame) 20 | setupViews() 21 | configureUI() 22 | } 23 | 24 | required init?(coder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | func setupViews() { 29 | contentView.addSubview(imageView) 30 | } 31 | 32 | func configureUI() { 33 | imageView.snp.makeConstraints { 34 | $0.edges.equalToSuperview() 35 | } 36 | } 37 | 38 | func bind(data: Data) { 39 | imageView.image = UIImage(data: data) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Record/ViewModel/RecordMainViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordViewModel.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/15. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxRelay 11 | 12 | class RecordMainViewModel: BaseViewModel { 13 | 14 | struct Input { 15 | var viewDidAppearEvent: Observable 16 | } 17 | 18 | struct Output { 19 | var weekDatas = PublishRelay<[RecordViewChartData]>() 20 | var recordSectionDatas = PublishRelay<(allDates: [String], 21 | totalRecordAtDate: [String: DayTotalRecord], 22 | recordsAtDate: [String: [Record]], 23 | mapAtMapId: [Int: Map])>() 24 | } 25 | 26 | var useCase: RecordMainViewUseCase 27 | var disposeBag = DisposeBag() 28 | 29 | init(useCase: RecordMainViewUseCase) { 30 | self.useCase = useCase 31 | } 32 | 33 | func transform(input: Input) -> Output { 34 | let output = Output() 35 | 36 | input.viewDidAppearEvent 37 | .subscribe(onNext: { [weak self] _ in 38 | guard let self else { return } 39 | self.useCase.loadMapData() 40 | .subscribe(onSuccess: { _ in 41 | self.useCase.loadRecordData() 42 | }).disposed(by: self.disposeBag) 43 | }).disposed(by: disposeBag) 44 | 45 | useCase.weekDatas 46 | .bind(to: output.weekDatas) 47 | .disposed(by: disposeBag) 48 | 49 | useCase.recordSectionDatas 50 | .bind(to: output.recordSectionDatas) 51 | .disposed(by: disposeBag) 52 | 53 | return output 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Acha/Acha/Presentation/TabBar/Record/ViewModel/RecordMapViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordMapViewModel.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/22. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxRelay 11 | 12 | final class RecordMapViewModel: BaseViewModel { 13 | 14 | struct Input { 15 | var viewDidAppearEvent: Observable 16 | var sectionHeaderCreateEvent: Observable 17 | var dropDownMenuTapEvent: Observable 18 | var categoryCellTapEvent: Observable 19 | } 20 | 21 | struct Output { 22 | var dropDownMenus = PublishRelay<[Map]>() 23 | var mapNameAndRecordDatas = PublishRelay<(mapImage: Data?, mapName: String, recordDatas: [Record])>() 24 | } 25 | 26 | private let useCase: RecordMapViewUseCase 27 | var disposeBag = DisposeBag() 28 | 29 | init(useCase: RecordMapViewUseCase) { 30 | self.useCase = useCase 31 | } 32 | 33 | func transform(input: Input) -> Output { 34 | let output = Output() 35 | 36 | input.viewDidAppearEvent 37 | .subscribe { [weak self] _ in 38 | guard let self else { return } 39 | self.useCase.loadMapData() 40 | self.useCase.getMapNameAndRecordsAtLocation(location: Locations.incheon.string) 41 | }.disposed(by: disposeBag) 42 | 43 | input.sectionHeaderCreateEvent 44 | .subscribe { [weak self] mapName in 45 | guard let self else { return } 46 | self.useCase.getDropDownMenus(mapName: mapName) 47 | }.disposed(by: disposeBag) 48 | 49 | input.dropDownMenuTapEvent 50 | .subscribe(onNext: { [weak self] mapName in 51 | guard let self else { return } 52 | self.useCase.getMapNameAndRecordDatasAtMapName(mapName: mapName) 53 | }).disposed(by: disposeBag) 54 | 55 | input.categoryCellTapEvent 56 | .subscribe(onNext: { [weak self] category in 57 | guard let self else { return } 58 | self.useCase.getMapNameAndRecordsAtLocation(location: category) 59 | }).disposed(by: disposeBag) 60 | 61 | useCase.dropDownMenus 62 | .bind(to: output.dropDownMenus) 63 | .disposed(by: disposeBag) 64 | 65 | useCase.mapNameAndRecordDatas 66 | .bind(to: output.mapNameAndRecordDatas) 67 | .disposed(by: disposeBag) 68 | 69 | return output 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Dependency/DIContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DIContainer.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/10. 6 | // 7 | 8 | import Foundation 9 | 10 | enum DIContainer { 11 | 12 | @propertyWrapper 13 | struct Resolve { 14 | private let type: T.Type 15 | private let container = DependenciesContainer.shared 16 | 17 | var wrappedValue: T { container.resolve(type) } 18 | 19 | init(_ type: T.Type) { 20 | self.type = type 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Dependency/DependenciesContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DependenciesContainer.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/10. 6 | // 7 | 8 | import Foundation 9 | 10 | final class DependenciesContainer { 11 | static let shared = DependenciesContainer() 12 | private init () {} 13 | 14 | // DependencyKey 클래스를 이용해서 형과 이름을 사용해서 분류 15 | private var dependencies: [DependencyKey: Any] = [:] 16 | 17 | func register( 18 | _ type: T.Type, 19 | implement: Any, 20 | name: String? = nil 21 | ) { 22 | let dependencyKey = DependencyKey(type: type, name: name) 23 | dependencies[dependencyKey] = implement 24 | } 25 | 26 | func resolve( 27 | _ type: T.Type, 28 | name: String? = nil 29 | ) -> T { 30 | let dependencyKey = DependencyKey(type: type, name: name) 31 | if let dependency = dependencies[dependencyKey] as? T { 32 | return dependency 33 | } else { 34 | let protocolName = "\(type)".components(separatedBy: ".").last! 35 | fatalError("\(protocolName) 의 의존성을 해결할 수 없습니다.") 36 | } 37 | } 38 | 39 | func remove(type: T.Type) { 40 | let key = DependencyKey(type: type) 41 | _ = dependencies.removeValue(forKey: key) 42 | } 43 | 44 | func reset() { 45 | dependencies.removeAll() 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Dependency/DependencyKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DependencyKey.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/10. 6 | // 7 | 8 | import Foundation 9 | 10 | final class DependencyKey: Hashable, Equatable { 11 | 12 | private let type: Any.Type 13 | private let name: String? 14 | 15 | init(type: Any.Type, name: String? = nil) { 16 | self.type = type 17 | self.name = name 18 | } 19 | 20 | func hash(into hasher: inout Hasher) { 21 | hasher.combine(ObjectIdentifier(type)) 22 | hasher.combine(name) 23 | } 24 | 25 | static func == (lhs: DependencyKey, rhs: DependencyKey) -> Bool { 26 | return lhs.type == rhs.type && lhs.name == rhs.name 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Enums/AnimationKeyPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationKeyPath.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/30. 6 | // 7 | 8 | import Foundation 9 | 10 | enum AnimationKeyPath { 11 | case path 12 | case opacity 13 | 14 | var string: String { 15 | switch self { 16 | case .path: 17 | return "path" 18 | case .opacity: 19 | return "opacity" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Enums/Errors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Errors.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/14. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Errors: Error { 11 | case decodeError 12 | case cannotDrawPolyLine 13 | 14 | var description: String { 15 | switch self { 16 | case .decodeError: 17 | return "decoding에 실패했습니다." 18 | case .cannotDrawPolyLine: 19 | return "땅의 경계선(PolyLine)을 그릴 수 없습니다." 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Enums/FirebaseRealtimeType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseRealtimeType.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/27. 6 | // 7 | 8 | import Foundation 9 | 10 | enum FirebaseRealtimeType { 11 | case mapList 12 | case recordList 13 | case record(id: Int) 14 | case user(id: String) 15 | case room(id: String) 16 | case postList 17 | case post(id: Int) 18 | case comment(postID: Int, commentID: Int) 19 | case badge 20 | 21 | var path: String { 22 | switch self { 23 | case .mapList: 24 | return "mapList" 25 | case .recordList: 26 | return "record" 27 | case .record(let id): 28 | return "record/\(id)" 29 | case .user(let id): 30 | return "User/\(id)" 31 | case .room(let id): 32 | return "Room/\(id)" 33 | case .post(let id): 34 | return "community/postList/\(id)" 35 | case .postList: 36 | return "community/postList" 37 | case .comment(let postID, let commentID): 38 | return "community/postList/\(postID)/comments/\(commentID)" 39 | case .badge: 40 | return "badge" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Enums/FirebaseStorageType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseStorageType.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/07. 6 | // 7 | 8 | import Foundation 9 | 10 | enum FirebaseStorageType { 11 | case map 12 | case category(String) 13 | case badge 14 | case pinCharacter 15 | 16 | var path: String { 17 | switch self { 18 | case .map: 19 | return "Map" 20 | case .category(let name): 21 | return "Category/\(name)" 22 | case .badge: 23 | return "Badge" 24 | case .pinCharacter: 25 | return "PinCharacter" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Enums/Locations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Locations.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/24. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Locations: CaseIterable { 11 | case seoul 12 | case incheon 13 | case gyeonggi 14 | case busan 15 | case gyeongbuk 16 | case gyeongnam 17 | case chungbuk 18 | case chungnam 19 | 20 | var string: String { 21 | switch self { 22 | case .seoul: 23 | return "서울" 24 | case .incheon: 25 | return "인천" 26 | case .gyeonggi: 27 | return "경기" 28 | case .busan: 29 | return "부산" 30 | case .gyeongbuk: 31 | return "경북" 32 | case .gyeongnam: 33 | return "경남" 34 | case .chungbuk: 35 | return "충북" 36 | case .chungnam: 37 | return "충남" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Enums/PinCharacter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PinCharacter.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/12/13. 6 | // 7 | 8 | import UIKit 9 | 10 | enum PinCharacter: String, CaseIterable { 11 | case firstAnnotation 12 | case secondAnnotation 13 | case thirdAnnotation 14 | case fourthAnnotation 15 | 16 | var image: UIImage { 17 | (UIImage(named: self.rawValue) ?? .firstAnnotation).imageWith(newSize: CGSize(width: 30, height: 30)) 18 | } 19 | 20 | var name: String { 21 | switch self { 22 | case .firstAnnotation: 23 | return "펭귄" 24 | case .secondAnnotation: 25 | return "강아지" 26 | case .thirdAnnotation: 27 | return "고양이" 28 | case .fourthAnnotation: 29 | return "토끼" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Enums/SystemImageNameSpace.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemImageNameSpace.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/15. 6 | // 7 | 8 | import UIKit 9 | 10 | enum SystemImageNameSpace: String { 11 | case locationCircle = "location.circle" 12 | case chevronDown = "chevron.down" 13 | case ellipsis = "ellipsis" 14 | case xmark = "xmark" 15 | 16 | var uiImage: UIImage { UIImage(systemName: self.rawValue) ?? UIImage() } 17 | 18 | func systemImageColorChange(color: UIColor) -> UIImage { 19 | return self.uiImage.withTintColor(color, renderingMode: .alwaysOriginal) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Enums/TabBarType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarType.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/14. 6 | // 7 | 8 | import Foundation 9 | 10 | enum TabBarType: CaseIterable { 11 | case home 12 | case record 13 | case community 14 | case myPage 15 | 16 | var pageNumber: Int { 17 | switch self { 18 | case .home: 19 | return 0 20 | case .record: 21 | return 1 22 | case .community: 23 | return 2 24 | case .myPage: 25 | return 3 26 | } 27 | } 28 | 29 | var pageTitle: String { 30 | switch self { 31 | case .home: 32 | return "땅따먹기" 33 | case .record: 34 | return "기록" 35 | case .community: 36 | return "커뮤니티" 37 | case .myPage: 38 | return "마이페이지" 39 | } 40 | } 41 | 42 | var iconImage: String { 43 | switch self { 44 | case .home: 45 | return "house" 46 | case .record: 47 | return "chart.bar" 48 | case .community: 49 | return "person.2" 50 | case .myPage: 51 | return "face.smiling" 52 | } 53 | } 54 | 55 | var selectedIconImage: String { 56 | switch self { 57 | case .home: 58 | return "house.fill" 59 | case .record: 60 | return "chart.bar.fill" 61 | case .community: 62 | return "person.2.fill" 63 | case .myPage: 64 | return "face.smiling.inverse" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Error/FirebaseServiceError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseServiceError.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | enum FirebaseServiceError: Error { 11 | case nilDataError 12 | case fetchError 13 | } 14 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/CALayer+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+Extension.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/15. 6 | // 7 | 8 | import UIKit 9 | 10 | extension CALayer { 11 | func addBorder( 12 | directions: [UIRectEdge], 13 | color: UIColor, 14 | width: CGFloat 15 | ) { 16 | for direction in directions { 17 | let border = CALayer() 18 | switch direction { 19 | case .top: 20 | border.frame = CGRect.init(x: 0, y: 0, width: frame.width, height: width) 21 | case .bottom: 22 | border.frame = CGRect.init(x: 0, y: frame.height - width, width: frame.width, height: width) 23 | case .left: 24 | border.frame = CGRect.init(x: 0, y: 0, width: width, height: frame.height) 25 | case .right: 26 | border.frame = CGRect.init(x: frame.width - width, y: 0, width: width, height: frame.height) 27 | default: 28 | break 29 | } 30 | border.backgroundColor = color.cgColor 31 | self.addSublayer(border) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/CLLocationCoordinate2D+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLLocationCoordinate2D+.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/15. 6 | // 7 | 8 | import MapKit 9 | 10 | // swiftlint:disable identifier_name 11 | extension CLLocationCoordinate2D { 12 | func distance(to: CLLocationCoordinate2D) -> CLLocationDistance { 13 | let from = CLLocation(latitude: latitude, longitude: longitude) 14 | let to = CLLocation(latitude: to.latitude, longitude: to.longitude) 15 | return to.distance(from: from) 16 | } 17 | 18 | var achaCoordinate: Coordinate { 19 | Coordinate(latitude: self.latitude, longitude: self.longitude) 20 | } 21 | 22 | static func from(coordiate: Coordinate) -> CLLocationCoordinate2D { 23 | CLLocationCoordinate2DMake(coordiate.latitude, coordiate.longitude) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/Collection+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Collection+.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/12/12. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Collection { 11 | // index에 해당하는 원소를 리턴. 없으면 nil 12 | subscript (safe index: Index) -> Element? { 13 | return indices.contains(index) ? self[index] : nil 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/Date+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Extension.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/16. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Date { 11 | func convertToStringFormat(format: String) -> String { 12 | let dateFormatter = DateFormatter() 13 | dateFormatter.dateFormat = format 14 | dateFormatter.locale = Locale(identifier: "ko_kr") 15 | return dateFormatter.string(from: self) 16 | } 17 | 18 | var secondsSince1970: Int64 { 19 | Int64((self.timeIntervalSince1970).rounded()) 20 | } 21 | 22 | init(seconds: Int64) { 23 | self = Date(timeIntervalSince1970: TimeInterval(seconds)) 24 | } 25 | 26 | func since(_ from: Int64) -> Date { 27 | return Date(seconds: self.secondsSince1970 - from) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/Double+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Double+.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/16. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Double { 11 | func degreeToRadian() -> Double { 12 | self * .pi / 180 13 | } 14 | 15 | func radianToDegree() -> Double { 16 | self * 180.0 / .pi 17 | } 18 | 19 | var meterToKmString: String { 20 | String(format: "%.2f", self/1000) + "km" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/Encodable+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Encodable+Extension.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - struct > Dictonary 변환 코드 11 | extension Encodable { 12 | subscript(key: String) -> Any? { 13 | return dictionary[key] 14 | } 15 | var dictionary: [String: Any] { 16 | return (try? JSONSerialization.jsonObject(with: JSONEncoder().encode(self))) as? [String: Any] ?? [:] 17 | } 18 | 19 | var toJSON: Data? { 20 | try? JSONEncoder().encode(self) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/Int+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+Extension.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/16. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Int { 11 | func convertToHourMinuteSecondFormat() -> String { 12 | var time = self 13 | let day = String(format: "%02d", time/3600) 14 | time %= 3600 15 | let hour = String(format: "%02d", time/60) 16 | time %= 60 17 | let minute = String(format: "%02d", time) 18 | return "\(day):\(hour):\(minute)" 19 | } 20 | 21 | var convertToDecimal: String { 22 | let numberFormatter = NumberFormatter() 23 | numberFormatter.numberStyle = .decimal 24 | 25 | return numberFormatter.string(from: NSNumber(value: self))! 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/MKCircle+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MKCircle+.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/07. 6 | // 7 | 8 | import UIKit 9 | import MapKit 10 | 11 | extension MKCircle { 12 | enum CircleType: Int { 13 | case first = 0 14 | case second = 1 15 | case third = 2 16 | case fourth = 3 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/Reactive+/MKMapView+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MKMapView+Rx.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/12/08. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxCocoa 11 | import MapKit 12 | 13 | class RxMKMapViewDelegateProxy: DelegateProxy, DelegateProxyType, MKMapViewDelegate { 14 | static func registerKnownImplementations() { 15 | self.register { mapView -> RxMKMapViewDelegateProxy in 16 | RxMKMapViewDelegateProxy(parentObject: mapView, delegateProxy: self) 17 | } 18 | } 19 | 20 | static func currentDelegate(for object: MKMapView) -> MKMapViewDelegate? { 21 | return object.delegate 22 | } 23 | 24 | static func setCurrentDelegate(_ delegate: MKMapViewDelegate?, to object: MKMapView) { 25 | object.delegate = delegate 26 | } 27 | } 28 | 29 | extension Reactive where Base: MKMapView { 30 | var delegate: DelegateProxy { 31 | return RxMKMapViewDelegateProxy.proxy(for: self.base) 32 | } 33 | 34 | var didSelectAnnotation: Observable { 35 | return delegate.methodInvoked(#selector(MKMapViewDelegate.mapView(_:didSelect:))) 36 | .map { parameters in 37 | let annotaionView = (parameters[1] as? MKAnnotationView) ?? MKAnnotationView() 38 | return annotaionView.annotation 39 | } 40 | } 41 | 42 | var didDeselectAnnotation: Observable { 43 | return delegate.methodInvoked(#selector(MKMapViewDelegate.mapView(_:didDeselect:))) 44 | .map { parameters in 45 | let annotaionView = (parameters[1] as? MKAnnotationView) ?? MKAnnotationView() 46 | return annotaionView.annotation 47 | } 48 | } 49 | 50 | var regionDidChange: Observable { 51 | return delegate.methodInvoked(#selector(MKMapViewDelegate.mapView(_:regionDidChangeAnimated:))) 52 | .map { parameters in 53 | let mapView = (parameters[0] as? MKMapView) ?? MKMapView() 54 | return mapView.region 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/Reactive+/UIApplication+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+Rx.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/09. 6 | // 7 | 8 | import RxSwift 9 | import RxCocoa 10 | import UIKit 11 | 12 | public enum Appstate: Equatable { 13 | case active 14 | case inactive 15 | case background 16 | case terminated 17 | } 18 | 19 | extension Reactive where Base: UIApplication { 20 | var applicationWillEnterForeground: Observable { 21 | return NotificationCenter.default.rx.notification(UIApplication.willEnterForegroundNotification) 22 | .map { _ in 23 | return .active 24 | } 25 | } 26 | 27 | var applicationDidBecomActive: Observable { 28 | return NotificationCenter.default.rx.notification(UIApplication.didBecomeActiveNotification) 29 | .map { _ in 30 | return .active 31 | } 32 | } 33 | 34 | var applicationDidEnterBackground: Observable { 35 | return NotificationCenter.default.rx.notification(UIApplication.didEnterBackgroundNotification) 36 | .map { _ in 37 | return .background 38 | } 39 | } 40 | 41 | var applicationWillResignActive: Observable { 42 | return NotificationCenter.default.rx.notification(UIApplication.willResignActiveNotification) 43 | .map { _ in 44 | return .inactive 45 | } 46 | } 47 | 48 | var applicationWillTerminate: Observable { 49 | return NotificationCenter.default.rx.notification(UIApplication.willTerminateNotification) 50 | .map { _ in 51 | return .terminated 52 | } 53 | } 54 | 55 | public var appState: Observable { 56 | return Observable.of( 57 | applicationDidBecomActive, 58 | applicationWillResignActive, 59 | applicationWillEnterForeground, 60 | applicationDidEnterBackground, 61 | applicationWillTerminate 62 | ) 63 | .merge() 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/Reactive+/UIView+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Rx+IndicatorAnimation.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/09. 6 | // 7 | 8 | import UIKit 9 | import RxSwift 10 | import Then 11 | 12 | extension Reactive where Base: UIView { 13 | 14 | var indicator: Binder { 15 | Binder(base) { base, indicator in 16 | 17 | if indicator { 18 | base.isUserInteractionEnabled = false 19 | let backView = UIView(frame: base.frame).then { 20 | $0.alpha = 0.6 21 | $0.backgroundColor = .black 22 | $0.tag = 444 23 | } 24 | let image = UIButton().then { 25 | $0.layer.borderColor = UIColor.pointDark.cgColor 26 | $0.layer.borderWidth = 4 27 | $0.layer.cornerRadius = 10 28 | $0.frame.size = CGSize(width: 60, height: 60) 29 | $0.tag = 444 30 | } 31 | base.addSubview(backView) 32 | backView.addSubview(image) 33 | image.center = base.center 34 | image.layer.add(animationGroup, forKey: nil) 35 | } else { 36 | base.isUserInteractionEnabled = true 37 | base.subviews.forEach { if $0.tag == 444 { 38 | $0.removeFromSuperview() 39 | } } 40 | } 41 | } 42 | } 43 | 44 | var animationGroup: CAAnimationGroup { 45 | let animationGroup = CAAnimationGroup() 46 | animationGroup.duration = 0.6 47 | animationGroup.fillMode = CAMediaTimingFillMode.forwards 48 | animationGroup.repeatCount = .infinity 49 | 50 | let animation3 = CABasicAnimation(keyPath: "position.x") 51 | animation3.fromValue = base.center.x-100 52 | animation3.toValue = base.center.x + 100 53 | 54 | let animation5 = CABasicAnimation(keyPath: "transform.rotation.x") 55 | animation5.fromValue = 0 56 | animation5.toValue = Double.pi / 3 57 | 58 | animationGroup.animations = [animation3, animation5] 59 | 60 | return animationGroup 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/Reactive+/UIViewCotnroller+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxSwift+Extension.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/11/24. 6 | // 7 | 8 | import UIKit 9 | import RxSwift 10 | import RxCocoa 11 | 12 | public extension Reactive where Base: UIViewController { 13 | var viewDidLoad: ControlEvent { 14 | let source = self.methodInvoked(#selector(Base.viewDidLoad)).map { _ in } 15 | return ControlEvent(events: source) 16 | } 17 | 18 | var viewWillAppear: ControlEvent { 19 | let source = self.methodInvoked(#selector(Base.viewWillAppear)).map { _ in } 20 | return ControlEvent(events: source) 21 | } 22 | var viewDidAppear: ControlEvent { 23 | let source = self.methodInvoked(#selector(Base.viewDidAppear)).map { _ in } 24 | return ControlEvent(events: source) 25 | } 26 | 27 | var viewWillDisappear: ControlEvent { 28 | let source = self.methodInvoked(#selector(Base.viewWillDisappear)).map { _ in } 29 | return ControlEvent(events: source) 30 | } 31 | var viewDidDisappear: ControlEvent { 32 | let source = self.methodInvoked(#selector(Base.viewDidDisappear)).map { _ in } 33 | return ControlEvent(events: source) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Extension/String+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/21. 6 | // 7 | 8 | import UIKit 9 | 10 | extension String { 11 | func convertToDateFormat(format: String) -> Date { 12 | let dateFormatter = DateFormatter() 13 | dateFormatter.dateFormat = "yyyy-MM-dd" 14 | dateFormatter.locale = Locale(identifier: "ko_kr") 15 | if let date = dateFormatter.date(from: self) { 16 | return date 17 | } 18 | return Date() 19 | } 20 | 21 | func stringCheck(pattern: String) -> Bool { 22 | do { 23 | let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive) 24 | let range = NSRange(location: 0, length: self.count) 25 | if regex.firstMatch(in: self, range: range) != nil { 26 | return true 27 | } else { 28 | return false 29 | } 30 | } catch { 31 | print(error.localizedDescription) 32 | return false 33 | } 34 | } 35 | 36 | func generateQRCode() -> UIImage? { 37 | let data = self.data(using: String.Encoding.ascii) 38 | 39 | if let filter = CIFilter(name: "CIQRCodeGenerator") { 40 | filter.setValue(data, forKey: "inputMessage") 41 | let transform = CGAffineTransform(scaleX: 3, y: 3) 42 | 43 | if let output = filter.outputImage?.transformed(by: transform) { 44 | return UIImage(ciImage: output) 45 | } 46 | } 47 | return nil 48 | } 49 | 50 | func stringLimit(_ number: Int) -> String { 51 | return self.count <= number ? self : self.map { String($0) }[0.. 16 | let keyboardHeight: Driver 17 | private let disposeBag = DisposeBag() 18 | 19 | private init() { 20 | let keyboardWillChangeFrame = UIResponder.keyboardWillChangeFrameNotification 21 | let keyboardWillHide = UIResponder.keyboardWillHideNotification 22 | let keyboardEndUserKey = UIResponder.keyboardFrameEndUserInfoKey 23 | 24 | let defaultFrame = CGRect( 25 | x: 0, 26 | y: UIScreen.main.bounds.height, 27 | width: UIScreen.main.bounds.width, 28 | height: 0 29 | ) 30 | 31 | let frameVariable = BehaviorRelay(value: defaultFrame) 32 | 33 | self.frame = frameVariable.asDriver().distinctUntilChanged() 34 | self.keyboardHeight = self.frame.map { UIScreen.main.bounds.height - $0.origin.y } 35 | 36 | // 키보드 프레임 변경 37 | let willChangeFrame = NotificationCenter.default.rx.notification(keyboardWillChangeFrame) 38 | .map { notification -> CGRect in 39 | let rectValue = notification.userInfo?[keyboardEndUserKey] as? NSValue 40 | return rectValue?.cgRectValue ?? defaultFrame 41 | } 42 | 43 | // 키보드가 없어짐 44 | let willHide = NotificationCenter.default.rx.notification(keyboardWillHide) 45 | .map { notification -> CGRect in 46 | let rectValue = notification.userInfo?[keyboardEndUserKey] as? NSValue 47 | return rectValue?.cgRectValue ?? defaultFrame 48 | } 49 | 50 | Observable.of(willChangeFrame, willHide) 51 | .merge() 52 | .bind(to: frameVariable) 53 | .disposed(by: disposeBag) 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Map/MapAnnotation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapAnnotation.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/15. 6 | // 7 | 8 | import MapKit 9 | 10 | // MapAnnotation은 NSObject를 상속해야함 11 | class MapAnnotation: NSObject, MKAnnotation { 12 | 13 | let map: Map 14 | let coordinate: CLLocationCoordinate2D 15 | let polyLine: MKPolyline 16 | 17 | init(map: Map, polyLine: MKPolyline) { 18 | self.map = map 19 | self.coordinate = CLLocationCoordinate2D(latitude: map.centerCoordinate.latitude, 20 | longitude: map.centerCoordinate.longitude) 21 | self.polyLine = polyLine 22 | super.init() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/DefaultKeychainService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultKeychainService.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/02. 6 | // 7 | import Foundation 8 | import RxSwift 9 | 10 | struct DefaultKeychainService: KeychainService { 11 | 12 | func get() -> String? { 13 | return try? KeyChainManager.get() 14 | } 15 | 16 | func save(uuid: String) { 17 | try? KeyChainManager.save(id: uuid) 18 | } 19 | 20 | func delete() { 21 | try? KeyChainManager.delete() 22 | } 23 | 24 | func update(uuid: String) { 25 | try? KeyChainManager.update(id: uuid) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/DefaultRandomService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultRandomService.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/05. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DefaultRandomService: RandomService { 11 | func make() -> String { 12 | let numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] 13 | var random = "" 14 | for _ in 0..<16 { 15 | random += numbers.randomElement()! 16 | } 17 | return random 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/DefaultTimerService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultTimerService.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/23. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | final class DefaultTimerService: TimerService { 12 | var disposeBag = DisposeBag() 13 | 14 | func start() -> Observable { 15 | Observable 16 | .interval(.seconds(1), scheduler: MainScheduler.asyncInstance) 17 | .map { $0 + 1 } 18 | } 19 | 20 | func start(until: Int) -> Observable { 21 | Observable 22 | .timer(.seconds(until), scheduler: MainScheduler.asyncInstance) 23 | .map { _ in } 24 | } 25 | 26 | func stop() { 27 | disposeBag = DisposeBag() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/Protocol/AuthService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthService.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/02. 6 | // 7 | import Foundation 8 | import RxSwift 9 | 10 | protocol AuthService { 11 | 12 | func signUp(data: SignUpData) -> Single 13 | func logIn(data: LoginData) -> Single 14 | func signOut() throws 15 | func delete() -> Single 16 | func update(email: String, password: String) -> Single 17 | } 18 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/Protocol/FirebaseStorageNetworkService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseStorageNetworkService.swift 3 | // Acha 4 | // 5 | // Created by 배남석 on 2022/12/07. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol FirebaseStorageNetworkService { 12 | func upload(type: FirebaseStorageType, data: Data, completion: @escaping (URL?) -> Void) 13 | func download(urlString: String, completion: @escaping (Data?) -> Void) 14 | func download(urlString: String) -> Observable 15 | } 16 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/Protocol/HealthKitService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthKitService.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/01. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol HealthKitService { 12 | /// 헬스 킷에 값 저장 가능 13 | func write(type: DefaultHealthKitServiceType) -> Observable 14 | /// 헬스 킷에서 최근 값 가져 옴 15 | func read(type: DefaultHealthKitServiceType) -> Observable 16 | /// 헬스 인증 요청 17 | func authorization() -> Observable 18 | } 19 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/Protocol/ImageCacheService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCacheService.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/12/11. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol ImageCacheService { 12 | func isExist(imageURL: String) -> Bool 13 | func load(imageURL: String) -> Single 14 | func write(imageURL: String, image: Data) 15 | } 16 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/Protocol/KeychainService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainService.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/02. 6 | // 7 | import Foundation 8 | 9 | protocol KeychainService { 10 | 11 | /// uuid 받아 오는 메서드 12 | func get() -> String? 13 | 14 | /// uuid 삭제 메서드 15 | func delete() 16 | 17 | /// uuid 저장 메서드 18 | func save(uuid: String) 19 | 20 | /// uuid 업데이트 메서드 21 | func update(uuid: String) 22 | } 23 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/Protocol/LocationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationService.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/27. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import CoreLocation 11 | 12 | protocol LocationService { 13 | var authorizationStatus: PublishSubject { get set } 14 | var userLocation: BehaviorSubject { get set } 15 | 16 | func start() 17 | func stop() 18 | } 19 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/Protocol/RandomService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomService.swift 3 | // Acha 4 | // 5 | // Created by hong on 2022/12/05. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol RandomService { 11 | /// 랜덤한 16자리 숫자 만들어 주는 메서드 ( 방 번호 만들기 ) 12 | func make() -> String 13 | } 14 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/Protocol/RealtimeDatabaseNetworkService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealtimeDatabaseNetworkService.swift 3 | // Acha 4 | // 5 | // Created by sangyeon on 2022/11/27. 6 | // 7 | 8 | import RxSwift 9 | 10 | protocol RealtimeDatabaseNetworkService { 11 | func fetch(type: FirebaseRealtimeType, 12 | child: String, 13 | value: Any?, 14 | limitCount: Int?) -> Single 15 | func fetch(type: FirebaseRealtimeType) -> Single 16 | func fetchAtKeyValue(type: FirebaseRealtimeType, 17 | value: Any, 18 | key: String) -> Single 19 | func upload(type: FirebaseRealtimeType, data: T) -> Single 20 | func delete(type: FirebaseRealtimeType) -> Single 21 | func observing(type: FirebaseRealtimeType) -> Observable 22 | func removeObserver(type: FirebaseRealtimeType) 23 | func terminate(type: FirebaseRealtimeType) 24 | } 25 | -------------------------------------------------------------------------------- /Acha/Acha/Util/Service/Protocol/TimerService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimerService.swift 3 | // Acha 4 | // 5 | // Created by 조승기 on 2022/11/23. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | protocol TimerService { 12 | var disposeBag: DisposeBag { get set } 13 | 14 | func start() -> Observable 15 | func start(until: Int) -> Observable 16 | func stop() 17 | } 18 | -------------------------------------------------------------------------------- /Acha/AchaTests/AchaTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AchaTests.swift 3 | // AchaTests 4 | // 5 | // Created by sangyeon on 2022/11/11. 6 | // 7 | 8 | import XCTest 9 | @testable import Acha 10 | 11 | final class AchaTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | // Any test you write for XCTest can be annotated as throws and async. 25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 27 | } 28 | 29 | func testPerformanceExample() throws { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Acha/AchaUITests/AchaUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AchaUITests.swift 3 | // AchaUITests 4 | // 5 | // Created by sangyeon on 2022/11/11. 6 | // 7 | 8 | import XCTest 9 | 10 | final class AchaUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | func testLaunchPerformance() throws { 34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 35 | // This measures how long it takes to launch your application. 36 | measure(metrics: [XCTApplicationLaunchMetric()]) { 37 | XCUIApplication().launch() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Acha/AchaUITests/AchaUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AchaUITestsLaunchTests.swift 3 | // AchaUITests 4 | // 5 | // Created by sangyeon on 2022/11/11. 6 | // 7 | 8 | import XCTest 9 | 10 | final class AchaUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Acha/GPX/WorkSpace by 25km:h.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13.91 5 | 6 | 7 | 8 | 14.48 9 | 10 | 11 | 12 | 13.91 13 | 14 | 15 | 16 | 13.38 17 | 18 | 19 | 20 | 13.81 21 | 22 | 23 | -------------------------------------------------------------------------------- /Acha/GPX/WorkSpace by 6km:h.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13.91 5 | 6 | 7 | 8 | 14.48 9 | 10 | 11 | 12 | 13.91 13 | 14 | 15 | 16 | 13.38 17 | 18 | 19 | 20 | 13.81 21 | 22 | 23 | -------------------------------------------------------------------------------- /Acha/GPX/국민대 12KMH.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 101.94 5 | 6 | 7 | 8 | 102.61 9 | 10 | 11 | 12 | 102.81 13 | 14 | 15 | 16 | 100.18 17 | 18 | 19 | 20 | 101.83 21 | 22 | 23 | -------------------------------------------------------------------------------- /Acha/GPX/국민대 30KMH.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 101.94 5 | 6 | 7 | 8 | 102.61 9 | 10 | 11 | 12 | 102.81 13 | 14 | 15 | 16 | 100.18 17 | 18 | 19 | 20 | 101.83 21 | 22 | 23 | -------------------------------------------------------------------------------- /Acha/GPX/부원여중 12KMH.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19.31 5 | 6 | 7 | 8 | 18.60 9 | 10 | 11 | 12 | 20.68 13 | 14 | 15 | 16 | 20.92 17 | 18 | 19 | 20 | 19.36 21 | 22 | 23 | -------------------------------------------------------------------------------- /Acha/GPX/부원여중 5MPH.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19.31 5 | 6 | 7 | 8 | 18.60 9 | 10 | 11 | 12 | 20.68 13 | 14 | 15 | 16 | 20.92 17 | 18 | 19 | 20 | 19.36 21 | 22 | 23 | -------------------------------------------------------------------------------- /Acha/GPX/부평동초등학교 20MPH.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11.73 5 | 6 | 7 | 8 | 11.81 9 | 10 | 11 | 12 | 12.35 13 | 14 | 15 | 16 | 12.27 17 | 18 | 19 | 20 | 12.19 21 | 22 | 23 | 24 | 11.74 25 | 26 | 27 | -------------------------------------------------------------------------------- /Acha/GPX/부평동초등학교 4.2MPH.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11.81 5 | 6 | 7 | 8 | 11.74 9 | 10 | 11 | 12 | 12.19 13 | 14 | 15 | 16 | 12.27 17 | 18 | 19 | 20 | 12.36 21 | 22 | 23 | 24 | 11.82 25 | 26 | 27 | -------------------------------------------------------------------------------- /Acha/GPX/부평동초등학교 4MPH.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11.81 5 | 6 | 7 | 8 | 11.74 9 | 10 | 11 | 12 | 12.19 13 | 14 | 15 | 16 | 12.27 17 | 18 | 19 | 20 | 12.36 21 | 22 | 23 | 24 | 11.82 25 | 26 | 27 | -------------------------------------------------------------------------------- /Acha/GPX/부평동초등학교 5MPH.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11.81 5 | 6 | 7 | 8 | 11.74 9 | 10 | 11 | 12 | 12.19 13 | 14 | 15 | 16 | 12.27 17 | 18 | 19 | 20 | 12.36 21 | 22 | 23 | 24 | 11.82 25 | 26 | 27 | -------------------------------------------------------------------------------- /AchaLibrary/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /AchaLibrary/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AchaLibrary/.swiftpm/xcode/xcshareddata/xcschemes/AchaLibrary.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AchaLibrary/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "AchaLibrary", 8 | platforms: [ 9 | .iOS(.v14) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "AchaLibrary", 15 | targets: ["AchaLibrary"]), 16 | ], 17 | dependencies: [ 18 | .package(url: "https://github.com/SnapKit/SnapKit", from: "5.0.0"), 19 | .package(url: "https://github.com/ReactiveX/RxSwift", from: "6.0.0"), 20 | .package(url: "https://github.com/devxoul/Then", from: "3.0.0"), 21 | .package(url: "https://github.com/firebase/firebase-ios-sdk.git", from: "9.0.0"), 22 | ], 23 | targets: [ 24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 25 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 26 | .target( 27 | name: "AchaLibrary", 28 | dependencies: [ 29 | .product(name: "FirebaseAuth", package: "firebase-ios-sdk"), 30 | .product(name: "FirebaseDatabase", package: "firebase-ios-sdk"), 31 | .product(name: "FirebaseAnalytics", package: "firebase-ios-sdk"), 32 | .product(name: "FirebaseFirestore", package: "firebase-ios-sdk"), 33 | .product(name: "FirebaseStorage", package: "firebase-ios-sdk"), 34 | .product(name: "RxCocoa", package: "RxSwift"), 35 | .product(name: "RxRelay", package: "RxSwift"), 36 | .product(name: "RxSwift", package: "RxSwift"), 37 | .product(name: "Then", package: "Then"), 38 | .product(name: "SnapKit", package: "SnapKit") 39 | ]), 40 | .testTarget( 41 | name: "AchaLibraryTests", 42 | dependencies: ["AchaLibrary"]), 43 | ] 44 | ) 45 | -------------------------------------------------------------------------------- /AchaLibrary/README.md: -------------------------------------------------------------------------------- 1 | # AchaLibrary 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /AchaLibrary/Sources/AchaLibrary/AchaLibrary.swift: -------------------------------------------------------------------------------- 1 | public struct AchaLibrary { 2 | public private(set) var text = "Hello, World!" 3 | 4 | public init() { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AchaLibrary/Tests/AchaLibraryTests/AchaLibraryTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AchaLibrary 3 | 4 | final class AchaLibraryTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(AchaLibrary().text, "Hello, World!") 10 | } 11 | } 12 | --------------------------------------------------------------------------------