├── .github ├── ISSUE_TEMPLATE │ ├── ---feat---.md │ ├── ---fix----.md │ └── PULL_REQUEST_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── 30-SOPKATHON-iOS ├── 30-SOPKATHON-iOS.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── kone.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── kone.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── 30-SOPKATHON-iOS.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── 30-SOPKATHON-iOS │ ├── Application │ │ ├── AppDelegate.swift │ │ ├── Coordinator │ │ │ ├── Coordinators │ │ │ │ └── BaseCoordinator.swift │ │ │ ├── Enums │ │ │ │ └── LaunchInstructor.swift │ │ │ ├── Factory │ │ │ │ ├── CoordinatorFactory.swift │ │ │ │ └── ModuleFactory.swift │ │ │ ├── Protocols │ │ │ │ ├── BaseControllable.swift │ │ │ │ ├── Coordinator.swift │ │ │ │ ├── CoordinatorFinishOutput.swift │ │ │ │ └── Presentable.swift │ │ │ └── Router │ │ │ │ └── Router.swift │ │ ├── Manager │ │ │ ├── Analytics │ │ │ │ ├── AnalyticsType.swift │ │ │ │ ├── AppLog.swift │ │ │ │ ├── EventType │ │ │ │ │ └── LogEventType.swift │ │ │ │ ├── FirebaseServiceProtocol.swift │ │ │ │ └── Provider │ │ │ │ │ ├── FirebaseAnalyticsProvider.swift │ │ │ │ │ └── RuntimeProviderType.swift │ │ │ └── Notification │ │ │ │ └── BaseNotiList.swift │ │ └── SceneDelegate.swift │ ├── Data │ │ ├── Entity │ │ │ ├── DayDetailEntity.swift │ │ │ ├── Feed │ │ │ │ └── FeedDataModel.swift │ │ │ ├── MonthListEntity.swift │ │ │ ├── RankingEntity.swift │ │ │ └── Sample │ │ │ │ └── SampleEntity.swift │ │ ├── Network │ │ │ ├── BaseAPI.swift │ │ │ ├── Config.swift │ │ │ ├── FeedService.swift │ │ │ ├── RankService.swift │ │ │ ├── ResponseObject.swift │ │ │ ├── Service │ │ │ │ └── BaseService.swift │ │ │ └── WriteCommentService.swift │ │ └── Repository │ │ │ └── Sample │ │ │ └── SampleRepository.swift │ ├── Domain │ │ ├── Model │ │ │ └── Sample │ │ │ │ └── SampleModel.swift │ │ └── Usecase │ │ │ └── Sample │ │ │ └── SampleUsecase.swift │ ├── Global │ │ ├── Extension │ │ │ ├── UINavigation + Extension.swift │ │ │ ├── addSubViewFromNib.swift │ │ │ ├── controllerFromStoryboard + UIViewController.swift │ │ │ ├── drawDashedLine + UIView.swift │ │ │ ├── setTextLineHeight + UILabel.swift │ │ │ └── successCatch + Result.swift │ │ ├── Literals │ │ │ ├── ColorLiterals.swift │ │ │ ├── Image │ │ │ │ └── ImageLiterals.swift │ │ │ ├── StoryboardLiterals.swift │ │ │ └── String │ │ │ │ └── StringLiterals.swift │ │ ├── Protocols │ │ │ ├── UICollectionViewRegisterable.swift │ │ │ ├── UITableViewRegisterable.swift │ │ │ └── ViewModelType.swift │ │ ├── Resources │ │ │ ├── Assets.xcassets │ │ │ │ ├── AccentColor.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Icon-1024.png │ │ │ │ │ ├── Icon-120.png │ │ │ │ │ ├── Icon-121.png │ │ │ │ │ ├── Icon-180.png │ │ │ │ │ ├── Icon-40.png │ │ │ │ │ ├── Icon-58.png │ │ │ │ │ ├── Icon-60.png │ │ │ │ │ ├── Icon-80.png │ │ │ │ │ └── Icon-87.png │ │ │ │ ├── Contents.json │ │ │ │ ├── angry_icon_0.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── angry_icon_0.png │ │ │ │ │ ├── angry_icon_0@2x.png │ │ │ │ │ └── angry_icon_0@3x.png │ │ │ │ ├── angry_icon_1.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── angry_icon_1.png │ │ │ │ │ ├── angry_icon_1@2x.png │ │ │ │ │ └── angry_icon_1@3x.png │ │ │ │ ├── angry_icon_2.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── angry_icon_2.png │ │ │ │ │ ├── angry_icon_2@2x.png │ │ │ │ │ └── angry_icon_2@3x.png │ │ │ │ ├── angry_icon_3.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── angry_icon_3.png │ │ │ │ │ ├── angry_icon_3@2x.png │ │ │ │ │ └── angry_icon_3@3x.png │ │ │ │ ├── angry_icon_4.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── angry_icon_4.png │ │ │ │ │ ├── angry_icon_4@2x.png │ │ │ │ │ └── angry_icon_4@3x.png │ │ │ │ ├── carbon_share.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── carbon_share.png │ │ │ │ │ ├── carbon_share@2x.png │ │ │ │ │ └── carbon_share@3x.png │ │ │ │ ├── ic_feed.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── ic_feed.png │ │ │ │ │ ├── ic_feed@2x.png │ │ │ │ │ └── ic_feed@3x.png │ │ │ │ ├── ic_home.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── ic_home.png │ │ │ │ │ ├── ic_home@2x.png │ │ │ │ │ └── ic_home@3x.png │ │ │ │ ├── ic_rank.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── ic_rank.png │ │ │ │ │ ├── ic_rank@2x.png │ │ │ │ │ └── ic_rank@3x.png │ │ │ │ ├── king_icon_1.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Group 33650.png │ │ │ │ │ ├── Group 33650@2x.png │ │ │ │ │ └── Group 33650@3x.png │ │ │ │ ├── king_icon_2.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Group 33649.png │ │ │ │ │ ├── Group 33649@2x.png │ │ │ │ │ └── Group 33649@3x.png │ │ │ │ ├── king_icon_3.imageset │ │ │ │ │ ├── 1 52.png │ │ │ │ │ ├── 1 52@2x.png │ │ │ │ │ ├── 1 52@3x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── main_card_1.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── main_card_1.png │ │ │ │ │ ├── main_card_1@2x.png │ │ │ │ │ └── main_card_1@3x.png │ │ │ │ ├── main_card_2.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── main_card_2.png │ │ │ │ │ ├── main_card_2@2x.png │ │ │ │ │ └── main_card_2@3x.png │ │ │ │ ├── main_icon.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── main_icon.png │ │ │ │ │ ├── main_icon@2x.png │ │ │ │ │ └── main_icon@3x.png │ │ │ │ ├── splash_view.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── splash_view.png │ │ │ │ │ ├── splash_view@2x.png │ │ │ │ │ └── splash_view@3x.png │ │ │ │ ├── tab-home-clicked.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── icn-home-1.png │ │ │ │ │ ├── icn-home@2x-1.png │ │ │ │ │ └── icn-home@3x-1.png │ │ │ │ ├── tab-home.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── icn-home.png │ │ │ │ │ ├── icn-home@2x.png │ │ │ │ │ └── icn-home@3x.png │ │ │ │ ├── tab-mypage-clicked.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Person-1.png │ │ │ │ │ ├── Person@2x-1.png │ │ │ │ │ └── Person@3x-1.png │ │ │ │ ├── tab-mypage.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Person.png │ │ │ │ │ ├── Person@2x.png │ │ │ │ │ └── Person@3x.png │ │ │ │ ├── voice_level_icon_list.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── voice_level_icon_list.png │ │ │ │ │ ├── voice_level_icon_list@2x.png │ │ │ │ │ └── voice_level_icon_list@3x.png │ │ │ │ └── voice_level_sample.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── voice_level_sample.png │ │ │ │ │ ├── voice_level_sample@2x.png │ │ │ │ │ └── voice_level_sample@3x.png │ │ │ └── Fonts │ │ │ │ └── crayon.TTF │ │ └── Supporting Files │ │ │ └── Base.lproj │ │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── Presentation │ │ ├── BaseScene │ │ │ ├── VC │ │ │ │ └── BaseVC.swift │ │ │ └── Views │ │ │ │ ├── Base.storyboard │ │ │ │ ├── Tabbar │ │ │ │ ├── TabbarView.swift │ │ │ │ └── TabbarView.xib │ │ │ │ └── TabbarIcon │ │ │ │ ├── TabbarIcon.swift │ │ │ │ └── TabbarIcon.xib │ │ ├── FeedCalendarScene │ │ │ ├── VC │ │ │ │ ├── FeedCalendarNC.swift │ │ │ │ └── FeedCalendarVC.swift │ │ │ └── Views │ │ │ │ └── FeedCalendar.storyboard │ │ ├── FeedScene │ │ │ ├── Cell │ │ │ │ └── TVC │ │ │ │ │ ├── FeedTVC.swift │ │ │ │ │ └── FeedTVC.xib │ │ │ ├── VC │ │ │ │ └── FeedVC.swift │ │ │ └── Views │ │ │ │ └── Feed.storyboard │ │ ├── HomeScene │ │ │ ├── VC │ │ │ │ ├── HomeNC.swift │ │ │ │ └── HomeVC.swift │ │ │ └── VIews │ │ │ │ └── Home.storyboard │ │ ├── Main.storyboard │ │ ├── RankingScene │ │ │ ├── Cell │ │ │ │ └── TVC │ │ │ │ │ ├── RankignDataModel.swift │ │ │ │ │ ├── RankingTVC.swift │ │ │ │ │ └── RankingTVC.xib │ │ │ ├── VC │ │ │ │ ├── DecibelVC.swift │ │ │ │ ├── DecibelViewController.swift │ │ │ │ ├── RankingVC.swift │ │ │ │ ├── TabVC.swift │ │ │ │ └── TabViewController.swift │ │ │ └── VIews │ │ │ │ ├── Decibel.storyboard │ │ │ │ ├── Ranking.storyboard │ │ │ │ └── Tab.storyboard │ │ ├── SampleScene │ │ │ ├── SampleViewController.swift │ │ │ └── SampleViewModel.swift │ │ ├── SoundKingScene │ │ │ ├── VC │ │ │ │ └── SoundKingVC.swift │ │ │ └── Views │ │ │ │ └── SoundKing.storyboard │ │ ├── SplashScene │ │ │ ├── VC │ │ │ │ └── SplashVC.swift │ │ │ └── Views │ │ │ │ └── Splash.storyboard │ │ ├── TouchKingScene │ │ │ ├── VC │ │ │ │ └── TouchKingVC.swift │ │ │ └── Views │ │ │ │ └── TouchKing.storyboard │ │ └── WritingScene │ │ │ ├── VC │ │ │ └── WritingVC.swift │ │ │ └── Views │ │ │ └── Writing.storyboard │ └── Utils │ │ ├── PanDirectionGesture.swift │ │ ├── addToolBar.swift │ │ ├── applyShadow.swift │ │ ├── calculatePastTime.swift │ │ ├── calculateTopInset.swift │ │ ├── delayWithSeconds.swift │ │ ├── downloadImage.swift │ │ ├── getClassName.swift │ │ ├── makeAlert.swift │ │ ├── makeVibrate.swift │ │ ├── manageObserverAction.swift │ │ ├── pressAction.swift │ │ ├── setDefaultFonts.swift │ │ ├── setImage.swift │ │ ├── showNetworkAlert.swift │ │ └── showToastMessage.swift ├── Podfile └── Podfile.lock └── README.md /.github/ISSUE_TEMPLATE/---feat---.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "✨ [FEAT] ✨" 3 | about: "\U0001F4CC 추가할 기능들에 대해 Task를 정리해주세요 " 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 📌 Feature Issue 11 | 12 | ## 📝 To-do 13 | ▶️ 14 | - [ ] 15 | 16 | ## 📰 Related Issues 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---fix----.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F6A8[FIX] \U0001F6A8" 3 | about: "\U0001F4CC 수정할 기능, 버그 해결할 기능들에 대해 Task를 적어주세요 \U0001F62D" 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 📌 Fix Issue 11 | 12 | ## 📝 To-do 13 | ▶️ 14 | - [ ] 15 | 16 | ## 📰 Related Issues 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## ✨ 작업 내용 2 | - [ ] 여기에 작업 내용을 적어주세요 3 | 4 | 5 | ## 📸 스크린샷(선택) 6 | 7 | ## ⚙️ 관계된 이슈, PR : 8 | 9 | ## 📚 레퍼런스 (또는 새로 알게 된 내용) 혹은 궁금한 사항들 10 | 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## ✨ 작업 내용 2 | - [ ] 여기에 작업 내용을 적어주세요 3 | 4 | 5 | ## 📸 스크린샷(선택) 6 | 7 | ## ⚙️ 관계된 이슈, PR : 8 | 9 | ## 📚 레퍼런스 (또는 새로 알게 된 내용) 혹은 궁금한 사항들 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | fastlane/ 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | 92 | *.DS_Store 93 | *.csv 94 | *.docx 95 | *.pdf 96 | *.xlsx 97 | 98 | Pods/ 99 | 100 | BeMyPlan/BeMyPlan/GoogleService-Info.plist 101 | BeMyPlan/BeMyPlan/Info.plist 102 | BeMyPlan/BeMyPlan/Global/Network/Config.swift 103 | 104 | ### Xcode Patch ### 105 | *.xcodeproj/* 106 | !*.xcodeproj/project.pbxproj 107 | !*.xcodeproj/xcshareddata/ 108 | !*.xcworkspace/contents.xcworkspacedata 109 | **/xcshareddata/WorkspaceSettings.xcsettings 110 | 111 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS.xcodeproj/project.xcworkspace/xcuserdata/kone.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS.xcodeproj/project.xcworkspace/xcuserdata/kone.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS.xcodeproj/xcuserdata/kone.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | 30-SOPKATHON-iOS.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 33 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/21. 6 | // 7 | 8 | import UIKit 9 | import KakaoSDKCommon 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | UIFont.overrideInitialize() 19 | KakaoSDK.initSDK(appKey: "df520deba24c90ddb760281e934ec094") 20 | return true 21 | } 22 | 23 | // MARK: UISceneSession Lifecycle 24 | 25 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 26 | // Called when a new scene session is being created. 27 | // Use this method to select a configuration to create the new scene with. 28 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 29 | } 30 | 31 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 32 | // Called when the user discards a scene session. 33 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 34 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 35 | } 36 | 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Coordinator/Coordinators/BaseCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCoordinator.swift 3 | 4 | 5 | class BaseCoordinator: Coordinator { 6 | 7 | // MARK: - Vars & Lets 8 | var childCoordinators = [Coordinator]() 9 | 10 | // MARK: - Public methods 11 | func addDependency(_ coordinator: Coordinator) { 12 | for element in childCoordinators { 13 | if element === coordinator { return } 14 | } 15 | childCoordinators.append(coordinator) 16 | } 17 | 18 | func removeDependency(_ coordinator: Coordinator?) { 19 | guard childCoordinators.isEmpty == false, let coordinator = coordinator else { return } 20 | 21 | for (index, element) in childCoordinators.enumerated() { 22 | if element === coordinator { 23 | childCoordinators.remove(at: index) 24 | break 25 | } 26 | } 27 | } 28 | 29 | // MARK: - Coordinator 30 | func start() { 31 | start() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Coordinator/Enums/LaunchInstructor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LaunchInstructor.swift 3 | 4 | 5 | import Foundation 6 | 7 | enum LaunchInstructor { 8 | case signing 9 | case main 10 | 11 | static func configure(_ isAuthorized: Bool = false) -> LaunchInstructor { 12 | switch isAuthorized { 13 | case false: return .signing 14 | case true: return .main 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Coordinator/Factory/CoordinatorFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoordinatorFactory.swift 3 | 4 | import Foundation 5 | 6 | protocol CoordinatorFactoryProtocol { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Coordinator/Factory/ModuleFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModuleFactory.swift 3 | 4 | import Foundation 5 | 6 | 7 | protocol ModuleFactoryProtocol { 8 | func makeBaseVC() -> BaseVC 9 | func makeHomeVC() -> HomeVC 10 | func makeHomeNC() -> HomeNC 11 | func makeSoundKingVC() -> SoundKingVC 12 | func makeTouchKingVC() -> TouchKingVC 13 | func makeWritingVC() -> WritingVC 14 | func makeFeedVC() -> FeedVC 15 | func makeRankingVC() -> RankingVC 16 | func makeDecibelVC() -> DecibelVC 17 | func makeTabVC() -> TabVC 18 | func makeFeedCalendarNC() -> FeedCalendarNC 19 | func makefeedCalendarVC() -> FeedCalendarVC 20 | 21 | } 22 | 23 | final class ModuleFactory: ModuleFactoryProtocol{ 24 | 25 | 26 | 27 | static var shared = ModuleFactory() 28 | private init() { } 29 | 30 | func makeBaseVC() -> BaseVC { 31 | return BaseVC.controllerFromStoryboard(.base) 32 | } 33 | 34 | func makeHomeVC() -> HomeVC { 35 | return HomeVC.controllerFromStoryboard(.home) 36 | } 37 | 38 | func makeHomeNC() -> HomeNC { 39 | return HomeNC.controllerFromStoryboard(.home) 40 | } 41 | 42 | func makeSoundKingVC() -> SoundKingVC { 43 | return SoundKingVC.controllerFromStoryboard(.soundKing) 44 | } 45 | 46 | func makeTouchKingVC() -> TouchKingVC { 47 | return TouchKingVC.controllerFromStoryboard(.touchKing) 48 | } 49 | 50 | func makeWritingVC() -> WritingVC { 51 | return WritingVC.controllerFromStoryboard(.writing) 52 | } 53 | 54 | func makeFeedVC() -> FeedVC { 55 | return FeedVC.controllerFromStoryboard(.feed) 56 | } 57 | 58 | func makeRankingVC() -> RankingVC { 59 | return RankingVC.controllerFromStoryboard(.ranking) 60 | } 61 | 62 | func makeDecibelVC() -> DecibelVC { 63 | return DecibelVC.controllerFromStoryboard(.decibel) 64 | } 65 | 66 | func makeTabVC() -> TabVC { 67 | return TabVC.controllerFromStoryboard(.tab) 68 | } 69 | 70 | func makeFeedCalendarNC() -> FeedCalendarNC { 71 | return FeedCalendarNC.controllerFromStoryboard(.feedCalendar) 72 | } 73 | 74 | func makefeedCalendarVC() -> FeedCalendarVC { 75 | return FeedCalendarVC.controllerFromStoryboard(.feedCalendar) 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Coordinator/Protocols/BaseControllable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseControllable.swift 3 | 4 | 5 | import Foundation 6 | 7 | protocol BaseControllable: NSObjectProtocol, Presentable {} 8 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Coordinator/Protocols/Coordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coordinator.swift 3 | 4 | 5 | import Foundation 6 | 7 | protocol Coordinator: AnyObject { 8 | func start() 9 | } 10 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Coordinator/Protocols/CoordinatorFinishOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoordinatorFinishOutput.swift 3 | 4 | protocol CoordinatorFinishOutput { 5 | var finishScene: (() -> Void)? { get set } 6 | } 7 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Coordinator/Protocols/Presentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Presentable.swift 3 | 4 | 5 | import UIKit 6 | 7 | protocol Presentable { 8 | func toPresent() -> UIViewController? 9 | } 10 | 11 | extension UIViewController: Presentable { 12 | func toPresent() -> UIViewController? { 13 | return self 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Manager/Analytics/AnalyticsType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnalyticsType.swift 3 | 4 | 5 | import Foundation 6 | 7 | public protocol AnalyticsType { 8 | associatedtype Event: EventType 9 | func register(provider: ProviderType) 10 | func log(_ event: Event) 11 | } 12 | 13 | public protocol ProviderType { 14 | func log(_ eventName: String, parameters: [String: Any]?) 15 | } 16 | 17 | public protocol EventType { 18 | func name(for provider: ProviderType) -> String? 19 | func parameters(for provider: ProviderType) -> [String: Any]? 20 | } 21 | 22 | open class AnalyticsManager: AnalyticsType { 23 | private(set) open var providers: [ProviderType] = [] 24 | 25 | public init() {} 26 | 27 | open func register(provider: ProviderType) { 28 | self.providers.append(provider) 29 | } 30 | 31 | open func log(_ event: Event) { 32 | for provider in self.providers { 33 | guard let eventName = event.name(for: provider) else { continue } 34 | let parameters = event.parameters(for: provider) 35 | provider.log(eventName, parameters: parameters) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Manager/Analytics/AppLog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppLog.swift 3 | 4 | import Foundation 5 | import FirebaseCore 6 | import FirebaseAnalytics 7 | import FirebaseCrashlytics 8 | 9 | enum LogProviderType: String { 10 | case firebaseAnalyticsProvider = "FirebaseAnalyticsProvider" 11 | } 12 | 13 | class AppLog { 14 | 15 | private static var crashlyticsService: CrashlyticsServiceProtocol.Type = Crashlytics.self 16 | private static var configureService: FirebaseConfigureServiceProtocol.Type = FirebaseApp.self 17 | private static var analyticsService: FirebaseAnalyticsServiceProtocol.Type = Analytics.self 18 | private static let analytics = AnalyticsManager() 19 | 20 | static func configure() { 21 | self.configureService.configure() 22 | self.analytics.register(provider: FirebaseAnalyticsProvider()) 23 | } 24 | 25 | static func setFirebaseUserProperty() { 26 | let userIdx = String(UserDefaults.standard.integer(forKey: "userIdx")) 27 | analyticsService.setUserProperty(userIdx, forName: "userIdx") 28 | } 29 | 30 | static func log(at providerType: ProviderType.Type, _ event: LogEventType) { 31 | self.analytics.log(at: providerType, event) 32 | } 33 | } 34 | 35 | extension AnalyticsManager { 36 | func log(at providerType: ProviderType.Type, _ event: Event) { 37 | for provider in self.providers { 38 | guard type(of: provider.self) == providerType.self else { continue } 39 | guard let eventName = event.name(for: provider) else { continue } 40 | let parameters = event.parameters(for: provider) 41 | provider.log(eventName, parameters: parameters) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Manager/Analytics/EventType/LogEventType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogEventType.swift 3 | 4 | import Foundation 5 | 6 | enum LogEventType { 7 | // 1. 앱 최초 실행 8 | case appFirstOpen // 앱 최초 실행 9 | 10 | // 2. 온보딩 11 | case onboardingFirstOpen // 온보딩 최초 실행 12 | case onboardingViewSecondPage // 온보딩 두번째 페이지 보기 13 | case onboardingViewThirdPage // 온보딩 세번째 페이지 보기 14 | case onboardingSkip // 온보딩 스킵 15 | case onboardingComplete // 온보딩 완료 16 | 17 | // 3. 로그인 18 | case signin(source: LoginSource) // 애플로그인,카카오로그인,둘러보기 19 | 20 | // 4. 회원가입 21 | case signupNickname(source: LoginSource) // 닉네임 작성 완료 22 | case signupEmail(source: LoginSource) // 이메일 작성 완료 23 | case signupComplete(source: LoginSource) // 회원가입 완료 24 | 25 | // 5. 탭바 클릭 액션 O 26 | case clickTab(source: TabSource) // 각자 탭바 클릭 27 | 28 | } 29 | 30 | extension LogEventType: EventType { 31 | func name(for provider: ProviderType) -> String? { 32 | switch self { 33 | case .appFirstOpen: return "firebase_first_open" 34 | case .onboardingFirstOpen: return "onboarding_first_open" 35 | case .onboardingViewSecondPage: return "onboarding_view_second_page" 36 | case .onboardingViewThirdPage: return "onboarding_view_third_page" 37 | case .onboardingSkip: return "onboarding_skip" 38 | case .onboardingComplete: return "onboarding_complete" 39 | case .signin: return "signin_click" 40 | case .signupNickname: return "signup_nickname" 41 | case .signupEmail: return "signup_email" 42 | case .signupComplete: return "signup_complete" 43 | case .clickTab: return "click_tab" 44 | } 45 | } 46 | 47 | func parameters(for provider: ProviderType) -> [String : Any]? { 48 | var params: [String: Any] = [ 49 | "device": "iOS", 50 | "userIdx": String(UserDefaults.standard.integer(forKey: "userIdx")) 51 | ] 52 | switch self { 53 | case .signin(let loginSource), 54 | .signupNickname(let loginSource), 55 | .signupEmail(let loginSource), 56 | .signupComplete(let loginSource): 57 | params["loginSource"] = loginSource.rawValue 58 | 59 | case .clickTab(let tabSource): 60 | params["tabSource"] = tabSource.rawValue 61 | 62 | 63 | 64 | default: break 65 | } 66 | return params 67 | } 68 | } 69 | 70 | extension LogEventType { 71 | enum LoginSource: String { 72 | case kakao 73 | case apple 74 | case guest } 75 | 76 | enum TabSource: String { 77 | case home 78 | case travelSpot 79 | case scrap 80 | case myPlan 81 | } 82 | 83 | enum ViewSource: String { 84 | case homeView 85 | case scrapView 86 | case scrapRecommendView 87 | case travelListView 88 | case planListView 89 | case planPreview 90 | case planDetail 91 | case myPlanView 92 | } 93 | 94 | enum PaymentSource: String{ 95 | case kakaoPay 96 | case naverPay 97 | case toss 98 | } 99 | 100 | enum MapSource: String{ 101 | case kakaoMap 102 | case naverMap 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Manager/Analytics/FirebaseServiceProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseServiceProtocol.swift 3 | 4 | import Foundation 5 | import FirebaseCore 6 | import FirebaseAnalytics 7 | import FirebaseCrashlytics 8 | 9 | protocol FirebaseConfigureServiceProtocol: AnyObject { 10 | static func configure() 11 | } 12 | 13 | protocol FirebaseAnalyticsServiceProtocol: AnyObject { 14 | static func logEvent(_ name: String, parameters: [String: Any]?) 15 | static func setUserProperty(_ value: String?, forName name: String) 16 | static func setUserID(_ userID: String?) 17 | static func resetAnalyticsData() 18 | } 19 | 20 | protocol CrashlyticsServiceProtocol: AnyObject { 21 | static func crashlytics() -> Self 22 | } 23 | 24 | extension FirebaseApp: FirebaseConfigureServiceProtocol {} 25 | extension Analytics: FirebaseAnalyticsServiceProtocol {} 26 | extension Crashlytics: CrashlyticsServiceProtocol {} 27 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Manager/Analytics/Provider/FirebaseAnalyticsProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseAnalyticsProvider.swift 3 | 4 | import FirebaseCore 5 | import FirebaseAnalytics 6 | 7 | open class FirebaseAnalyticsProvider: RuntimeProviderType { 8 | public let className: String = "FIRAnalytics" 9 | public let selectorName: String = "logEventWithName:parameters:" 10 | 11 | public init() {} 12 | } 13 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Manager/Analytics/Provider/RuntimeProviderType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuntimeProviderType.swift 3 | 4 | 5 | import Foundation 6 | 7 | public protocol RuntimeProviderType: ProviderType { 8 | var className: String { get } 9 | var instanceSelectorName: String? { get } // optional 10 | var selectorName: String { get } 11 | } 12 | 13 | public extension RuntimeProviderType { 14 | var cls: NSObject.Type? { 15 | return NSClassFromString(self.className) as? NSObject.Type 16 | } 17 | 18 | var instanceSelectorName: String? { 19 | return nil 20 | } 21 | 22 | var instance: AnyObject? { 23 | guard let cls = self.cls else { return nil } 24 | guard let sel = self.instanceSelectorName.flatMap(NSSelectorFromString) else { return nil } 25 | guard cls.responds(to: sel) else { return nil } 26 | return cls.perform(sel)?.takeUnretainedValue() 27 | } 28 | 29 | var selector: Selector { 30 | return NSSelectorFromString(self.selectorName) 31 | } 32 | 33 | var responds: Bool { 34 | guard let cls = self.cls else { return false } 35 | if let instance = self.instance { 36 | return instance.responds(to: self.selector) 37 | } else { 38 | return cls.responds(to: self.selector) 39 | } 40 | } 41 | 42 | func log(_ eventName: String, parameters: [String: Any]?) { 43 | guard self.responds else { return } 44 | if let instance = self.instance { 45 | _ = instance.perform(self.selector, with: eventName, with: parameters) 46 | } else { 47 | _ = self.cls?.perform(self.selector, with: eventName, with: parameters) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/Manager/Notification/BaseNotiList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseNotiList.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import Foundation 9 | 10 | enum BaseNotiList : String{ 11 | case homeButtonClicked 12 | case feedButtonClicked 13 | case rankingButtonClicked 14 | case moveFeedDetail 15 | case moveSettingView 16 | case logout 17 | case writeComplete 18 | 19 | static func makeNotiName(list : BaseNotiList) -> NSNotification.Name{ 20 | return Notification.Name(String(describing: list)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Application/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import UIKit 9 | import KakaoSDKAuth 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let _ = (scene as? UIWindowScene) else { return } 17 | setRootScene(.splash) 18 | } 19 | 20 | private func setRootScene(_ storyboardType: Storyboards) { 21 | window!.rootViewController = UIStoryboard.list(storyboardType).instantiateInitialViewController()! 22 | } 23 | 24 | func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { 25 | if let url = URLContexts.first?.url { 26 | if (AuthApi.isKakaoTalkLoginUrl(url)) { 27 | _ = AuthController.handleOpenUrl(url: url) 28 | } 29 | } 30 | } 31 | 32 | func sceneDidDisconnect(_ scene: UIScene) { 33 | // Called as the scene is being released by the system. 34 | // This occurs shortly after the scene enters the background, or when its session is discarded. 35 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 36 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 37 | } 38 | 39 | func sceneDidBecomeActive(_ scene: UIScene) { 40 | // Called when the scene has moved from an inactive state to an active state. 41 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 42 | } 43 | 44 | func sceneWillResignActive(_ scene: UIScene) { 45 | // Called when the scene will move from an active state to an inactive state. 46 | // This may occur due to temporary interruptions (ex. an incoming phone call). 47 | } 48 | 49 | func sceneWillEnterForeground(_ scene: UIScene) { 50 | // Called as the scene transitions from the background to the foreground. 51 | // Use this method to undo the changes made on entering the background. 52 | } 53 | 54 | func sceneDidEnterBackground(_ scene: UIScene) { 55 | // Called as the scene transitions from the foreground to the background. 56 | // Use this method to save data, release shared resources, and store enough scene-specific state information 57 | // to restore the scene back to its current state. 58 | } 59 | 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Entity/DayDetailEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DayDetailEntity.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DayDetailEntity: Codable { 11 | let month: Int 12 | let topEmojiLevel: Int 13 | let records: [DayDetailListEntity] 14 | } 15 | 16 | 17 | struct DayDetailListEntity: Codable { 18 | let emojiLevel: Int 19 | let score: Int 20 | let comment: Int 21 | } 22 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Entity/Feed/FeedDataModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeedDataModel.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by Juhyeon Byun on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | struct FeedDataModel { 11 | let gaugeImageName: String 12 | var gaugeImage: UIImage? { 13 | return UIImage(named: gaugeImageName) 14 | } 15 | let gauge: Int 16 | let reason: String 17 | } 18 | 19 | extension FeedDataModel { 20 | static let sampleData: [FeedDataModel] = [ 21 | FeedDataModel(gaugeImageName: "angry_icon_4", gauge: 3, reason: "늦은 밤에 시끄러운 자식 너무 짜증나잖아!!!! \n화가난다!!!!!!!!!!!!!!"), 22 | FeedDataModel(gaugeImageName: "angry_icon_4", gauge: 3, reason: "야식 먹어서 폭주했어요.."), 23 | FeedDataModel(gaugeImageName: "angry_icon_4", gauge: 2, reason: "지하철 아침에 놓침"), 24 | FeedDataModel(gaugeImageName: "angry_icon_4", gauge: 3, reason: "버스 아침에 놓쳤어요..."), 25 | FeedDataModel(gaugeImageName: "angry_icon_4", gauge: 3, reason: "떡볶이를 엎었어요.."), 26 | FeedDataModel(gaugeImageName: "angry_icon_4", gauge: 3, reason: "잠이 와요..") 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Entity/MonthListEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MonthListEntity.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct MonthListEntity: Codable { 11 | let month: Int 12 | let topEmojiLevel: Int 13 | let days: [DayListEntity] 14 | } 15 | 16 | 17 | struct DayListEntity: Codable { 18 | let day: Int 19 | let emojiLevel: Int 20 | } 21 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Entity/RankingEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RankingEntityu.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct RankingEntity: Codable { 11 | let myRank: Int 12 | let myEmojiLevel: Int 13 | let myScore: Int 14 | let topRankers: [UserRankEntity] 15 | let rankers: [UserRankEntity] 16 | 17 | } 18 | 19 | struct UserRankEntity: Codable { 20 | let userName: String 21 | let emojiLevel: Int 22 | let score: Int 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Entity/Sample/SampleEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleEntity.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SampleEntity { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Network/BaseAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseAPI.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import Moya 9 | import Alamofire 10 | 11 | enum BaseAPI{ 12 | case sampleAPI 13 | case postGameResult(type: String, score: Int, comment: String, emojiLevel: Int) 14 | case getMyRecordInMonth 15 | case getMyRecordInDay 16 | case getRanking(type: String) 17 | 18 | } 19 | 20 | extension BaseAPI: TargetType { 21 | // MARK: - Base URL & Path 22 | /// - Parameters: 23 | 24 | /// 25 | public var baseURL: URL { 26 | var base = Config.Network.baseURL 27 | switch self{ 28 | case .postGameResult: 29 | base += "/game" 30 | 31 | case .getMyRecordInMonth: 32 | base += "/myrecord/month" 33 | 34 | case .getMyRecordInDay: 35 | base += "/myrecord/date" 36 | 37 | case .getRanking: 38 | base += "/game/ranking" 39 | 40 | case .sampleAPI: 41 | base += "/" 42 | } 43 | guard let url = URL(string: base) else { 44 | fatalError("baseURL could not be configured") 45 | } 46 | return url 47 | } 48 | 49 | // MARK: - Path 50 | /// - note : 51 | /// path에 필요한 parameter를 넣어야 되는 경우, 52 | /// enum을 정의했을때 적은 파라미터가 53 | /// .case이름(let 변수이름): 54 | /// 형태로 작성하면 변수를 받아올 수 있습니다. 55 | /// 56 | var path: String { 57 | switch self{ 58 | 59 | default : 60 | return "" 61 | } 62 | } 63 | 64 | // MARK: - Method 65 | /// - note : 66 | /// 각 case 별로 get,post,delete,put 인지 정의합니다. 67 | var method: Moya.Method { 68 | switch self{ 69 | case .postGameResult: 70 | return .post 71 | default : 72 | return .get 73 | 74 | } 75 | } 76 | 77 | // MARK: - Data 78 | var sampleData: Data { 79 | return Data() 80 | } 81 | 82 | // MARK: - Parameters 83 | /// - note : 84 | /// post를 할때, body Parameter를 담아서 전송해야하는 경우가 있는데, 85 | /// 이 경우에 사용하는 부분입니다. 86 | /// 87 | /// (get에서는 사용 ❌, get의 경우에는 쿼리로) 88 | /// 89 | private var bodyParameters: Parameters? { 90 | var params: Parameters = [:] 91 | switch self{ 92 | 93 | 94 | case .postGameResult(let type, let score, let comment, let emojiLevel) : 95 | params["type"] = type 96 | params["score"] = score 97 | params["comment"] = comment 98 | params["emojiLevel"] = emojiLevel 99 | 100 | case .getMyRecordInMonth: 101 | params["month"] = 5 102 | 103 | case .getMyRecordInDay: 104 | params["month"] = 5 105 | params["day"] = 9 106 | 107 | case .getRanking(let type) : 108 | params["type"] = type 109 | 110 | default: 111 | break 112 | 113 | } 114 | return params 115 | } 116 | 117 | // MARK: - MultiParts 118 | 119 | /// - note : 120 | /// 사진등을 업로드 할때 사용하는 multiparts 부분이라 따로 사용 X 121 | /// 122 | private var multiparts: [Moya.MultipartFormData] { 123 | switch self{ 124 | 125 | default : return [] 126 | } 127 | } 128 | 129 | /// - note : 130 | /// query문을 사용하는 경우 URLEncoding 을 사용해야 합니다 131 | /// 나머지는 그냥 전부 다 default 처리. 132 | /// 133 | private var parameterEncoding : ParameterEncoding{ 134 | switch self { 135 | case .sampleAPI,.getMyRecordInMonth, .getMyRecordInDay, .getRanking: 136 | return URLEncoding.init(destination: .queryString, arrayEncoding: .noBrackets, boolEncoding: .literal) 137 | default : 138 | return JSONEncoding.default 139 | 140 | } 141 | } 142 | 143 | /// - note : 144 | /// body Parameters가 있는 경우 requestParameters case 처리. 145 | /// 일반적인 처리는 모두 requestPlain으로 사용. 146 | /// 147 | var task: Task { 148 | switch self{ 149 | case .sampleAPI,.postGameResult, .getMyRecordInMonth, .getMyRecordInDay, .getRanking: 150 | return .requestParameters(parameters: bodyParameters ?? [:], encoding: parameterEncoding) 151 | default: 152 | return .requestPlain 153 | 154 | } 155 | } 156 | 157 | public var headers: [String: String]? { 158 | if let userToken = UserDefaults.standard.string(forKey: "userToken") { 159 | return ["Authorization": userToken, 160 | "Content-Type": "application/json"] 161 | } else { 162 | return ["Content-Type": "application/json"] 163 | } 164 | } 165 | 166 | public var validationType: ValidationType { 167 | return .successCodes 168 | } 169 | 170 | typealias Response = Codable 171 | } 172 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Network/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Config 11 | { 12 | enum Network { 13 | static var baseURL: String { 14 | return "3.37.36.153:8000" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Network/FeedService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeedService.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | protocol FeedServiceType { 12 | func getMonthData(month: Int,completion: @escaping (Result) -> Void) 13 | func getDayData(month: Int,day: Int,completion: @escaping (Result) -> Void) 14 | } 15 | 16 | extension BaseService: FeedServiceType { 17 | func getMonthData(month: Int,completion: @escaping (Result) -> Void) { 18 | requestObject(.getMyRecordInMonth, completion: completion) 19 | } 20 | 21 | func getDayData(month: Int,day: Int,completion: @escaping (Result) -> Void) { 22 | requestObject(.getMyRecordInDay, completion: completion) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Network/RankService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RankService.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol RankServiceType { 11 | func getRankList(type: String,completion: @escaping (Result) -> Void) 12 | } 13 | 14 | extension BaseService: RankServiceType { 15 | func getRankList(type: String,completion: @escaping (Result) -> Void) { 16 | requestObject(.getRanking(type: type), completion: completion) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Network/ResponseObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResponseObject.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ResponseObject { 11 | let status: Int? 12 | let success: Bool? 13 | let message: String? 14 | let data: T? 15 | 16 | enum CodingKeys: String, CodingKey { 17 | case status 18 | case success 19 | case message 20 | case data 21 | } 22 | } 23 | 24 | extension ResponseObject: Decodable where T: Decodable { 25 | init(from decoder: Decoder) throws { 26 | let container = try decoder.container(keyedBy: CodingKeys.self) 27 | status = try? container.decode(Int.self, forKey: .status) 28 | success = try? container.decode(Bool.self, forKey: .success) 29 | message = try? container.decode(String.self, forKey: .message) 30 | data = try? container.decode(T.self, forKey: .data) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Network/Service/BaseService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseService.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import Moya 9 | import Alamofire 10 | import RxSwift 11 | 12 | fileprivate let provider: MoyaProvider = { 13 | let provider = MoyaProvider(endpointClosure: endpointClosure, session: DefaultAlamofireManager.shared) 14 | return provider 15 | }() 16 | 17 | fileprivate let endpointClosure = { (target: BaseAPI) -> Endpoint in 18 | let url = target.baseURL.appendingPathComponent(target.path).absoluteString 19 | var endpoint: Endpoint = Endpoint(url: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, task: target.task, httpHeaderFields: target.headers) 20 | return endpoint 21 | } 22 | 23 | fileprivate class DefaultAlamofireManager: Alamofire.Session { 24 | static let shared: DefaultAlamofireManager = { 25 | let configuration = URLSessionConfiguration.default 26 | configuration.timeoutIntervalForRequest = 10 27 | configuration.timeoutIntervalForResource = 10 28 | configuration.requestCachePolicy = .reloadIgnoringLocalCacheData 29 | 30 | return DefaultAlamofireManager(configuration: configuration) 31 | }() 32 | } 33 | 34 | class BaseService{ 35 | static let `default` = BaseService() 36 | var disposeBag = DisposeBag() 37 | private init() {} 38 | 39 | 40 | func requestObjectInRx(_ target: BaseAPI) -> Observable{ 41 | return Observable.create { observer in 42 | provider.rx 43 | .request(target) 44 | .subscribe { event in 45 | switch event { 46 | case .success(let value): 47 | do { 48 | let decoder = JSONDecoder() 49 | let body = try decoder.decode(ResponseObject.self, from: value.data) 50 | observer.onNext(body.data) 51 | observer.onCompleted() 52 | } catch let error { 53 | observer.onError(error) 54 | } 55 | case .failure(let error): 56 | observer.onError(error) 57 | } 58 | }.disposed(by: self.disposeBag) 59 | return Disposables.create() 60 | } 61 | } 62 | 63 | func requestObject(_ target: BaseAPI, completion: @escaping (Result) -> Void) { 64 | provider.request(target) { response in 65 | 66 | switch response { 67 | case .success(let value): 68 | do { 69 | let decoder = JSONDecoder() 70 | let body = try decoder.decode(ResponseObject.self, from: value.data) 71 | completion(.success(body.data)) 72 | } catch let error { 73 | completion(.failure(error)) 74 | } 75 | case .failure(let error): 76 | switch error { 77 | case .underlying(let error, _): 78 | if error.asAFError?.isSessionTaskError ?? false { 79 | 80 | } 81 | default: break 82 | } 83 | completion(.failure(error)) 84 | } 85 | } 86 | } 87 | 88 | func requestArray(_ target: BaseAPI, completion: @escaping (Result<[T], Error>) -> Void) { 89 | provider.request(target) { response in 90 | switch response { 91 | case .success(let value): 92 | do { 93 | let decoder = JSONDecoder() 94 | let body = try decoder.decode(ResponseObject<[T]>.self, from: value.data) 95 | completion(.success(body.data ?? [])) 96 | } catch let error { 97 | completion(.failure(error)) 98 | } 99 | case .failure(let error): 100 | switch error { 101 | case .underlying(let error, _): 102 | if error.asAFError?.isSessionTaskError ?? false { 103 | 104 | } 105 | default: break 106 | } 107 | completion(.failure(error)) 108 | } 109 | } 110 | } 111 | 112 | func requestObjectWithNoResult(_ target: BaseAPI, completion: @escaping (Result) -> Void) { 113 | provider.request(target) { response in 114 | switch response { 115 | case .success(let value): 116 | 117 | completion(.success(value.statusCode)) 118 | 119 | case .failure(let error): 120 | switch error { 121 | case .underlying(let error, _): 122 | if error.asAFError?.isSessionTaskError ?? false { 123 | 124 | } 125 | default: break 126 | } 127 | completion(.failure(error)) 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Network/WriteCommentService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RankService.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol WriteCommentServiceType { 11 | func writeRankList(type: String,score: Int, comment: String, emojiLevel: Int, completion: @escaping (Result) -> Void) 12 | } 13 | 14 | extension BaseService: WriteCommentServiceType { 15 | func writeRankList(type: String,score: Int, comment: String, emojiLevel: Int, completion: @escaping (Result) -> Void) { 16 | requestObject(.postGameResult(type: type, 17 | score: score, 18 | comment: comment, 19 | emojiLevel: emojiLevel), completion: completion) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Data/Repository/Sample/SampleRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleRepository.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import RxSwift 9 | 10 | protocol SampleRepository { 11 | 12 | } 13 | 14 | final class DefaultSampleRepository { 15 | 16 | private let disposeBag = DisposeBag() 17 | 18 | init() { 19 | 20 | } 21 | } 22 | 23 | extension DefaultSampleRepository: SampleRepository { 24 | 25 | } 26 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Domain/Model/Sample/SampleModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleModel.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SampleModel { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Domain/Usecase/Sample/SampleUsecase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleUsecase.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import RxSwift 9 | 10 | protocol SampleUseCase { 11 | 12 | } 13 | 14 | final class DefaultSampleUseCase { 15 | 16 | private let repository: SampleRepository 17 | private let disposeBag = DisposeBag() 18 | 19 | init(repository: SampleRepository) { 20 | self.repository = repository 21 | } 22 | } 23 | 24 | extension DefaultSampleUseCase: SampleUseCase { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Extension/UINavigation + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigation + Extension.swift 3 | 4 | import UIKit 5 | 6 | public extension UINavigationController { 7 | func fixInteractivePopGestureRecognizer(delegate: UIGestureRecognizerDelegate) { 8 | guard 9 | let popGestureRecognizer = interactivePopGestureRecognizer, 10 | let targets = popGestureRecognizer.value(forKey: "targets") as? NSMutableArray, 11 | let gestureRecognizers = view.gestureRecognizers, 12 | targets.count > 0 13 | else { return } 14 | 15 | /// 기존 Gesture Recognizer에서 16 | /// 현재 Navigation Controller가 쥐고있는 17 | /// gestureRecognizer와 popGestureRecognizer를 들고옵니다. 18 | 19 | /// VC가 1개인 경우는 첫번째 VC를 의미하기 때문에, 기존에 popDirectionGesture를 쥐고있다면, nil로 해제하는 방식으로 처리 20 | 21 | if viewControllers.count == 1 { 22 | for recognizer in gestureRecognizers where recognizer is PanDirectionGestureRecognizer { 23 | view.removeGestureRecognizer(recognizer) 24 | popGestureRecognizer.isEnabled = false 25 | recognizer.delegate = nil 26 | } 27 | } else { 28 | /// gesture -> edgePanSwipeGesture 1개만 있는 경우에 29 | /// 기존 popGesture를 fail 시키고 30 | /// 새로 PanDirectionGestureRecognizer를 등록하는 식으로 처리 31 | /// 1번 등록하게 되면 그 이후에는 계속해서 사용하게 됨 32 | if gestureRecognizers.count == 1 { 33 | let gestureRecognizer = PanDirectionGestureRecognizer(axis: .horizontal, direction: .right) 34 | gestureRecognizer.cancelsTouchesInView = false 35 | gestureRecognizer.setValue(targets, forKey: "targets") 36 | gestureRecognizer.require(toFail: popGestureRecognizer) 37 | gestureRecognizer.delegate = delegate 38 | popGestureRecognizer.isEnabled = true 39 | 40 | view.addGestureRecognizer(gestureRecognizer) 41 | } 42 | } 43 | } 44 | 45 | func removePopGesture(){ 46 | view.gestureRecognizers?.removeAll() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Extension/addSubViewFromNib.swift: -------------------------------------------------------------------------------- 1 | // 2 | // addSubViewFromNib.swift 3 | 4 | 5 | import UIKit 6 | extension UIView{ 7 | public func addSubviewFromNib(view : UIView){ 8 | let view = Bundle.main.loadNibNamed(view.className, owner: self, options: nil)?.first as! UIView 9 | view.frame = bounds 10 | view.clipsToBounds = true 11 | addSubview(view) 12 | } 13 | } 14 | 15 | class XibView : UIView{ 16 | override init(frame: CGRect) { 17 | super.init(frame: frame) 18 | addSubviewFromNib(view: self) 19 | } 20 | 21 | required init?(coder aDecoder: NSCoder) { 22 | super.init(coder: aDecoder) 23 | addSubviewFromNib(view: self) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Extension/controllerFromStoryboard + UIViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // controllerFromStoryboard + UIViewController.swift 3 | 4 | 5 | import UIKit 6 | 7 | /** 8 | 9 | - Description: 10 | 11 | Module Factory 형태에서 VC들을 간편하게 선언하기 위해 만든 extension 입니다. 12 | 1) 스토리보드를 enum 형으로 안전하게 선언해서, 13 | 2) 자체 className을 활용해 identifier을 선언하고, 14 | 3) instantitateVieController 기본 메서드를 활용해 VC 인스턴스를 생성합니다. 15 | 16 | 다음 메서드는 ModuleFactory에서 사용됩니다. 17 | 18 | */ 19 | 20 | extension UIViewController { 21 | 22 | private class func instantiateControllerInStoryboard(_ storyboard: UIStoryboard, identifier: String) -> T { 23 | return storyboard.instantiateViewController(withIdentifier: identifier) as! T 24 | } 25 | 26 | class func controllerInStoryboard(_ storyboard: UIStoryboard, identifier: String) -> Self { 27 | return instantiateControllerInStoryboard(storyboard, identifier: identifier) 28 | } 29 | 30 | class func controllerInStoryboard(_ storyboard: UIStoryboard) -> Self { 31 | return controllerInStoryboard(storyboard, identifier: className) 32 | } 33 | 34 | class func controllerFromStoryboard(_ storyboard: Storyboards) -> Self { 35 | return controllerInStoryboard(UIStoryboard(name: storyboard.rawValue, bundle: nil), identifier: className) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Extension/drawDashedLine + UIView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // drawDotLine + UIView.swift 3 | 4 | import UIKit 5 | 6 | extension UIView { 7 | func createDashedLine(from point1: CGPoint, to point2: CGPoint, color: UIColor, strokeLength: NSNumber, gapLength: NSNumber, width: CGFloat) { 8 | let shapeLayer = CAShapeLayer() 9 | 10 | shapeLayer.strokeColor = color.cgColor 11 | shapeLayer.lineWidth = width 12 | shapeLayer.lineDashPattern = [strokeLength, gapLength] 13 | let path = CGMutablePath() 14 | path.addLines(between: [point1, point2]) 15 | shapeLayer.path = path 16 | layer.addSublayer(shapeLayer) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Extension/setTextLineHeight + UILabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // setTextLineHeight + UILabel.swift 3 | 4 | import UIKit 5 | 6 | extension UILabel { 7 | func setTextWithLineHeight(text: String?, lineHeight: CGFloat) { 8 | if let text = text { 9 | let style = NSMutableParagraphStyle() 10 | style.maximumLineHeight = lineHeight 11 | style.minimumLineHeight = lineHeight 12 | 13 | let attributes: [NSAttributedString.Key: Any] = [ 14 | .paragraphStyle: style 15 | ] 16 | 17 | let attrString = NSAttributedString(string: text, 18 | attributes: attributes) 19 | self.attributedText = attrString 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Extension/successCatch + Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // successCatch+ Result.swift 3 | 4 | import Foundation 5 | 6 | extension Result { 7 | @discardableResult 8 | func success(_ successHandler: (Success) -> Void) -> Result { 9 | if case .success(let value) = self { 10 | successHandler(value) 11 | } 12 | return self 13 | } 14 | 15 | @discardableResult 16 | func `catch`(_ failureHandler: (Failure) -> Void) -> Result { 17 | if case .failure(let error) = self { 18 | failureHandler(error) 19 | } 20 | return self 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Literals/ColorLiterals.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorLiterals.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIColor { 11 | @nonobjc class var grey00: UIColor { 12 | return UIColor(red: 248.0 / 255.0, green: 248.0 / 255.0, blue: 248.0 / 255.0, alpha: 1.0) 13 | } 14 | 15 | @nonobjc class var grey01: UIColor { 16 | return UIColor(red: 214.0 / 255.0, green: 217.0 / 255.0, blue: 225.0 / 255.0, alpha: 1.0) 17 | } 18 | 19 | @nonobjc class var grey02: UIColor { 20 | return UIColor(red: 186.0 / 255.0, green: 186.0 / 255.0, blue: 186.0 / 255.0, alpha: 1.0) 21 | } 22 | 23 | @nonobjc class var grey03: UIColor { 24 | return UIColor(red: 117.0 / 255.0, green: 117.0 / 255.0, blue: 117.0 / 255.0, alpha: 1.0) 25 | } 26 | 27 | @nonobjc class var grey04: UIColor { 28 | return UIColor(red: 85.0 / 255.0, green: 85.0 / 255.0, blue: 85.0 / 255.0, alpha: 1.0) 29 | } 30 | 31 | @nonobjc class var grey05: UIColor { 32 | return UIColor(red: 41.0 / 255.0, green: 41.0 / 255.0, blue: 41.0 / 255.0, alpha: 1.0) 33 | } 34 | 35 | /// grey02 - 03 사이의 grey 색상 36 | @nonobjc class var grey06: UIColor { 37 | return UIColor(red: 147.0 / 255.0, green: 147.0 / 255.0, blue: 147.0 / 255.0, alpha: 1.0) 38 | } 39 | 40 | /// grey01 - 02 사이의 grey 색상 41 | @nonobjc class var grey07: UIColor { 42 | return UIColor(red: 229.0 / 255.0, green: 229.0 / 255.0, blue: 229.0 / 255.0, alpha: 1.0) 43 | } 44 | 45 | /// grey03 - 04 사이의 grey 색상 46 | @nonobjc class var grey08: UIColor { 47 | return UIColor(red: 79.0 / 255.0, green: 79.0 / 255.0, blue: 79.0 / 255.0, alpha: 1.0) 48 | } 49 | 50 | /// grey01 - 02 사이의 grey 색상 51 | @nonobjc class var grey09: UIColor { 52 | return UIColor(red: 200.0 / 255.0, green: 200.0 / 255.0, blue: 200.0 / 255.0, alpha: 1.0) 53 | } 54 | 55 | // writeCheckVC의 userNameLabel에 사용 56 | @nonobjc class var grey10: UIColor { 57 | return UIColor(red: 101.0 / 255.0, green: 101.0 / 255.0, blue: 101.0 / 255.0, alpha: 1.0) 58 | } 59 | 60 | // writeCheckVC의 dateLabel에 사용 61 | @nonobjc class var grey11: UIColor { 62 | return UIColor(red: 186.0 / 255.0, green: 186.0 / 255.0, blue: 186.0 / 255.0, alpha: 1.0) 63 | } 64 | 65 | // alert divideLineView 66 | @nonobjc class var grey12: UIColor { 67 | return UIColor(red: 236.0 / 255.0, green: 236.0 / 255.0, blue: 236.0 / 255.0, alpha: 1.0) 68 | } 69 | 70 | // filter resetButton 71 | @nonobjc class var grey13: UIColor { 72 | return UIColor(red: 131.0 / 255.0, green: 131.0 / 255.0, blue: 131.0 / 255.0, alpha: 1.0) 73 | } 74 | 75 | // categoryLabel 76 | @nonobjc class var grey14: UIColor { 77 | return UIColor(red: 146.0 / 255.0, green: 146.0 / 255.0, blue: 146.0 / 255.0, alpha: 1.0) 78 | } 79 | 80 | @nonobjc class var grey15: UIColor { 81 | return UIColor(red: 250.0 / 255.0, green: 250.0 / 255.0, blue: 250.0 / 255.0, alpha: 1.0) 82 | } 83 | 84 | @nonobjc class var yellow: UIColor { 85 | return UIColor(red: 255.0 / 255.0, green: 202.0 / 255.0, blue: 122.0 / 255.0, alpha: 1.0) 86 | } 87 | 88 | @nonobjc class var mainBlue: UIColor { 89 | return UIColor(red: 121.0 / 255.0, green: 165.0 / 255.0, blue: 251.0 / 255.0, alpha: 1.0) 90 | } 91 | 92 | @nonobjc class var mainBlue_60: UIColor { 93 | return UIColor(red: 121.0 / 255.0, green: 165.0 / 255.0, blue: 251.0 / 255.0, alpha: 0.6) 94 | } 95 | 96 | @nonobjc class var subBlue: UIColor { 97 | return UIColor(red: 243.0 / 255.0, green: 247.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0) 98 | } 99 | 100 | @nonobjc class var pointBlue: UIColor { 101 | return UIColor(red: 163.0 / 255.0, green: 194.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0) 102 | } 103 | 104 | @nonobjc class var bgBlue: UIColor { 105 | return UIColor(red: 244.0 / 255.0, green: 248.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0) 106 | } 107 | 108 | @nonobjc class var alertRed: UIColor { 109 | return UIColor(red: 210.0 / 255.0, green: 57.0 / 255.0, blue: 57.0 / 255.0, alpha: 1.0) 110 | } 111 | 112 | @nonobjc class var logoutRed: UIColor { 113 | return UIColor(red: 251.0 / 255.0 , green: 121.0 / 255.0, blue: 121.0 / 255.0, alpha: 1.0) 114 | } 115 | 116 | @nonobjc class var enabledTabbarColor: UIColor { 117 | return UIColor(red: 111.0/255.0, green: 116.0/255.0, blue: 135.0/255.0, alpha: 1) 118 | } 119 | 120 | @nonobjc class var disabledTabbarColor: UIColor { 121 | return UIColor(red: 214.0/255.0, green: 217.0/255.0, blue: 225.0/255.0, alpha: 1) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Literals/Image/ImageLiterals.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLiterals.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import UIKit 9 | 10 | 11 | 12 | struct ImageLiterals{ 13 | 14 | struct TabBar{ 15 | 16 | static let home = UIImage(named: "ic_home")! 17 | static let homeSelected = UIImage(named: "tab-mypage-clicked")! 18 | 19 | static let feed = UIImage(named: "ic_feed")! 20 | static let feedSelected = UIImage(named: "tab-home-clicked")! 21 | 22 | static let ranking = UIImage(named: "ic_rank")! 23 | static let rankingSelected = UIImage(named: "tab-mypage-clicked")! 24 | } 25 | 26 | struct MainIcon { 27 | static let angryIcon0 = UIImage(named: "angry_icon_0")! 28 | static let angryIcon1 = UIImage(named: "angry_icon_1")! 29 | static let angryIcon2 = UIImage(named: "angry_icon_2")! 30 | static let angryIcon3 = UIImage(named: "angry_icon_3")! 31 | static let angryIcon4 = UIImage(named: "angry_icon_4")! 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Literals/StoryboardLiterals.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoryboardLiterals.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import UIKit 9 | 10 | enum Storyboards: String { 11 | case splash = "Splash" 12 | case base = "Base" 13 | case home = "Home" 14 | case soundKing = "SoundKing" 15 | case touchKing = "TouchKing" 16 | case writing = "Writing" 17 | case feed = "Feed" 18 | case feedCalendar = "FeedCalendar" 19 | case ranking = "Ranking" 20 | case decibel = "Decibel" 21 | case tab = "Tab" 22 | } 23 | 24 | extension UIStoryboard{ 25 | static func list(_ name : Storyboards) -> UIStoryboard{ 26 | return UIStoryboard(name: name.rawValue, bundle: nil) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Literals/String/StringLiterals.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringLiterals.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import Foundation 9 | 10 | struct I18N { 11 | 12 | struct Alert { 13 | static let error = "에러" 14 | static let networkError = "네트워크 오류가 발생하였습니다." 15 | } 16 | 17 | struct Component { 18 | static let startButton = "시작하기" 19 | } 20 | 21 | struct Tabbar { 22 | static let home = "홈" 23 | static let feed = "피드" 24 | static let ranking = "랭킹" 25 | } 26 | 27 | struct Onboarding { 28 | static let skip = "건너뛰기" 29 | static let next = "다음" 30 | static let start = "시작하기" 31 | 32 | static let headerFirst = "타인의 독서 기록을\n통한 짧은 독서" 33 | static let headerSecond = "간편한 독서 기록" 34 | static let headerThird = "나만의 공간에 보관하기" 35 | static let descriptionFirst = "다른 사람들이 어떤 책을 읽고,\n무엇을 느꼈는지 한 눈에 보아요" 36 | static let descriptionSecond = "책에서 인상 깊은 문장과\n느낀 점만 작성하면 돼요" 37 | static let descriptionThird = "독서 당시의 울림을 오래 소장해 보아요" 38 | } 39 | 40 | struct Login { 41 | static let guideText = "간단한 회원가입으로 리드미를 시작해볼까요?" 42 | static let guideEmphasisText = "간단한 회원가입" 43 | static let kakao = "카카오" 44 | static let apple = "애플" 45 | static let loginFailMessage = " 로그인에 실패하였습니다." 46 | } 47 | 48 | struct Signup { 49 | static let title = "닉네임을 알려주세요" 50 | static let subtitle = "설정된 닉네임은 수정이 불가합니다." 51 | static let textfieldPlacehodler = "특수문자는 사용할 수 없습니다" 52 | static let nicknameDuplicatedErr = "중복된 닉네임입니다." 53 | static let characterErr = "특수문자를 사용할 수 없습니다." 54 | static let byteExceedErr = "닉네임은 20자를 초과할 수 없습니다." 55 | static let availableNickname = "사용 가능한 닉네임입니다." 56 | } 57 | 58 | struct Search { 59 | static let textfieldPlaceholder = "책 제목을 검색하세요" 60 | static let recentRead = "최근에 읽었어요" 61 | static let emptyBeforeSearch = "책을 검색하고 읽은 책을 추가해보세요" 62 | static let emptyAfterSearch = "책 검색 결과가 없습니다" 63 | } 64 | 65 | struct FeedList { 66 | static let categoryNoSelect = "관심 있는 카테고리를" 67 | static let categoryNoSelectBold = "관심 있는 카테고리" 68 | static let categoryDescriptionNoselect = "선택해보세요" 69 | static let categoryDescription = "관심이 있어요" 70 | static let emptyTopDescription = "관련 카테고리 글이 아직 없어요." 71 | static let emptyBottomDescription = "다른 카테고리를 둘러보는 것은 어떨까요?" 72 | } 73 | 74 | struct MyPage { 75 | static let nicknameDescription = " 님의 책장" 76 | static let count = "개" 77 | static let total = "총 " 78 | static let countDescription = "의 글이 있어요" 79 | static let emptyTopDescription = "작성한 글이 없어요" 80 | static let emptyBottomDescription = "아래 + 버튼을 눌러 글을 추가해 보세요" 81 | } 82 | 83 | struct Setting { 84 | static let settingTitle = "환경설정" 85 | static let contact = "문의하기" 86 | static let agreement = "약관 보기" 87 | static let logout = "로그아웃" 88 | } 89 | 90 | struct Write { 91 | static let startCheer = " 님의 시작을 응원해요!" 92 | static let heartCheer = " 님의 마음을 응원해요!" 93 | static let startDescribe = "기록은 독서의 시작입니다. 책을 끝까지 읽지 못했더라도 읽는 도중 인상 깊었던 문장을 적어보세요." 94 | static let heartDescribe = "글을 눈으로 읽어내려가는 것에만 그친다면 진정한 독서라고 할 수 없죠. 인상 깊었던 문장에 대한 느낀점도 남겨볼까요?" 95 | static let categoryTitle = "책의 카테고리를 선택해주세요" 96 | static let quoteTitle = "인상 깊었던 문장을 적어주세요" 97 | static let quotePlaceholder = "어떤 문장이 인상 깊었나요?" 98 | static let impressionTitle = "느낀점을 적어주세요" 99 | static let impressionPlaceholder = "책을 읽었을 때 무엇을 느끼고 배우셨나요?" 100 | static let selectedBook = "내가 선택한 책" 101 | static let interestedSentence = "에서 인상 깊었던 문장" 102 | } 103 | 104 | struct WriteCheck { 105 | static let titleThrough = "기록을 통해 " 106 | static let titleDay = " 님의 하루가\n 더 풍요로워졌길 바라요!" 107 | } 108 | 109 | struct WriteComplete { 110 | static let title = "등록을 완료했어요" 111 | static let subtitle = "피드 와 마이페이지 에서\n작성한 감상글을 확인할 수 있어요" 112 | static let move = "피드 구경하러 가기" 113 | } 114 | 115 | struct Filter { 116 | 117 | } 118 | 119 | struct Button { 120 | static let next = "다음" 121 | static let register = "등록하기" 122 | static let reset = " 필터 초기화" 123 | static let apply = "적용" 124 | static let report = "신고하기" 125 | static let delete = "삭제하기" 126 | } 127 | 128 | struct ReadmeAlert { 129 | static let title = "기록한 내용을 버리고\n돌아가시겠습니까?" 130 | static let description = "작성한 내용이 저장되지 않습니다" 131 | static let cancel = "취소" 132 | static let ok = "확인" 133 | } 134 | 135 | struct NavigationBar { 136 | static let search = "책 선택하기" 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Protocols/UICollectionViewRegisterable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewRegisterable.swift 3 | 4 | 5 | import UIKit 6 | 7 | protocol UICollectionViewRegisterable { 8 | static var isFromNib: Bool { get } 9 | static func register(target: UICollectionView) 10 | } 11 | 12 | extension UICollectionViewRegisterable where Self: UICollectionViewCell { 13 | static func register(target: UICollectionView) { 14 | if self.isFromNib { 15 | target.register(UINib(nibName: Self.className, bundle: nil), forCellWithReuseIdentifier: Self.className) 16 | } else { 17 | target.register(Self.self, forCellWithReuseIdentifier: Self.className) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Protocols/UITableViewRegisterable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableViewRegisterable.swift 3 | 4 | import UIKit 5 | 6 | protocol UITableViewRegisterable { 7 | static var isFromNib: Bool { get } 8 | static func register(target: UITableView) 9 | } 10 | 11 | extension UITableViewRegisterable where Self: UITableViewCell { 12 | static func register(target: UITableView) { 13 | if self.isFromNib { 14 | target.register(UINib(nibName: Self.className, bundle: nil), forCellReuseIdentifier: Self.className) 15 | } else { 16 | target.register(Self.self, forCellReuseIdentifier: Self.className) 17 | } 18 | } 19 | } 20 | //필요한 셀에 TVC.register(target:TV이름) 형태로 등록 해주기!!!! (원래는 TVC, TV위치가 반대) 21 | //isFromNib는 true일 경우에는 Xib로 TVC 만든거고, false일 경우에는 코드로 TVC 만든거! 22 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Protocols/ViewModelType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModelType.swift 3 | 4 | 5 | import Foundation 6 | import RxSwift 7 | 8 | protocol ViewModelType{ 9 | associatedtype Input 10 | associatedtype Output 11 | 12 | func transform(from input: Input, disposeBag: DisposeBag) -> Output 13 | } 14 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "Icon-60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "Icon-58.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "Icon-87.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "Icon-80.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "Icon-120.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "Icon-121.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "Icon-180.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "idiom" : "ipad", 53 | "scale" : "1x", 54 | "size" : "20x20" 55 | }, 56 | { 57 | "idiom" : "ipad", 58 | "scale" : "2x", 59 | "size" : "20x20" 60 | }, 61 | { 62 | "idiom" : "ipad", 63 | "scale" : "1x", 64 | "size" : "29x29" 65 | }, 66 | { 67 | "idiom" : "ipad", 68 | "scale" : "2x", 69 | "size" : "29x29" 70 | }, 71 | { 72 | "idiom" : "ipad", 73 | "scale" : "1x", 74 | "size" : "40x40" 75 | }, 76 | { 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "idiom" : "ipad", 83 | "scale" : "1x", 84 | "size" : "76x76" 85 | }, 86 | { 87 | "idiom" : "ipad", 88 | "scale" : "2x", 89 | "size" : "76x76" 90 | }, 91 | { 92 | "idiom" : "ipad", 93 | "scale" : "2x", 94 | "size" : "83.5x83.5" 95 | }, 96 | { 97 | "filename" : "Icon-1024.png", 98 | "idiom" : "ios-marketing", 99 | "scale" : "1x", 100 | "size" : "1024x1024" 101 | } 102 | ], 103 | "info" : { 104 | "author" : "xcode", 105 | "version" : 1 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-1024.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-120.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-121.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-121.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-180.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-58.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-80.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/AppIcon.appiconset/Icon-87.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "angry_icon_0.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "angry_icon_0@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "angry_icon_0@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_0.imageset/angry_icon_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_0.imageset/angry_icon_0.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_0.imageset/angry_icon_0@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_0.imageset/angry_icon_0@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_0.imageset/angry_icon_0@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_0.imageset/angry_icon_0@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "angry_icon_1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "angry_icon_1@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "angry_icon_1@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_1.imageset/angry_icon_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_1.imageset/angry_icon_1.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_1.imageset/angry_icon_1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_1.imageset/angry_icon_1@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_1.imageset/angry_icon_1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_1.imageset/angry_icon_1@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "angry_icon_2.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "angry_icon_2@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "angry_icon_2@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_2.imageset/angry_icon_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_2.imageset/angry_icon_2.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_2.imageset/angry_icon_2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_2.imageset/angry_icon_2@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_2.imageset/angry_icon_2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_2.imageset/angry_icon_2@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "angry_icon_3.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "angry_icon_3@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "angry_icon_3@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_3.imageset/angry_icon_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_3.imageset/angry_icon_3.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_3.imageset/angry_icon_3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_3.imageset/angry_icon_3@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_3.imageset/angry_icon_3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_3.imageset/angry_icon_3@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "angry_icon_4.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "angry_icon_4@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "angry_icon_4@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_4.imageset/angry_icon_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_4.imageset/angry_icon_4.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_4.imageset/angry_icon_4@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_4.imageset/angry_icon_4@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_4.imageset/angry_icon_4@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/angry_icon_4.imageset/angry_icon_4@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/carbon_share.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "carbon_share.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "carbon_share@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "carbon_share@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/carbon_share.imageset/carbon_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/carbon_share.imageset/carbon_share.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/carbon_share.imageset/carbon_share@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/carbon_share.imageset/carbon_share@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/carbon_share.imageset/carbon_share@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/carbon_share.imageset/carbon_share@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_feed.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ic_feed.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "ic_feed@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "ic_feed@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_feed.imageset/ic_feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_feed.imageset/ic_feed.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_feed.imageset/ic_feed@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_feed.imageset/ic_feed@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_feed.imageset/ic_feed@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_feed.imageset/ic_feed@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_home.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ic_home.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "ic_home@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "ic_home@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_home.imageset/ic_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_home.imageset/ic_home.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_home.imageset/ic_home@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_home.imageset/ic_home@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_home.imageset/ic_home@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_home.imageset/ic_home@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_rank.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ic_rank.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "ic_rank@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "ic_rank@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_rank.imageset/ic_rank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_rank.imageset/ic_rank.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_rank.imageset/ic_rank@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_rank.imageset/ic_rank@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_rank.imageset/ic_rank@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/ic_rank.imageset/ic_rank@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Group 33650.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Group 33650@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Group 33650@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_1.imageset/Group 33650.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_1.imageset/Group 33650.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_1.imageset/Group 33650@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_1.imageset/Group 33650@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_1.imageset/Group 33650@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_1.imageset/Group 33650@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Group 33649.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Group 33649@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Group 33649@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_2.imageset/Group 33649.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_2.imageset/Group 33649.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_2.imageset/Group 33649@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_2.imageset/Group 33649@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_2.imageset/Group 33649@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_2.imageset/Group 33649@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_3.imageset/1 52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_3.imageset/1 52.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_3.imageset/1 52@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_3.imageset/1 52@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_3.imageset/1 52@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_3.imageset/1 52@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/king_icon_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1 52.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "1 52@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "1 52@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "main_card_1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "main_card_1@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "main_card_1@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_1.imageset/main_card_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_1.imageset/main_card_1.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_1.imageset/main_card_1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_1.imageset/main_card_1@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_1.imageset/main_card_1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_1.imageset/main_card_1@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "main_card_2.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "main_card_2@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "main_card_2@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_2.imageset/main_card_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_2.imageset/main_card_2.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_2.imageset/main_card_2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_2.imageset/main_card_2@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_2.imageset/main_card_2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_card_2.imageset/main_card_2@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "main_icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "main_icon@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "main_icon@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_icon.imageset/main_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_icon.imageset/main_icon.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_icon.imageset/main_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_icon.imageset/main_icon@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_icon.imageset/main_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/main_icon.imageset/main_icon@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/splash_view.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "splash_view.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "splash_view@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "splash_view@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/splash_view.imageset/splash_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/splash_view.imageset/splash_view.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/splash_view.imageset/splash_view@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/splash_view.imageset/splash_view@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/splash_view.imageset/splash_view@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/splash_view.imageset/splash_view@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home-clicked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icn-home-1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icn-home@2x-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "icn-home@3x-1.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home-clicked.imageset/icn-home-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home-clicked.imageset/icn-home-1.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home-clicked.imageset/icn-home@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home-clicked.imageset/icn-home@2x-1.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home-clicked.imageset/icn-home@3x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home-clicked.imageset/icn-home@3x-1.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icn-home.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icn-home@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "icn-home@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home.imageset/icn-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home.imageset/icn-home.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home.imageset/icn-home@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home.imageset/icn-home@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home.imageset/icn-home@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-home.imageset/icn-home@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage-clicked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Person-1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Person@2x-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Person@3x-1.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage-clicked.imageset/Person-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage-clicked.imageset/Person-1.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage-clicked.imageset/Person@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage-clicked.imageset/Person@2x-1.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage-clicked.imageset/Person@3x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage-clicked.imageset/Person@3x-1.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Person.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Person@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Person@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage.imageset/Person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage.imageset/Person.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage.imageset/Person@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage.imageset/Person@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage.imageset/Person@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/tab-mypage.imageset/Person@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_icon_list.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "voice_level_icon_list.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "voice_level_icon_list@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "voice_level_icon_list@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_icon_list.imageset/voice_level_icon_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_icon_list.imageset/voice_level_icon_list.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_icon_list.imageset/voice_level_icon_list@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_icon_list.imageset/voice_level_icon_list@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_icon_list.imageset/voice_level_icon_list@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_icon_list.imageset/voice_level_icon_list@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_sample.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "voice_level_sample.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "voice_level_sample@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "voice_level_sample@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_sample.imageset/voice_level_sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_sample.imageset/voice_level_sample.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_sample.imageset/voice_level_sample@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_sample.imageset/voice_level_sample@2x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_sample.imageset/voice_level_sample@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Assets.xcassets/voice_level_sample.imageset/voice_level_sample@3x.png -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Fonts/crayon.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SOPKATHON-12/Client-iOS/3b739dde335260c1460b6b615a55b9facfd0ef7a/30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Resources/Fonts/crayon.TTF -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Global/Supporting Files/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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleURLTypes 6 | 7 | 8 | CFBundleTypeRole 9 | Editor 10 | CFBundleURLSchemes 11 | 12 | kakaodf520deba24c90ddb760281e934ec094 13 | 14 | 15 | 16 | UIAppFonts 17 | 18 | crayon.TTF 19 | 20 | UIApplicationSceneManifest 21 | 22 | UIApplicationSupportsMultipleScenes 23 | 24 | UISceneConfigurations 25 | 26 | UIWindowSceneSessionRoleApplication 27 | 28 | 29 | UISceneConfigurationName 30 | Default Configuration 31 | UISceneDelegateClassName 32 | $(PRODUCT_MODULE_NAME).SceneDelegate 33 | UISceneStoryboardFile 34 | Base 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/BaseScene/VC/BaseVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseVC.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/12. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | 11 | let screenWidth = UIScreen.main.bounds.width 12 | let screenHeight = UIScreen.main.bounds.height 13 | 14 | class BaseVC: UIViewController { 15 | // MARK: - Vars & Lets Part 16 | private let moduleFactory = ModuleFactory.shared 17 | private var tabList: [TabbarIconType] = [] 18 | 19 | // MARK: - UI Component Part 20 | @IBOutlet weak var sceneContainerView: UIView! 21 | @IBOutlet weak var tabbar: TabbarView! 22 | 23 | // MARK: - Life Cycle Part 24 | override func viewDidLoad() { 25 | self.sceneContainerView.alpha = 0 26 | self.tabbar.alpha = 0 27 | super.viewDidLoad() 28 | configureTabbarDelegate() 29 | tabbarClicked(.home) 30 | configureUI() 31 | addObserver() 32 | } 33 | 34 | override func viewWillAppear(_ animated: Bool) { 35 | self.showAnimation() 36 | } 37 | 38 | override func viewDidAppear(_ animated: Bool) { 39 | guard let navigationController = navigationController else { return } 40 | for (index,vc) in navigationController.viewControllers.enumerated() { 41 | if vc.className == SplashVC.className { 42 | navigationController.viewControllers.remove(at: index) 43 | } 44 | } 45 | } 46 | 47 | open override func didMove(toParent parent: UIViewController?) { 48 | navigationController?.fixInteractivePopGestureRecognizer(delegate: self) 49 | } 50 | 51 | private func showAnimation() { 52 | UIView.animate(withDuration: 1, delay: 0) { 53 | self.sceneContainerView.alpha = 1 54 | self.tabbar.alpha = 1 55 | } 56 | } 57 | } 58 | 59 | // MARK: - 탭바를 제외한 Scene 세팅하는 부분 60 | 61 | extension BaseVC: MainTabbarDelegate{ 62 | private func configureTabbarDelegate(){ 63 | tabbar.delegate = self 64 | } 65 | 66 | private func configureUI() { 67 | tabbar.layer.cornerRadius = 15 68 | tabbar.layer.applyShadow(color: .black, alpha: 0.25, x: 2, y: 3, blur: 4, spread: 0) 69 | } 70 | 71 | func tabbarClicked(_ type: TabbarIconType) { 72 | 73 | 74 | if !tabList.contains(type){ 75 | let vc = makeScene(type) 76 | vc.view.translatesAutoresizingMaskIntoConstraints = false 77 | self.addChild(vc) 78 | sceneContainerView.addSubview(vc.view) 79 | vc.view.snp.makeConstraints{ $0.edges.equalToSuperview() } 80 | vc.didMove(toParent: self) 81 | tabList.append(type) 82 | }else { 83 | if let index = tabList.firstIndex(of: type) { 84 | let vc = sceneContainerView.subviews[index] 85 | sceneContainerView.bringSubviewToFront(vc) 86 | tabList.remove(at: index) 87 | tabList.append(type) 88 | } 89 | 90 | 91 | } 92 | 93 | 94 | 95 | } 96 | 97 | func plusButtonClicked() { 98 | 99 | } 100 | 101 | private func makeScene(_ type: TabbarIconType) -> UIViewController{ 102 | switch(type) { 103 | case .home: return moduleFactory.makeHomeNC() 104 | case .feed: return moduleFactory.makeFeedCalendarNC() 105 | case .ranking: return moduleFactory.makeRankingVC() 106 | } 107 | } 108 | } 109 | 110 | extension BaseVC { 111 | func addObserver() { 112 | 113 | addObserverAction(.logout) { _ in 114 | 115 | } 116 | } 117 | } 118 | 119 | extension BaseVC : UIGestureRecognizerDelegate { 120 | public func gestureRecognizer( 121 | _ gestureRecognizer: UIGestureRecognizer, 122 | shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer 123 | ) -> Bool { 124 | return otherGestureRecognizer is PanDirectionGestureRecognizer 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/BaseScene/Views/Base.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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/BaseScene/Views/Tabbar/TabbarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tabbar.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/12. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol MainTabbarDelegate{ 11 | func tabbarClicked(_ type: TabbarIconType) 12 | func plusButtonClicked() 13 | } 14 | 15 | final class TabbarView: XibView{ 16 | var delegate: MainTabbarDelegate? 17 | private var currentTab: TabbarIconType = .home { 18 | didSet{ setTabbarViewModel() } 19 | } 20 | 21 | @IBOutlet weak var homeIcon: TabbarIcon! 22 | @IBOutlet weak var feedIcon: TabbarIcon! 23 | @IBOutlet weak var rankingIcon: TabbarIcon! 24 | 25 | required init?(coder aDecoder: NSCoder) { 26 | super.init(coder: aDecoder) 27 | setTabbarViewModel() 28 | } 29 | 30 | @IBAction func homeButtonClicked(_ sender: Any) { 31 | makeVibrate() 32 | if currentTab != .feed{ 33 | delegate?.tabbarClicked(.feed) 34 | currentTab = .feed 35 | } else { 36 | postObserverAction(.feedButtonClicked) 37 | } 38 | } 39 | 40 | 41 | @IBAction func feedClicked(_ sender: Any) { 42 | 43 | 44 | makeVibrate() 45 | if currentTab != .home{ 46 | delegate?.tabbarClicked(.home) 47 | currentTab = .home 48 | } else { 49 | postObserverAction(.homeButtonClicked) 50 | } 51 | 52 | 53 | 54 | } 55 | 56 | @IBAction func rankingClicked(_ sender: Any) { 57 | makeVibrate() 58 | if currentTab != .ranking{ 59 | delegate?.tabbarClicked(.ranking) 60 | currentTab = .ranking 61 | } else { 62 | postObserverAction(.rankingButtonClicked) 63 | } 64 | } 65 | 66 | 67 | private func setTabbarViewModel() { 68 | 69 | homeIcon.viewModel = TabbarIconViewModel(type: .feed, clicked: currentTab == .feed) 70 | feedIcon.viewModel = TabbarIconViewModel(type: .home, clicked: currentTab == .home) 71 | rankingIcon.viewModel = TabbarIconViewModel(type: .ranking, clicked: currentTab == .ranking) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/BaseScene/Views/TabbarIcon/TabbarIcon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabbarIcon.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/12. 6 | // 7 | 8 | import UIKit 9 | 10 | enum TabbarIconType { 11 | case home 12 | case feed 13 | case ranking 14 | } 15 | 16 | struct TabbarIconViewModel { 17 | var type: TabbarIconType 18 | var clicked: Bool 19 | } 20 | 21 | final class TabbarIcon: XibView{ 22 | 23 | @IBOutlet weak var iconImageView: UIImageView! 24 | 25 | var viewModel: TabbarIconViewModel? { 26 | didSet{ 27 | configureUI() 28 | } 29 | } 30 | 31 | private func configureUI() { 32 | guard let viewModel = viewModel else { return } 33 | if viewModel.clicked { 34 | iconImageView.alpha = 1 35 | } else { 36 | iconImageView.alpha = 0.5 37 | } 38 | 39 | switch(viewModel.type){ 40 | case .home: 41 | iconImageView.image = ImageLiterals.TabBar.home 42 | 43 | case .feed: 44 | iconImageView.image = ImageLiterals.TabBar.feed 45 | 46 | case .ranking: 47 | iconImageView.image = ImageLiterals.TabBar.ranking 48 | } 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/BaseScene/Views/TabbarIcon/TabbarIcon.xib: -------------------------------------------------------------------------------- 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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/FeedCalendarScene/VC/FeedCalendarNC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeedCalendarNC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class FeedCalendarNC: UINavigationController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | 19 | /* 20 | // MARK: - Navigation 21 | 22 | // In a storyboard-based application, you will often want to do a little preparation before navigation 23 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 24 | // Get the new view controller using segue.destination. 25 | // Pass the selected object to the new view controller. 26 | } 27 | */ 28 | 29 | } 30 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/FeedCalendarScene/VC/FeedCalendarVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeedCalendarVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class FeedCalendarVC: UIViewController { 11 | 12 | // MARK: - Vars & Lets Part 13 | var monthEntity: MonthListEntity? 14 | 15 | // MARK: - UI Component Part 16 | 17 | 18 | // MARK: - Life Cycle Part 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | fetchData() 23 | } 24 | 25 | 26 | private func fetchData() { 27 | BaseService.default.getMonthData(month: 5) { result in 28 | result.success { entity in 29 | self.monthEntity = entity 30 | } 31 | } 32 | } 33 | 34 | 35 | // MARK: - IBAction Part 36 | 37 | @IBAction func iconClicked(_ sender: Any) { 38 | let vc = ModuleFactory.shared.makeFeedVC() 39 | self.navigationController?.pushViewController(vc, animated: true) 40 | 41 | } 42 | 43 | // MARK: - Custom Method Part 44 | 45 | 46 | // MARK: - @objc Function Part 47 | 48 | } 49 | // MARK: - Extension Part 50 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/FeedScene/Cell/TVC/FeedTVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeedTVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by Juhyeon Byun on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class FeedTVC: UITableViewCell, UITableViewRegisterable { 11 | static var isFromNib: Bool = true 12 | 13 | // MARK: - IBOutlet 14 | @IBOutlet weak var profileImageView: UIImageView! 15 | @IBOutlet weak var decibelLabel: UILabel! 16 | @IBOutlet weak var reasonLabel: UILabel! 17 | @IBOutlet weak var backView: UIView! 18 | 19 | override func awakeFromNib() { 20 | super.awakeFromNib() 21 | 22 | backgroundColor = UIColor(red: 0.961, green: 0.961, blue: 0.961, alpha: 1) 23 | backView.layer.borderWidth = 0.5 24 | backView.layer.borderColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1).cgColor 25 | } 26 | } 27 | 28 | // MARK: - Custom Methods 29 | extension FeedTVC { 30 | func setData(_ feedData: FeedDataModel) { 31 | profileImageView.image = feedData.gaugeImage 32 | decibelLabel.text = "\(feedData.gauge) dB" 33 | reasonLabel.text = feedData.reason 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/FeedScene/VC/FeedVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeedVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class FeedVC: UIViewController { 11 | 12 | var monthEntity: MonthListEntity? 13 | // MARK: - UI Component Part 14 | @IBOutlet weak var gaugeImageView: UIImageView! 15 | @IBOutlet weak var monthLabel: UILabel! 16 | @IBOutlet weak var feedTV: UITableView! 17 | 18 | // MARK: - Life Cycle Part 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | setTVC() 23 | setView() 24 | addObserver() 25 | } 26 | } 27 | 28 | // MARK: - Custom Method Part 29 | extension FeedVC { 30 | private func setTVC() { 31 | FeedTVC.register(target: feedTV) 32 | feedTV.delegate = self 33 | feedTV.dataSource = self 34 | } 35 | 36 | private func setView() { 37 | self.view.backgroundColor = UIColor(red: 0.961, green: 0.961, blue: 0.961, alpha: 1) 38 | self.feedTV.backgroundColor = UIColor(red: 0.961, green: 0.961, blue: 0.961, alpha: 1) 39 | } 40 | 41 | private func fetchData() { 42 | BaseService.default.getMonthData(month: 5) { result in 43 | result.success { entity in 44 | self.monthEntity = entity 45 | } 46 | } 47 | } 48 | 49 | private func addObserver() { 50 | addObserverAction(.feedButtonClicked) { _ in 51 | self.navigationController?.popViewController(animated: true) 52 | } 53 | } 54 | } 55 | 56 | // MARK: - UITableViewDelegate 57 | extension FeedVC: UITableViewDelegate { 58 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 59 | return 97 60 | } 61 | } 62 | 63 | // MARK: - UITableViewDataSource 64 | extension FeedVC: UITableViewDataSource { 65 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 66 | return FeedDataModel.sampleData.count 67 | } 68 | 69 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 70 | guard let cell = tableView.dequeueReusableCell(withIdentifier: FeedTVC.className, for: indexPath) as? FeedTVC else { return UITableViewCell() } 71 | 72 | cell.setData(FeedDataModel.sampleData[indexPath.row]) 73 | 74 | return cell 75 | } 76 | 77 | func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 78 | cell.transform = CGAffineTransform(translationX: 0, y: 74 * 1.4) 79 | cell.alpha = 0 80 | UIView.animate( 81 | withDuration: 0.5, 82 | delay: 0.15 * Double(indexPath.row), 83 | options: [.curveEaseInOut], 84 | animations: { 85 | cell.transform = CGAffineTransform(translationX: 0, y: 0) 86 | cell.alpha = 1 87 | 88 | } 89 | ) 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/FeedScene/Views/Feed.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/HomeScene/VC/HomeNC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeNC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class HomeNC: UINavigationController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | 19 | /* 20 | // MARK: - Navigation 21 | 22 | // In a storyboard-based application, you will often want to do a little preparation before navigation 23 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 24 | // Get the new view controller using segue.destination. 25 | // Pass the selected object to the new view controller. 26 | } 27 | */ 28 | 29 | } 30 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/HomeScene/VC/HomeVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class HomeVC: UIViewController { 11 | 12 | // MARK: - Vars & Lets Part 13 | 14 | 15 | // MARK: - UI Component Part 16 | @IBOutlet weak var decibelButton: UIButton! 17 | @IBOutlet weak var touchButton: UIButton! 18 | 19 | // MARK: - Life Cycle Part 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | } 25 | 26 | // MARK: - IBAction Part 27 | @IBAction func tapDecibelButton(_ sender: Any) { 28 | moveDecible() 29 | } 30 | 31 | @IBAction func tapTouchButton(_ sender: Any) { 32 | moveTap() 33 | } 34 | 35 | private func moveDecible() { 36 | let decibelVC = ModuleFactory.shared.makeSoundKingVC() 37 | self.navigationController?.pushViewController(decibelVC, animated: true) 38 | } 39 | 40 | private func moveTap() { 41 | let tapVC = ModuleFactory.shared.makeTouchKingVC() 42 | self.navigationController?.pushViewController(tapVC, animated: true) 43 | } 44 | } 45 | 46 | 47 | // MARK: - Custom Method Part 48 | 49 | 50 | // MARK: - @objc Function Part 51 | 52 | // MARK: - Extension Part 53 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/Main.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 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/RankingScene/Cell/TVC/RankignDataModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RankignDataModel.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 강윤서 on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | struct RankingDataModel { 11 | let rankingLabel: String 12 | let userImg: String 13 | let nameLabel: String 14 | let decibelLabel: String 15 | } 16 | 17 | extension RankingDataModel { 18 | static let sampleData: [RankingDataModel] = [ 19 | RankingDataModel(rankingLabel: "3", userImg: "angry_icon_4", nameLabel: "이승헌", decibelLabel: "99"), 20 | RankingDataModel(rankingLabel: "1", userImg: "angry_icon_4", nameLabel: "변주현", decibelLabel: "110"), 21 | RankingDataModel(rankingLabel: "2", userImg: "angry_icon_4", nameLabel: "손연주", decibelLabel: "100"), 22 | RankingDataModel(rankingLabel: "3", userImg: "angry_icon_4", nameLabel: "이승헌", decibelLabel: "99"), 23 | RankingDataModel(rankingLabel: "4", userImg: "angry_icon_4", nameLabel: "강윤서", decibelLabel: "95"), 24 | RankingDataModel(rankingLabel: "5", userImg: "angry_icon_4", nameLabel: "송지훈", decibelLabel: "90"), 25 | RankingDataModel(rankingLabel: "6", userImg: "angry_icon_4", nameLabel: "이유리", decibelLabel: "87"), 26 | RankingDataModel(rankingLabel: "7", userImg: "angry_icon_4", nameLabel: "김시하", decibelLabel: "83"), 27 | RankingDataModel(rankingLabel: "8", userImg: "angry_icon_4", nameLabel: "김혜정", decibelLabel: "70"), 28 | RankingDataModel(rankingLabel: "9", userImg: "angry_icon_4", nameLabel: "김현희", decibelLabel: "70") 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/RankingScene/Cell/TVC/RankingTVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RankingTVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 강윤서 on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | import KakaoSDKCommon 11 | import KakaoSDKTemplate 12 | import KakaoSDKLink 13 | 14 | class RankingTVC: UITableViewCell { 15 | 16 | static let identifier = "RankingTVC" 17 | 18 | @IBOutlet weak var rankingLabel: UILabel! 19 | @IBOutlet weak var userImg: UIImageView! 20 | @IBOutlet weak var nameLabel: UILabel! 21 | @IBOutlet weak var decibelLabel: UILabel! 22 | @IBOutlet weak var dBLabel: UILabel! 23 | 24 | override func awakeFromNib() { 25 | super.awakeFromNib() 26 | // Initialization code 27 | contentView.layer.cornerRadius = 10 28 | 29 | } 30 | @IBAction func shareButtonCLicked(_ sender: Any) { 31 | shareAction() 32 | 33 | } 34 | 35 | override func layoutSubviews() { 36 | super.layoutSubviews() 37 | contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: 7, right: 0)) 38 | } 39 | 40 | func setData(_ rankingData: RankingDataModel){ 41 | // userImg.image = rankingData.userImg 42 | userImg.image = UIImage(named: rankingData.userImg) 43 | nameLabel.text = rankingData.nameLabel 44 | decibelLabel.text = rankingData.decibelLabel 45 | rankingLabel.text = rankingData.rankingLabel 46 | } 47 | 48 | func shareAction() // 친구에게 공유하기 49 | { 50 | 51 | 52 | 53 | 54 | let messageJsonData = 55 | """ 56 | { 57 | "object_type": "feed", 58 | "content": { 59 | "title": "한도초과", 60 | "description": "오늘 99dB만큼 화가 났어..", 61 | 62 | "image_url": "https://user-images.githubusercontent.com/60260284/169671505-55d27842-7e81-4874-9203-4f6d9be203b8.png", 63 | "link": { 64 | "mobile_web_url": "https://developers.kakao.com", 65 | "web_url": "https://developers.kakao.com" 66 | } 67 | }, 68 | 69 | "buttons": [ 70 | { 71 | "title": "나의 분노 dB 확인하러 가기", 72 | "link": { 73 | "mobile_web_url": "https://developers.kakao.com", 74 | "web_url": "https://developers.kakao.com" 75 | } 76 | } 77 | ] 78 | } 79 | """.data(using: .utf8)! 80 | 81 | 82 | 83 | 84 | if let templatable = try? SdkJSONDecoder.custom.decode(FeedTemplate.self, from: messageJsonData) { 85 | LinkApi.shared.defaultLink(templatable: templatable) {(linkResult, error) in 86 | if let error = error { 87 | print(error) 88 | print("여기서 안될걸?2") 89 | } 90 | else { 91 | print("defaultLink() success.") 92 | 93 | if let linkResult = linkResult { 94 | UIApplication.shared.open(linkResult.url, options: [:], completionHandler: nil) 95 | } 96 | } 97 | } 98 | 99 | 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/RankingScene/VC/DecibelVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecibelVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 강윤서 on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class DecibelVC: UIViewController { 11 | 12 | @IBOutlet weak var rankingTV: UITableView! 13 | 14 | @IBOutlet weak var king2BackView: UIView! 15 | @IBOutlet weak var king3BackView: UIView! 16 | @IBOutlet weak var king1BackView: UIView! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | let nib = UINib(nibName: RankingTVC.identifier, bundle: nil) 22 | rankingTV.register(nib, forCellReuseIdentifier: RankingTVC.identifier) 23 | 24 | rankingTV.delegate = self 25 | rankingTV.dataSource = self 26 | 27 | // rankingTV.backgroundColor = UIColor(red: 0.961, green: 0.961, blue: 0.961, alpha: 1) 28 | rankingTV.backgroundColor = .clear 29 | 30 | setBackViews() 31 | 32 | } 33 | } 34 | 35 | extension DecibelVC: UITableViewDelegate { 36 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 37 | return 99 38 | } 39 | 40 | } 41 | 42 | extension DecibelVC: UITableViewDataSource { 43 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 44 | return RankingDataModel.sampleData.count 45 | } 46 | 47 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 48 | guard let cell = tableView.dequeueReusableCell(withIdentifier: RankingTVC.identifier, for: indexPath) as? RankingTVC else { return UITableViewCell() } 49 | 50 | if indexPath.row == 0 { 51 | cell.contentView.backgroundColor = .white 52 | cell.layer.borderColor = UIColor.black.cgColor 53 | cell.layer.cornerRadius = 10 54 | } else { 55 | cell.contentView.backgroundColor = UIColor.init(red: 227/255, green: 227/255, blue: 227/255, alpha: 1) 56 | cell.layer.borderColor = .none 57 | 58 | } 59 | 60 | cell.setData(RankingDataModel.sampleData[indexPath.row]) 61 | 62 | return cell 63 | } 64 | 65 | func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 66 | 67 | cell.transform = CGAffineTransform(translationX: 0, y: 74 * 1.4) 68 | cell.alpha = 0 69 | UIView.animate( 70 | withDuration: 0.5, 71 | delay: 0.15 * Double(indexPath.row), 72 | options: [.curveEaseInOut], 73 | animations: { 74 | cell.transform = CGAffineTransform(translationX: 0, y: 0) 75 | cell.alpha = 1 76 | 77 | } 78 | ) 79 | 80 | } 81 | 82 | 83 | } 84 | 85 | extension DecibelVC { 86 | private func setBackViews() { 87 | king1BackView.backgroundColor = UIColor.white 88 | king2BackView.backgroundColor = UIColor.white 89 | king3BackView.backgroundColor = UIColor.white 90 | king1BackView.layer.cornerRadius = 9 91 | king2BackView.layer.cornerRadius = 9 92 | king3BackView.layer.cornerRadius = 10 93 | king1BackView.layer.borderWidth = 1 94 | king2BackView.layer.borderWidth = 1 95 | king3BackView.layer.borderWidth = 1 96 | 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/RankingScene/VC/DecibelViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecibelViewController.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 강윤서 on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class DecibelViewController: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | 19 | /* 20 | // MARK: - Navigation 21 | 22 | // In a storyboard-based application, you will often want to do a little preparation before navigation 23 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 24 | // Get the new view controller using segue.destination. 25 | // Pass the selected object to the new view controller. 26 | } 27 | */ 28 | 29 | } 30 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/RankingScene/VC/RankingVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RankingVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class RankingVC: UIViewController { 11 | 12 | // MARK: - Vars & Lets Part 13 | private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal) 14 | 15 | private let contents: [UIViewController] = [ 16 | ModuleFactory.shared.makeDecibelVC(), 17 | ModuleFactory.shared.makeTabVC() 18 | ] 19 | 20 | private var currentIndex: Int = 0 21 | private var tappedButton: Bool = false 22 | // MARK: - UI Component Part 23 | @IBOutlet weak var tabStackView: UIStackView! 24 | @IBOutlet weak var barBackgroundView: UIView! 25 | @IBOutlet weak var barView: UIView! 26 | 27 | @IBOutlet weak var barWidth: NSLayoutConstraint! 28 | @IBOutlet weak var barLeading: NSLayoutConstraint! 29 | 30 | @IBOutlet weak var containerView: UIView! 31 | 32 | // MARK: - Life Cycle Part 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | 37 | setupUI() 38 | setupPageVC() 39 | 40 | } 41 | 42 | // MARK: - IBAction Part 43 | 44 | @IBAction func tabButtonClicked(_ sender: UIButton) { 45 | guard let index = tabStackView.arrangedSubviews.firstIndex(where: { $0 == sender }), 46 | index != currentIndex else { 47 | return 48 | } 49 | tappedButton = true 50 | UIView.animate(withDuration: 0.3){ 51 | self.barLeading.constant = CGFloat(index) * self.barView.frame.width 52 | self.barBackgroundView.layoutIfNeeded() 53 | } 54 | 55 | let content = contents[index] 56 | 57 | pageVC.setViewControllers([content], direction: currentIndex < index ? .forward : .reverse, animated: true) { _ in 58 | self.currentIndex = index 59 | self.tappedButton = false 60 | 61 | } 62 | } 63 | 64 | // MARK: - Custom Method Part 65 | private func setupUI() { 66 | barWidth.constant = view.bounds.width / CGFloat(tabStackView.arrangedSubviews.count) 67 | } 68 | 69 | private func setupPageVC() { 70 | self.addChild(pageVC) 71 | 72 | containerView.frame = pageVC.view.frame 73 | self.containerView.addSubview(pageVC.view) 74 | 75 | pageVC.didMove(toParent: self) 76 | 77 | pageVC.delegate = self 78 | pageVC.dataSource = self 79 | 80 | if let firstVC = contents.first { 81 | pageVC.setViewControllers([firstVC], direction: .forward, animated: true) 82 | } 83 | 84 | for subview in pageVC.view.subviews { 85 | if let scrollView = subview as? UIScrollView { 86 | scrollView.delegate = self 87 | } 88 | } 89 | } 90 | 91 | // MARK: - @objc Function Part 92 | 93 | } 94 | // MARK: - Extension Part 95 | extension RankingVC: UIPageViewControllerDelegate, UIPageViewControllerDataSource { 96 | func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { 97 | guard let viewController = pageVC.viewControllers?.first, 98 | let index = contents.firstIndex(where: { $0 == viewController }) else {return} 99 | 100 | currentIndex = index 101 | } 102 | 103 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { 104 | guard let index = contents.firstIndex(of: viewController) else {return nil} 105 | let prevIndex = index - 1 106 | 107 | if prevIndex < 0 { 108 | return nil 109 | } 110 | return contents[prevIndex] 111 | } 112 | 113 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { 114 | guard let index = contents.firstIndex(of: viewController) else {return nil} 115 | let nextIndex = index + 1 116 | 117 | if nextIndex == contents.count { 118 | return nil 119 | } 120 | return contents[nextIndex] 121 | } 122 | } 123 | 124 | extension RankingVC: UIScrollViewDelegate { 125 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 126 | //print(scrollView.contentOffset.x) 127 | 128 | let offsetX = scrollView.contentOffset.x 129 | let contentWidth = pageVC.view.frame.width 130 | 131 | let percent = (offsetX - contentWidth) / contentWidth 132 | 133 | let constant = barView.frame.width * (CGFloat(currentIndex) + percent) 134 | barView.frame.origin.x = constant 135 | } 136 | } 137 | 138 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/RankingScene/VC/TabVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 강윤서 on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class TabVC: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | 19 | /* 20 | // MARK: - Navigation 21 | 22 | // In a storyboard-based application, you will often want to do a little preparation before navigation 23 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 24 | // Get the new view controller using segue.destination. 25 | // Pass the selected object to the new view controller. 26 | } 27 | */ 28 | 29 | } 30 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/RankingScene/VC/TabViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabViewController.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 강윤서 on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class TabViewController: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | 19 | /* 20 | // MARK: - Navigation 21 | 22 | // In a storyboard-based application, you will often want to do a little preparation before navigation 23 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 24 | // Get the new view controller using segue.destination. 25 | // Pass the selected object to the new view controller. 26 | } 27 | */ 28 | 29 | } 30 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/RankingScene/VIews/Tab.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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/SampleScene/SampleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleViewController.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import UIKit 9 | import RxSwift 10 | 11 | class SampleVC: UIViewController { 12 | // MARK: - Vars & Lets Part 13 | private let disposeBag = DisposeBag() 14 | var viewModel: SampleViewModel! 15 | 16 | // MARK: - UI Component Part 17 | 18 | // MARK: - Life Cycle Part 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | self.bindViewModels() 22 | } 23 | } 24 | 25 | extension SampleVC { 26 | 27 | // MARK: - Custom Method Part 28 | private func bindViewModels() { 29 | let input = SampleViewModel.Input() 30 | let output = self.viewModel.transform(from: input, disposeBag: self.disposeBag) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/SampleScene/SampleViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleViewModel.swift 3 | // ReadMe-iOS 4 | // 5 | // Created by 송지훈 on 2022/04/03. 6 | // 7 | 8 | import RxSwift 9 | 10 | final class SampleViewModel: ViewModelType { 11 | 12 | private let useCase: SampleUseCase 13 | private let disposeBag = DisposeBag() 14 | 15 | // MARK: - Inputs 16 | struct Input { 17 | 18 | } 19 | 20 | // MARK: - Outputs 21 | struct Output { 22 | 23 | } 24 | 25 | init(useCase: SampleUseCase) { 26 | self.useCase = useCase 27 | } 28 | } 29 | 30 | extension SampleViewModel { 31 | func transform(from input: Input, disposeBag: DisposeBag) -> Output { 32 | let output = Output() 33 | self.bindOutput(output: output, disposeBag: disposeBag) 34 | // input,output 상관관계 작성 35 | 36 | return output 37 | } 38 | 39 | private func bindOutput(output: Output, disposeBag: DisposeBag) { 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/SoundKingScene/VC/SoundKingVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SoundKingVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/21. 6 | // 7 | 8 | import UIKit 9 | import AVFoundation 10 | import CoreAudio 11 | 12 | class SoundKingVC: UIViewController { 13 | 14 | // MARK: - Vars & Lets Part 15 | 16 | weak var videoTimer: Timer? 17 | var minutes = 0 18 | var seconds = 8 19 | var milliseconds = 0 20 | 21 | var recorder: AVAudioRecorder! 22 | var levelTimer = Timer() 23 | var maxDB = 0 24 | var maxLevel = 0 25 | 26 | // MARK: - UI Component Part 27 | 28 | @IBOutlet weak var timerLabel: UILabel! 29 | @IBOutlet weak var levelScrollView: UIScrollView! 30 | @IBOutlet weak var lblDecibel: UILabel! 31 | 32 | // MARK: - Life Cycle Part 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | initRecord() 37 | startTimer() 38 | addObserver() 39 | } 40 | 41 | private func addObserver() { 42 | addObserverAction(.homeButtonClicked) { _ in 43 | self.navigationController?.popViewController(animated: true) 44 | } 45 | addObserverAction(.writeComplete) { _ in 46 | self.navigationController?.popViewController(animated: true) 47 | } 48 | } 49 | 50 | func initRecord() { 51 | 52 | 53 | switch AVAudioSession.sharedInstance().recordPermission { 54 | case AVAudioSession.RecordPermission.granted: 55 | record() 56 | case AVAudioSession.RecordPermission.denied: 57 | recordNotAllowed() 58 | case AVAudioSession.RecordPermission.undetermined: 59 | AVAudioSession.sharedInstance().requestRecordPermission({ (granted) in 60 | print(granted) 61 | if granted { 62 | 63 | DispatchQueue.main.sync { 64 | self.record() 65 | } 66 | } else { 67 | self.recordNotAllowed() 68 | } 69 | }) 70 | default: 71 | break 72 | } 73 | } 74 | 75 | 76 | func startTimer() { 77 | 78 | videoTimer?.invalidate() 79 | 80 | videoTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] _ in 81 | self?.timerIsRunning() 82 | }) 83 | RunLoop.current.add(videoTimer!, forMode: RunLoop.Mode.common) 84 | } 85 | 86 | func timerIsRunning() { 87 | 88 | showTimer() 89 | 90 | if seconds == 0 { 91 | if minutes != 0 { 92 | minutes -= 1 93 | } 94 | } 95 | 96 | if milliseconds == 0 { 97 | seconds -= 1 98 | } 99 | 100 | if seconds < 0 { 101 | seconds = 59 102 | } 103 | milliseconds -= 1 104 | if milliseconds < 0 { 105 | milliseconds = 9 106 | } 107 | if minutes == 0 && seconds == 0 && milliseconds == 0 { 108 | showTimer() 109 | videoTimer?.invalidate() 110 | recorder.stop() 111 | showWriteVC() 112 | } 113 | } 114 | 115 | private func showWriteVC() { 116 | let writeVC = ModuleFactory.shared.makeWritingVC() 117 | writeVC.modalTransitionStyle = .crossDissolve 118 | writeVC.modalPresentationStyle = .overCurrentContext 119 | let maxLevel = ( maxDB - 60 ) / 20 120 | writeVC.angryLevel = maxLevel 121 | self.present(writeVC, animated: true) 122 | } 123 | 124 | 125 | func showTimer() { 126 | let millisecStr = "\(milliseconds)" 127 | let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)" 128 | let minutesStr = minutes > 9 ? "\(minutes)" : "0\(minutes)" 129 | 130 | timerLabel.text = minutesStr + ". " + secondsStr + ". " + millisecStr 131 | print(minutesStr,secondsStr,millisecStr) 132 | } 133 | 134 | func recordNotAllowed() { 135 | print("permission denied") 136 | } 137 | 138 | func record() { 139 | 140 | let audioSession = AVAudioSession.sharedInstance() 141 | 142 | // userDomainMask에 녹음 파일 생성 143 | let documents = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]) 144 | let url = documents.appendingPathComponent("record.caf") 145 | 146 | // 녹음 세팅 147 | let recordSettings: [String: Any] = [ 148 | 149 | AVFormatIDKey: kAudioFormatAppleIMA4, 150 | AVSampleRateKey: 44100.0, // 44100.0(표준), 32kHz, 24, 16, 12 151 | AVNumberOfChannelsKey: 1, // 1: 모노 2: 스테레오(표준) 152 | AVEncoderBitRateKey: 128, // 32k, 96, 128(표준), 160, 192, 256, 320 153 | AVLinearPCMBitDepthKey: 16, // 4, 8, 11, 12, 16(표준), 18, 154 | AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue 155 | ] 156 | 157 | do { 158 | try audioSession.setCategory(AVAudioSession.Category.playAndRecord) 159 | try audioSession.setActive(true) 160 | try recorder = AVAudioRecorder(url:url, settings: recordSettings) 161 | } catch { 162 | return 163 | } 164 | 165 | recorder.prepareToRecord() 166 | recorder.isMeteringEnabled = true 167 | recorder.record() 168 | 169 | // 타이머는 main thread 에서 실행됨 170 | levelTimer = Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: #selector(levelTimerCallback), userInfo: nil, repeats: true) 171 | } 172 | 173 | @objc func levelTimerCallback() { 174 | recorder.updateMeters() 175 | 176 | let level = recorder.averagePower(forChannel: 0) 177 | let power = pow(10.0, level / 20.0) * 70.0 + 55 178 | let powerInt = String(Int(round(power))) 179 | maxDB = max(maxDB, Int(round(power))) 180 | 181 | 182 | let a = (round(power) - 60) / 12 183 | // maxLabel.text = "최대 데시벨 : " + String(maxDB) 184 | 185 | levelScrollView.setContentOffset(CGPoint(x: CGFloat(a) * screenWidth, y: 0), animated: true) 186 | 187 | lblDecibel.text = powerInt 188 | 189 | } 190 | 191 | override func didReceiveMemoryWarning() { 192 | super.didReceiveMemoryWarning() 193 | // Dispose of any resources that can be recreated. 194 | } 195 | } 196 | // MARK: - Extension Part 197 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/SplashScene/VC/SplashVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class SplashVC: UIViewController { 11 | 12 | // MARK: - UI Component Part 13 | 14 | @IBOutlet weak var splashIconView: UIImageView! 15 | 16 | // MARK: - Life Cycle Part 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | self.splashIconView.alpha = 0 22 | delayWithSeconds(0.5) { 23 | self.showAnimation() 24 | } 25 | } 26 | 27 | private func showAnimation() { 28 | 29 | UIView.animate(withDuration: 0.5, delay: 0) { 30 | self.splashIconView.alpha = 1 31 | } completion: { _ in 32 | UIView.animate(withDuration: 1, delay: 1.0) { 33 | self.splashIconView.alpha = 0 34 | } completion: { _ in 35 | self.moveBaseView() 36 | } 37 | } 38 | 39 | } 40 | 41 | private func moveBaseView() { 42 | let baseVC = ModuleFactory.shared.makeBaseVC() 43 | self.navigationController?.pushViewController(baseVC, animated: false) 44 | } 45 | 46 | // MARK: - @objc Function Part 47 | 48 | } 49 | // MARK: - Extension Part 50 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/SplashScene/Views/Splash.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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/TouchKingScene/VC/TouchKingVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TouchKingVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class TouchKingVC: UIViewController { 11 | 12 | // MARK: - Vars & Lets Part 13 | 14 | weak var videoTimer: Timer? 15 | var minutes = 0 16 | var seconds = 15 17 | var milliseconds = 0 18 | var level = 0 19 | 20 | var isTimerValid = false 21 | var count = 0 { 22 | didSet{ 23 | countLabel.text = String(count) 24 | } 25 | } 26 | 27 | // MARK: - UI Component Part 28 | 29 | @IBOutlet weak var angryIconView: UIImageView! 30 | @IBOutlet weak var timerLabel: UILabel! 31 | @IBOutlet weak var countLabel: UILabel! 32 | 33 | // MARK: - Life Cycle Part 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | startTimer() 38 | addObserver() 39 | } 40 | // MARK: - IBAction Part 41 | 42 | 43 | @IBAction func targetButtonsClicked(_ sender: Any) { 44 | if isTimerValid { 45 | UIView.animate(withDuration: 0.1) { 46 | self.angryIconView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1) 47 | } completion: { _ in 48 | UIView.animate(withDuration: 0.1) { 49 | self.angryIconView.transform = .identity 50 | } 51 | } 52 | makeVibrate(degree: .medium) 53 | count += 1 54 | makeAngryIcon() 55 | } 56 | } 57 | // MARK: - Custom Method Part 58 | 59 | private func addObserver() { 60 | addObserverAction(.homeButtonClicked) { _ in 61 | self.navigationController?.popViewController(animated: true) 62 | } 63 | addObserverAction(.writeComplete) { _ in 64 | self.navigationController?.popViewController(animated: true) 65 | } 66 | } 67 | 68 | private func makeAngryIcon() { 69 | switch (count) { 70 | case 0 ... 10 : 71 | self.angryIconView.image = ImageLiterals.MainIcon.angryIcon1 72 | level = 1 73 | 74 | case 11 ... 40 : 75 | self.angryIconView.image = ImageLiterals.MainIcon.angryIcon2 76 | level = 2 77 | 78 | 79 | case 41 ... 70 : 80 | self.angryIconView.image = ImageLiterals.MainIcon.angryIcon3 81 | level = 3 82 | 83 | 84 | default : 85 | self.angryIconView.image = ImageLiterals.MainIcon.angryIcon4 86 | level = 4 87 | 88 | 89 | } 90 | } 91 | 92 | 93 | // MARK: - @objc Function Part 94 | 95 | } 96 | // MARK: - Extension Part 97 | 98 | extension TouchKingVC { 99 | 100 | func startTimer() { 101 | 102 | isTimerValid = true 103 | videoTimer?.invalidate() 104 | 105 | videoTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] _ in 106 | self?.timerIsRunning() 107 | }) 108 | RunLoop.current.add(videoTimer!, forMode: RunLoop.Mode.common) 109 | } 110 | 111 | func timerIsRunning() { 112 | 113 | showTimer() 114 | if seconds == 0 { 115 | if minutes != 0 { 116 | minutes -= 1 117 | } 118 | } 119 | 120 | if milliseconds == 0 { 121 | seconds -= 1 122 | } 123 | if seconds < 0 { 124 | seconds = 59 125 | } 126 | milliseconds -= 1 127 | if milliseconds < 0 { 128 | milliseconds = 9 129 | } 130 | if minutes == 0 && seconds == 0 && milliseconds == 0 { 131 | showTimer() 132 | videoTimer?.invalidate() 133 | isTimerValid = false 134 | showWriteVC() 135 | 136 | } 137 | } 138 | 139 | private func showWriteVC() { 140 | let writeVC = ModuleFactory.shared.makeWritingVC() 141 | writeVC.modalTransitionStyle = .crossDissolve 142 | writeVC.modalPresentationStyle = .overCurrentContext 143 | let maxLevel = ( count - 60 ) / 20 144 | writeVC.angryLevel = level 145 | self.present(writeVC, animated: true) 146 | } 147 | 148 | func showTimer() { 149 | let millisecStr = "\(milliseconds)" 150 | let secondsStr = seconds > 9 ? "\(seconds)" : "\(seconds)" 151 | let minutesStr = minutes > 9 ? "\(minutes)" : "0\(minutes)" 152 | 153 | timerLabel.text = secondsStr + " : " + millisecStr 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Presentation/WritingScene/VC/WritingVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WritingVC.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class WritingVC: UIViewController { 11 | 12 | // MARK: - Vars & Lets Part 13 | 14 | var angryLevel = 0 15 | 16 | @IBOutlet weak var writeContainerTopConstraint: NSLayoutConstraint! 17 | 18 | // MARK: - UI Component Part 19 | @IBOutlet weak var writingView: UIView! 20 | @IBOutlet weak var nameLabel: UILabel! 21 | @IBOutlet weak var upsetLabel: UILabel! 22 | @IBOutlet weak var userImgView: UIImageView! 23 | @IBOutlet weak var commentTextField: UITextField! 24 | @IBOutlet weak var finishButton: UIButton! 25 | // MARK: - Life Cycle Part 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | addTapGesture() 30 | writingView.layer.cornerRadius = 10 31 | commentTextField.layer.cornerRadius = 10 32 | writingView.layer.applyShadow(color: .black, alpha: 0.25, x: 2.23, y: 4.47, blur: 4.47, spread: 0) 33 | 34 | } 35 | 36 | override func viewWillAppear(_ animated: Bool) { 37 | registerForKeyboardNotifications() 38 | } 39 | 40 | override func viewDidDisappear(_ animated: Bool) { 41 | unregisterForKeyboardNotifications() 42 | } 43 | // MARK: - IBAction Part 44 | @IBAction func writeFinishButton(_ sender: Any) { 45 | dismiss(animated: true) 46 | postObserverAction(.writeComplete) 47 | 48 | BaseService.default.writeRankList(type: "tab", score: 100, comment: "한줄평", emojiLevel: 2) { result in 49 | 50 | } 51 | } 52 | 53 | 54 | // MARK: - Custom Method Part 55 | 56 | 57 | private func registerForKeyboardNotifications() { 58 | NotificationCenter.default.addObserver(self, selector:#selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) 59 | NotificationCenter.default.addObserver(self, selector:#selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) 60 | } 61 | 62 | private func unregisterForKeyboardNotifications() { 63 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) 64 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) 65 | } 66 | 67 | 68 | @objc private func keyboardWillShow(_ notification: Notification) { 69 | let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as! Double 70 | if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue { 71 | let keyboardRectangle = keyboardFrame.cgRectValue 72 | let keyboardHeight = keyboardRectangle.height 73 | writeContainerTopConstraint.constant = 50 74 | } 75 | 76 | UIView.animate(withDuration: duration, delay: 0){ 77 | self.view.layoutIfNeeded() 78 | } 79 | } 80 | 81 | @objc private func keyboardWillHide(_ notification: Notification) { 82 | let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as! Double 83 | let curve = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as! UInt 84 | writeContainerTopConstraint.constant = 216 85 | UIView.animate(withDuration: duration, delay: 0, options: .init(rawValue: curve)) { 86 | self.view.layoutIfNeeded() 87 | } 88 | } 89 | 90 | private func makeImg() { 91 | 92 | 93 | switch (angryLevel) { 94 | case 1: 95 | self.userImgView.image = ImageLiterals.MainIcon.angryIcon1 96 | 97 | case 2 : 98 | self.userImgView.image = ImageLiterals.MainIcon.angryIcon2 99 | 100 | case 3 : 101 | self.userImgView.image = ImageLiterals.MainIcon.angryIcon3 102 | 103 | default : 104 | self.userImgView.image = ImageLiterals.MainIcon.angryIcon4 105 | 106 | } 107 | } 108 | 109 | // MARK: - @objc Function Part 110 | 111 | } 112 | // MARK: - Extension Part 113 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/PanDirectionGesture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PanDirectionGesture.swift 3 | 4 | 5 | import UIKit 6 | 7 | public enum PanAxis { 8 | case vertical 9 | case horizontal 10 | } 11 | 12 | public enum PanDirection { 13 | case left 14 | case right 15 | case up 16 | case down 17 | case normal 18 | } 19 | 20 | public class PanDirectionGestureRecognizer: UIPanGestureRecognizer { 21 | let axis: PanAxis 22 | let direction: PanDirection 23 | 24 | public init(axis: PanAxis, direction: PanDirection = .normal, target: AnyObject? = nil, action: Selector? = nil) { 25 | self.axis = axis 26 | self.direction = direction 27 | super.init(target: target, action: action) 28 | } 29 | 30 | override public func touchesMoved(_ touches: Set, with event: UIEvent) { 31 | super.touchesMoved(touches, with: event) 32 | 33 | if state == .began { 34 | let vel = velocity(in: view) 35 | switch axis { 36 | case .horizontal where abs(vel.y) > abs(vel.x): 37 | state = .cancelled 38 | case .vertical where abs(vel.x) > abs(vel.y): 39 | state = .cancelled 40 | default: 41 | break 42 | } 43 | 44 | let isIncrement = axis == .horizontal ? vel.x > 0 : vel.y > 0 45 | 46 | switch direction { 47 | case .left where isIncrement: 48 | state = .cancelled 49 | case .right where !isIncrement: 50 | state = .cancelled 51 | case .up where isIncrement: 52 | state = .cancelled 53 | case .down where !isIncrement: 54 | state = .cancelled 55 | default: 56 | break 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/addToolBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // addToolBar.swift 3 | 4 | 5 | import Foundation 6 | import UIKit 7 | 8 | extension UIViewController{ 9 | 10 | public func addToolbar(textfields : [UITextField]){ 11 | let toolBarKeyboard = UIToolbar() 12 | toolBarKeyboard.sizeToFit() 13 | let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) 14 | let btnDoneBar = UIBarButtonItem(title: "닫기", style: .done, target: self, action: #selector(self.dismissKeyBoard)) 15 | toolBarKeyboard.items = [flexSpace,btnDoneBar] 16 | for (_,item) in textfields.enumerated(){ 17 | item.inputAccessoryView = toolBarKeyboard 18 | 19 | } 20 | } 21 | 22 | public func addToolBar(textView : UITextView){ 23 | let toolBarKeyboard = UIToolbar() 24 | toolBarKeyboard.sizeToFit() 25 | let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) 26 | let btnDoneBar = UIBarButtonItem(title: "닫기", style: .done, target: self, action: #selector(self.dismissKeyBoard)) 27 | toolBarKeyboard.items = [flexSpace,btnDoneBar] 28 | toolBarKeyboard.tintColor = .blue 29 | textView.inputAccessoryView = toolBarKeyboard 30 | } 31 | 32 | @objc func dismissKeyBoard(){ 33 | self.view.endEditing(true) 34 | } 35 | 36 | public func addTapGesture(){ 37 | let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyBoard)) 38 | self.view.addGestureRecognizer(tap) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/applyShadow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // applyShadow.swift 3 | 4 | import Foundation 5 | import UIKit 6 | 7 | /** 8 | 9 | - Description: 10 | 11 | View의 Layer 계층(CALayer)에 shadow를 간편하게 입힐 수 있는 메서드입니다. 12 | 피그마에 나와있는 shadow 속성 값을 그대로 기입하면 됩니다! 13 | 14 | */ 15 | 16 | extension CALayer { 17 | func applyShadow( 18 | color: UIColor = .black, 19 | alpha: Float = 0.5, 20 | x: CGFloat = 0, 21 | y: CGFloat = 2, 22 | blur: CGFloat = 4, 23 | spread: CGFloat = 0) { 24 | 25 | masksToBounds = false 26 | shadowColor = color.cgColor 27 | shadowOpacity = alpha 28 | shadowOffset = CGSize(width: x, height: y) 29 | shadowRadius = blur / 2.0 30 | if spread == 0 { 31 | shadowPath = nil 32 | } else { 33 | let dx = -spread 34 | let rect = bounds.insetBy(dx: dx, dy: dx) 35 | shadowPath = UIBezierPath(rect: rect).cgPath 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/calculatePastTime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // calculatePastTime.swift 3 | 4 | 5 | import Foundation 6 | public func calculatePastTime(date : String) -> String 7 | { 8 | 9 | let minute = 60 10 | let hour = minute * 60 11 | let day = hour * 60 12 | let week = day * 7 13 | 14 | var message : String = "" 15 | 16 | let UTCDate = Date() 17 | let formatter = DateFormatter() 18 | formatter.timeZone = TimeZone(secondsFromGMT: 32400) 19 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 20 | let defaultTimeZoneStr = formatter.string(from: UTCDate) 21 | 22 | let format = DateFormatter() 23 | format.dateFormat = "yyyy-MM-dd HH:mm:ss" 24 | format.locale = Locale(identifier: "ko_KR") 25 | 26 | guard let tempDate = format.date(from: date) else {return ""} 27 | let krTime = format.date(from: defaultTimeZoneStr) 28 | 29 | let articleDate = format.string(from: tempDate) 30 | var useTime = Int(krTime!.timeIntervalSince(tempDate)) 31 | useTime = useTime - 32400 32 | 33 | if useTime < minute { 34 | message = "방금 전" 35 | }else if useTime < hour{ 36 | message = String(useTime/minute) + "분 전" 37 | }else if useTime < day{ 38 | message = String(useTime/hour) + "시간 전" 39 | }else if useTime < week{ 40 | message = String(useTime/day) + "일 전" 41 | }else if useTime < week * 4{ 42 | message = String(useTime/week) + "주 전" 43 | }else{ 44 | let timeArray = articleDate.components(separatedBy: " ") 45 | let dateArray = timeArray[0].components(separatedBy: "-") 46 | message = dateArray[1] + "월 " + dateArray[2] + "일" 47 | } 48 | 49 | return message 50 | } 51 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/calculateTopInset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // calculateTopInset.swift 3 | 4 | import Foundation 5 | import UIKit 6 | 7 | /** 8 | - Description: 9 | 10 | 상단의 탑 safe Area를 계산하는 함수입니다 11 | 노치가 있는 경우에는 safe area inset 만큼 음수값을 주고, 12 | 노치가 없는 경우에는 -44로 고정값을 부여합니다 13 | */ 14 | 15 | extension UIViewController { 16 | func calculateTopInset() -> CGFloat { 17 | let windows = UIApplication.shared.windows[0] 18 | let topInset = windows.safeAreaInsets.top 19 | 20 | if UIDevice.current.hasNotch { 21 | return topInset * -1 22 | } else { 23 | return -44 24 | } 25 | } 26 | } 27 | 28 | /** 29 | 30 | - Description: 31 | 32 | 해당 기기가, notch를 가지고 있는지 bottom safe area inset를 계산해서 판단하는 연산 프로퍼티입니다. 33 | 34 | */ 35 | 36 | extension UIDevice { 37 | var hasNotch: Bool { 38 | UIScreen.main.bounds.height > 736 ? true : false 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/delayWithSeconds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // delayWithSeconds.swift 3 | // 30-SOPKATHON-iOS 4 | // 5 | // Created by 송지훈 on 2022/05/22. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIViewController { 11 | func delayWithSeconds(_ seconds: Double, completion: @escaping () -> ()) { 12 | DispatchQueue.main.asyncAfter(deadline: .now() + seconds) { 13 | completion() 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/downloadImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // downloadImage.swift 3 | 4 | 5 | import UIKit 6 | import Kingfisher 7 | 8 | extension NSObject{ 9 | func downloadImage(with urlString : String , imageCompletionHandler: @escaping (UIImage?) -> Void){ 10 | guard let url = URL.init(string: urlString) else { 11 | return imageCompletionHandler(nil) 12 | } 13 | let resource = ImageResource(downloadURL: url) 14 | 15 | KingfisherManager.shared.retrieveImage(with: resource, options: nil, progressBlock: nil) { result in 16 | switch result { 17 | case .success(let value): 18 | imageCompletionHandler(value.image) 19 | case .failure: 20 | imageCompletionHandler(nil) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/getClassName.swift: -------------------------------------------------------------------------------- 1 | // 2 | // getClassName.swift 3 | 4 | 5 | import Foundation 6 | import UIKit 7 | 8 | /** 9 | 10 | - Description: 11 | 12 | 각 VC,TVC,CVC의 className을 String으로 가져올 수 있도록 연산 프로퍼티를 설정합니다. 13 | 요 값들은 나중에 Identifier에 잘 써먹을 수 있습니다 ^__^ 14 | */ 15 | 16 | extension NSObject { 17 | 18 | static var className: String { 19 | NSStringFromClass(self.classForCoder()).components(separatedBy: ".").last! 20 | } 21 | var className: String { 22 | NSStringFromClass(self.classForCoder).components(separatedBy: ".").last! 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/makeAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // makeAlert.swift 3 | 4 | 5 | import UIKit 6 | 7 | /** 8 | 9 | - Description: 10 | 11 | 요청하는(OK,취소)버튼만 있는 UIAlertController를 간편하게 만들기 위한 extension입니다. 12 | 13 | - parameters: 14 | - title: 알림창에 뜨는 타이틀 부분입니다. 15 | - message: 타이틀 밑에 뜨는 메세지 부분입니다. 16 | - okAction: 확인버튼을 눌렀을 때 동작하는 부분입니다. 17 | - cancelAction: 취소버튼을 눌렀을 때 동작하는 부분입니다. 18 | - completion: 해당 UIAlertController가 띄워졌을 때, 동작하는 부분입니다. 19 | 20 | 21 | */ 22 | extension UIViewController { 23 | func makeAlert(title : String, 24 | message : String, 25 | okAction : ((UIAlertAction) -> Void)? = nil, 26 | completion : (() -> Void)? = nil) 27 | { 28 | makeVibrate() 29 | let alertViewController = UIAlertController(title: title, message: message, 30 | preferredStyle: .alert) 31 | let okAction = UIAlertAction(title: "확인", style: .default, handler: okAction) 32 | alertViewController.addAction(okAction) 33 | self.present(alertViewController, animated: true, completion: completion) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/makeVibrate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // makeVibrate.swift 3 | 4 | 5 | import Foundation 6 | import UIKit 7 | 8 | /** 9 | 10 | - Description: 11 | 12 | VC나 View 내에서 해당 함수를 호출하면, 햅틱이 발생하는 메서드입니다. 13 | 버튼을 누르거나 유저에게 특정 행동이 발생했다는 것을 알려주기 위해 다음과 같은 햅틱을 활용합니다. 14 | 15 | - parameters: 16 | - degree: 터치의 세기 정도를 정의합니다. 보통은 medium,light를 제일 많이 활용합니다?! 17 | 따라서 파라미터 기본값을 . medium으로 정의했습니다. 18 | 19 | */ 20 | 21 | extension UIViewController{ 22 | 23 | public func makeVibrate(degree : UIImpactFeedbackGenerator.FeedbackStyle = .medium) 24 | { 25 | let generator = UIImpactFeedbackGenerator(style: degree) 26 | generator.impactOccurred() 27 | } 28 | } 29 | 30 | extension UIView{ 31 | 32 | public func makeVibrate(degree : UIImpactFeedbackGenerator.FeedbackStyle = .medium) 33 | { 34 | let generator = UIImpactFeedbackGenerator(style: degree) 35 | generator.impactOccurred() 36 | } 37 | } 38 | 39 | extension ViewModelType{ 40 | 41 | public func makeVibrate(degree : UIImpactFeedbackGenerator.FeedbackStyle = .medium) 42 | { 43 | let generator = UIImpactFeedbackGenerator(style: degree) 44 | generator.impactOccurred() 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/manageObserverAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // manageObserverAction.swift 3 | 4 | 5 | import Foundation 6 | 7 | extension NSObject{ 8 | func postObserverAction(_ keyName :BaseNotiList, object : Any? = nil){ 9 | NotificationCenter.default.post(name: BaseNotiList.makeNotiName(list: keyName), object: object) 10 | } 11 | 12 | func addObserverAction(_ keyName : BaseNotiList, action : @escaping (Notification) -> ()){ 13 | NotificationCenter.default.addObserver(forName: BaseNotiList.makeNotiName(list: keyName), 14 | object: nil, 15 | queue: nil, 16 | using: action) 17 | } 18 | 19 | func removeObserverAction(_ keyName : BaseNotiList){ 20 | NotificationCenter.default.removeObserver(self, name: BaseNotiList.makeNotiName(list: keyName), object: nil) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/pressAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // pressAction.swift 3 | 4 | 5 | import Foundation 6 | import UIKit 7 | extension UIButton { 8 | 9 | /** 10 | - Description: 11 | button에 대해 addTarget해서 일일이 처리안하고, closure 형태로 동작을 처리하기 위해 다음과 같은 extension을 활용합니다 12 | press를 작성하고, 안에 버튼이 눌렸을 때, 동작하는 함수를 만듭니다. 13 | 14 | clicked(completion : @escaping ((Bool) -> Void)) 함수를 활용해, 15 | 버튼이 눌렸을때, 줄어들었다가 다시 늘어나는 (Popping)효과와 햅틱을 추가해서 16 | 사용자에게 버튼이 눌렸다는 인터렉션을 제공합니다! 17 | 18 | */ 19 | 20 | // iOS14부터 UIAction이 addAction가능하기에... 이전에는 NSObject형태로 등록해서 처리하는 방식으로... 21 | func press(animated : Bool = false,for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping()->()) { 22 | self.addAction(UIAction { (action: UIAction) in closure() 23 | self.makeVibrate(degree: .medium) 24 | if animated {self.clickedAnimation()} 25 | }, for: controlEvents) 26 | } 27 | 28 | // 해당 함수를 통해서 Poppin 효과를 처리합니다. 줄어드는 정도를 조절하고싶다면 ,ScaleX,Y값을 조절합니다(최대값 1) 29 | func clickedAnimation() { 30 | UIView.animate(withDuration: 0.1, animations: { 31 | self.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) }, completion: { (finish: Bool) in 32 | UIView.animate(withDuration: 0.1, animations: { 33 | self.transform = CGAffineTransform.identity 34 | }) 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/setDefaultFonts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // setDefaultFonts.swift 3 | 4 | 5 | import Foundation 6 | import UIKit 7 | 8 | struct AppFontName { 9 | static let bold = "Crayon" 10 | static let regular = "Crayon" 11 | static let light = "Crayon" 12 | } 13 | 14 | extension UIFontDescriptor.AttributeName { 15 | static let nsctFontUIUsage = UIFontDescriptor.AttributeName(rawValue: "NSCTFontUIUsageAttribute") 16 | 17 | } 18 | 19 | extension UIFont { 20 | 21 | @objc class func mySystemFont(ofSize size: CGFloat) -> UIFont { 22 | return UIFont(name: AppFontName.regular, size: size)! 23 | } 24 | 25 | @objc class func myBoldSystemFont(ofSize size: CGFloat) -> UIFont { 26 | return UIFont(name: AppFontName.bold, size: size)! 27 | } 28 | 29 | @objc class func myItalicSystemFont(ofSize size: CGFloat) -> UIFont { 30 | return UIFont(name: AppFontName.light, size: size)! 31 | } 32 | 33 | @objc convenience init(myCoder aDecoder: NSCoder) { 34 | if let fontDescriptor = aDecoder.decodeObject(forKey: "UIFontDescriptor") as? UIFontDescriptor { 35 | if let fontAttribute = fontDescriptor.fontAttributes[.nsctFontUIUsage] as? String { 36 | var fontName = "" 37 | switch fontAttribute { 38 | case "CTFontRegularUsage": 39 | fontName = AppFontName.regular 40 | case "CTFontEmphasizedUsage", "CTFontBoldUsage": 41 | fontName = AppFontName.bold 42 | case "CTFontObliqueUsage": 43 | fontName = AppFontName.light 44 | default: 45 | fontName = AppFontName.regular 46 | } 47 | self.init(name: fontName, size: fontDescriptor.pointSize)! 48 | } else { 49 | self.init(myCoder: aDecoder) 50 | } 51 | } else { 52 | self.init(myCoder: aDecoder) 53 | } 54 | } 55 | 56 | class func overrideInitialize() { 57 | if self == UIFont.self { 58 | let systemFontMethod = class_getClassMethod(self, #selector(systemFont(ofSize:))) 59 | let mySystemFontMethod = class_getClassMethod(self, #selector(mySystemFont(ofSize:))) 60 | method_exchangeImplementations(systemFontMethod!, mySystemFontMethod!) 61 | 62 | let boldSystemFontMethod = class_getClassMethod(self, #selector(boldSystemFont(ofSize:))) 63 | let myBoldSystemFontMethod = class_getClassMethod(self, #selector(myBoldSystemFont(ofSize:))) 64 | method_exchangeImplementations(boldSystemFontMethod!, myBoldSystemFontMethod!) 65 | 66 | let italicSystemFontMethod = class_getClassMethod(self, #selector(italicSystemFont(ofSize:))) 67 | let myItalicSystemFontMethod = class_getClassMethod(self, #selector(myItalicSystemFont(ofSize:))) 68 | method_exchangeImplementations(italicSystemFontMethod!, myItalicSystemFontMethod!) 69 | 70 | let initCoderMethod = class_getInstanceMethod(self, #selector(UIFontDescriptor.init(coder:))) 71 | let myInitCoderMethod = class_getInstanceMethod(self, #selector(UIFont.init(myCoder:))) 72 | method_exchangeImplementations(initCoderMethod!, myInitCoderMethod!) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/setImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // setImage.swift 3 | 4 | 5 | import UIKit 6 | import Kingfisher 7 | 8 | extension UIImageView { 9 | func setImage(with urlString: String, placeholder: String? = nil, completion: ((UIImage?) -> Void)? = nil) { 10 | let cache = ImageCache.default 11 | if urlString == "" { 12 | // URL 빈 이미지로 넘겨 받았을 경우, 아래에 UIImage에 기본 사진을 추가 하면 된다. 13 | self.image = UIImage() 14 | } else { 15 | cache.retrieveImage(forKey: urlString) { result in 16 | result.success { imageCache in 17 | if let image = imageCache.image { 18 | self.image = image 19 | completion?(image) 20 | } else { 21 | self.setNewImage(with: urlString, placeholder: placeholder, completion: completion) 22 | } 23 | }.catch { _ in 24 | self.setNewImage(with: urlString, placeholder: placeholder, completion: completion) 25 | } 26 | } 27 | } 28 | } 29 | 30 | private func setNewImage(with urlString: String, placeholder: String? = "img_placeholder", completion: ((UIImage?) -> Void)? = nil) { 31 | guard let url = URL(string: urlString) else { return } 32 | let resource = ImageResource(downloadURL: url, cacheKey: urlString) 33 | let placeholderImage = UIImage(named: "img_placeholder") 34 | let placeholder = placeholderImage 35 | 36 | self.kf.setImage( 37 | with: resource, 38 | placeholder: placeholder, 39 | options: [ 40 | .scaleFactor(UIScreen.main.scale/4), 41 | .transition(.fade(0.5)), 42 | .cacheMemoryOnly 43 | ], 44 | completionHandler: { result in 45 | result.success { imageResult in 46 | completion?(imageResult.image) 47 | } 48 | } 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/showNetworkAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // showNetworkAlert.swift 3 | 4 | 5 | import UIKit 6 | 7 | extension UIViewController{ 8 | func showNetworkErrorAlert(){ 9 | // makeAlert(title: I18N.Alert.error, message: I18N.Alert.networkError) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/30-SOPKATHON-iOS/Utils/showToastMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // showToastMessage.swift 3 | 4 | 5 | import UIKit 6 | import SnapKit 7 | 8 | extension UIViewController{ 9 | func showToast(message: String){ 10 | Toast.show(message: message, controller: self) 11 | } 12 | } 13 | 14 | final class Toast { 15 | static func show(message: String, controller: UIViewController) { 16 | 17 | let toastContainer = UIView() 18 | let toastLabel = UILabel() 19 | 20 | toastContainer.backgroundColor = UIColor.black.withAlphaComponent(0.7) 21 | toastContainer.alpha = 1 22 | toastContainer.layer.cornerRadius = 5 23 | toastContainer.clipsToBounds = true 24 | toastContainer.isUserInteractionEnabled = false 25 | 26 | toastLabel.textColor = UIColor.white 27 | toastLabel.textAlignment = .center 28 | toastLabel.font = .systemFont(ofSize: 13) 29 | toastLabel.text = message 30 | toastLabel.clipsToBounds = true 31 | toastLabel.numberOfLines = 0 32 | toastLabel.sizeToFit() 33 | 34 | toastContainer.addSubview(toastLabel) 35 | controller.view.addSubview(toastContainer) 36 | 37 | toastContainer.snp.makeConstraints { 38 | $0.center.equalToSuperview() 39 | $0.width.equalTo(toastLabel.frame.width + 20) 40 | $0.height.equalTo(toastLabel.frame.height + 20) 41 | } 42 | 43 | toastLabel.snp.makeConstraints { 44 | $0.center.equalToSuperview() 45 | } 46 | 47 | UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: { 48 | toastContainer.alpha = 1.0 49 | }, completion: { _ in 50 | UIView.animate(withDuration: 0.4, delay: 1.0, options: .curveEaseOut, animations: { 51 | toastContainer.alpha = 0.0 52 | }, completion: {_ in 53 | toastContainer.removeFromSuperview() 54 | }) 55 | }) 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /30-SOPKATHON-iOS/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target '30-SOPKATHON-iOS' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for 30-SOPKATHON-iOS 9 | pod 'SnapKit', '~> 5.0.0' 10 | pod 'SwiftLint' 11 | pod 'KakaoSDKLink' # 메시지(카카오링크) 12 | pod 'KakaoSDKTemplate' # 메시지 템플릿 13 | pod 'KakaoSDKCommon' # 필수 요소를 담은 공통 모듈 14 | pod 'KakaoSDKAuth' # 카카오 로그인 15 | pod 'KakaoSDKUser' # 사용자 관리 16 | pod 'lottie-ios' 17 | pod 'Kingfisher', '~> 5.0' 18 | pod 'Then' 19 | pod 'Moya/RxSwift', '~> 15.0' 20 | pod 'SwiftyJSON', '~> 4.0' 21 | pod 'Firebase/Analytics' 22 | pod 'Firebase/Crashlytics' 23 | pod 'PanModal' 24 | pod 'Gedatsu', configuration: %w(Debug) 25 | pod 'SkeletonView' 26 | pod 'RxSwift' 27 | pod 'RxCocoa' 28 | pod 'RxGesture' 29 | pod 'RxRelay' 30 | end 31 | 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 한(恨)도초과 2 | **내 ‘한’이 초과되는 순간 게임으로 풀어보는 내 마음속 '한'이야기.**
3 | 4 | 5 | 6 | - 누가 널 빡치게 했니? 일상속 생긴 스트레스 나한테 풀고! 얘기해봐! 7 | - 소리지르기/부수기 게임으로 '랭킹'확인 최고의 빡침러가 되고 친구에게 오늘의 빡침을 '공유'해보자! 8 | - 게임 기록 '회상'을 통해 나의 빡침 일상을 되돌아보고 성장하자! 9 | 10 |
11 | 12 | ## 🤸🏻‍♀️ GUI Flow 13 | 14 | 15 | ## 🍎 iOS Developer 16 | - 지훈 17 | ``` 18 | - 프로젝트 셋팅 19 | - 데시벨 측정 게임 뷰 (타이머 기능 및 데시벨에 따른 이미지 이동) 20 | - 탭 게임 뷰 (타이머 기능 및 탭 수에 따른 이미지 변경) 21 | - 스플래시 22 | - 서버 통신 23 | - 카카오톡 공유 기능 24 | ``` 25 | 26 | - 주현 27 | ``` 28 | - 홈 뷰 (게임 선택) 29 | - 피드 뷰 (개인 데시벨 측정 기록) 30 | ``` 31 | 32 | - 윤서 33 | ``` 34 | - 작성 뷰 (감정 기록) 35 | - 랭킹 뷰 (게임에 따른 랭킹) 36 | ``` 37 | 38 |
39 | 40 | ## 💻 Coding Convention 41 | https://flaxen-warlock-70e.notion.site/5ea6de813b04448db1ec2441c3943e38 42 | 43 | ## 🗂 Foldering Convention 44 | https://flaxen-warlock-70e.notion.site/e6de1c39e1ae46858c35c89fdc1f993f 45 | 46 | ## ✨Github Convention 47 | https://flaxen-warlock-70e.notion.site/ec5a0d076e794b12b45f507708533a35 48 | 49 |
50 | 51 | ## 🚨 구현 여부 및 참여도 52 | | 기능 | 구현 여부 | 개발자 | 53 | | :--: | :-----: | :--: | 54 | | UI | ✔️ | 지훈, 주현, 윤서 | 55 | | 서버 연결 | ✔️ | 지훈 | 56 | 57 | ## 🥹 Album 58 |
59 | 😎 60 |
61 | 62 | image 63 | 64 | image 65 | 66 | image 67 | 68 | 69 |
70 |
71 | --------------------------------------------------------------------------------