├── .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 |

63 |
64 |

65 |
66 |

67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------