diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8edfb9b --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.pbxproj binary merge=union diff --git a/.github/ISSUE_TEMPLATE/seemeet-bug-issue-template.md b/.github/ISSUE_TEMPLATE/seemeet-bug-issue-template.md new file mode 100644 index 0000000..d8774ea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/seemeet-bug-issue-template.md @@ -0,0 +1,15 @@ +--- +name: SeeMeet Bug Issue Template +about: 씨-밋 버그 이슈 템플릿 +title: "[BUG]" +labels: '' +assignees: '' + +--- + +## 🐞 버그 설명 + + + +## 📝 todo +- [ ] todo diff --git a/.github/ISSUE_TEMPLATE/seemeet-feature-issue-template.md b/.github/ISSUE_TEMPLATE/seemeet-feature-issue-template.md new file mode 100644 index 0000000..2e3fd03 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/seemeet-feature-issue-template.md @@ -0,0 +1,15 @@ +--- +name: SeeMeet Feature Issue Template +about: 씨-밋 기능 이슈 템플릿 +title: "[FEAT]" +labels: '' +assignees: '' + +--- + +## 📌 Feature Issue + + +## 📝 To-do + +- [ ] todo ! diff --git a/.github/ISSUE_TEMPLATE/seemeet-hotfix-issue-template.md b/.github/ISSUE_TEMPLATE/seemeet-hotfix-issue-template.md new file mode 100644 index 0000000..51874c2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/seemeet-hotfix-issue-template.md @@ -0,0 +1,16 @@ +--- +name: SeeMeet Hotfix Issue Template +about: 씨-밋 핫픽스 이슈 템플릿 +title: "[HOTFIX]" +labels: '' +assignees: '' + +--- + +## 🐞 버그 설명 + + + + +## 📝 todo +- [ ] todo diff --git a/.github/ISSUE_TEMPLATE/seemeet-refactoring-issue-template.md b/.github/ISSUE_TEMPLATE/seemeet-refactoring-issue-template.md new file mode 100644 index 0000000..22a8c66 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/seemeet-refactoring-issue-template.md @@ -0,0 +1,15 @@ +--- +name: SeeMeet Refactor Issue Template +about: 씨-밋 리팩토링 이슈 템플릿 +title: "[REFACTOR]" +labels: '' +assignees: '' + +--- + +## 🔨 Refactor Issue + + +## 📝 To-do + +- [ ] todo ! diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..249e00c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +## 📌 관련 이슈 + + + +## 📌 변경 사항 및 이유 + + + +## 📌 PR Point + + + +## 📌 참고 사항 + + +## 📌 구동 영상 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..334ff21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,146 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/swift,xcode,cocoapods,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=swift,xcode,cocoapods,macos + +### CocoaPods ### +## CocoaPods GitIgnore Template + +# CocoaPods - Only use to conserve bandwidth / Save time on Pushing +# - Also handy if you have a large number of dependant pods +# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE +*/Pods/ +*/.Podfile.swp +SeeMeet/Podfile.lock + + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Xcode ### +# Xcode +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + + + + +## Gcc Patch +/*.gcno + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcworkspace/contents.xcworkspacedata +**/xcshareddata/WorkspaceSettings.xcsettings + +# End of https://www.toptal.com/developers/gitignore/api/swift,xcode,cocoapods,macos diff --git a/README.md b/README.md new file mode 100644 index 0000000..26f389d --- /dev/null +++ b/README.md @@ -0,0 +1,175 @@ +# 씨밋 - SeeMeet +> **약속부터 만남까지, 더 가까운 우리 사이 SeeMeet**
+> +> SOPT 29th APP JAM
+> 프로젝트 기간 : 2022.1.3 ~ + + + + + +
+ +
+ +## SeeMeet iOS Contributors + | ![KakaoTalk_Photo_2022-01-12-23-24-01](https://user-images.githubusercontent.com/51031771/149158747-9d7343b9-932b-40c7-87fd-996a8db21ae3.jpeg) | ![KakaoTalk_Photo_2022-01-12-23-25-07](https://user-images.githubusercontent.com/51031771/149158516-134a88b5-d165-48f9-a231-d712ee093eab.jpeg) | + :---------:|:----------:|:---------: + 🍎 박익범 | 🍎 김인환 | 🍎 이유진 + [swikkft](https://github.com/parkikbum) | [loinsir](https://github.com/loinsir) | [yujinnee](https://github.com/yujinnee) + +
+
+ +## Development Environment and Using Library +- Development Environment +

+ + + + + Instagram: SeeMeet_Official + +

+ +- Architecture + +MVC-C 기반의 아키텍처로, 애플 MVC에 코디네이터 패턴을 적용해 화면 전환 로직을 분리시켰습니다. + +- Library + +라이브러리 | 사용 목적 | Version +:---------:|:----------:|:---------: + Alamofire | 서버 통신 | 5.4 + SnapKit | UI Layout | 5.0.0 + Then | code Support | - + FSCalendar | 캘린더 | - + RxSwift | 비동기 반응형 프로그래밍 | - + Kingfisher | 프로필 이미지 처리 | - + kakao-ios-sdk | 카카오 로그인 | - + Cocoapods | 의존성 라이브러리 관리 | - + +- framework + +프레임워크 | 사용 목적 +:---------:|:----------: + UIKit |   + +
+
+ +## Our Convention +
+ ⚡ Git Branch Convention +
+ + --- + + - **Branch Naming Rule** + - Issue 작성 후 생성되는 번호와 Issue의 간략한 설명 등을 조합하여 Branch 이름 결정 + - `/-` +- **Commit Message Rule** + - `[Prefix] : - ` +- **Code Review Rule** + - 리뷰를 합리적, 중립적으로 받아들이기 (무조껀 좋아 무조껀 싫어는 곤란합니다^^) + - 반영이 어렵다면, 왜 어려운지 합리적인 이유를 대야 함 + - 모든 리뷰는 합리적 판단에 의거하여 한번 더 생각할 수 있는 기회가 될 수 있도록 함 + +
+ +
+
+ +
+ ⚡ Git Flow +
+ + --- + + ``` +1. Issue 생성 : 담당자, 라벨(우선순위,담당자라벨), 프로젝트 연결 + +2. 로컬에서 develop 최신화 : git pull (origin develop) + +3. feature Branch 생성⭐️ : git switch -c Prefix/IssueNumber-description + +4. Add - Commit - Push - Pull Request 의 과정을 거친다. + ⚠️ commit template 사용하여 이슈번호쓰기 ex. [CHORE] : #12 - UIstyle 적용 + +5. Pull Request 작성 + closed: #IssueNumber로 이슈 연결, 프로젝트 연결, 리뷰어 지정 + +5. Code Review 완료 → Pull Request 작성자가 develop Branch로 merge + +6. 종료된 Issue와 Pull Request의 Label과 Project를 관리 +``` + +
+ +
+
+ +
+ ⚡ Naming Convention +
+ + --- + +- 함수 : **lowerCamelCase** 사용하고 동사로 시작 +- 변수, 상수 : **lowerCamelCase** 사용 +- 클래스 : **UpperCamelCase** 사용 +- 파일명 (약어사용) + - ViewController → `VC` + - TableViewCell → `TVC` + - CollectionViewCell → `CVC` +
+ +
+
+ +
+ ⚡ Foldering Convention +
+ + --- + + + +
+ +
+
+ +## 구현한 부분 + +대분류 | 기능 | 구현 여부 | 담당자 +:---------:|---------|:----------:|:---------: + Auth | 스플래시 |O| - +   | 회원가입 |O| 박익범 +   | 로그인 |O| 박익범 +   | 카카오로그인 |O| 김인환, 이유진 +   | 애플로그인 |O| 김인환, 이유진 + 메인뷰 | 메인뷰 컨텐츠 |O| 박익범 +   | 마이페이지 |O| 박익범 + 친구 | 친구목록 |O| 김인환 +   | 친구관리 |O| 김인환 + 캘린더| 캘린더뷰 |O| 김인환 +   | 캘린더상세 데이터 |O| 김인환 + 약속신청| 보낼친구검색 |O| 이유진 +   | 약속내용설정 |O| 이유진 +   | 약속기한설정 |O| 이유진 + 약속리스트| 약속리스트 |O| 박익범 +   | 받은약속/응답 |O| 박익범 +   | 보낸약속/응답 |O| 박익범 +   | 마이페이지 |O| 이유진 + 리팩토링 |   |~| 이유진, 김인환 + +
+
+ + 아요 최종과제 제출 ㅋ.ㅋ
+ 링크 +
+ + +--- diff --git a/SeeMeet/Podfile b/SeeMeet/Podfile new file mode 100644 index 0000000..0619212 --- /dev/null +++ b/SeeMeet/Podfile @@ -0,0 +1,32 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' +# Comment the next line if you don't want to use dynamic frameworks +use_frameworks! + +def pods + pod 'SnapKit', '~> 5.0.0' + pod 'Then' + pod 'FSCalendar' + pod 'Firebase/Analytics' + pod 'Firebase/Crashlytics' + pod 'FirebaseMessaging' + + pod 'KakaoSDKCommon' + pod 'KakaoSDKAuth' + pod 'KakaoSDKUser' + pod 'KakaoSDKTalk' + pod 'KakaoSDKShare' + + pod 'Kingfisher', '~> 7.0' + + pod 'RxSwift', '~> 6.5' + pod 'RxCocoa', '6.5.0' + pod 'RxRelay', '~> 6.5' + + +end + + +target 'SeeMeet' do + pods +end diff --git a/SeeMeet/SeeMeet.xcodeproj/project.pbxproj b/SeeMeet/SeeMeet.xcodeproj/project.pbxproj new file mode 100644 index 0000000..bede790 --- /dev/null +++ b/SeeMeet/SeeMeet.xcodeproj/project.pbxproj @@ -0,0 +1,1704 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 120FFED660BC55B756EBD72A /* Pods_SeeMeet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E4152A410430753EE447189 /* Pods_SeeMeet.framework */; }; + 1906B3EE27CA804D00F94DD8 /* ConfirmTimeOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1906B3ED27CA804D00F94DD8 /* ConfirmTimeOptionView.swift */; }; + 1906B3F727D496A000F94DD8 /* KakaoAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1906B3F627D496A000F94DD8 /* KakaoAuthService.swift */; }; + 19110ACC27D71E9D001121AE /* AvailTimeOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19110ACB27D71E9D001121AE /* AvailTimeOptionView.swift */; }; + 19110AD127D73AB5001121AE /* AvailTimeOptionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 19110AD027D73AB5001121AE /* AvailTimeOptionView.xib */; }; + 1912FCAE278AB7A000F33952 /* CalendarVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1912FCAD278AB7A000F33952 /* CalendarVC.swift */; }; + 1912FCB0278AB7BB00F33952 /* Calendar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1912FCAF278AB7BB00F33952 /* Calendar.storyboard */; }; + 191779992859A1A5004E7E63 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 191779982859A1A5004E7E63 /* GoogleService-Info.plist */; }; + 1919F5D4278B5E18009F33AE /* CalendarPlansCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1919F5D2278B5E18009F33AE /* CalendarPlansCVC.swift */; }; + 1919F5D5278B5E18009F33AE /* CalendarPlansCVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1919F5D3278B5E18009F33AE /* CalendarPlansCVC.xib */; }; + 191E8C18278F01C60042B8F1 /* SMPopUpVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191E8C17278F01C60042B8F1 /* SMPopUpVC.swift */; }; + 191E8C1A278F01DD0042B8F1 /* SMPopUp.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 191E8C19278F01DD0042B8F1 /* SMPopUp.storyboard */; }; + 191E8C1C278F38DC0042B8F1 /* String++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191E8C1B278F38DC0042B8F1 /* String++.swift */; }; + 191E8C1F278F477B0042B8F1 /* FriendsListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191E8C1E278F477B0042B8F1 /* FriendsListVC.swift */; }; + 191E8C22278F47990042B8F1 /* FriendsList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 191E8C21278F47990042B8F1 /* FriendsList.storyboard */; }; + 191E8C26278F490A0042B8F1 /* FriendsListTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191E8C24278F490A0042B8F1 /* FriendsListTVC.swift */; }; + 191E8C27278F490A0042B8F1 /* FriendsListTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 191E8C25278F490A0042B8F1 /* FriendsListTVC.xib */; }; + 191E8C2A278FFB870042B8F1 /* SMSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191E8C29278FFB870042B8F1 /* SMSearchBar.swift */; }; + 191F1BFC28B650E40007E375 /* Notification++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191F1BFB28B650E40007E375 /* Notification++.swift */; }; + 192C6EF52877B32000490821 /* SearchInputFieldCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192C6EF42877B32000490821 /* SearchInputFieldCVC.swift */; }; + 194F91022847247900E95333 /* PlansCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194F91012847247900E95333 /* PlansCoordinator.swift */; }; + 19509B652796C00D00CAA4C2 /* MonthlyPlansResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19509B642796C00D00CAA4C2 /* MonthlyPlansResponseModel.swift */; }; + 19509B672796C25100CAA4C2 /* CalendarService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19509B662796C25100CAA4C2 /* CalendarService.swift */; }; + 19509B692798583500CAA4C2 /* FriendsSearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19509B682798583500CAA4C2 /* FriendsSearchService.swift */; }; + 19509B6B27986D2A00CAA4C2 /* FriendsSearchResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19509B6A27986D2A00CAA4C2 /* FriendsSearchResponseModel.swift */; }; + 19509B6D27989CD800CAA4C2 /* FriendsAddResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19509B6C27989CD800CAA4C2 /* FriendsAddResponseModel.swift */; }; + 19509B6F27989DAF00CAA4C2 /* FriendsAddService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19509B6E27989DAF00CAA4C2 /* FriendsAddService.swift */; }; + 1950E5142791DAA300ED357B /* FriendsAddVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1950E5132791DAA300ED357B /* FriendsAddVC.swift */; }; + 1950E5162791DAC900ED357B /* FriendsAdd.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1950E5152791DAC900ED357B /* FriendsAdd.storyboard */; }; + 1950E51B2791FE7B00ED357B /* FriendsAddTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1950E5192791FE7B00ED357B /* FriendsAddTVC.swift */; }; + 1950E51C2791FE7B00ED357B /* FriendsAddTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1950E51A2791FE7B00ED357B /* FriendsAddTVC.xib */; }; + 1960D42027B10C93002558C4 /* SelectedDateSheetTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1960D41E27B10C93002558C4 /* SelectedDateSheetTVC.swift */; }; + 1960D42127B10C93002558C4 /* SelectedDateSheetTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1960D41F27B10C93002558C4 /* SelectedDateSheetTVC.xib */; }; + 1969D94028B0E57D009BCCA0 /* PostRefreshAccessTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1969D93F28B0E57D009BCCA0 /* PostRefreshAccessTokenService.swift */; }; + 1969D94228B0EA65009BCCA0 /* AccessTokenRefreshDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1969D94128B0EA65009BCCA0 /* AccessTokenRefreshDataModel.swift */; }; + 1969D94428B100E0009BCCA0 /* AccessInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1969D94328B100E0009BCCA0 /* AccessInterceptor.swift */; }; + 197253FA27E2EF56004BBBB7 /* SocialLoginDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 197253F927E2EF56004BBBB7 /* SocialLoginDataModel.swift */; }; + 197F57B5279913C000FB4467 /* PlanDetailResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 197F57B4279913C000FB4467 /* PlanDetailResponseModel.swift */; }; + 1985C742283CAA2400E7A147 /* CalendarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1985C741283CAA2400E7A147 /* CalendarCoordinator.swift */; }; + 19A0256827D73CC400ADB0C5 /* ConfirmTimeOptionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 19A0256727D73CC400ADB0C5 /* ConfirmTimeOptionView.xib */; }; + 19B1E6A12856007F009D6507 /* FriendsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B1E6A02856007F009D6507 /* FriendsCoordinator.swift */; }; + 19B1E6A32856054D009D6507 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B1E6A22856054D009D6507 /* LoginCoordinator.swift */; }; + 19B1E6A528562335009D6507 /* RegisterCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B1E6A428562335009D6507 /* RegisterCoordinator.swift */; }; + 19B1E6A728563B8F009D6507 /* RequestCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B1E6A628563B8F009D6507 /* RequestCoordinator.swift */; }; + 19B635D628A510180097895A /* PostPushNotificationSetService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B635D528A510180097895A /* PostPushNotificationSetService.swift */; }; + 19B635D828A5134C0097895A /* NotificationSetResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B635D728A5134C0097895A /* NotificationSetResponseModel.swift */; }; + 19B7C1EC27F017A2004A2F68 /* UserDefaults++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B7C1EB27F017A2004A2F68 /* UserDefaults++.swift */; }; + 19CEDB842813142C006E1DFD /* PutNameAndIdResponseDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19CEDB832813142C006E1DFD /* PutNameAndIdResponseDataModel.swift */; }; + 19CEDB86281314FE006E1DFD /* PutNameAndIdService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19CEDB85281314FE006E1DFD /* PutNameAndIdService.swift */; }; + 19D7B2DC2838BC1900EC5EBD /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D7B2DB2838BC1900EC5EBD /* Coordinator.swift */; }; + 19D7B2DE2838C02800EC5EBD /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D7B2DD2838C02800EC5EBD /* HomeCoordinator.swift */; }; + 19D7B2E02838C04100EC5EBD /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D7B2DF2838C04100EC5EBD /* AppCoordinator.swift */; }; + 19E3BE4328A1E2CC00F0EC86 /* PutPlanDeleteService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E3BE4228A1E2CC00F0EC86 /* PutPlanDeleteService.swift */; }; + 19E3BE4528A1E5CF00F0EC86 /* PlansDeleteResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E3BE4428A1E5CF00F0EC86 /* PlansDeleteResponseModel.swift */; }; + 19E3BE5828A37EC000F0EC86 /* NetworkRechabilityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E3BE5728A37EC000F0EC86 /* NetworkRechabilityService.swift */; }; + 19E3BE5A28A38BB300F0EC86 /* PutInvitationListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E3BE5928A38BB300F0EC86 /* PutInvitationListService.swift */; }; + 19E3BE5C28A38D7E00F0EC86 /* InvitationListCancelDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E3BE5B28A38D7E00F0EC86 /* InvitationListCancelDataModel.swift */; }; + 19E633DE278D84F200776D0C /* CalendarDetailVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E633DD278D84F200776D0C /* CalendarDetailVC.swift */; }; + 19E633E1278D853500776D0C /* CalendarDetail.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 19E633E0278D853500776D0C /* CalendarDetail.storyboard */; }; + 19F1AF722792AB0300022A6A /* SelectedDateSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F1AF712792AB0300022A6A /* SelectedDateSheet.swift */; }; + 19F1AF7A2793453700022A6A /* SelectedDateSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 19F1AF792793453700022A6A /* SelectedDateSheet.xib */; }; + 360E6F4627898D4400DACFD8 /* HomeEventCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360E6F4427898D4400DACFD8 /* HomeEventCVC.swift */; }; + 360E6F4727898D4400DACFD8 /* HomeEventCVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 360E6F4527898D4400DACFD8 /* HomeEventCVC.xib */; }; + 3623EA83278F65AB00853F87 /* ProgressReceiveCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EA81278F65AB00853F87 /* ProgressReceiveCVC.swift */; }; + 3623EA84278F65AB00853F87 /* ProgressSendCVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3623EA82278F65AB00853F87 /* ProgressSendCVC.xib */; }; + 3623EA87278F686A00853F87 /* ProgressSendCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EA85278F686A00853F87 /* ProgressSendCVC.swift */; }; + 3623EA88278F686A00853F87 /* ProgressReceiveCVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3623EA86278F686A00853F87 /* ProgressReceiveCVC.xib */; }; + 3623EA8F278FDE0C00853F87 /* CompletePlansCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EA8D278FDE0C00853F87 /* CompletePlansCVC.swift */; }; + 3623EA90278FDE0C00853F87 /* CompletePlansCVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3623EA8E278FDE0C00853F87 /* CompletePlansCVC.xib */; }; + 3623EA922791282700853F87 /* PlansReceiveVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EA912791282700853F87 /* PlansReceiveVC.swift */; }; + 3623EA942791283600853F87 /* PlansReceiveList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3623EA932791283600853F87 /* PlansReceiveList.storyboard */; }; + 3623EA9A2792A8D100853F87 /* SMRequestPopUpVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EA992792A8D100853F87 /* SMRequestPopUpVC.swift */; }; + 3623EA9C2792A90E00853F87 /* SMRequestPopUp.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3623EA9B2792A90E00853F87 /* SMRequestPopUp.storyboard */; }; + 3623EA9E27931EAC00853F87 /* PlansSendListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EA9D27931EAC00853F87 /* PlansSendListVC.swift */; }; + 3623EAA027931EC000853F87 /* PlansSendList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3623EA9F27931EC000853F87 /* PlansSendList.storyboard */; }; + 3623EAA42795437400853F87 /* EmailLoginVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAA32795437400853F87 /* EmailLoginVC.swift */; }; + 3623EAA62795437D00853F87 /* EmailLogin.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3623EAA52795437D00853F87 /* EmailLogin.storyboard */; }; + 3623EAAA2795701D00853F87 /* GrayTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAA92795701D00853F87 /* GrayTextField.swift */; }; + 3623EAAC279570A000853F87 /* GrayTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAAB279570A000853F87 /* GrayTextView.swift */; }; + 3623EAAD2795928000853F87 /* PlansReceiveCVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3623EA962791D1D300853F87 /* PlansReceiveCVC.xib */; }; + 3623EAAE2795929000853F87 /* PlansReceiveCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EA952791D1D300853F87 /* PlansReceiveCVC.swift */; }; + 3623EAB22796A77A00853F87 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAB12796A77A00853F87 /* Constants.swift */; }; + 3623EAB42796A7DE00853F87 /* NetworkResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAB32796A7DE00853F87 /* NetworkResult.swift */; }; + 3623EAB92796AA0600853F87 /* EmailRegisterVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAB82796AA0600853F87 /* EmailRegisterVC.swift */; }; + 3623EABC2796AA2800853F87 /* EmailRegister.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3623EABB2796AA2800853F87 /* EmailRegister.storyboard */; }; + 3623EABE2796E76500853F87 /* RegisterDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EABD2796E76500853F87 /* RegisterDataModel.swift */; }; + 3623EAC02796ED1000853F87 /* PostRegisterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EABF2796ED1000853F87 /* PostRegisterService.swift */; }; + 3623EAC22796F8A500853F87 /* TokenUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAC12796F8A500853F87 /* TokenUtils.swift */; }; + 3623EAC42797038100853F87 /* PostLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAC32797038100853F87 /* PostLoginService.swift */; }; + 3623EAC6279705C500853F87 /* ToastMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAC5279705C500853F87 /* ToastMessageView.swift */; }; + 3623EACC2797324600853F87 /* HomeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EACB2797324600853F87 /* HomeDataModel.swift */; }; + 3623EACE2797330800853F87 /* GetHomeDataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EACD2797330800853F87 /* GetHomeDataService.swift */; }; + 3623EAD02797D6FA00853F87 /* LastDateDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EACF2797D6FA00853F87 /* LastDateDataModel.swift */; }; + 3623EAD22797D72500853F87 /* GetLastDateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAD12797D72500853F87 /* GetLastDateService.swift */; }; + 3623EAD42797DAE900853F87 /* FriendsListDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAD32797DAE900853F87 /* FriendsListDataModel.swift */; }; + 3623EAD62797DC4500853F87 /* GetFriendsListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAD52797DC4500853F87 /* GetFriendsListService.swift */; }; + 3623EAD827980EC000853F87 /* PlansListDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAD727980EC000853F87 /* PlansListDataModel.swift */; }; + 3623EADA27980F0600853F87 /* GetPlansListDataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAD927980F0600853F87 /* GetPlansListDataService.swift */; }; + 3623EADC27988AFE00853F87 /* PlansDetailDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EADB27988AFE00853F87 /* PlansDetailDataModel.swift */; }; + 3623EADE27988B8C00853F87 /* GetPlansDetailService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EADD27988B8C00853F87 /* GetPlansDetailService.swift */; }; + 3623EAE02798AF8A00853F87 /* ResponseDateDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EADF2798AF8A00853F87 /* ResponseDateDataModel.swift */; }; + 3623EAE22798B06C00853F87 /* GetResponseDateDataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAE12798B06C00853F87 /* GetResponseDateDataService.swift */; }; + 3623EAE427994D7A00853F87 /* InvitationPlansDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAE327994D7A00853F87 /* InvitationPlansDataModel.swift */; }; + 3623EAE627994DE400853F87 /* PostInvitationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAE527994DE400853F87 /* PostInvitationService.swift */; }; + 3623EAE82799A39900853F87 /* InvitationRejectDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAE72799A39900853F87 /* InvitationRejectDataModel.swift */; }; + 3623EAEA2799A3D700853F87 /* PostInvitationRejectService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAE92799A3D700853F87 /* PostInvitationRejectService.swift */; }; + 3623EAEC2799D20C00853F87 /* PlansSendDetailDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAEB2799D20C00853F87 /* PlansSendDetailDataModel.swift */; }; + 3623EAEE2799D28800853F87 /* GetPlansSendDetailService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAED2799D28800853F87 /* GetPlansSendDetailService.swift */; }; + 3623EAF02799F07B00853F87 /* PlansRequestAcceptDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAEF2799F07B00853F87 /* PlansRequestAcceptDataModel.swift */; }; + 3623EAF22799F11500853F87 /* PostPlansRequestAcceptService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAF12799F11500853F87 /* PostPlansRequestAcceptService.swift */; }; + 3623EAF42799F61200853F87 /* InvitationCancelDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAF32799F61200853F87 /* InvitationCancelDataModel.swift */; }; + 3623EAF62799F65900853F87 /* PutInvitationCancelService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAF52799F65900853F87 /* PutInvitationCancelService.swift */; }; + 3623EAF8279A9EA500853F87 /* MyPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3623EAF7279A9EA500853F87 /* MyPageView.swift */; }; + 36545312278EA30400A0770F /* UILabel++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36545311278EA30400A0770F /* UILabel++.swift */; }; + 3682123E2785E643005D7236 /* DINNeuzeitGroteskStd-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = 3682122F2785E642005D7236 /* DINNeuzeitGroteskStd-Light.otf */; }; + 3682123F2785E643005D7236 /* SpoqaHanSansNeo-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 368212302785E642005D7236 /* SpoqaHanSansNeo-Light.ttf */; }; + 368212402785E643005D7236 /* DINMittelschriftStd.otf in Resources */ = {isa = PBXBuildFile; fileRef = 368212312785E642005D7236 /* DINMittelschriftStd.otf */; }; + 368212412785E643005D7236 /* DINPro-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = 368212322785E642005D7236 /* DINPro-Medium.otf */; }; + 368212422785E643005D7236 /* SpoqaHanSansNeo-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 368212332785E642005D7236 /* SpoqaHanSansNeo-Thin.ttf */; }; + 368212432785E643005D7236 /* DINPro-Black.otf in Resources */ = {isa = PBXBuildFile; fileRef = 368212342785E642005D7236 /* DINPro-Black.otf */; }; + 368212442785E643005D7236 /* SpoqaHanSansNeo-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 368212352785E642005D7236 /* SpoqaHanSansNeo-Regular.ttf */; }; + 368212452785E643005D7236 /* DINPro-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 368212362785E642005D7236 /* DINPro-Regular.otf */; }; + 368212462785E643005D7236 /* DINNeuzeitGroteskStd-BdCond.otf in Resources */ = {isa = PBXBuildFile; fileRef = 368212372785E642005D7236 /* DINNeuzeitGroteskStd-BdCond.otf */; }; + 368212472785E643005D7236 /* DINEngschriftStd.otf in Resources */ = {isa = PBXBuildFile; fileRef = 368212382785E642005D7236 /* DINEngschriftStd.otf */; }; + 368212482785E643005D7236 /* DINPro-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 368212392785E642005D7236 /* DINPro-Bold.otf */; }; + 368212492785E643005D7236 /* SpoqaHanSansNeo-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3682123A2785E642005D7236 /* SpoqaHanSansNeo-Medium.ttf */; }; + 3682124A2785E643005D7236 /* DINPro-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = 3682123B2785E642005D7236 /* DINPro-Light.otf */; }; + 3682124B2785E643005D7236 /* SpoqaHanSansNeo-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3682123C2785E642005D7236 /* SpoqaHanSansNeo-Bold.ttf */; }; + 3682124C2785E643005D7236 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 3682123D2785E642005D7236 /* LICENSE */; }; + 36D9E8AF2789A7BF00D5244D /* Tabbar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 36D9E8AE2789A7BF00D5244D /* Tabbar.storyboard */; }; + 36D9E8B12789A81A00D5244D /* TabbarVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36D9E8B02789A81A00D5244D /* TabbarVC.swift */; }; + 36D9E8B8278BDCCC00D5244D /* PlansListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36D9E8B7278BDCCC00D5244D /* PlansListVC.swift */; }; + 36D9E8BB278BDCE800D5244D /* PlansList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 36D9E8BA278BDCE800D5244D /* PlansList.storyboard */; }; + 36EFE2ED2785682200466818 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EFE2EC2785682200466818 /* AppDelegate.swift */; }; + 36EFE2EF2785682200466818 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EFE2EE2785682200466818 /* SceneDelegate.swift */; }; + 36EFE2F62785682400466818 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 36EFE2F52785682400466818 /* Assets.xcassets */; }; + 36EFE2F92785682400466818 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 36EFE2F72785682400466818 /* LaunchScreen.storyboard */; }; + 36EFE30F2785C8CA00466818 /* Home.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 36EFE30E2785C8CA00466818 /* Home.storyboard */; }; + 36EFE3112785C8D600466818 /* HomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EFE3102785C8D600466818 /* HomeVC.swift */; }; + 36EFE31A2785CC3B00466818 /* UIViewController++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EFE3122785CC3B00466818 /* UIViewController++.swift */; }; + 36EFE31B2785CC3B00466818 /* UIScreen++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EFE3132785CC3B00466818 /* UIScreen++.swift */; }; + 36EFE31C2785CC3B00466818 /* UICollectionView++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EFE3142785CC3B00466818 /* UICollectionView++.swift */; }; + 36EFE31D2785CC3B00466818 /* UIColor++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EFE3152785CC3B00466818 /* UIColor++.swift */; }; + 36EFE31E2785CC3B00466818 /* UIView++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EFE3162785CC3B00466818 /* UIView++.swift */; }; + 36EFE31F2785CC3B00466818 /* UIFont++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EFE3172785CC3B00466818 /* UIFont++.swift */; }; + 36EFE3202785CC3B00466818 /* UITabelView++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EFE3182785CC3B00466818 /* UITabelView++.swift */; }; + 36EFE3212785CC3B00466818 /* Date++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EFE3192785CC3B00466818 /* Date++.swift */; }; + 6A139082289D72C40029FFD1 /* EmailLoginResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A139081289D72C40029FFD1 /* EmailLoginResponseModel.swift */; }; + 6A16EFA7286A265F00387C0B /* UserDefaultsKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A16EFA6286A265F00387C0B /* UserDefaultsKey.swift */; }; + 6A226DCA27D4B73E003B5FC4 /* MainLoginVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A226DC927D4B73E003B5FC4 /* MainLoginVC.swift */; }; + 6A226DCC27D5174A003B5FC4 /* MainLogin.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6A226DCB27D5174A003B5FC4 /* MainLogin.storyboard */; }; + 6A2BEFFD28993A9B001BC91F /* MyPageCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2BEFFC28993A9B001BC91F /* MyPageCoordinator.swift */; }; + 6A39C2F0286EDAE000BA31CF /* ImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A39C2EF286EDAE000BA31CF /* ImageManager.swift */; }; + 6A3B24C1284C80A400DA939F /* UserInfo.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6A3B24C0284C80A400DA939F /* UserInfo.storyboard */; }; + 6A3B24C5284C80E900DA939F /* UserInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3B24C4284C80E900DA939F /* UserInfoVC.swift */; }; + 6A3B24C7284CC50600DA939F /* ChangePassword.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6A3B24C6284CC50600DA939F /* ChangePassword.storyboard */; }; + 6A3B24CB284CC53500DA939F /* ChangePasswordVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3B24CA284CC53500DA939F /* ChangePasswordVC.swift */; }; + 6A4A20ED278B5F7000DD4DCD /* RequestPlansContentsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4A20EC278B5F7000DD4DCD /* RequestPlansContentsVC.swift */; }; + 6A4A20F0278B5FE300DD4DCD /* RequestPlansContents.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6A4A20EF278B5FE300DD4DCD /* RequestPlansContents.storyboard */; }; + 6A5FCEE627E640C200FEA926 /* AppleAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A5FCEE527E640C200FEA926 /* AppleAuthService.swift */; }; + 6A7C7C0B27920F80006D39F6 /* SearchFieldTokenCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7C7C0A27920F80006D39F6 /* SearchFieldTokenCVC.swift */; }; + 6A7C7C0F279364CA006D39F6 /* RequestPlansDateVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7C7C0E279364CA006D39F6 /* RequestPlansDateVC.swift */; }; + 6A7C7C13279367DF006D39F6 /* RequestPlansDate.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6A7C7C12279367DF006D39F6 /* RequestPlansDate.storyboard */; }; + 6A8E387C27958A2F00A480F7 /* SelectableWeekDayCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8E387B27958A2E00A480F7 /* SelectableWeekDayCVC.swift */; }; + 6A8E38802795B82400A480F7 /* ScheduleTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8E387F2795B82400A480F7 /* ScheduleTVC.swift */; }; + 6A8E388427970D7000A480F7 /* PickedDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8E388327970D7000A480F7 /* PickedDate.swift */; }; + 6AAD47C6278DF01300D8C800 /* UITextField++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AAD47C5278DF01300D8C800 /* UITextField++.swift */; }; + 6ABF643E28707C980017A974 /* PutPasswordService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ABF643D28707C980017A974 /* PutPasswordService.swift */; }; + 6ABF644028707D3B0017A974 /* PutPasswordResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ABF643F28707D3B0017A974 /* PutPasswordResponseModel.swift */; }; + 6AC748662798256B0027B823 /* WeekDay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC748652798256B0027B823 /* WeekDay.swift */; }; + 6AD9949028A403EE0014D60B /* NameChipCVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD9948F28A403EE0014D60B /* NameChipCVC.swift */; }; + 6AD9949228A763210014D60B /* CanceledPlanDetailModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD9949128A763210014D60B /* CanceledPlanDetailModel.swift */; }; + 6AD9949428A7645B0014D60B /* GetCanceldPlansDetailService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD9949328A7645B0014D60B /* GetCanceldPlansDetailService.swift */; }; + 6AE14B7627D72734000CD46F /* ProfileRegister.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6AE14B7527D72734000CD46F /* ProfileRegister.storyboard */; }; + 6AE14B7827D7277C000CD46F /* ProfileRegisterVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE14B7727D7277C000CD46F /* ProfileRegisterVC.swift */; }; + 6AE7F265286C83A80042FD79 /* PostProfileImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE7F264286C83A70042FD79 /* PostProfileImageService.swift */; }; + 6AE7F268286D3C330042FD79 /* ProfileImageResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE7F267286D3C330042FD79 /* ProfileImageResponseModel.swift */; }; + 6AE863032799DEB70050A978 /* InvitationPlanDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE863022799DEB70050A978 /* InvitationPlanDataModel.swift */; }; + 6AE863052799DFA20050A978 /* PlanRequestDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE863042799DFA20050A978 /* PlanRequestDataModel.swift */; }; + 6AE863072799E2EA0050A978 /* PostRequestPlansService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE863062799E2EA0050A978 /* PostRequestPlansService.swift */; }; + 6AE863092799FABC0050A978 /* GetScheduleService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE863082799FABC0050A978 /* GetScheduleService.swift */; }; + 6AE9B384285F31920021E6CE /* PutWithdrawalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE9B383285F31920021E6CE /* PutWithdrawalService.swift */; }; + 6AE9B386285F38350021E6CE /* WithdrawalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE9B385285F38350021E6CE /* WithdrawalDataModel.swift */; }; + 6AF609222790BA6000F49DF4 /* SearchTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AF609212790BA6000F49DF4 /* SearchTVC.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1906B3ED27CA804D00F94DD8 /* ConfirmTimeOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmTimeOptionView.swift; sourceTree = ""; }; + 1906B3F627D496A000F94DD8 /* KakaoAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KakaoAuthService.swift; sourceTree = ""; }; + 19110ACB27D71E9D001121AE /* AvailTimeOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvailTimeOptionView.swift; sourceTree = ""; }; + 19110AD027D73AB5001121AE /* AvailTimeOptionView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AvailTimeOptionView.xib; sourceTree = ""; }; + 1912FCAD278AB7A000F33952 /* CalendarVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarVC.swift; sourceTree = ""; }; + 1912FCAF278AB7BB00F33952 /* Calendar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Calendar.storyboard; sourceTree = ""; }; + 191779982859A1A5004E7E63 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 1919F5D2278B5E18009F33AE /* CalendarPlansCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarPlansCVC.swift; sourceTree = ""; }; + 1919F5D3278B5E18009F33AE /* CalendarPlansCVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CalendarPlansCVC.xib; sourceTree = ""; }; + 191E8C17278F01C60042B8F1 /* SMPopUpVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMPopUpVC.swift; sourceTree = ""; }; + 191E8C19278F01DD0042B8F1 /* SMPopUp.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SMPopUp.storyboard; sourceTree = ""; }; + 191E8C1B278F38DC0042B8F1 /* String++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String++.swift"; sourceTree = ""; }; + 191E8C1E278F477B0042B8F1 /* FriendsListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsListVC.swift; sourceTree = ""; }; + 191E8C21278F47990042B8F1 /* FriendsList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = FriendsList.storyboard; sourceTree = ""; }; + 191E8C24278F490A0042B8F1 /* FriendsListTVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsListTVC.swift; sourceTree = ""; }; + 191E8C25278F490A0042B8F1 /* FriendsListTVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FriendsListTVC.xib; sourceTree = ""; }; + 191E8C29278FFB870042B8F1 /* SMSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMSearchBar.swift; sourceTree = ""; }; + 191F1BFB28B650E40007E375 /* Notification++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification++.swift"; sourceTree = ""; }; + 192C6EF42877B32000490821 /* SearchInputFieldCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchInputFieldCVC.swift; sourceTree = ""; }; + 194F91012847247900E95333 /* PlansCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlansCoordinator.swift; sourceTree = ""; }; + 19509B642796C00D00CAA4C2 /* MonthlyPlansResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthlyPlansResponseModel.swift; sourceTree = ""; }; + 19509B662796C25100CAA4C2 /* CalendarService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarService.swift; sourceTree = ""; }; + 19509B682798583500CAA4C2 /* FriendsSearchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsSearchService.swift; sourceTree = ""; }; + 19509B6A27986D2A00CAA4C2 /* FriendsSearchResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsSearchResponseModel.swift; sourceTree = ""; }; + 19509B6C27989CD800CAA4C2 /* FriendsAddResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsAddResponseModel.swift; sourceTree = ""; }; + 19509B6E27989DAF00CAA4C2 /* FriendsAddService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsAddService.swift; sourceTree = ""; }; + 1950E5132791DAA300ED357B /* FriendsAddVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsAddVC.swift; sourceTree = ""; }; + 1950E5152791DAC900ED357B /* FriendsAdd.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = FriendsAdd.storyboard; sourceTree = ""; }; + 1950E5192791FE7B00ED357B /* FriendsAddTVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsAddTVC.swift; sourceTree = ""; }; + 1950E51A2791FE7B00ED357B /* FriendsAddTVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FriendsAddTVC.xib; sourceTree = ""; }; + 1960D41E27B10C93002558C4 /* SelectedDateSheetTVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedDateSheetTVC.swift; sourceTree = ""; }; + 1960D41F27B10C93002558C4 /* SelectedDateSheetTVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SelectedDateSheetTVC.xib; sourceTree = ""; }; + 1969D93F28B0E57D009BCCA0 /* PostRefreshAccessTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostRefreshAccessTokenService.swift; sourceTree = ""; }; + 1969D94128B0EA65009BCCA0 /* AccessTokenRefreshDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessTokenRefreshDataModel.swift; sourceTree = ""; }; + 1969D94328B100E0009BCCA0 /* AccessInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessInterceptor.swift; sourceTree = ""; }; + 197253F927E2EF56004BBBB7 /* SocialLoginDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginDataModel.swift; sourceTree = ""; }; + 1973C24F28A7F69700E990DC /* ci_post_clone.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = ""; }; + 197F57B4279913C000FB4467 /* PlanDetailResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanDetailResponseModel.swift; sourceTree = ""; }; + 1985C741283CAA2400E7A147 /* CalendarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarCoordinator.swift; sourceTree = ""; }; + 19A0256727D73CC400ADB0C5 /* ConfirmTimeOptionView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfirmTimeOptionView.xib; sourceTree = ""; }; + 19B1E6A02856007F009D6507 /* FriendsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsCoordinator.swift; sourceTree = ""; }; + 19B1E6A22856054D009D6507 /* LoginCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginCoordinator.swift; sourceTree = ""; }; + 19B1E6A428562335009D6507 /* RegisterCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterCoordinator.swift; sourceTree = ""; }; + 19B1E6A628563B8F009D6507 /* RequestCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestCoordinator.swift; sourceTree = ""; }; + 19B635D528A510180097895A /* PostPushNotificationSetService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostPushNotificationSetService.swift; sourceTree = ""; }; + 19B635D728A5134C0097895A /* NotificationSetResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSetResponseModel.swift; sourceTree = ""; }; + 19B7C1EB27F017A2004A2F68 /* UserDefaults++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults++.swift"; sourceTree = ""; }; + 19CEDB832813142C006E1DFD /* PutNameAndIdResponseDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutNameAndIdResponseDataModel.swift; sourceTree = ""; }; + 19CEDB85281314FE006E1DFD /* PutNameAndIdService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutNameAndIdService.swift; sourceTree = ""; }; + 19D7B2DB2838BC1900EC5EBD /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; + 19D7B2DD2838C02800EC5EBD /* HomeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCoordinator.swift; sourceTree = ""; }; + 19D7B2DF2838C04100EC5EBD /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; + 19E3BE4228A1E2CC00F0EC86 /* PutPlanDeleteService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutPlanDeleteService.swift; sourceTree = ""; }; + 19E3BE4428A1E5CF00F0EC86 /* PlansDeleteResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlansDeleteResponseModel.swift; sourceTree = ""; }; + 19E3BE5728A37EC000F0EC86 /* NetworkRechabilityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkRechabilityService.swift; sourceTree = ""; }; + 19E3BE5928A38BB300F0EC86 /* PutInvitationListService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutInvitationListService.swift; sourceTree = ""; }; + 19E3BE5B28A38D7E00F0EC86 /* InvitationListCancelDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationListCancelDataModel.swift; sourceTree = ""; }; + 19E633DD278D84F200776D0C /* CalendarDetailVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDetailVC.swift; sourceTree = ""; }; + 19E633E0278D853500776D0C /* CalendarDetail.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = CalendarDetail.storyboard; sourceTree = ""; }; + 19F1AF712792AB0300022A6A /* SelectedDateSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedDateSheet.swift; sourceTree = ""; }; + 19F1AF792793453700022A6A /* SelectedDateSheet.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SelectedDateSheet.xib; sourceTree = ""; }; + 360E6F4427898D4400DACFD8 /* HomeEventCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeEventCVC.swift; sourceTree = ""; }; + 360E6F4527898D4400DACFD8 /* HomeEventCVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HomeEventCVC.xib; sourceTree = ""; }; + 3623EA81278F65AB00853F87 /* ProgressReceiveCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressReceiveCVC.swift; sourceTree = ""; }; + 3623EA82278F65AB00853F87 /* ProgressSendCVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProgressSendCVC.xib; sourceTree = ""; }; + 3623EA85278F686A00853F87 /* ProgressSendCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressSendCVC.swift; sourceTree = ""; }; + 3623EA86278F686A00853F87 /* ProgressReceiveCVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProgressReceiveCVC.xib; sourceTree = ""; }; + 3623EA8D278FDE0C00853F87 /* CompletePlansCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletePlansCVC.swift; sourceTree = ""; }; + 3623EA8E278FDE0C00853F87 /* CompletePlansCVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CompletePlansCVC.xib; sourceTree = ""; }; + 3623EA912791282700853F87 /* PlansReceiveVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlansReceiveVC.swift; sourceTree = ""; }; + 3623EA932791283600853F87 /* PlansReceiveList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PlansReceiveList.storyboard; sourceTree = ""; }; + 3623EA952791D1D300853F87 /* PlansReceiveCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlansReceiveCVC.swift; sourceTree = ""; }; + 3623EA962791D1D300853F87 /* PlansReceiveCVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PlansReceiveCVC.xib; sourceTree = ""; }; + 3623EA992792A8D100853F87 /* SMRequestPopUpVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMRequestPopUpVC.swift; sourceTree = ""; }; + 3623EA9B2792A90E00853F87 /* SMRequestPopUp.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SMRequestPopUp.storyboard; sourceTree = ""; }; + 3623EA9D27931EAC00853F87 /* PlansSendListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlansSendListVC.swift; sourceTree = ""; }; + 3623EA9F27931EC000853F87 /* PlansSendList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PlansSendList.storyboard; sourceTree = ""; }; + 3623EAA32795437400853F87 /* EmailLoginVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailLoginVC.swift; sourceTree = ""; }; + 3623EAA52795437D00853F87 /* EmailLogin.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = EmailLogin.storyboard; sourceTree = ""; }; + 3623EAA92795701D00853F87 /* GrayTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrayTextField.swift; sourceTree = ""; }; + 3623EAAB279570A000853F87 /* GrayTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrayTextView.swift; sourceTree = ""; }; + 3623EAB12796A77A00853F87 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 3623EAB32796A7DE00853F87 /* NetworkResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResult.swift; sourceTree = ""; }; + 3623EAB82796AA0600853F87 /* EmailRegisterVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailRegisterVC.swift; sourceTree = ""; }; + 3623EABB2796AA2800853F87 /* EmailRegister.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = EmailRegister.storyboard; sourceTree = ""; }; + 3623EABD2796E76500853F87 /* RegisterDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterDataModel.swift; sourceTree = ""; }; + 3623EABF2796ED1000853F87 /* PostRegisterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostRegisterService.swift; sourceTree = ""; }; + 3623EAC12796F8A500853F87 /* TokenUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenUtils.swift; sourceTree = ""; }; + 3623EAC32797038100853F87 /* PostLoginService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostLoginService.swift; sourceTree = ""; }; + 3623EAC5279705C500853F87 /* ToastMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastMessageView.swift; sourceTree = ""; }; + 3623EAC727971F1600853F87 /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3623EACB2797324600853F87 /* HomeDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeDataModel.swift; sourceTree = ""; }; + 3623EACD2797330800853F87 /* GetHomeDataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetHomeDataService.swift; sourceTree = ""; }; + 3623EACF2797D6FA00853F87 /* LastDateDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastDateDataModel.swift; sourceTree = ""; }; + 3623EAD12797D72500853F87 /* GetLastDateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetLastDateService.swift; sourceTree = ""; }; + 3623EAD32797DAE900853F87 /* FriendsListDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsListDataModel.swift; sourceTree = ""; }; + 3623EAD52797DC4500853F87 /* GetFriendsListService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetFriendsListService.swift; sourceTree = ""; }; + 3623EAD727980EC000853F87 /* PlansListDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlansListDataModel.swift; sourceTree = ""; }; + 3623EAD927980F0600853F87 /* GetPlansListDataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPlansListDataService.swift; sourceTree = ""; }; + 3623EADB27988AFE00853F87 /* PlansDetailDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlansDetailDataModel.swift; sourceTree = ""; }; + 3623EADD27988B8C00853F87 /* GetPlansDetailService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPlansDetailService.swift; sourceTree = ""; }; + 3623EADF2798AF8A00853F87 /* ResponseDateDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseDateDataModel.swift; sourceTree = ""; }; + 3623EAE12798B06C00853F87 /* GetResponseDateDataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetResponseDateDataService.swift; sourceTree = ""; }; + 3623EAE327994D7A00853F87 /* InvitationPlansDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationPlansDataModel.swift; sourceTree = ""; }; + 3623EAE527994DE400853F87 /* PostInvitationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostInvitationService.swift; sourceTree = ""; }; + 3623EAE72799A39900853F87 /* InvitationRejectDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationRejectDataModel.swift; sourceTree = ""; }; + 3623EAE92799A3D700853F87 /* PostInvitationRejectService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostInvitationRejectService.swift; sourceTree = ""; }; + 3623EAEB2799D20C00853F87 /* PlansSendDetailDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlansSendDetailDataModel.swift; sourceTree = ""; }; + 3623EAED2799D28800853F87 /* GetPlansSendDetailService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPlansSendDetailService.swift; sourceTree = ""; }; + 3623EAEF2799F07B00853F87 /* PlansRequestAcceptDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlansRequestAcceptDataModel.swift; sourceTree = ""; }; + 3623EAF12799F11500853F87 /* PostPlansRequestAcceptService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostPlansRequestAcceptService.swift; sourceTree = ""; }; + 3623EAF32799F61200853F87 /* InvitationCancelDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationCancelDataModel.swift; sourceTree = ""; }; + 3623EAF52799F65900853F87 /* PutInvitationCancelService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutInvitationCancelService.swift; sourceTree = ""; }; + 3623EAF7279A9EA500853F87 /* MyPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageView.swift; sourceTree = ""; }; + 36545311278EA30400A0770F /* UILabel++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel++.swift"; sourceTree = ""; }; + 3682122F2785E642005D7236 /* DINNeuzeitGroteskStd-Light.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINNeuzeitGroteskStd-Light.otf"; sourceTree = ""; }; + 368212302785E642005D7236 /* SpoqaHanSansNeo-Light.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpoqaHanSansNeo-Light.ttf"; sourceTree = ""; }; + 368212312785E642005D7236 /* DINMittelschriftStd.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = DINMittelschriftStd.otf; sourceTree = ""; }; + 368212322785E642005D7236 /* DINPro-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINPro-Medium.otf"; sourceTree = ""; }; + 368212332785E642005D7236 /* SpoqaHanSansNeo-Thin.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpoqaHanSansNeo-Thin.ttf"; sourceTree = ""; }; + 368212342785E642005D7236 /* DINPro-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINPro-Black.otf"; sourceTree = ""; }; + 368212352785E642005D7236 /* SpoqaHanSansNeo-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpoqaHanSansNeo-Regular.ttf"; sourceTree = ""; }; + 368212362785E642005D7236 /* DINPro-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINPro-Regular.otf"; sourceTree = ""; }; + 368212372785E642005D7236 /* DINNeuzeitGroteskStd-BdCond.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINNeuzeitGroteskStd-BdCond.otf"; sourceTree = ""; }; + 368212382785E642005D7236 /* DINEngschriftStd.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = DINEngschriftStd.otf; sourceTree = ""; }; + 368212392785E642005D7236 /* DINPro-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINPro-Bold.otf"; sourceTree = ""; }; + 3682123A2785E642005D7236 /* SpoqaHanSansNeo-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpoqaHanSansNeo-Medium.ttf"; sourceTree = ""; }; + 3682123B2785E642005D7236 /* DINPro-Light.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINPro-Light.otf"; sourceTree = ""; }; + 3682123C2785E642005D7236 /* SpoqaHanSansNeo-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpoqaHanSansNeo-Bold.ttf"; sourceTree = ""; }; + 3682123D2785E642005D7236 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 3682125327897806005D7236 /* dummy1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dummy1.swift; sourceTree = ""; }; + 36D9E8AE2789A7BF00D5244D /* Tabbar.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Tabbar.storyboard; sourceTree = ""; }; + 36D9E8B02789A81A00D5244D /* TabbarVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbarVC.swift; sourceTree = ""; }; + 36D9E8B7278BDCCC00D5244D /* PlansListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlansListVC.swift; sourceTree = ""; }; + 36D9E8BA278BDCE800D5244D /* PlansList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PlansList.storyboard; sourceTree = ""; }; + 36EFE2E92785682200466818 /* SeeMeet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SeeMeet.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 36EFE2EC2785682200466818 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 36EFE2EE2785682200466818 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 36EFE2F52785682400466818 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 36EFE2F82785682400466818 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 36EFE2FA2785682400466818 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 36EFE30E2785C8CA00466818 /* Home.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Home.storyboard; sourceTree = ""; }; + 36EFE3102785C8D600466818 /* HomeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeVC.swift; sourceTree = ""; }; + 36EFE3122785CC3B00466818 /* UIViewController++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController++.swift"; sourceTree = ""; }; + 36EFE3132785CC3B00466818 /* UIScreen++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScreen++.swift"; sourceTree = ""; }; + 36EFE3142785CC3B00466818 /* UICollectionView++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView++.swift"; sourceTree = ""; }; + 36EFE3152785CC3B00466818 /* UIColor++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor++.swift"; sourceTree = ""; }; + 36EFE3162785CC3B00466818 /* UIView++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView++.swift"; sourceTree = ""; }; + 36EFE3172785CC3B00466818 /* UIFont++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIFont++.swift"; sourceTree = ""; }; + 36EFE3182785CC3B00466818 /* UITabelView++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITabelView++.swift"; sourceTree = ""; }; + 36EFE3192785CC3B00466818 /* Date++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date++.swift"; sourceTree = ""; }; + 6A139081289D72C40029FFD1 /* EmailLoginResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailLoginResponseModel.swift; sourceTree = ""; }; + 6A16EFA6286A265F00387C0B /* UserDefaultsKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsKey.swift; sourceTree = ""; }; + 6A226DC927D4B73E003B5FC4 /* MainLoginVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainLoginVC.swift; sourceTree = ""; }; + 6A226DCB27D5174A003B5FC4 /* MainLogin.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = MainLogin.storyboard; sourceTree = ""; }; + 6A2BEFFC28993A9B001BC91F /* MyPageCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageCoordinator.swift; sourceTree = ""; }; + 6A39C2EF286EDAE000BA31CF /* ImageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = ""; }; + 6A3B24C0284C80A400DA939F /* UserInfo.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = UserInfo.storyboard; sourceTree = ""; }; + 6A3B24C4284C80E900DA939F /* UserInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoVC.swift; sourceTree = ""; }; + 6A3B24C6284CC50600DA939F /* ChangePassword.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ChangePassword.storyboard; sourceTree = ""; }; + 6A3B24CA284CC53500DA939F /* ChangePasswordVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangePasswordVC.swift; sourceTree = ""; }; + 6A4A20EC278B5F7000DD4DCD /* RequestPlansContentsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestPlansContentsVC.swift; sourceTree = ""; }; + 6A4A20EF278B5FE300DD4DCD /* RequestPlansContents.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = RequestPlansContents.storyboard; sourceTree = ""; }; + 6A5FCEE427E639B900FEA926 /* SeeMeet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SeeMeet.entitlements; sourceTree = ""; }; + 6A5FCEE527E640C200FEA926 /* AppleAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAuthService.swift; sourceTree = ""; }; + 6A7C7C0A27920F80006D39F6 /* SearchFieldTokenCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFieldTokenCVC.swift; sourceTree = ""; }; + 6A7C7C0E279364CA006D39F6 /* RequestPlansDateVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestPlansDateVC.swift; sourceTree = ""; }; + 6A7C7C12279367DF006D39F6 /* RequestPlansDate.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = RequestPlansDate.storyboard; sourceTree = ""; }; + 6A8E387B27958A2E00A480F7 /* SelectableWeekDayCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableWeekDayCVC.swift; sourceTree = ""; }; + 6A8E387F2795B82400A480F7 /* ScheduleTVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleTVC.swift; sourceTree = ""; }; + 6A8E388327970D7000A480F7 /* PickedDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickedDate.swift; sourceTree = ""; }; + 6AAD47C5278DF01300D8C800 /* UITextField++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField++.swift"; sourceTree = ""; }; + 6ABF643D28707C980017A974 /* PutPasswordService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutPasswordService.swift; sourceTree = ""; }; + 6ABF643F28707D3B0017A974 /* PutPasswordResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutPasswordResponseModel.swift; sourceTree = ""; }; + 6AC748652798256B0027B823 /* WeekDay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekDay.swift; sourceTree = ""; }; + 6AD9948F28A403EE0014D60B /* NameChipCVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NameChipCVC.swift; sourceTree = ""; }; + 6AD9949128A763210014D60B /* CanceledPlanDetailModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanceledPlanDetailModel.swift; sourceTree = ""; }; + 6AD9949328A7645B0014D60B /* GetCanceldPlansDetailService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetCanceldPlansDetailService.swift; sourceTree = ""; }; + 6AE14B7527D72734000CD46F /* ProfileRegister.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ProfileRegister.storyboard; sourceTree = ""; }; + 6AE14B7727D7277C000CD46F /* ProfileRegisterVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRegisterVC.swift; sourceTree = ""; }; + 6AE7F264286C83A70042FD79 /* PostProfileImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostProfileImageService.swift; sourceTree = ""; }; + 6AE7F267286D3C330042FD79 /* ProfileImageResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileImageResponseModel.swift; sourceTree = ""; }; + 6AE863022799DEB70050A978 /* InvitationPlanDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationPlanDataModel.swift; sourceTree = ""; }; + 6AE863042799DFA20050A978 /* PlanRequestDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanRequestDataModel.swift; sourceTree = ""; }; + 6AE863062799E2EA0050A978 /* PostRequestPlansService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostRequestPlansService.swift; sourceTree = ""; }; + 6AE863082799FABC0050A978 /* GetScheduleService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetScheduleService.swift; sourceTree = ""; }; + 6AE9B383285F31920021E6CE /* PutWithdrawalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutWithdrawalService.swift; sourceTree = ""; }; + 6AE9B385285F38350021E6CE /* WithdrawalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithdrawalDataModel.swift; sourceTree = ""; }; + 6AF609212790BA6000F49DF4 /* SearchTVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTVC.swift; sourceTree = ""; }; + 7E4152A410430753EE447189 /* Pods_SeeMeet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SeeMeet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B3594175EA1BA01667464A70 /* Pods-SeeMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SeeMeet.debug.xcconfig"; path = "Target Support Files/Pods-SeeMeet/Pods-SeeMeet.debug.xcconfig"; sourceTree = ""; }; + DF75C365F4903A6B444D31C6 /* Pods-SeeMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SeeMeet.release.xcconfig"; path = "Target Support Files/Pods-SeeMeet/Pods-SeeMeet.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 36EFE2E62785682200466818 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 120FFED660BC55B756EBD72A /* Pods_SeeMeet.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1906B3EF27CD280F00F94DD8 /* Components */ = { + isa = PBXGroup; + children = ( + 1906B3ED27CA804D00F94DD8 /* ConfirmTimeOptionView.swift */, + 19110ACB27D71E9D001121AE /* AvailTimeOptionView.swift */, + ); + path = Components; + sourceTree = ""; + }; + 1906B3F527D4968900F94DD8 /* Authentication */ = { + isa = PBXGroup; + children = ( + 1906B3F627D496A000F94DD8 /* KakaoAuthService.swift */, + 6A5FCEE527E640C200FEA926 /* AppleAuthService.swift */, + 19CEDB85281314FE006E1DFD /* PutNameAndIdService.swift */, + 6AE9B383285F31920021E6CE /* PutWithdrawalService.swift */, + 1969D94328B100E0009BCCA0 /* AccessInterceptor.swift */, + 1969D93F28B0E57D009BCCA0 /* PostRefreshAccessTokenService.swift */, + ); + path = Authentication; + sourceTree = ""; + }; + 19110ACD27D73A30001121AE /* Components */ = { + isa = PBXGroup; + children = ( + 19110AD027D73AB5001121AE /* AvailTimeOptionView.xib */, + 19A0256727D73CC400ADB0C5 /* ConfirmTimeOptionView.xib */, + ); + path = Components; + sourceTree = ""; + }; + 1912FCAB278AB72600F33952 /* CalendarScene */ = { + isa = PBXGroup; + children = ( + 1912FCAF278AB7BB00F33952 /* Calendar.storyboard */, + 19E633E0278D853500776D0C /* CalendarDetail.storyboard */, + ); + path = CalendarScene; + sourceTree = ""; + }; + 1912FCAC278AB77900F33952 /* CalendarScene */ = { + isa = PBXGroup; + children = ( + 6AD9948E28A2A5270014D60B /* Components */, + 1912FCAD278AB7A000F33952 /* CalendarVC.swift */, + 19E633DD278D84F200776D0C /* CalendarDetailVC.swift */, + ); + path = CalendarScene; + sourceTree = ""; + }; + 191E8C1D278F471E0042B8F1 /* FriendsScene */ = { + isa = PBXGroup; + children = ( + 19580EFA27E064E40014DDCD /* Components */, + 191E8C1E278F477B0042B8F1 /* FriendsListVC.swift */, + 1950E5132791DAA300ED357B /* FriendsAddVC.swift */, + ); + path = FriendsScene; + sourceTree = ""; + }; + 191E8C20278F47890042B8F1 /* FriendsScene */ = { + isa = PBXGroup; + children = ( + 191E8C21278F47990042B8F1 /* FriendsList.storyboard */, + 1950E5152791DAC900ED357B /* FriendsAdd.storyboard */, + ); + path = FriendsScene; + sourceTree = ""; + }; + 191E8C23278F488E0042B8F1 /* FriendsScene */ = { + isa = PBXGroup; + children = ( + 191E8C24278F490A0042B8F1 /* FriendsListTVC.swift */, + 1950E5192791FE7B00ED357B /* FriendsAddTVC.swift */, + ); + path = FriendsScene; + sourceTree = ""; + }; + 191E8C28278F49250042B8F1 /* FriendsScene */ = { + isa = PBXGroup; + children = ( + 1950E51A2791FE7B00ED357B /* FriendsAddTVC.xib */, + 191E8C25278F490A0042B8F1 /* FriendsListTVC.xib */, + ); + path = FriendsScene; + sourceTree = ""; + }; + 19580EFA27E064E40014DDCD /* Components */ = { + isa = PBXGroup; + children = ( + 191E8C29278FFB870042B8F1 /* SMSearchBar.swift */, + ); + path = Components; + sourceTree = ""; + }; + 1960D41D27AFC6E2002558C4 /* Components */ = { + isa = PBXGroup; + children = ( + 19F1AF712792AB0300022A6A /* SelectedDateSheet.swift */, + 6A8E387B27958A2E00A480F7 /* SelectableWeekDayCVC.swift */, + ); + path = Components; + sourceTree = ""; + }; + 1960D42227B1638E002558C4 /* RequestScene */ = { + isa = PBXGroup; + children = ( + 1960D41E27B10C93002558C4 /* SelectedDateSheetTVC.swift */, + 1960D41F27B10C93002558C4 /* SelectedDateSheetTVC.xib */, + ); + path = RequestScene; + sourceTree = ""; + }; + 19634D512880FE2100119D86 /* RequestScene */ = { + isa = PBXGroup; + children = ( + 6A7C7C0A27920F80006D39F6 /* SearchFieldTokenCVC.swift */, + 192C6EF42877B32000490821 /* SearchInputFieldCVC.swift */, + ); + path = RequestScene; + sourceTree = ""; + }; + 197253F827E2EF36004BBBB7 /* Authentication */ = { + isa = PBXGroup; + children = ( + 197253F927E2EF56004BBBB7 /* SocialLoginDataModel.swift */, + 19CEDB832813142C006E1DFD /* PutNameAndIdResponseDataModel.swift */, + 6AE9B385285F38350021E6CE /* WithdrawalDataModel.swift */, + 6A139081289D72C40029FFD1 /* EmailLoginResponseModel.swift */, + 1969D94128B0EA65009BCCA0 /* AccessTokenRefreshDataModel.swift */, + ); + path = Authentication; + sourceTree = ""; + }; + 1973C24E28A7F58900E990DC /* ci_scripts */ = { + isa = PBXGroup; + children = ( + 1973C24F28A7F69700E990DC /* ci_post_clone.sh */, + ); + path = ci_scripts; + sourceTree = ""; + }; + 19D7B2DA2838BC0300EC5EBD /* Coordinators */ = { + isa = PBXGroup; + children = ( + 19D7B2DB2838BC1900EC5EBD /* Coordinator.swift */, + 19D7B2DF2838C04100EC5EBD /* AppCoordinator.swift */, + 19D7B2DD2838C02800EC5EBD /* HomeCoordinator.swift */, + 1985C741283CAA2400E7A147 /* CalendarCoordinator.swift */, + 194F91012847247900E95333 /* PlansCoordinator.swift */, + 19B1E6A02856007F009D6507 /* FriendsCoordinator.swift */, + 19B1E6A22856054D009D6507 /* LoginCoordinator.swift */, + 19B1E6A428562335009D6507 /* RegisterCoordinator.swift */, + 19B1E6A628563B8F009D6507 /* RequestCoordinator.swift */, + 6A2BEFFC28993A9B001BC91F /* MyPageCoordinator.swift */, + ); + path = Coordinators; + sourceTree = ""; + }; + 19E633DC278D73CF00776D0C /* CalendarScene */ = { + isa = PBXGroup; + children = ( + 1919F5D2278B5E18009F33AE /* CalendarPlansCVC.swift */, + ); + path = CalendarScene; + sourceTree = ""; + }; + 19E633DF278D851900776D0C /* CalendarScene */ = { + isa = PBXGroup; + children = ( + ); + path = CalendarScene; + sourceTree = ""; + }; + 19F0EC5627A195BA003B209D /* CalendarScene */ = { + isa = PBXGroup; + children = ( + 19509B642796C00D00CAA4C2 /* MonthlyPlansResponseModel.swift */, + 197F57B4279913C000FB4467 /* PlanDetailResponseModel.swift */, + 19E3BE4428A1E5CF00F0EC86 /* PlansDeleteResponseModel.swift */, + 6AD9949128A763210014D60B /* CanceledPlanDetailModel.swift */, + ); + path = CalendarScene; + sourceTree = ""; + }; + 19F0EC5727A19613003B209D /* FriendsScene */ = { + isa = PBXGroup; + children = ( + 3623EAD32797DAE900853F87 /* FriendsListDataModel.swift */, + 19509B6A27986D2A00CAA4C2 /* FriendsSearchResponseModel.swift */, + 19509B6C27989CD800CAA4C2 /* FriendsAddResponseModel.swift */, + ); + path = FriendsScene; + sourceTree = ""; + }; + 19F0EC5827A197D7003B209D /* PlansScene */ = { + isa = PBXGroup; + children = ( + 3623EADF2798AF8A00853F87 /* ResponseDateDataModel.swift */, + 3623EAEB2799D20C00853F87 /* PlansSendDetailDataModel.swift */, + 3623EADB27988AFE00853F87 /* PlansDetailDataModel.swift */, + 3623EAD727980EC000853F87 /* PlansListDataModel.swift */, + 3623EAEF2799F07B00853F87 /* PlansRequestAcceptDataModel.swift */, + ); + path = PlansScene; + sourceTree = ""; + }; + 19F0EC5927A19850003B209D /* HomeScene */ = { + isa = PBXGroup; + children = ( + 3623EACF2797D6FA00853F87 /* LastDateDataModel.swift */, + 3623EACB2797324600853F87 /* HomeDataModel.swift */, + ); + path = HomeScene; + sourceTree = ""; + }; + 19F0EC5A27A198CC003B209D /* RegisterScene */ = { + isa = PBXGroup; + children = ( + 3623EABD2796E76500853F87 /* RegisterDataModel.swift */, + ); + path = RegisterScene; + sourceTree = ""; + }; + 360E6F48278998CB00DACFD8 /* HomeScene */ = { + isa = PBXGroup; + children = ( + 360E6F4427898D4400DACFD8 /* HomeEventCVC.swift */, + ); + path = HomeScene; + sourceTree = ""; + }; + 360E6F49278998DA00DACFD8 /* HomeScene */ = { + isa = PBXGroup; + children = ( + 360E6F4527898D4400DACFD8 /* HomeEventCVC.xib */, + ); + path = HomeScene; + sourceTree = ""; + }; + 360E6F4A2789A74200DACFD8 /* Tabbar */ = { + isa = PBXGroup; + children = ( + 36D9E8B02789A81A00D5244D /* TabbarVC.swift */, + ); + path = Tabbar; + sourceTree = ""; + }; + 3623EAA12795434600853F87 /* LoginScene */ = { + isa = PBXGroup; + children = ( + 3623EAA32795437400853F87 /* EmailLoginVC.swift */, + 6A226DC927D4B73E003B5FC4 /* MainLoginVC.swift */, + ); + path = LoginScene; + sourceTree = ""; + }; + 3623EAA22795435E00853F87 /* LoginScene */ = { + isa = PBXGroup; + children = ( + 3623EAA52795437D00853F87 /* EmailLogin.storyboard */, + 6A226DCB27D5174A003B5FC4 /* MainLogin.storyboard */, + ); + path = LoginScene; + sourceTree = ""; + }; + 3623EAAF2796A74900853F87 /* Services */ = { + isa = PBXGroup; + children = ( + 6A39C2EF286EDAE000BA31CF /* ImageManager.swift */, + 1906B3F527D4968900F94DD8 /* Authentication */, + 6A5302AF279F1D2C00DB4C3F /* RequestScene */, + 3623EAB12796A77A00853F87 /* Constants.swift */, + 3623EAB32796A7DE00853F87 /* NetworkResult.swift */, + 19E3BE4228A1E2CC00F0EC86 /* PutPlanDeleteService.swift */, + 3623EABF2796ED1000853F87 /* PostRegisterService.swift */, + 3623EAC12796F8A500853F87 /* TokenUtils.swift */, + 3623EAC32797038100853F87 /* PostLoginService.swift */, + 19509B662796C25100CAA4C2 /* CalendarService.swift */, + 3623EACD2797330800853F87 /* GetHomeDataService.swift */, + 3623EAD12797D72500853F87 /* GetLastDateService.swift */, + 3623EAD52797DC4500853F87 /* GetFriendsListService.swift */, + 19509B682798583500CAA4C2 /* FriendsSearchService.swift */, + 19509B6E27989DAF00CAA4C2 /* FriendsAddService.swift */, + 3623EAD927980F0600853F87 /* GetPlansListDataService.swift */, + 3623EADD27988B8C00853F87 /* GetPlansDetailService.swift */, + 3623EAE12798B06C00853F87 /* GetResponseDateDataService.swift */, + 3623EAE527994DE400853F87 /* PostInvitationService.swift */, + 3623EAE92799A3D700853F87 /* PostInvitationRejectService.swift */, + 3623EAED2799D28800853F87 /* GetPlansSendDetailService.swift */, + 3623EAF12799F11500853F87 /* PostPlansRequestAcceptService.swift */, + 3623EAF52799F65900853F87 /* PutInvitationCancelService.swift */, + 6AE7F264286C83A70042FD79 /* PostProfileImageService.swift */, + 6ABF643D28707C980017A974 /* PutPasswordService.swift */, + 19E3BE5728A37EC000F0EC86 /* NetworkRechabilityService.swift */, + 19E3BE5928A38BB300F0EC86 /* PutInvitationListService.swift */, + 19B635D528A510180097895A /* PostPushNotificationSetService.swift */, + 6AD9949328A7645B0014D60B /* GetCanceldPlansDetailService.swift */, + ); + path = Services; + sourceTree = ""; + }; + 3623EAB02796A75100853F87 /* Models */ = { + isa = PBXGroup; + children = ( + 6AE7F266286D3BD50042FD79 /* MypageScene */, + 197253F827E2EF36004BBBB7 /* Authentication */, + 19F0EC5A27A198CC003B209D /* RegisterScene */, + 19F0EC5627A195BA003B209D /* CalendarScene */, + 6A5302B0279F1DE300DB4C3F /* RequestScene */, + 19F0EC5727A19613003B209D /* FriendsScene */, + 19F0EC5927A19850003B209D /* HomeScene */, + 19F0EC5827A197D7003B209D /* PlansScene */, + 3623EAE327994D7A00853F87 /* InvitationPlansDataModel.swift */, + 3623EAE72799A39900853F87 /* InvitationRejectDataModel.swift */, + 3623EAF32799F61200853F87 /* InvitationCancelDataModel.swift */, + 19E3BE5B28A38D7E00F0EC86 /* InvitationListCancelDataModel.swift */, + 19B635D728A5134C0097895A /* NotificationSetResponseModel.swift */, + ); + path = Models; + sourceTree = ""; + }; + 3623EAB72796A9F000853F87 /* RegisterScene */ = { + isa = PBXGroup; + children = ( + 3623EAB82796AA0600853F87 /* EmailRegisterVC.swift */, + 6AE14B7727D7277C000CD46F /* ProfileRegisterVC.swift */, + ); + path = RegisterScene; + sourceTree = ""; + }; + 3623EABA2796AA0F00853F87 /* RegisterScene */ = { + isa = PBXGroup; + children = ( + 3623EABB2796AA2800853F87 /* EmailRegister.storyboard */, + 6AE14B7527D72734000CD46F /* ProfileRegister.storyboard */, + ); + path = RegisterScene; + sourceTree = ""; + }; + 36545309278E7EB900A0770F /* PlansScene */ = { + isa = PBXGroup; + children = ( + 3623EA81278F65AB00853F87 /* ProgressReceiveCVC.swift */, + 3623EA85278F686A00853F87 /* ProgressSendCVC.swift */, + 3623EA8D278FDE0C00853F87 /* CompletePlansCVC.swift */, + 3623EA952791D1D300853F87 /* PlansReceiveCVC.swift */, + ); + path = PlansScene; + sourceTree = ""; + }; + 3654530E278E7F0F00A0770F /* PlansScene */ = { + isa = PBXGroup; + children = ( + 3623EA82278F65AB00853F87 /* ProgressSendCVC.xib */, + 3623EA8E278FDE0C00853F87 /* CompletePlansCVC.xib */, + 3623EA86278F686A00853F87 /* ProgressReceiveCVC.xib */, + 3623EA962791D1D300853F87 /* PlansReceiveCVC.xib */, + ); + path = PlansScene; + sourceTree = ""; + }; + 3682122E2785E579005D7236 /* Font */ = { + isa = PBXGroup; + children = ( + 368212382785E642005D7236 /* DINEngschriftStd.otf */, + 368212312785E642005D7236 /* DINMittelschriftStd.otf */, + 368212372785E642005D7236 /* DINNeuzeitGroteskStd-BdCond.otf */, + 3682122F2785E642005D7236 /* DINNeuzeitGroteskStd-Light.otf */, + 368212342785E642005D7236 /* DINPro-Black.otf */, + 368212392785E642005D7236 /* DINPro-Bold.otf */, + 3682123B2785E642005D7236 /* DINPro-Light.otf */, + 368212322785E642005D7236 /* DINPro-Medium.otf */, + 368212362785E642005D7236 /* DINPro-Regular.otf */, + 3682123D2785E642005D7236 /* LICENSE */, + 3682123C2785E642005D7236 /* SpoqaHanSansNeo-Bold.ttf */, + 368212302785E642005D7236 /* SpoqaHanSansNeo-Light.ttf */, + 3682123A2785E642005D7236 /* SpoqaHanSansNeo-Medium.ttf */, + 368212352785E642005D7236 /* SpoqaHanSansNeo-Regular.ttf */, + 368212332785E642005D7236 /* SpoqaHanSansNeo-Thin.ttf */, + ); + path = Font; + sourceTree = ""; + }; + 3682125927898C49005D7236 /* TVC */ = { + isa = PBXGroup; + children = ( + 6AF609232790BA7B00F49DF4 /* RequestScene */, + 191E8C23278F488E0042B8F1 /* FriendsScene */, + ); + path = TVC; + sourceTree = ""; + }; + 3682125A27898C51005D7236 /* CVC */ = { + isa = PBXGroup; + children = ( + 19634D512880FE2100119D86 /* RequestScene */, + 36545309278E7EB900A0770F /* PlansScene */, + 19E633DC278D73CF00776D0C /* CalendarScene */, + 360E6F48278998CB00DACFD8 /* HomeScene */, + ); + path = CVC; + sourceTree = ""; + }; + 36D9E8AD2789A7B500D5244D /* Tabbar */ = { + isa = PBXGroup; + children = ( + 36D9E8AE2789A7BF00D5244D /* Tabbar.storyboard */, + ); + path = Tabbar; + sourceTree = ""; + }; + 36D9E8B6278BDC7B00D5244D /* PlansScene */ = { + isa = PBXGroup; + children = ( + 1906B3EF27CD280F00F94DD8 /* Components */, + 36D9E8B7278BDCCC00D5244D /* PlansListVC.swift */, + 3623EA9D27931EAC00853F87 /* PlansSendListVC.swift */, + 3623EA912791282700853F87 /* PlansReceiveVC.swift */, + ); + path = PlansScene; + sourceTree = ""; + }; + 36D9E8B9278BDCD400D5244D /* PlansScene */ = { + isa = PBXGroup; + children = ( + 19110ACD27D73A30001121AE /* Components */, + 36D9E8BA278BDCE800D5244D /* PlansList.storyboard */, + 3623EA9F27931EC000853F87 /* PlansSendList.storyboard */, + 3623EA932791283600853F87 /* PlansReceiveList.storyboard */, + ); + path = PlansScene; + sourceTree = ""; + }; + 36EFE2E02785682200466818 = { + isa = PBXGroup; + children = ( + 1973C24E28A7F58900E990DC /* ci_scripts */, + 36EFE2EB2785682200466818 /* SeeMeet */, + 36EFE2EA2785682200466818 /* Products */, + FC4A432ECF9271CDC920B7C1 /* Pods */, + 6B37187F1B9AA6194E839C69 /* Frameworks */, + 6A7C7C0D27934D58006D39F6 /* Recovered References */, + ); + sourceTree = ""; + }; + 36EFE2EA2785682200466818 /* Products */ = { + isa = PBXGroup; + children = ( + 36EFE2E92785682200466818 /* SeeMeet.app */, + ); + name = Products; + sourceTree = ""; + }; + 36EFE2EB2785682200466818 /* SeeMeet */ = { + isa = PBXGroup; + children = ( + 6A5FCEE427E639B900FEA926 /* SeeMeet.entitlements */, + 191779982859A1A5004E7E63 /* GoogleService-Info.plist */, + 36EFE3002785C78600466818 /* Source */, + 36EFE3012785C79200466818 /* Resource */, + ); + path = SeeMeet; + sourceTree = ""; + }; + 36EFE3002785C78600466818 /* Source */ = { + isa = PBXGroup; + children = ( + 3623EAB02796A75100853F87 /* Models */, + 3623EAAF2796A74900853F87 /* Services */, + 36EFE3022785C7AF00466818 /* Extension */, + 36EFE30B2785C88F00466818 /* Support */, + 36EFE3032785C7B700466818 /* Views */, + ); + path = Source; + sourceTree = ""; + }; + 36EFE3012785C79200466818 /* Resource */ = { + isa = PBXGroup; + children = ( + 3682122E2785E579005D7236 /* Font */, + 36EFE2F52785682400466818 /* Assets.xcassets */, + 36EFE2FA2785682400466818 /* Info.plist */, + 36EFE3072785C80300466818 /* Xibs */, + 36EFE30A2785C84C00466818 /* Storyboards */, + ); + path = Resource; + sourceTree = ""; + }; + 36EFE3022785C7AF00466818 /* Extension */ = { + isa = PBXGroup; + children = ( + 6A16EFA5286A263900387C0B /* Constants++ */, + 36EFE3192785CC3B00466818 /* Date++.swift */, + 36EFE3142785CC3B00466818 /* UICollectionView++.swift */, + 36EFE3152785CC3B00466818 /* UIColor++.swift */, + 36EFE3172785CC3B00466818 /* UIFont++.swift */, + 36EFE3132785CC3B00466818 /* UIScreen++.swift */, + 36EFE3182785CC3B00466818 /* UITabelView++.swift */, + 36EFE3162785CC3B00466818 /* UIView++.swift */, + 36EFE3122785CC3B00466818 /* UIViewController++.swift */, + 6AAD47C5278DF01300D8C800 /* UITextField++.swift */, + 36545311278EA30400A0770F /* UILabel++.swift */, + 191E8C1B278F38DC0042B8F1 /* String++.swift */, + 19B7C1EB27F017A2004A2F68 /* UserDefaults++.swift */, + 191F1BFB28B650E40007E375 /* Notification++.swift */, + ); + path = Extension; + sourceTree = ""; + }; + 36EFE3032785C7B700466818 /* Views */ = { + isa = PBXGroup; + children = ( + 19D7B2DA2838BC0300EC5EBD /* Coordinators */, + 36EFE3052785C7D500466818 /* Cells */, + 36EFE3062785C7F500466818 /* VCs */, + ); + path = Views; + sourceTree = ""; + }; + 36EFE3042785C7BE00466818 /* HomeScene */ = { + isa = PBXGroup; + children = ( + 36EFE3102785C8D600466818 /* HomeVC.swift */, + ); + path = HomeScene; + sourceTree = ""; + }; + 36EFE3052785C7D500466818 /* Cells */ = { + isa = PBXGroup; + children = ( + 3682125927898C49005D7236 /* TVC */, + 3682125A27898C51005D7236 /* CVC */, + ); + path = Cells; + sourceTree = ""; + }; + 36EFE3062785C7F500466818 /* VCs */ = { + isa = PBXGroup; + children = ( + 6AB4266E284388B400790262 /* MyPageScene */, + 3623EAA12795434600853F87 /* LoginScene */, + 3623EAB72796A9F000853F87 /* RegisterScene */, + 3623EAA92795701D00853F87 /* GrayTextField.swift */, + 3623EAAB279570A000853F87 /* GrayTextView.swift */, + 3623EAC5279705C500853F87 /* ToastMessageView.swift */, + 6A4A20EB278B58FC00DD4DCD /* RequestScene */, + 191E8C17278F01C60042B8F1 /* SMPopUpVC.swift */, + 3623EA992792A8D100853F87 /* SMRequestPopUpVC.swift */, + 191E8C1D278F471E0042B8F1 /* FriendsScene */, + 36D9E8B6278BDC7B00D5244D /* PlansScene */, + 1912FCAC278AB77900F33952 /* CalendarScene */, + 360E6F4A2789A74200DACFD8 /* Tabbar */, + 36EFE3042785C7BE00466818 /* HomeScene */, + ); + path = VCs; + sourceTree = ""; + }; + 36EFE3072785C80300466818 /* Xibs */ = { + isa = PBXGroup; + children = ( + 36EFE3092785C81F00466818 /* TVC */, + 36EFE3082785C81700466818 /* CVC */, + ); + path = Xibs; + sourceTree = ""; + }; + 36EFE3082785C81700466818 /* CVC */ = { + isa = PBXGroup; + children = ( + 19E633DF278D851900776D0C /* CalendarScene */, + 3654530E278E7F0F00A0770F /* PlansScene */, + 360E6F49278998DA00DACFD8 /* HomeScene */, + 1919F5D3278B5E18009F33AE /* CalendarPlansCVC.xib */, + ); + path = CVC; + sourceTree = ""; + }; + 36EFE3092785C81F00466818 /* TVC */ = { + isa = PBXGroup; + children = ( + 1960D42227B1638E002558C4 /* RequestScene */, + 191E8C28278F49250042B8F1 /* FriendsScene */, + ); + path = TVC; + sourceTree = ""; + }; + 36EFE30A2785C84C00466818 /* Storyboards */ = { + isa = PBXGroup; + children = ( + 6AB4266F2843CC8E00790262 /* MyPageScene */, + 3623EABA2796AA0F00853F87 /* RegisterScene */, + 3623EAA22795435E00853F87 /* LoginScene */, + 3623EA9B2792A90E00853F87 /* SMRequestPopUp.storyboard */, + 6A4A20EE278B5FB300DD4DCD /* RequestScene */, + 191E8C19278F01DD0042B8F1 /* SMPopUp.storyboard */, + 19F1AF792793453700022A6A /* SelectedDateSheet.xib */, + 191E8C20278F47890042B8F1 /* FriendsScene */, + 36D9E8B9278BDCD400D5244D /* PlansScene */, + 36D9E8AD2789A7B500D5244D /* Tabbar */, + 36EFE30D2785C8BF00466818 /* HomeScene */, + 1912FCAB278AB72600F33952 /* CalendarScene */, + 36EFE30C2785C8B000466818 /* LaunchScreen */, + ); + path = Storyboards; + sourceTree = ""; + }; + 36EFE30B2785C88F00466818 /* Support */ = { + isa = PBXGroup; + children = ( + 36EFE2EC2785682200466818 /* AppDelegate.swift */, + 36EFE2EE2785682200466818 /* SceneDelegate.swift */, + ); + path = Support; + sourceTree = ""; + }; + 36EFE30C2785C8B000466818 /* LaunchScreen */ = { + isa = PBXGroup; + children = ( + 36EFE2F72785682400466818 /* LaunchScreen.storyboard */, + ); + path = LaunchScreen; + sourceTree = ""; + }; + 36EFE30D2785C8BF00466818 /* HomeScene */ = { + isa = PBXGroup; + children = ( + 36EFE30E2785C8CA00466818 /* Home.storyboard */, + ); + path = HomeScene; + sourceTree = ""; + }; + 6A16EFA5286A263900387C0B /* Constants++ */ = { + isa = PBXGroup; + children = ( + 6A16EFA6286A265F00387C0B /* UserDefaultsKey.swift */, + ); + path = "Constants++"; + sourceTree = ""; + }; + 6A4A20EB278B58FC00DD4DCD /* RequestScene */ = { + isa = PBXGroup; + children = ( + 1960D41D27AFC6E2002558C4 /* Components */, + 6A4A20EC278B5F7000DD4DCD /* RequestPlansContentsVC.swift */, + 6A7C7C0E279364CA006D39F6 /* RequestPlansDateVC.swift */, + 6A8E388327970D7000A480F7 /* PickedDate.swift */, + 6AC748652798256B0027B823 /* WeekDay.swift */, + ); + path = RequestScene; + sourceTree = ""; + }; + 6A4A20EE278B5FB300DD4DCD /* RequestScene */ = { + isa = PBXGroup; + children = ( + 6A4A20EF278B5FE300DD4DCD /* RequestPlansContents.storyboard */, + 6A7C7C12279367DF006D39F6 /* RequestPlansDate.storyboard */, + ); + path = RequestScene; + sourceTree = ""; + }; + 6A5302AF279F1D2C00DB4C3F /* RequestScene */ = { + isa = PBXGroup; + children = ( + 6AE863062799E2EA0050A978 /* PostRequestPlansService.swift */, + 6AE863082799FABC0050A978 /* GetScheduleService.swift */, + ); + path = RequestScene; + sourceTree = ""; + }; + 6A5302B0279F1DE300DB4C3F /* RequestScene */ = { + isa = PBXGroup; + children = ( + 6AE863022799DEB70050A978 /* InvitationPlanDataModel.swift */, + 6AE863042799DFA20050A978 /* PlanRequestDataModel.swift */, + ); + path = RequestScene; + sourceTree = ""; + }; + 6A7C7C0D27934D58006D39F6 /* Recovered References */ = { + isa = PBXGroup; + children = ( + 3682125327897806005D7236 /* dummy1.swift */, + ); + name = "Recovered References"; + sourceTree = ""; + }; + 6AB4266E284388B400790262 /* MyPageScene */ = { + isa = PBXGroup; + children = ( + 3623EAF7279A9EA500853F87 /* MyPageView.swift */, + 6A3B24C4284C80E900DA939F /* UserInfoVC.swift */, + 6A3B24CA284CC53500DA939F /* ChangePasswordVC.swift */, + ); + path = MyPageScene; + sourceTree = ""; + }; + 6AB4266F2843CC8E00790262 /* MyPageScene */ = { + isa = PBXGroup; + children = ( + 6A3B24C0284C80A400DA939F /* UserInfo.storyboard */, + 6A3B24C6284CC50600DA939F /* ChangePassword.storyboard */, + ); + path = MyPageScene; + sourceTree = ""; + }; + 6AD9948E28A2A5270014D60B /* Components */ = { + isa = PBXGroup; + children = ( + 6AD9948F28A403EE0014D60B /* NameChipCVC.swift */, + ); + path = Components; + sourceTree = ""; + }; + 6AE7F266286D3BD50042FD79 /* MypageScene */ = { + isa = PBXGroup; + children = ( + 6AE7F267286D3C330042FD79 /* ProfileImageResponseModel.swift */, + 6ABF643F28707D3B0017A974 /* PutPasswordResponseModel.swift */, + ); + path = MypageScene; + sourceTree = ""; + }; + 6AF609232790BA7B00F49DF4 /* RequestScene */ = { + isa = PBXGroup; + children = ( + 6AF609212790BA6000F49DF4 /* SearchTVC.swift */, + 6A8E387F2795B82400A480F7 /* ScheduleTVC.swift */, + ); + path = RequestScene; + sourceTree = ""; + }; + 6B37187F1B9AA6194E839C69 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3623EAC727971F1600853F87 /* Kingfisher.framework */, + 7E4152A410430753EE447189 /* Pods_SeeMeet.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + FC4A432ECF9271CDC920B7C1 /* Pods */ = { + isa = PBXGroup; + children = ( + B3594175EA1BA01667464A70 /* Pods-SeeMeet.debug.xcconfig */, + DF75C365F4903A6B444D31C6 /* Pods-SeeMeet.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 36EFE2E82785682200466818 /* SeeMeet */ = { + isa = PBXNativeTarget; + buildConfigurationList = 36EFE2FD2785682400466818 /* Build configuration list for PBXNativeTarget "SeeMeet" */; + buildPhases = ( + 4AC1AF953BA0AEABEE007DF3 /* [CP] Check Pods Manifest.lock */, + 36EFE2E52785682200466818 /* Sources */, + 36EFE2E62785682200466818 /* Frameworks */, + 36EFE2E72785682200466818 /* Resources */, + 7C18FC687DA2A0628F958E62 /* [CP] Embed Pods Frameworks */, + 19EBCD7E27AAD08300EC80E0 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SeeMeet; + productName = SeeMeet; + productReference = 36EFE2E92785682200466818 /* SeeMeet.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 36EFE2E12785682200466818 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1320; + LastUpgradeCheck = 1340; + TargetAttributes = { + 36EFE2E82785682200466818 = { + CreatedOnToolsVersion = 13.2.1; + }; + }; + }; + buildConfigurationList = 36EFE2E42785682200466818 /* Build configuration list for PBXProject "SeeMeet" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 36EFE2E02785682200466818; + productRefGroup = 36EFE2EA2785682200466818 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 36EFE2E82785682200466818 /* SeeMeet */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 36EFE2E72785682200466818 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3623EAAD2795928000853F87 /* PlansReceiveCVC.xib in Resources */, + 3623EA90278FDE0C00853F87 /* CompletePlansCVC.xib in Resources */, + 3682123F2785E643005D7236 /* SpoqaHanSansNeo-Light.ttf in Resources */, + 36EFE30F2785C8CA00466818 /* Home.storyboard in Resources */, + 36EFE2F92785682400466818 /* LaunchScreen.storyboard in Resources */, + 6A3B24C1284C80A400DA939F /* UserInfo.storyboard in Resources */, + 3682124B2785E643005D7236 /* SpoqaHanSansNeo-Bold.ttf in Resources */, + 3623EA9C2792A90E00853F87 /* SMRequestPopUp.storyboard in Resources */, + 191E8C1A278F01DD0042B8F1 /* SMPopUp.storyboard in Resources */, + 19E633E1278D853500776D0C /* CalendarDetail.storyboard in Resources */, + 368212492785E643005D7236 /* SpoqaHanSansNeo-Medium.ttf in Resources */, + 3623EAA62795437D00853F87 /* EmailLogin.storyboard in Resources */, + 36EFE2F62785682400466818 /* Assets.xcassets in Resources */, + 6A3B24C7284CC50600DA939F /* ChangePassword.storyboard in Resources */, + 3623EA88278F686A00853F87 /* ProgressReceiveCVC.xib in Resources */, + 3682124C2785E643005D7236 /* LICENSE in Resources */, + 191E8C27278F490A0042B8F1 /* FriendsListTVC.xib in Resources */, + 368212482785E643005D7236 /* DINPro-Bold.otf in Resources */, + 3623EAA027931EC000853F87 /* PlansSendList.storyboard in Resources */, + 19110AD127D73AB5001121AE /* AvailTimeOptionView.xib in Resources */, + 1912FCB0278AB7BB00F33952 /* Calendar.storyboard in Resources */, + 36D9E8AF2789A7BF00D5244D /* Tabbar.storyboard in Resources */, + 3623EABC2796AA2800853F87 /* EmailRegister.storyboard in Resources */, + 368212442785E643005D7236 /* SpoqaHanSansNeo-Regular.ttf in Resources */, + 368212452785E643005D7236 /* DINPro-Regular.otf in Resources */, + 36D9E8BB278BDCE800D5244D /* PlansList.storyboard in Resources */, + 6AE14B7627D72734000CD46F /* ProfileRegister.storyboard in Resources */, + 3682124A2785E643005D7236 /* DINPro-Light.otf in Resources */, + 360E6F4727898D4400DACFD8 /* HomeEventCVC.xib in Resources */, + 3623EA84278F65AB00853F87 /* ProgressSendCVC.xib in Resources */, + 19A0256827D73CC400ADB0C5 /* ConfirmTimeOptionView.xib in Resources */, + 368212462785E643005D7236 /* DINNeuzeitGroteskStd-BdCond.otf in Resources */, + 368212402785E643005D7236 /* DINMittelschriftStd.otf in Resources */, + 368212432785E643005D7236 /* DINPro-Black.otf in Resources */, + 191E8C22278F47990042B8F1 /* FriendsList.storyboard in Resources */, + 6A7C7C13279367DF006D39F6 /* RequestPlansDate.storyboard in Resources */, + 368212472785E643005D7236 /* DINEngschriftStd.otf in Resources */, + 1960D42127B10C93002558C4 /* SelectedDateSheetTVC.xib in Resources */, + 3682123E2785E643005D7236 /* DINNeuzeitGroteskStd-Light.otf in Resources */, + 1950E51C2791FE7B00ED357B /* FriendsAddTVC.xib in Resources */, + 19F1AF7A2793453700022A6A /* SelectedDateSheet.xib in Resources */, + 6A226DCC27D5174A003B5FC4 /* MainLogin.storyboard in Resources */, + 1950E5162791DAC900ED357B /* FriendsAdd.storyboard in Resources */, + 368212422785E643005D7236 /* SpoqaHanSansNeo-Thin.ttf in Resources */, + 6A4A20F0278B5FE300DD4DCD /* RequestPlansContents.storyboard in Resources */, + 1919F5D5278B5E18009F33AE /* CalendarPlansCVC.xib in Resources */, + 368212412785E643005D7236 /* DINPro-Medium.otf in Resources */, + 3623EA942791283600853F87 /* PlansReceiveList.storyboard in Resources */, + 191779992859A1A5004E7E63 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 19EBCD7E27AAD08300EC80E0 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", + "$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ \"${CONFIGURATION}\" != \"Debug\" ]; then\n \"${PODS_ROOT}/FirebaseCrashlytics/run\"\n \"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols\" -gsp ${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist -p ios ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}\nfi\n"; + }; + 4AC1AF953BA0AEABEE007DF3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SeeMeet-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 7C18FC687DA2A0628F958E62 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SeeMeet/Pods-SeeMeet-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SeeMeet/Pods-SeeMeet-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SeeMeet/Pods-SeeMeet-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 36EFE2E52785682200466818 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6AC748662798256B0027B823 /* WeekDay.swift in Sources */, + 6AE7F268286D3C330042FD79 /* ProfileImageResponseModel.swift in Sources */, + 3623EAAE2795929000853F87 /* PlansReceiveCVC.swift in Sources */, + 3623EAD02797D6FA00853F87 /* LastDateDataModel.swift in Sources */, + 197253FA27E2EF56004BBBB7 /* SocialLoginDataModel.swift in Sources */, + 36EFE3212785CC3B00466818 /* Date++.swift in Sources */, + 1960D42027B10C93002558C4 /* SelectedDateSheetTVC.swift in Sources */, + 6A39C2F0286EDAE000BA31CF /* ImageManager.swift in Sources */, + 1969D94028B0E57D009BCCA0 /* PostRefreshAccessTokenService.swift in Sources */, + 19B1E6A728563B8F009D6507 /* RequestCoordinator.swift in Sources */, + 19509B652796C00D00CAA4C2 /* MonthlyPlansResponseModel.swift in Sources */, + 6A5FCEE627E640C200FEA926 /* AppleAuthService.swift in Sources */, + 19F1AF722792AB0300022A6A /* SelectedDateSheet.swift in Sources */, + 191E8C2A278FFB870042B8F1 /* SMSearchBar.swift in Sources */, + 197F57B5279913C000FB4467 /* PlanDetailResponseModel.swift in Sources */, + 6A226DCA27D4B73E003B5FC4 /* MainLoginVC.swift in Sources */, + 3623EADA27980F0600853F87 /* GetPlansListDataService.swift in Sources */, + 19B635D828A5134C0097895A /* NotificationSetResponseModel.swift in Sources */, + 3623EA8F278FDE0C00853F87 /* CompletePlansCVC.swift in Sources */, + 3623EA9E27931EAC00853F87 /* PlansSendListVC.swift in Sources */, + 3623EAE627994DE400853F87 /* PostInvitationService.swift in Sources */, + 3623EA922791282700853F87 /* PlansReceiveVC.swift in Sources */, + 191F1BFC28B650E40007E375 /* Notification++.swift in Sources */, + 36EFE31F2785CC3B00466818 /* UIFont++.swift in Sources */, + 3623EAB92796AA0600853F87 /* EmailRegisterVC.swift in Sources */, + 3623EAD42797DAE900853F87 /* FriendsListDataModel.swift in Sources */, + 3623EAC42797038100853F87 /* PostLoginService.swift in Sources */, + 1912FCAE278AB7A000F33952 /* CalendarVC.swift in Sources */, + 6AE863072799E2EA0050A978 /* PostRequestPlansService.swift in Sources */, + 3623EAD827980EC000853F87 /* PlansListDataModel.swift in Sources */, + 36EFE31E2785CC3B00466818 /* UIView++.swift in Sources */, + 1919F5D4278B5E18009F33AE /* CalendarPlansCVC.swift in Sources */, + 6AD9949028A403EE0014D60B /* NameChipCVC.swift in Sources */, + 1985C742283CAA2400E7A147 /* CalendarCoordinator.swift in Sources */, + 6AF609222790BA6000F49DF4 /* SearchTVC.swift in Sources */, + 6AE7F265286C83A80042FD79 /* PostProfileImageService.swift in Sources */, + 36EFE31B2785CC3B00466818 /* UIScreen++.swift in Sources */, + 3623EACE2797330800853F87 /* GetHomeDataService.swift in Sources */, + 3623EAEE2799D28800853F87 /* GetPlansSendDetailService.swift in Sources */, + 3623EAEC2799D20C00853F87 /* PlansSendDetailDataModel.swift in Sources */, + 3623EAF02799F07B00853F87 /* PlansRequestAcceptDataModel.swift in Sources */, + 3623EAF42799F61200853F87 /* InvitationCancelDataModel.swift in Sources */, + 6A2BEFFD28993A9B001BC91F /* MyPageCoordinator.swift in Sources */, + 191E8C26278F490A0042B8F1 /* FriendsListTVC.swift in Sources */, + 1969D94228B0EA65009BCCA0 /* AccessTokenRefreshDataModel.swift in Sources */, + 3623EAC6279705C500853F87 /* ToastMessageView.swift in Sources */, + 3623EADC27988AFE00853F87 /* PlansDetailDataModel.swift in Sources */, + 3623EAF22799F11500853F87 /* PostPlansRequestAcceptService.swift in Sources */, + 19509B6B27986D2A00CAA4C2 /* FriendsSearchResponseModel.swift in Sources */, + 36EFE3112785C8D600466818 /* HomeVC.swift in Sources */, + 6AE863032799DEB70050A978 /* InvitationPlanDataModel.swift in Sources */, + 19509B6F27989DAF00CAA4C2 /* FriendsAddService.swift in Sources */, + 3623EAA42795437400853F87 /* EmailLoginVC.swift in Sources */, + 6ABF644028707D3B0017A974 /* PutPasswordResponseModel.swift in Sources */, + 6A139082289D72C40029FFD1 /* EmailLoginResponseModel.swift in Sources */, + 6A4A20ED278B5F7000DD4DCD /* RequestPlansContentsVC.swift in Sources */, + 3623EAF8279A9EA500853F87 /* MyPageView.swift in Sources */, + 6AE9B384285F31920021E6CE /* PutWithdrawalService.swift in Sources */, + 19E3BE5828A37EC000F0EC86 /* NetworkRechabilityService.swift in Sources */, + 3623EAD22797D72500853F87 /* GetLastDateService.swift in Sources */, + 36EFE2ED2785682200466818 /* AppDelegate.swift in Sources */, + 6A3B24CB284CC53500DA939F /* ChangePasswordVC.swift in Sources */, + 6AD9949428A7645B0014D60B /* GetCanceldPlansDetailService.swift in Sources */, + 19D7B2DE2838C02800EC5EBD /* HomeCoordinator.swift in Sources */, + 6A7C7C0B27920F80006D39F6 /* SearchFieldTokenCVC.swift in Sources */, + 19509B6D27989CD800CAA4C2 /* FriendsAddResponseModel.swift in Sources */, + 19B1E6A32856054D009D6507 /* LoginCoordinator.swift in Sources */, + 191E8C18278F01C60042B8F1 /* SMPopUpVC.swift in Sources */, + 19E3BE4328A1E2CC00F0EC86 /* PutPlanDeleteService.swift in Sources */, + 6A8E38802795B82400A480F7 /* ScheduleTVC.swift in Sources */, + 19110ACC27D71E9D001121AE /* AvailTimeOptionView.swift in Sources */, + 3623EAD62797DC4500853F87 /* GetFriendsListService.swift in Sources */, + 6AE14B7827D7277C000CD46F /* ProfileRegisterVC.swift in Sources */, + 19D7B2E02838C04100EC5EBD /* AppCoordinator.swift in Sources */, + 1906B3EE27CA804D00F94DD8 /* ConfirmTimeOptionView.swift in Sources */, + 36EFE31D2785CC3B00466818 /* UIColor++.swift in Sources */, + 3623EAAA2795701D00853F87 /* GrayTextField.swift in Sources */, + 3623EAAC279570A000853F87 /* GrayTextView.swift in Sources */, + 19B635D628A510180097895A /* PostPushNotificationSetService.swift in Sources */, + 19E3BE5A28A38BB300F0EC86 /* PutInvitationListService.swift in Sources */, + 6A3B24C5284C80E900DA939F /* UserInfoVC.swift in Sources */, + 1969D94428B100E0009BCCA0 /* AccessInterceptor.swift in Sources */, + 19509B672796C25100CAA4C2 /* CalendarService.swift in Sources */, + 6AE9B386285F38350021E6CE /* WithdrawalDataModel.swift in Sources */, + 191E8C1C278F38DC0042B8F1 /* String++.swift in Sources */, + 19E3BE4528A1E5CF00F0EC86 /* PlansDeleteResponseModel.swift in Sources */, + 194F91022847247900E95333 /* PlansCoordinator.swift in Sources */, + 3623EAB22796A77A00853F87 /* Constants.swift in Sources */, + 6AE863092799FABC0050A978 /* GetScheduleService.swift in Sources */, + 360E6F4627898D4400DACFD8 /* HomeEventCVC.swift in Sources */, + 6A8E388427970D7000A480F7 /* PickedDate.swift in Sources */, + 19509B692798583500CAA4C2 /* FriendsSearchService.swift in Sources */, + 3623EA87278F686A00853F87 /* ProgressSendCVC.swift in Sources */, + 36EFE31A2785CC3B00466818 /* UIViewController++.swift in Sources */, + 3623EA9A2792A8D100853F87 /* SMRequestPopUpVC.swift in Sources */, + 6A16EFA7286A265F00387C0B /* UserDefaultsKey.swift in Sources */, + 6AD9949228A763210014D60B /* CanceledPlanDetailModel.swift in Sources */, + 3623EAB42796A7DE00853F87 /* NetworkResult.swift in Sources */, + 19CEDB86281314FE006E1DFD /* PutNameAndIdService.swift in Sources */, + 36EFE31C2785CC3B00466818 /* UICollectionView++.swift in Sources */, + 3623EAF62799F65900853F87 /* PutInvitationCancelService.swift in Sources */, + 3623EA83278F65AB00853F87 /* ProgressReceiveCVC.swift in Sources */, + 19CEDB842813142C006E1DFD /* PutNameAndIdResponseDataModel.swift in Sources */, + 1906B3F727D496A000F94DD8 /* KakaoAuthService.swift in Sources */, + 3623EAE427994D7A00853F87 /* InvitationPlansDataModel.swift in Sources */, + 3623EABE2796E76500853F87 /* RegisterDataModel.swift in Sources */, + 6AAD47C6278DF01300D8C800 /* UITextField++.swift in Sources */, + 3623EAE82799A39900853F87 /* InvitationRejectDataModel.swift in Sources */, + 191E8C1F278F477B0042B8F1 /* FriendsListVC.swift in Sources */, + 19B1E6A12856007F009D6507 /* FriendsCoordinator.swift in Sources */, + 3623EACC2797324600853F87 /* HomeDataModel.swift in Sources */, + 36EFE2EF2785682200466818 /* SceneDelegate.swift in Sources */, + 3623EAE22798B06C00853F87 /* GetResponseDateDataService.swift in Sources */, + 6A7C7C0F279364CA006D39F6 /* RequestPlansDateVC.swift in Sources */, + 36EFE3202785CC3B00466818 /* UITabelView++.swift in Sources */, + 1950E5142791DAA300ED357B /* FriendsAddVC.swift in Sources */, + 192C6EF52877B32000490821 /* SearchInputFieldCVC.swift in Sources */, + 3623EAC02796ED1000853F87 /* PostRegisterService.swift in Sources */, + 6AE863052799DFA20050A978 /* PlanRequestDataModel.swift in Sources */, + 3623EADE27988B8C00853F87 /* GetPlansDetailService.swift in Sources */, + 3623EAEA2799A3D700853F87 /* PostInvitationRejectService.swift in Sources */, + 19E633DE278D84F200776D0C /* CalendarDetailVC.swift in Sources */, + 19D7B2DC2838BC1900EC5EBD /* Coordinator.swift in Sources */, + 36545312278EA30400A0770F /* UILabel++.swift in Sources */, + 3623EAC22796F8A500853F87 /* TokenUtils.swift in Sources */, + 19B1E6A528562335009D6507 /* RegisterCoordinator.swift in Sources */, + 3623EAE02798AF8A00853F87 /* ResponseDateDataModel.swift in Sources */, + 36D9E8B12789A81A00D5244D /* TabbarVC.swift in Sources */, + 19E3BE5C28A38D7E00F0EC86 /* InvitationListCancelDataModel.swift in Sources */, + 19B7C1EC27F017A2004A2F68 /* UserDefaults++.swift in Sources */, + 6A8E387C27958A2F00A480F7 /* SelectableWeekDayCVC.swift in Sources */, + 36D9E8B8278BDCCC00D5244D /* PlansListVC.swift in Sources */, + 6ABF643E28707C980017A974 /* PutPasswordService.swift in Sources */, + 1950E51B2791FE7B00ED357B /* FriendsAddTVC.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 36EFE2F72785682400466818 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 36EFE2F82785682400466818 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 36EFE2FB2785682400466818 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 36EFE2FC2785682400466818 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 36EFE2FE2785682400466818 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B3594175EA1BA01667464A70 /* Pods-SeeMeet.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CODE_SIGN_ENTITLEMENTS = SeeMeet/SeeMeet.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 3H85AXMVD3; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SeeMeet/Resource/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = SeeMeet; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle"; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "프로필 사진 설정을 위해 사진 라이브러리 권한이 필요합니다."; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Tabbar; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.ios.seemeet; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 36EFE2FF2785682400466818 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DF75C365F4903A6B444D31C6 /* Pods-SeeMeet.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CODE_SIGN_ENTITLEMENTS = SeeMeet/SeeMeet.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; + DEVELOPMENT_TEAM = 3H85AXMVD3; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SeeMeet/Resource/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = SeeMeet; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle"; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "프로필 사진 설정을 위해 사진 라이브러리 권한이 필요합니다."; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Tabbar; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.ios.seemeet; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 36EFE2E42785682200466818 /* Build configuration list for PBXProject "SeeMeet" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 36EFE2FB2785682400466818 /* Debug */, + 36EFE2FC2785682400466818 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 36EFE2FD2785682400466818 /* Build configuration list for PBXNativeTarget "SeeMeet" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 36EFE2FE2785682400466818 /* Debug */, + 36EFE2FF2785682400466818 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 36EFE2E12785682200466818 /* Project object */; +} diff --git a/SeeMeet/SeeMeet.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SeeMeet/SeeMeet.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/SeeMeet/SeeMeet.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SeeMeet/SeeMeet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SeeMeet/SeeMeet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/SeeMeet/SeeMeet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SeeMeet/SeeMeet.xcodeproj/xcshareddata/xcschemes/SeeMeet 1.xcscheme b/SeeMeet/SeeMeet.xcodeproj/xcshareddata/xcschemes/SeeMeet 1.xcscheme new file mode 100644 index 0000000..5186265 --- /dev/null +++ b/SeeMeet/SeeMeet.xcodeproj/xcshareddata/xcschemes/SeeMeet 1.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet.xcodeproj/xcshareddata/xcschemes/SeeMeet.xcscheme b/SeeMeet/SeeMeet.xcodeproj/xcshareddata/xcschemes/SeeMeet.xcscheme new file mode 100644 index 0000000..6aec799 --- /dev/null +++ b/SeeMeet/SeeMeet.xcodeproj/xcshareddata/xcschemes/SeeMeet.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet.xcworkspace/contents.xcworkspacedata b/SeeMeet/SeeMeet.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ab1ee73 --- /dev/null +++ b/SeeMeet/SeeMeet.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/SeeMeet/SeeMeet.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SeeMeet/SeeMeet.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/SeeMeet/SeeMeet.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SeeMeet/SeeMeet/Info.plist b/SeeMeet/SeeMeet/Info.plist new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/SeeMeet/SeeMeet/Info.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AccentColor.colorset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/1024.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..139177a Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/120-1.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/120-1.png new file mode 100644 index 0000000..19c357b Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/120-1.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/120.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..19c357b Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/152.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..b93bff6 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/167.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..1672b48 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/180.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..35cd1b3 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/20.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..2dd8c3c Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/29.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..1250583 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/40-1.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/40-1.png new file mode 100644 index 0000000..80ea2d0 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/40-1.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/40-2.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/40-2.png new file mode 100644 index 0000000..80ea2d0 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/40-2.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/40.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..80ea2d0 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/58-1.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/58-1.png new file mode 100644 index 0000000..91d51a4 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/58-1.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/58.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..91d51a4 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/60.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..3b3b318 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/76.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..bde0aff Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/80-1.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/80-1.png new file mode 100644 index 0000000..4f4c21d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/80-1.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/80.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..4f4c21d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/87.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..602ba58 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..e594077 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "120-1.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40-1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58-1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40-2.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80-1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Contents.json new file mode 100644 index 0000000..8f3c444 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Ellipse_dummy.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Ellipse_dummy@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Ellipse_dummy@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Ellipse_dummy.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Ellipse_dummy.png new file mode 100644 index 0000000..e97369f Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Ellipse_dummy.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Ellipse_dummy@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Ellipse_dummy@2x.png new file mode 100644 index 0000000..709d3a4 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Ellipse_dummy@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Ellipse_dummy@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Ellipse_dummy@3x.png new file mode 100644 index 0000000..154bbd0 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Ellipse_dummy.imageset/Ellipse_dummy@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Contents.json new file mode 100644 index 0000000..9aee7c7 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Image_dummy.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Image_dummy@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Image_dummy@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Image_dummy.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Image_dummy.png new file mode 100644 index 0000000..7accc8a Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Image_dummy.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Image_dummy@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Image_dummy@2x.png new file mode 100644 index 0000000..b9c6d64 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Image_dummy@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Image_dummy@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Image_dummy@3x.png new file mode 100644 index 0000000..de80e97 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/Image_dummy.imageset/Image_dummy@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/Contents.json new file mode 100644 index 0000000..116db12 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "scale" : "1x", + "filename" : "btnAddFriends.png", + "idiom" : "universal" + }, + { + "filename" : "btnAddFriends@2x.png", + "scale" : "2x", + "idiom" : "universal" + }, + { + "scale" : "3x", + "idiom" : "universal", + "filename" : "btnAddFriends@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/btnAddFriends.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/btnAddFriends.png new file mode 100644 index 0000000..1bac5d1 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/btnAddFriends.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/btnAddFriends@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/btnAddFriends@2x.png new file mode 100644 index 0000000..1b575b1 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/btnAddFriends@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/btnAddFriends@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/btnAddFriends@3x.png new file mode 100644 index 0000000..d6fb427 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends.imageset/btnAddFriends@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/Contents.json new file mode 100644 index 0000000..210cf29 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "propCircle.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "propCircle@2x.png", + "scale" : "2x" + }, + { + "filename" : "propCircle@3x.png", + "scale" : "3x", + "idiom" : "universal" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/propCircle.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/propCircle.png new file mode 100644 index 0000000..8e364f4 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/propCircle.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/propCircle@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/propCircle@2x.png new file mode 100644 index 0000000..a390e9f Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/propCircle@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/propCircle@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/propCircle@3x.png new file mode 100644 index 0000000..b40a051 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_circle.imageset/propCircle@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/Contents.json new file mode 100644 index 0000000..d17a676 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "btnAddFriendsFin.png", + "scale" : "1x" + }, + { + "scale" : "2x", + "idiom" : "universal", + "filename" : "btnAddFriendsFin@2x.png" + }, + { + "scale" : "3x", + "filename" : "btnAddFriendsFin@3x.png", + "idiom" : "universal" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/btnAddFriendsFin.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/btnAddFriendsFin.png new file mode 100644 index 0000000..d5e1835 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/btnAddFriendsFin.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/btnAddFriendsFin@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/btnAddFriendsFin@2x.png new file mode 100644 index 0000000..3bba9dd Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/btnAddFriendsFin@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/btnAddFriendsFin@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/btnAddFriendsFin@3x.png new file mode 100644 index 0000000..e278d50 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-friends_fin.imageset/btnAddFriendsFin@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/Contents.json new file mode 100644 index 0000000..03e28e6 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btnAddMessage.png", + "scale" : "1x", + "idiom" : "universal" + }, + { + "scale" : "2x", + "idiom" : "universal", + "filename" : "btnAddMessage@2x.png" + }, + { + "scale" : "3x", + "idiom" : "universal", + "filename" : "btnAddMessage@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/btnAddMessage.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/btnAddMessage.png new file mode 100644 index 0000000..c6d2a02 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/btnAddMessage.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/btnAddMessage@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/btnAddMessage@2x.png new file mode 100644 index 0000000..95c1073 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/btnAddMessage@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/btnAddMessage@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/btnAddMessage@3x.png new file mode 100644 index 0000000..57b6e6c Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_add-message.imageset/btnAddMessage@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/Contents.json new file mode 100644 index 0000000..c284192 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_back.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_back@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_back@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/btn_back.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/btn_back.png new file mode 100644 index 0000000..83229f3 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/btn_back.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/btn_back@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/btn_back@2x.png new file mode 100644 index 0000000..642ba09 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/btn_back@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/btn_back@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/btn_back@3x.png new file mode 100644 index 0000000..80f1aa1 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back.imageset/btn_back@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/Contents.json new file mode 100644 index 0000000..93d769a --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "btnBackWhite.png", + "scale" : "1x" + }, + { + "filename" : "btnBackWhite@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btnBackWhite@3x.png", + "scale" : "3x", + "idiom" : "universal" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/btnBackWhite.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/btnBackWhite.png new file mode 100644 index 0000000..949348b Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/btnBackWhite.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/btnBackWhite@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/btnBackWhite@2x.png new file mode 100644 index 0000000..51e0dee Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/btnBackWhite@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/btnBackWhite@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/btnBackWhite@3x.png new file mode 100644 index 0000000..fa100ed Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_back_white.imageset/btnBackWhite@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Component 184.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Component 184.png new file mode 100644 index 0000000..d7da013 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Component 184.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Component 184@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Component 184@2x.png new file mode 100644 index 0000000..803b61a Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Component 184@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Component 184@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Component 184@3x.png new file mode 100644 index 0000000..47bfefe Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Component 184@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Contents.json new file mode 100644 index 0000000..7d99ea0 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Component 184.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Component 184@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Component 184@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/Contents.json new file mode 100644 index 0000000..9308e4f --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_close_bold.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_close_bold@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_close_bold@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/btn_close_bold.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/btn_close_bold.png new file mode 100644 index 0000000..0e725a3 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/btn_close_bold.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/btn_close_bold@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/btn_close_bold@2x.png new file mode 100644 index 0000000..b0ceeea Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/btn_close_bold@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/btn_close_bold@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/btn_close_bold@3x.png new file mode 100644 index 0000000..0bbef2d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_bold.imageset/btn_close_bold@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/Contents.json new file mode 100644 index 0000000..72cd9ca --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btnCloseWhite.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btnCloseWhite@2x.png", + "scale" : "2x", + "idiom" : "universal" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "btnCloseWhite@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/btnCloseWhite.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/btnCloseWhite.png new file mode 100644 index 0000000..e2849c7 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/btnCloseWhite.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/btnCloseWhite@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/btnCloseWhite@2x.png new file mode 100644 index 0000000..a280bcc Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/btnCloseWhite@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/btnCloseWhite@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/btnCloseWhite@3x.png new file mode 100644 index 0000000..7ec58ad Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_close_white.imageset/btnCloseWhite@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/Contents.json new file mode 100644 index 0000000..2c6ef98 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_embody.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_embody@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_embody@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/btn_embody.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/btn_embody.png new file mode 100644 index 0000000..9f2c9c6 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/btn_embody.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/btn_embody@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/btn_embody@2x.png new file mode 100644 index 0000000..6730030 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/btn_embody@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/btn_embody@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/btn_embody@3x.png new file mode 100644 index 0000000..dee754d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody.imageset/btn_embody@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/Contents.json new file mode 100644 index 0000000..6827bb5 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_embody_on.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_embody_on@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_embody_on@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/btn_embody_on.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/btn_embody_on.png new file mode 100644 index 0000000..4ea4616 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/btn_embody_on.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/btn_embody_on@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/btn_embody_on@2x.png new file mode 100644 index 0000000..e015806 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/btn_embody_on@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/btn_embody_on@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/btn_embody_on@3x.png new file mode 100644 index 0000000..db8771d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_embody_on.imageset/btn_embody_on@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/Contents.json new file mode 100644 index 0000000..00ca7ec --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_friends.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_friends@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_friends@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/btn_friends.svg b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/btn_friends.svg new file mode 100644 index 0000000..5a7ede1 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/btn_friends.svg @@ -0,0 +1,4 @@ + + + + diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/btn_friends@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/btn_friends@2x.png new file mode 100644 index 0000000..c7f545a Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/btn_friends@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/btn_friends@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/btn_friends@3x.png new file mode 100644 index 0000000..b08e3ca Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_friends.imageset/btn_friends@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/Contents.json new file mode 100644 index 0000000..0c07afd --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_menu.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_menu@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_menu@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/btn_menu.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/btn_menu.png new file mode 100644 index 0000000..1e730cd Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/btn_menu.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/btn_menu@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/btn_menu@2x.png new file mode 100644 index 0000000..b0fdf23 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/btn_menu@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/btn_menu@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/btn_menu@3x.png new file mode 100644 index 0000000..eb9518f Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_menu.imageset/btn_menu@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Contents.json new file mode 100644 index 0000000..61b50df --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Frame 216.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 216@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 216@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Frame 216.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Frame 216.png new file mode 100644 index 0000000..abc3e0c Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Frame 216.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Frame 216@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Frame 216@2x.png new file mode 100644 index 0000000..c37b8c5 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Frame 216@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Frame 216@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Frame 216@3x.png new file mode 100644 index 0000000..c317242 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plansName.imageset/Frame 216@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/Contents.json new file mode 100644 index 0000000..a6d8687 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_plus.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_plus@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_plus@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/btn_plus.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/btn_plus.png new file mode 100644 index 0000000..416b445 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/btn_plus.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/btn_plus@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/btn_plus@2x.png new file mode 100644 index 0000000..9d1fd0d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/btn_plus@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/btn_plus@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/btn_plus@3x.png new file mode 100644 index 0000000..069025d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_plus.imageset/btn_plus@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/Contents.json new file mode 100644 index 0000000..55d46dd --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btnRemove.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btnRemove@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btnRemove@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/btnRemove.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/btnRemove.png new file mode 100644 index 0000000..fe6184b Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/btnRemove.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/btnRemove@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/btnRemove@2x.png new file mode 100644 index 0000000..bf79298 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/btnRemove@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/btnRemove@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/btnRemove@3x.png new file mode 100644 index 0000000..9c9cbf8 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove.imageset/btnRemove@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/Contents.json new file mode 100644 index 0000000..d45156c --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btnRemoveBlack.png", + "scale" : "1x", + "idiom" : "universal" + }, + { + "filename" : "btnRemoveBlack@2x.png", + "scale" : "2x", + "idiom" : "universal" + }, + { + "idiom" : "universal", + "filename" : "btnRemoveBlack@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/btnRemoveBlack.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/btnRemoveBlack.png new file mode 100644 index 0000000..18f16cf Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/btnRemoveBlack.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/btnRemoveBlack@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/btnRemoveBlack@2x.png new file mode 100644 index 0000000..4773004 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/btnRemoveBlack@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/btnRemoveBlack@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/btnRemoveBlack@3x.png new file mode 100644 index 0000000..3c9a1bf Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_remove_black.imageset/btnRemoveBlack@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/Contents.json new file mode 100644 index 0000000..a5cbc4c --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "btn_send_message.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_send_message@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_send_message@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/btn_send_message.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/btn_send_message.png new file mode 100644 index 0000000..e07b61d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/btn_send_message.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/btn_send_message@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/btn_send_message@2x.png new file mode 100644 index 0000000..6fe8528 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/btn_send_message@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/btn_send_message@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/btn_send_message@3x.png new file mode 100644 index 0000000..6fc2cc2 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_send_message.imageset/btn_send_message@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/Contents.json new file mode 100644 index 0000000..a3774fd --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_sign-up.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_sign-up@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_sign-up@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/btn_sign-up.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/btn_sign-up.png new file mode 100644 index 0000000..2a73793 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/btn_sign-up.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/btn_sign-up@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/btn_sign-up@2x.png new file mode 100644 index 0000000..c4de9c6 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/btn_sign-up@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/btn_sign-up@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/btn_sign-up@3x.png new file mode 100644 index 0000000..86ab97e Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/btn_sign-up.imageset/btn_sign-up@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Component 89.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Component 89.png new file mode 100644 index 0000000..322e0d2 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Component 89.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Component 89@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Component 89@2x.png new file mode 100644 index 0000000..83a650c Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Component 89@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Component 89@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Component 89@3x.png new file mode 100644 index 0000000..e340959 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Component 89@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Contents.json new file mode 100644 index 0000000..82055ca --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Component 89.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Component 89@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Component 89@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Component 89.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Component 89.png new file mode 100644 index 0000000..b20f2e8 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Component 89.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Component 89@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Component 89@2x.png new file mode 100644 index 0000000..3ee6143 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Component 89@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Component 89@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Component 89@3x.png new file mode 100644 index 0000000..320ecdf Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Component 89@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Contents.json new file mode 100644 index 0000000..82055ca --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/calendar_ic_clicked.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Component 89.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Component 89@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Component 89@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/Contents.json new file mode 100644 index 0000000..c614e9b --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "dummyImageView.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "dummyImageView@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "dummyImageView@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/dummyImageView.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/dummyImageView.png new file mode 100644 index 0000000..abb7b30 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/dummyImageView.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/dummyImageView@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/dummyImageView@2x.png new file mode 100644 index 0000000..8723021 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/dummyImageView@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/dummyImageView@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/dummyImageView@3x.png new file mode 100644 index 0000000..ef8d722 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/dummyImageView.imageset/dummyImageView@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Component 88.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Component 88.png new file mode 100644 index 0000000..2f78cea Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Component 88.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Component 88@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Component 88@2x.png new file mode 100644 index 0000000..e0738eb Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Component 88@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Component 88@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Component 88@3x.png new file mode 100644 index 0000000..62afd14 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Component 88@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Contents.json new file mode 100644 index 0000000..6616636 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Component 88.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Component 88@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Component 88@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Component 88.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Component 88.png new file mode 100644 index 0000000..657e7aa Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Component 88.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Component 88@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Component 88@2x.png new file mode 100644 index 0000000..8cfbe4b Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Component 88@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Component 88@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Component 88@3x.png new file mode 100644 index 0000000..0e6dc13 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Component 88@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Contents.json new file mode 100644 index 0000000..6616636 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/home_ic_clicked.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Component 88.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Component 88@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Component 88@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/Contents.json new file mode 100644 index 0000000..db60526 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "ic_calendar_right.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_calendar_right@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_calendar_right@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/ic_calendar_right.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/ic_calendar_right.png new file mode 100644 index 0000000..4be5920 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/ic_calendar_right.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/ic_calendar_right@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/ic_calendar_right@2x.png new file mode 100644 index 0000000..222cffd Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/ic_calendar_right@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/ic_calendar_right@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/ic_calendar_right@3x.png new file mode 100644 index 0000000..dbb2817 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_calendar_right.imageset/ic_calendar_right@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/Contents.json new file mode 100644 index 0000000..644c088 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "ic_camera.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_camera@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_camera@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/ic_camera.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/ic_camera.png new file mode 100644 index 0000000..cc1487f Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/ic_camera.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/ic_camera@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/ic_camera@2x.png new file mode 100644 index 0000000..30f3f2d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/ic_camera@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/ic_camera@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/ic_camera@3x.png new file mode 100644 index 0000000..d83614c Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_camera.imageset/ic_camera@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Contents.json new file mode 100644 index 0000000..7a7ff8e --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Property 1=ic_check_active.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Property 1=ic_check_active@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Property 1=ic_check_active@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Property 1=ic_check_active.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Property 1=ic_check_active.png new file mode 100644 index 0000000..dcb716c Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Property 1=ic_check_active.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Property 1=ic_check_active@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Property 1=ic_check_active@2x.png new file mode 100644 index 0000000..0c06b44 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Property 1=ic_check_active@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Property 1=ic_check_active@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Property 1=ic_check_active@3x.png new file mode 100644 index 0000000..8e3c9de Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_active.imageset/Property 1=ic_check_active@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Contents.json new file mode 100644 index 0000000..d767666 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Property 1=ic_check_grey.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Property 1=ic_check_grey@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Property 1=ic_check_grey@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Property 1=ic_check_grey.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Property 1=ic_check_grey.png new file mode 100644 index 0000000..c0b95a6 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Property 1=ic_check_grey.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Property 1=ic_check_grey@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Property 1=ic_check_grey@2x.png new file mode 100644 index 0000000..bb5da83 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Property 1=ic_check_grey@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Property 1=ic_check_grey@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Property 1=ic_check_grey@3x.png new file mode 100644 index 0000000..9bf8db1 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_check_grey.imageset/Property 1=ic_check_grey@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/Contents.json new file mode 100644 index 0000000..e1c13c9 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "ic_e-mail.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_e-mail@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_e-mail@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/ic_e-mail.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/ic_e-mail.png new file mode 100644 index 0000000..5bcc713 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/ic_e-mail.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/ic_e-mail@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/ic_e-mail@2x.png new file mode 100644 index 0000000..47a6bf3 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/ic_e-mail@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/ic_e-mail@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/ic_e-mail@3x.png new file mode 100644 index 0000000..9cfd395 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_e-mail.imageset/ic_e-mail@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Contents.json new file mode 100644 index 0000000..5e3930b --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Property 1=ic_finish_check_inactive.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Property 1=ic_finish_check_inactive@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Property 1=ic_finish_check_inactive@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Property 1=ic_finish_check_inactive.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Property 1=ic_finish_check_inactive.png new file mode 100644 index 0000000..60dcc82 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Property 1=ic_finish_check_inactive.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Property 1=ic_finish_check_inactive@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Property 1=ic_finish_check_inactive@2x.png new file mode 100644 index 0000000..0605bea Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Property 1=ic_finish_check_inactive@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Property 1=ic_finish_check_inactive@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Property 1=ic_finish_check_inactive@3x.png new file mode 100644 index 0000000..66d5d9d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_finish_check_inactive.imageset/Property 1=ic_finish_check_inactive@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/Contents.json new file mode 100644 index 0000000..452a88b --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "ic_friend.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_friend@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_friend@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/ic_friend.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/ic_friend.png new file mode 100644 index 0000000..e098572 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/ic_friend.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/ic_friend@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/ic_friend@2x.png new file mode 100644 index 0000000..381a230 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/ic_friend@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/ic_friend@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/ic_friend@3x.png new file mode 100644 index 0000000..d4c57ee Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_friend.imageset/ic_friend@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/Contents.json new file mode 100644 index 0000000..3fff9c8 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "scale" : "1x", + "filename" : "icMypageLogin.png", + "idiom" : "universal" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "icMypageLogin@2x.png" + }, + { + "filename" : "icMypageLogin@3x.png", + "scale" : "3x", + "idiom" : "universal" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/icMypageLogin.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/icMypageLogin.png new file mode 100644 index 0000000..01567da Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/icMypageLogin.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/icMypageLogin@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/icMypageLogin@2x.png new file mode 100644 index 0000000..64853f1 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/icMypageLogin@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/icMypageLogin@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/icMypageLogin@3x.png new file mode 100644 index 0000000..ea31514 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_mypage_login.imageset/icMypageLogin@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Contents.json new file mode 100644 index 0000000..356bc50 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Group 1387.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group 1387@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group 1387@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Group 1387.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Group 1387.png new file mode 100644 index 0000000..f0769e0 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Group 1387.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Group 1387@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Group 1387@2x.png new file mode 100644 index 0000000..cfdf29c Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Group 1387@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Group 1387@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Group 1387@3x.png new file mode 100644 index 0000000..8b0f4d1 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_noti.imageset/Group 1387@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/Contents.json new file mode 100644 index 0000000..8b735af --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "ic_password.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_password@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_password@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/ic_password.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/ic_password.png new file mode 100644 index 0000000..a0fcece Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/ic_password.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/ic_password@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/ic_password@2x.png new file mode 100644 index 0000000..6e938b4 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/ic_password@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/ic_password@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/ic_password@3x.png new file mode 100644 index 0000000..7e765a9 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password.imageset/ic_password@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Component 261.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Component 261.png new file mode 100644 index 0000000..595be05 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Component 261.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Component 261@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Component 261@2x.png new file mode 100644 index 0000000..ed04478 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Component 261@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Component 261@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Component 261@3x.png new file mode 100644 index 0000000..d022b2a Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Component 261@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Contents.json new file mode 100644 index 0000000..33bb548 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_notsee.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Component 261.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Component 261@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Component 261@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Component 261.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Component 261.png new file mode 100644 index 0000000..c4c57ff Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Component 261.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Component 261@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Component 261@2x.png new file mode 100644 index 0000000..02b084f Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Component 261@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Component 261@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Component 261@3x.png new file mode 100644 index 0000000..438e148 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Component 261@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Contents.json new file mode 100644 index 0000000..33bb548 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_password_see.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Component 261.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Component 261@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Component 261@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/Contents.json new file mode 100644 index 0000000..89a4989 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icSearch.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icSearch@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icSearch@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/icSearch.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/icSearch.png new file mode 100644 index 0000000..eae65d3 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/icSearch.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/icSearch@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/icSearch@2x.png new file mode 100644 index 0000000..e8cf2cd Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/icSearch@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/icSearch@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/icSearch@3x.png new file mode 100644 index 0000000..688d5f6 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/ic_search.imageset/icSearch@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/Contents.json new file mode 100644 index 0000000..4ebf4af --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_illust_1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_illust_1@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_illust_1@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/img_illust_1.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/img_illust_1.png new file mode 100644 index 0000000..37f163b Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/img_illust_1.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/img_illust_1@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/img_illust_1@2x.png new file mode 100644 index 0000000..fb2de02 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/img_illust_1@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/img_illust_1@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/img_illust_1@3x.png new file mode 100644 index 0000000..e16362e Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_1.imageset/img_illust_1@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/Contents.json new file mode 100644 index 0000000..eded7e7 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_illust_10.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_illust_10@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_illust_10@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/img_illust_10.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/img_illust_10.png new file mode 100644 index 0000000..44a9e55 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/img_illust_10.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/img_illust_10@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/img_illust_10@2x.png new file mode 100644 index 0000000..761fb42 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/img_illust_10@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/img_illust_10@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/img_illust_10@3x.png new file mode 100644 index 0000000..38f75bd Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_10.imageset/img_illust_10@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/Contents.json new file mode 100644 index 0000000..08975d1 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_illust_11.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_illust_11@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_illust_11@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/img_illust_11.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/img_illust_11.png new file mode 100644 index 0000000..8da4e1d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/img_illust_11.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/img_illust_11@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/img_illust_11@2x.png new file mode 100644 index 0000000..5e037bd Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/img_illust_11@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/img_illust_11@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/img_illust_11@3x.png new file mode 100644 index 0000000..96b0041 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_11.imageset/img_illust_11@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/Contents.json new file mode 100644 index 0000000..8371680 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_illust_2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_illust_2@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_illust_2@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/img_illust_2.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/img_illust_2.png new file mode 100644 index 0000000..d695665 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/img_illust_2.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/img_illust_2@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/img_illust_2@2x.png new file mode 100644 index 0000000..a2f1470 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/img_illust_2@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/img_illust_2@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/img_illust_2@3x.png new file mode 100644 index 0000000..af46e8c Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_2.imageset/img_illust_2@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/Contents.json new file mode 100644 index 0000000..794d672 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_illust_3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_illust_3@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_illust_3@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/img_illust_3.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/img_illust_3.png new file mode 100644 index 0000000..7e6a0f9 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/img_illust_3.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/img_illust_3@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/img_illust_3@2x.png new file mode 100644 index 0000000..5d88cca Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/img_illust_3@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/img_illust_3@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/img_illust_3@3x.png new file mode 100644 index 0000000..1c4fee2 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_3.imageset/img_illust_3@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/Contents.json new file mode 100644 index 0000000..fb730a2 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_illust_4.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_illust_4@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_illust_4@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/img_illust_4.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/img_illust_4.png new file mode 100644 index 0000000..66b87fe Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/img_illust_4.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/img_illust_4@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/img_illust_4@2x.png new file mode 100644 index 0000000..e0178e6 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/img_illust_4@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/img_illust_4@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/img_illust_4@3x.png new file mode 100644 index 0000000..4ed11b5 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_4.imageset/img_illust_4@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/Contents.json new file mode 100644 index 0000000..97b7a75 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_illust_5.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_illust_5@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_illust_5@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/img_illust_5.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/img_illust_5.png new file mode 100644 index 0000000..87b14c1 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/img_illust_5.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/img_illust_5@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/img_illust_5@2x.png new file mode 100644 index 0000000..07b2d8d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/img_illust_5@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/img_illust_5@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/img_illust_5@3x.png new file mode 100644 index 0000000..00334ff Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_5.imageset/img_illust_5@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/Contents.json new file mode 100644 index 0000000..1390936 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_illust_6.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_illust_6@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_illust_6@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/img_illust_6.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/img_illust_6.png new file mode 100644 index 0000000..a68f5b7 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/img_illust_6.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/img_illust_6@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/img_illust_6@2x.png new file mode 100644 index 0000000..f6bfb1d Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/img_illust_6@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/img_illust_6@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/img_illust_6@3x.png new file mode 100644 index 0000000..98b8f18 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_6.imageset/img_illust_6@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/Contents.json new file mode 100644 index 0000000..9944e3f --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_illust_7.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_illust_7@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_illust_7@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/img_illust_7.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/img_illust_7.png new file mode 100644 index 0000000..8e9124b Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/img_illust_7.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/img_illust_7@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/img_illust_7@2x.png new file mode 100644 index 0000000..7482038 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/img_illust_7@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/img_illust_7@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/img_illust_7@3x.png new file mode 100644 index 0000000..0f4c2a6 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_7.imageset/img_illust_7@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/Contents.json new file mode 100644 index 0000000..0fc7e23 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_illust_8.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_illust_8@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_illust_8@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/img_illust_8.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/img_illust_8.png new file mode 100644 index 0000000..16c67f5 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/img_illust_8.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/img_illust_8@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/img_illust_8@2x.png new file mode 100644 index 0000000..65c2748 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/img_illust_8@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/img_illust_8@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/img_illust_8@3x.png new file mode 100644 index 0000000..6a42e62 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_8.imageset/img_illust_8@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/Contents.json new file mode 100644 index 0000000..e15248a --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "imgIllust9.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "imgIllust9@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "imgIllust9@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/imgIllust9.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/imgIllust9.png new file mode 100644 index 0000000..cde367b Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/imgIllust9.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/imgIllust9@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/imgIllust9@2x.png new file mode 100644 index 0000000..d20f104 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/imgIllust9@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/imgIllust9@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/imgIllust9@3x.png new file mode 100644 index 0000000..9310322 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_illust_9.imageset/imgIllust9@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/Contents.json new file mode 100644 index 0000000..db8d0a8 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_logo_apple.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_logo_apple@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_logo_apple@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/img_logo_apple.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/img_logo_apple.png new file mode 100644 index 0000000..a25829c Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/img_logo_apple.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/img_logo_apple@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/img_logo_apple@2x.png new file mode 100644 index 0000000..d6c90d8 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/img_logo_apple@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/img_logo_apple@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/img_logo_apple@3x.png new file mode 100644 index 0000000..b5f2860 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_apple.imageset/img_logo_apple@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/Contents.json new file mode 100644 index 0000000..9384f7b --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_logo_kakao.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_logo_kakao@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_logo_kakao@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/img_logo_kakao.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/img_logo_kakao.png new file mode 100644 index 0000000..137d8d5 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/img_logo_kakao.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/img_logo_kakao@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/img_logo_kakao@2x.png new file mode 100644 index 0000000..f2a2a10 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/img_logo_kakao@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/img_logo_kakao@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/img_logo_kakao@3x.png new file mode 100644 index 0000000..2dc9d41 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_logo_kakao.imageset/img_logo_kakao@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_network_fail.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_network_fail.imageset/Contents.json new file mode 100644 index 0000000..5cc11e5 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_network_fail.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "img_network_fail.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_network_fail.imageset/img_network_fail.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_network_fail.imageset/img_network_fail.png new file mode 100644 index 0000000..2efdf82 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_network_fail.imageset/img_network_fail.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/Contents.json new file mode 100644 index 0000000..ac82cb2 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "scale" : "1x", + "filename" : "imgProfile.png", + "idiom" : "universal" + }, + { + "idiom" : "universal", + "filename" : "imgProfile@2x.png", + "scale" : "2x" + }, + { + "scale" : "3x", + "idiom" : "universal", + "filename" : "imgProfile@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/imgProfile.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/imgProfile.png new file mode 100644 index 0000000..6e864aa Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/imgProfile.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/imgProfile@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/imgProfile@2x.png new file mode 100644 index 0000000..6ddc1b2 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/imgProfile@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/imgProfile@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/imgProfile@3x.png new file mode 100644 index 0000000..86ea98e Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_profile.imageset/imgProfile@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/Contents.json new file mode 100644 index 0000000..aec63ae --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "img_seemeet_logo_symbol+typo_square.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "img_seemeet_logo_symbol+typo_square@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "img_seemeet_logo_symbol+typo_square@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/img_seemeet_logo_symbol+typo_square.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/img_seemeet_logo_symbol+typo_square.png new file mode 100644 index 0000000..561837b Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/img_seemeet_logo_symbol+typo_square.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/img_seemeet_logo_symbol+typo_square@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/img_seemeet_logo_symbol+typo_square@2x.png new file mode 100644 index 0000000..9c7eafc Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/img_seemeet_logo_symbol+typo_square@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/img_seemeet_logo_symbol+typo_square@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/img_seemeet_logo_symbol+typo_square@3x.png new file mode 100644 index 0000000..9eea2b7 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/img_seemeet_logo.imageset/img_seemeet_logo_symbol+typo_square@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/Contents.json new file mode 100644 index 0000000..e04af5c --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "chevron_left.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "chevron_left@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "chevron_left@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/chevron_left.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/chevron_left.png new file mode 100644 index 0000000..8d9d27f Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/chevron_left.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/chevron_left@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/chevron_left@2x.png new file mode 100644 index 0000000..3b0bdf8 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/chevron_left@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/chevron_left@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/chevron_left@3x.png new file mode 100644 index 0000000..efe261a Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/leftchevron.imageset/chevron_left@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/Contents.json new file mode 100644 index 0000000..078799a --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "my_page.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "my_page@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "my_page@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/my_page.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/my_page.png new file mode 100644 index 0000000..9094201 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/my_page.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/my_page@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/my_page@2x.png new file mode 100644 index 0000000..6920b4e Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/my_page@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/my_page@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/my_page@3x.png new file mode 100644 index 0000000..c65a57c Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/my_page.imageset/my_page@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/Contents.json new file mode 100644 index 0000000..fc86cc2 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "profile_Dummy.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "profile_Dummy@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "profile_Dummy@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/profile_Dummy.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/profile_Dummy.png new file mode 100644 index 0000000..823a201 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/profile_Dummy.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/profile_Dummy@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/profile_Dummy@2x.png new file mode 100644 index 0000000..40632c3 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/profile_Dummy@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/profile_Dummy@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/profile_Dummy@3x.png new file mode 100644 index 0000000..c47e117 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/profile_Dummy.imageset/profile_Dummy@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/Contents.json new file mode 100644 index 0000000..4923ddc --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "property1Black.png", + "scale" : "1x" + }, + { + "filename" : "property1Black@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "scale" : "3x", + "idiom" : "universal", + "filename" : "property1Black@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/property1Black.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/property1Black.png new file mode 100644 index 0000000..0799436 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/property1Black.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/property1Black@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/property1Black@2x.png new file mode 100644 index 0000000..84f139a Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/property1Black@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/property1Black@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/property1Black@3x.png new file mode 100644 index 0000000..4057a67 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Black.imageset/property1Black@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/Contents.json new file mode 100644 index 0000000..f6e4880 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "property1Grey.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "property1Grey@2x.png", + "scale" : "2x", + "idiom" : "universal" + }, + { + "scale" : "3x", + "idiom" : "universal", + "filename" : "property1Grey@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/property1Grey.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/property1Grey.png new file mode 100644 index 0000000..fe6184b Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/property1Grey.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/property1Grey@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/property1Grey@2x.png new file mode 100644 index 0000000..bf79298 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/property1Grey@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/property1Grey@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/property1Grey@3x.png new file mode 100644 index 0000000..9c9cbf8 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1Grey.imageset/property1Grey@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/Contents.json new file mode 100644 index 0000000..2858de7 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "property1White.png" + }, + { + "scale" : "2x", + "idiom" : "universal", + "filename" : "property1White@2x.png" + }, + { + "scale" : "3x", + "idiom" : "universal", + "filename" : "property1White@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/property1White.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/property1White.png new file mode 100644 index 0000000..9f3f436 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/property1White.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/property1White@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/property1White@2x.png new file mode 100644 index 0000000..e4df21f Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/property1White@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/property1White@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/property1White@3x.png new file mode 100644 index 0000000..4614b0a Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/property1White.imageset/property1White@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/Contents.json b/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/Contents.json new file mode 100644 index 0000000..9a515b9 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "chevron_right.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "chevron_right@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "chevron_right@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/chevron_right.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/chevron_right.png new file mode 100644 index 0000000..42fc0b0 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/chevron_right.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/chevron_right@2x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/chevron_right@2x.png new file mode 100644 index 0000000..6408e94 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/chevron_right@2x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/chevron_right@3x.png b/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/chevron_right@3x.png new file mode 100644 index 0000000..cdd5c32 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Assets.xcassets/rightchevron.imageset/chevron_right@3x.png differ diff --git a/SeeMeet/SeeMeet/Resource/Font/DINEngschriftStd.otf b/SeeMeet/SeeMeet/Resource/Font/DINEngschriftStd.otf new file mode 100644 index 0000000..ae85f8e Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/DINEngschriftStd.otf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/DINMittelschriftStd.otf b/SeeMeet/SeeMeet/Resource/Font/DINMittelschriftStd.otf new file mode 100644 index 0000000..9a6e0d4 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/DINMittelschriftStd.otf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/DINNeuzeitGroteskStd-BdCond.otf b/SeeMeet/SeeMeet/Resource/Font/DINNeuzeitGroteskStd-BdCond.otf new file mode 100644 index 0000000..1da42b0 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/DINNeuzeitGroteskStd-BdCond.otf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/DINNeuzeitGroteskStd-Light.otf b/SeeMeet/SeeMeet/Resource/Font/DINNeuzeitGroteskStd-Light.otf new file mode 100644 index 0000000..0cda2e5 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/DINNeuzeitGroteskStd-Light.otf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/DINPro-Black.otf b/SeeMeet/SeeMeet/Resource/Font/DINPro-Black.otf new file mode 100644 index 0000000..2092a7b Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/DINPro-Black.otf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/DINPro-Bold.otf b/SeeMeet/SeeMeet/Resource/Font/DINPro-Bold.otf new file mode 100644 index 0000000..7c83953 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/DINPro-Bold.otf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/DINPro-Light.otf b/SeeMeet/SeeMeet/Resource/Font/DINPro-Light.otf new file mode 100644 index 0000000..8a7f085 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/DINPro-Light.otf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/DINPro-Medium.otf b/SeeMeet/SeeMeet/Resource/Font/DINPro-Medium.otf new file mode 100644 index 0000000..b4608d0 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/DINPro-Medium.otf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/DINPro-Regular.otf b/SeeMeet/SeeMeet/Resource/Font/DINPro-Regular.otf new file mode 100644 index 0000000..84d57ab Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/DINPro-Regular.otf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/LICENSE b/SeeMeet/SeeMeet/Resource/Font/LICENSE new file mode 100644 index 0000000..b2fd469 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Font/LICENSE @@ -0,0 +1,94 @@ +Copyright (c) 2015-10-09 Spoqa (spoqa.com), with Reserved Font Name Spoqa Han Sans. +Copyright (c) 2015-10-09 Spoqa (spoqa.com), with Reserved Font Name Spoqa Han Sans JP. +Copyright (c) 2020-11-30 Spoqa (spoqa.com), with Reserved Font Name Spoqa Han Sans Neo. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Bold.ttf b/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Bold.ttf new file mode 100644 index 0000000..62a9dcf Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Bold.ttf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Light.ttf b/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Light.ttf new file mode 100644 index 0000000..db53af9 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Light.ttf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Medium.ttf b/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Medium.ttf new file mode 100644 index 0000000..035f379 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Medium.ttf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Regular.ttf b/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Regular.ttf new file mode 100644 index 0000000..81916a3 Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Regular.ttf differ diff --git a/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Thin.ttf b/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Thin.ttf new file mode 100644 index 0000000..4a75b2e Binary files /dev/null and b/SeeMeet/SeeMeet/Resource/Font/SpoqaHanSansNeo-Thin.ttf differ diff --git a/SeeMeet/SeeMeet/Resource/Info.plist b/SeeMeet/SeeMeet/Resource/Info.plist new file mode 100644 index 0000000..6067327 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Info.plist @@ -0,0 +1,72 @@ + + + + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + + + + + FirebaseAppDelegateProxyEnabled + + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + kakaolink + kakaokompassauth + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIAppFonts + + DINEngschriftStd.otf + DINMittelschriftStd.otf + DINNeuzeitGroteskStd-BdCond.otf + DINNeuzeitGroteskStd-Light.otf + DINPro-Black.otf + DINPro-Bold.otf + DINPro-Light.otf + DINPro-Medium.otf + DINPro-Regular.otf + SpoqaHanSansNeo-Bold.ttf + SpoqaHanSansNeo-Light.ttf + SpoqaHanSansNeo-Medium.ttf + SpoqaHanSansNeo-Regular.ttf + SpoqaHanSansNeo-Thin.ttf + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Tabbar + + + + + UIBackgroundModes + + fetch + remote-notification + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/CalendarScene/Calendar.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/CalendarScene/Calendar.storyboard new file mode 100644 index 0000000..68bc35f --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/CalendarScene/Calendar.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/CalendarScene/CalendarDetail.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/CalendarScene/CalendarDetail.storyboard new file mode 100644 index 0000000..b8be863 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/CalendarScene/CalendarDetail.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/FriendsScene/FriendsAdd.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/FriendsScene/FriendsAdd.storyboard new file mode 100644 index 0000000..e5f6b96 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/FriendsScene/FriendsAdd.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/FriendsScene/FriendsList.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/FriendsScene/FriendsList.storyboard new file mode 100644 index 0000000..f282bc5 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/FriendsScene/FriendsList.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/HomeScene/Home.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/HomeScene/Home.storyboard new file mode 100644 index 0000000..3c0890c --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/HomeScene/Home.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/LaunchScreen/Base.lproj/LaunchScreen.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/LaunchScreen/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..ef1d801 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/LaunchScreen/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/LoginScene/EmailLogin.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/LoginScene/EmailLogin.storyboard new file mode 100644 index 0000000..5d378c2 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/LoginScene/EmailLogin.storyboard @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/LoginScene/MainLogin.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/LoginScene/MainLogin.storyboard new file mode 100644 index 0000000..ccd6e2e --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/LoginScene/MainLogin.storyboard @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/MyPageScene/ChangePassword.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/MyPageScene/ChangePassword.storyboard new file mode 100644 index 0000000..872d576 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/MyPageScene/ChangePassword.storyboard @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/MyPageScene/UserInfo.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/MyPageScene/UserInfo.storyboard new file mode 100644 index 0000000..86df7f2 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/MyPageScene/UserInfo.storyboard @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/Components/AvailTimeOptionView.xib b/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/Components/AvailTimeOptionView.xib new file mode 100644 index 0000000..bef9753 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/Components/AvailTimeOptionView.xib @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/Components/ConfirmTimeOptionView.xib b/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/Components/ConfirmTimeOptionView.xib new file mode 100644 index 0000000..c86c3f3 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/Components/ConfirmTimeOptionView.xib @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/PlansList.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/PlansList.storyboard new file mode 100644 index 0000000..462b6c6 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/PlansList.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/PlansReceiveList.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/PlansReceiveList.storyboard new file mode 100644 index 0000000..e92d78e --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/PlansReceiveList.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/PlansSendList.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/PlansSendList.storyboard new file mode 100644 index 0000000..29b1b13 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/PlansScene/PlansSendList.storyboard @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/RegisterScene/EmailRegister.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/RegisterScene/EmailRegister.storyboard new file mode 100644 index 0000000..cdde318 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/RegisterScene/EmailRegister.storyboard @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/RegisterScene/ProfileRegister.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/RegisterScene/ProfileRegister.storyboard new file mode 100644 index 0000000..df72354 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/RegisterScene/ProfileRegister.storyboard @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/RequestScene/RequestPlansContents.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/RequestScene/RequestPlansContents.storyboard new file mode 100644 index 0000000..23bacc2 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/RequestScene/RequestPlansContents.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/RequestScene/RequestPlansDate.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/RequestScene/RequestPlansDate.storyboard new file mode 100644 index 0000000..994f0c9 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/RequestScene/RequestPlansDate.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/SMPopUp.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/SMPopUp.storyboard new file mode 100644 index 0000000..152c7e4 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/SMPopUp.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/SMRequestPopUp.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/SMRequestPopUp.storyboard new file mode 100644 index 0000000..d658712 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/SMRequestPopUp.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/SelectedDateSheet.xib b/SeeMeet/SeeMeet/Resource/Storyboards/SelectedDateSheet.xib new file mode 100644 index 0000000..4f8f5b7 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/SelectedDateSheet.xib @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Storyboards/Tabbar/Tabbar.storyboard b/SeeMeet/SeeMeet/Resource/Storyboards/Tabbar/Tabbar.storyboard new file mode 100644 index 0000000..d0aa6a8 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Storyboards/Tabbar/Tabbar.storyboard @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Xibs/CVC/CalendarPlansCVC.xib b/SeeMeet/SeeMeet/Resource/Xibs/CVC/CalendarPlansCVC.xib new file mode 100644 index 0000000..77bfaf9 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Xibs/CVC/CalendarPlansCVC.xib @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Xibs/CVC/CalendarScene/CalendarPlansCVC.xib b/SeeMeet/SeeMeet/Resource/Xibs/CVC/CalendarScene/CalendarPlansCVC.xib new file mode 100644 index 0000000..77bfaf9 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Xibs/CVC/CalendarScene/CalendarPlansCVC.xib @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Xibs/CVC/HomeScene/HomeEventCVC.xib b/SeeMeet/SeeMeet/Resource/Xibs/CVC/HomeScene/HomeEventCVC.xib new file mode 100644 index 0000000..85980c7 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Xibs/CVC/HomeScene/HomeEventCVC.xib @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/CompletePlansCVC.xib b/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/CompletePlansCVC.xib new file mode 100644 index 0000000..b681ebe --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/CompletePlansCVC.xib @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/PlansReceiveCVC.xib b/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/PlansReceiveCVC.xib new file mode 100644 index 0000000..3b2a4b0 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/PlansReceiveCVC.xib @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/ProgressReceiveCVC.xib b/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/ProgressReceiveCVC.xib new file mode 100644 index 0000000..9576509 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/ProgressReceiveCVC.xib @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/ProgressSendCVC.xib b/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/ProgressSendCVC.xib new file mode 100644 index 0000000..7b93532 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Xibs/CVC/PlansScene/ProgressSendCVC.xib @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Xibs/TVC/FriendsScene/FriendsAddTVC.xib b/SeeMeet/SeeMeet/Resource/Xibs/TVC/FriendsScene/FriendsAddTVC.xib new file mode 100644 index 0000000..e46fefa --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Xibs/TVC/FriendsScene/FriendsAddTVC.xib @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Xibs/TVC/FriendsScene/FriendsListTVC.xib b/SeeMeet/SeeMeet/Resource/Xibs/TVC/FriendsScene/FriendsListTVC.xib new file mode 100644 index 0000000..0c904d2 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Xibs/TVC/FriendsScene/FriendsListTVC.xib @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/Resource/Xibs/TVC/RequestScene/SelectedDateSheetTVC.swift b/SeeMeet/SeeMeet/Resource/Xibs/TVC/RequestScene/SelectedDateSheetTVC.swift new file mode 100644 index 0000000..6f8def9 --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Xibs/TVC/RequestScene/SelectedDateSheetTVC.swift @@ -0,0 +1,82 @@ +import UIKit +import RxSwift +import RxCocoa + +class SelectedDateSheetTVC: UITableViewCell { + + // MARK: - UI Components + + private let ticketBodyView: UIView = UIView().then { + $0.backgroundColor = UIColor.pink02 + $0.layer.cornerRadius = 10 + } + + let dateLabel: UILabel = UILabel().then { + $0.font = UIFont.dinProBoldFont(ofSize: 18) + $0.textColor = UIColor.grey06 + } + + let timeLabel: UILabel = UILabel().then { + $0.font = UIFont.dinProRegularFont(ofSize: 14) + $0.textColor = UIColor.grey06 + $0.text = "오전 11:00 ~ 오후 11:00" //임시 + } + + let removeButton: UIButton = UIButton().then { + $0.setImage(UIImage(named: "btn_remove_black"), for: .normal) + } + + // MARK: - properties + + static let identifier: String = "SelectedDateSheetTVC" + + private let disposeBag = DisposeBag() + + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + setAutoLayouts() + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setAutoLayouts() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setAutoLayouts() + } + + // MARK: - Layouts + + private func setAutoLayouts() { + isUserInteractionEnabled = true + contentView.addSubviews([ticketBodyView, removeButton]) //컨텐트 뷰에 올려야 버튼 터치 가능해진다 + ticketBodyView.addSubviews([dateLabel, timeLabel]) + + ticketBodyView.snp.makeConstraints { + $0.height.equalTo(53 * heightRatio) + $0.leading.equalToSuperview() + $0.trailing.equalToSuperview().offset(-21 * widthRatio) + $0.bottom.equalToSuperview() + } + + dateLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(46 * CGFloat(widthRatio)) + $0.centerY.equalToSuperview() + } + + timeLabel.snp.makeConstraints { + $0.trailing.equalTo(-33 * widthRatio) + $0.centerY.equalToSuperview() + } + + removeButton.snp.makeConstraints { + $0.width.height.equalTo(40 * widthRatio) + $0.trailing.equalToSuperview().offset(-4 * widthRatio) + $0.bottom.equalToSuperview().offset(-33 * heightRatio) + } + } +} diff --git a/SeeMeet/SeeMeet/Resource/Xibs/TVC/RequestScene/SelectedDateSheetTVC.xib b/SeeMeet/SeeMeet/Resource/Xibs/TVC/RequestScene/SelectedDateSheetTVC.xib new file mode 100644 index 0000000..afd3e1e --- /dev/null +++ b/SeeMeet/SeeMeet/Resource/Xibs/TVC/RequestScene/SelectedDateSheetTVC.xib @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeeMeet/SeeMeet/SeeMeet.entitlements b/SeeMeet/SeeMeet/SeeMeet.entitlements new file mode 100644 index 0000000..80b5221 --- /dev/null +++ b/SeeMeet/SeeMeet/SeeMeet.entitlements @@ -0,0 +1,12 @@ + + + + + aps-environment + development + com.apple.developer.applesignin + + Default + + + diff --git a/SeeMeet/SeeMeet/Source/Extension/Constants++/UserDefaultsKey.swift b/SeeMeet/SeeMeet/Source/Extension/Constants++/UserDefaultsKey.swift new file mode 100644 index 0000000..0492962 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/Constants++/UserDefaultsKey.swift @@ -0,0 +1,20 @@ +// +// UserDefaultsKey.swift +// SeeMeet +// +// Created by 이유진 on 2022/06/28. +// + +import Foundation +extension Constants { + struct UserDefaultsKey { + static let hasBeenLaunchedBeforeFlag = "hasBeenLaunchedBeforeFlag" + static let userName = "userName" + static let userNickname = "userNickname" + static let isLogin = "isLogin" + static let userEmail = "userEmail" + static let isAppleLogin = "isAppleLogin" + static let userAppleID = "userAppleID" + static let isPushNotificationOn = "isPushNotificationOn" + } +} diff --git a/SeeMeet/SeeMeet/Source/Extension/Date++.swift b/SeeMeet/SeeMeet/Source/Extension/Date++.swift new file mode 100644 index 0000000..c968520 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/Date++.swift @@ -0,0 +1,171 @@ +// +// Date++.swift +// SeeMeet_iOS +// +// Created by 박익범 on 2022/01/05. +// + +import Foundation + + +extension Date { + + enum DayOfTheWeek: String, CaseIterable { + case mon = "월" + case tue = "화" + case wed = "수" + case thu = "목" + case fri = "금" + case sat = "토" + case sun = "일" + } + + static func getCurrentYear() -> String{ + let nowDate = Date() // 현재의 Date (ex: 2020-08-13 09:14:48 +0000) + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy" // 2020-08-13 16:30 + let str = dateFormatter.string(from: nowDate) + return str + } + + static func getCurrentMonth() -> String{ + let nowDate = Date() // 현재의 Date (ex: 2020-08-13 09:14:48 +0000) + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "MM" // 2020-08-13 16:30 + let str = dateFormatter.string(from: nowDate) + return str + } + + static func getCurrentDate() -> String{ + let nowDate = Date() // 현재의 Date (ex: 2020-08-13 09:14:48 +0000) + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd" // 2020-08-13 16:30 + let str = dateFormatter.string(from: nowDate) + return str + } + + static func getCurrentHour() -> String{ + let nowDate = Date() // 현재의 Date (ex: 2020-08-13 09:14:48 +0000) + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "hh" // 2020-08-13 16:30 + let str = dateFormatter.string(from: nowDate) + return str + } + + static func getCurrentMinute() -> String{ + let nowDate = Date() // 현재의 Date (ex: 2020-08-13 09:14:48 +0000) + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "mm" // 2020-08-13 16:30 + let str = dateFormatter.string(from: nowDate) + return str + } + + static func getCurrentKoreanWeekDay() -> String { + getKoreanWeekDay(from: Date()) + } + + static func getKoreanWeekDay(from date: Date) -> String { + let currentDay = Calendar.current.component(.weekday, from: date) + + switch currentDay { + case 1: + return "일" + case 2: + return "월" + case 3: + return "화" + case 4: + return "수" + case 5: + return "목" + case 6: + return "금" + case 7: + return "토" + default: + return "일" + } + } + + public var year: Int { + return Calendar.current.component(.year, from: self) + } + + public var month: Int { + return Calendar.current.component(.month, from: self) + } + + public var day: Int { + return Calendar.current.component(.day, from: self) + } + + public var hour: Int { + return Calendar.current.component(.hour, from: self) + } + public var minute: Int { + return Calendar.current.component(.minute, from: self) + } + + public var weekday: Int{ + return Calendar.current.component(.weekday, from: self) - 1 + } + + public var isLeapMonth: Bool{ + if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0){ + return true + } + else{ + return false + } + } + + public var numberOfMonth: Int{ + let numberList = [0,31,28,31,30,31,30,31,31,30,31,30,31] + if isLeapMonth && month == 2{ + return 29 + } + else{ + return numberList[month] + } + } + + public var firstWeekday: Int{ + var dateComponent = DateComponents() + dateComponent.year = Calendar.current.component(.year, from: self) + dateComponent.month = Calendar.current.component(.month, from: self) + dateComponent.day = 1 + dateComponent.weekday = Calendar.current.component(.weekday,from: Calendar.current.date(from: dateComponent)!) + /// 리턴값 : 일 - 토 -> 0 - 6 + return dateComponent.weekday! - 1 + } + + func nextDate() -> Date { + let nextDate = Calendar.current.date(byAdding: .day, value: 1, to: self) + return nextDate ?? Date() + } + + func nextDate(value: Int) -> Date{ + let nextDate = Calendar.current.date(byAdding: .day, value: value, to: self) + return nextDate ?? Date() + } + + func previousDate() -> Date { + let previousDate = Calendar.current.date(byAdding: .day, value: -1, to: self) + return previousDate ?? Date() + } + + func previousDate(value: Int) -> Date { + let previousDate = Calendar.current.date(byAdding: .day, value: -value, to: self) + return previousDate ?? Date() + } + func nextWeekDate() -> Date { + let nextDate = Calendar.current.date(byAdding: .day, value: 7, to: self) + return nextDate ?? Date() + } + func prevWeekDate() -> Date { + let nextDate = Calendar.current.date(byAdding: .day, value: -7, to: self) + return nextDate ?? Date() + } + +} + diff --git a/SeeMeet/SeeMeet/Source/Extension/Notification++.swift b/SeeMeet/SeeMeet/Source/Extension/Notification++.swift new file mode 100644 index 0000000..a82381a --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/Notification++.swift @@ -0,0 +1,14 @@ +// +// Notification++.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/24. +// + +import Foundation + +extension NSNotification.Name { + static let RefreshTokenExpired = NSNotification.Name("RefreshTokenExpired") + static let PushNotificationDidSet = NSNotification.Name("PushNotificationDidSet") + static let DidLogout = NSNotification.Name("DidLogout") +} diff --git a/SeeMeet/SeeMeet/Source/Extension/Notification.Name++.swift b/SeeMeet/SeeMeet/Source/Extension/Notification.Name++.swift new file mode 100644 index 0000000..8560c42 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/Notification.Name++.swift @@ -0,0 +1,8 @@ +// +// Notification.Name++.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/08. +// + +import Foundation diff --git a/SeeMeet/SeeMeet/Source/Extension/String++.swift b/SeeMeet/SeeMeet/Source/Extension/String++.swift new file mode 100644 index 0000000..18b513d --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/String++.swift @@ -0,0 +1,79 @@ +import UIKit + +enum ConvertError: Error { + case NotAcceptableFormatError +} + +extension String { + static func getAttributedText(text: String, letterSpacing: CGFloat?, lineSpacing: CGFloat?) -> NSAttributedString { + let attributedString = NSMutableAttributedString(string: text) + + if let letterSpacing = letterSpacing { + attributedString.addAttribute(.kern, value: letterSpacing, range: NSRange(location: 0, length: text.count)) + } + + if let lineSpacing = lineSpacing { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = lineSpacing + paragraphStyle.lineBreakMode = .byTruncatingTail + attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: text.count)) + } + + return NSAttributedString(attributedString: attributedString) + } + + static func getTimeIntervalString(from dateString: String, by dateFormat: String? = nil) -> String { // ISO 형식에 맞아야 한다. + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = dateFormat ?? "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + + let startDate = dateFormatter.date(from: Date.getCurrentYear() + "-" + Date.getCurrentMonth() + "-" + Date.getCurrentDate()) ?? Date() + let endDate = dateFormatter.date(from: dateString) ?? Date() + + let interval = endDate.timeIntervalSince(startDate) + let days = Int(interval / 86400) + + return String(abs(days)) + } + + static func getTimeInterval(from dateString: String, by dateFormat: String? = nil) -> Int { // ISO 형식에 맞아야 한다. + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = dateFormat ?? "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + + let startDate = dateFormatter.date(from: Date.getCurrentYear() + "-" + Date.getCurrentMonth() + "-" + Date.getCurrentDate()) ?? Date() + let endDate = dateFormatter.date(from: dateString) ?? Date() + + let interval = endDate.timeIntervalSince(startDate) + let days = Int(interval / 86400) + + return days + } + + static func getAMPMTimeString(from timeString: String) throws -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "HH:mm" + + if let date = dateFormatter.date(from: timeString) { + dateFormatter.locale = Locale(identifier: "ko_KR") + dateFormatter.dateFormat = "a hh:mm" + dateFormatter.amSymbol = "오전" + dateFormatter.pmSymbol = "오후" + + return dateFormatter.string(from: date) + } else { + throw ConvertError.NotAcceptableFormatError + } + } + + func hasCharacters() -> Bool{ + do{ + let regex = try NSRegularExpression(pattern: "^[가-힣ㄱ-ㅎㅏ-ㅣ\\s]$", options: .caseInsensitive) + if let _ = regex.firstMatch(in: self, options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSMakeRange(0, self.count)){ + return true + } + }catch{ + print(error.localizedDescription) + return false + } + return false + } +} diff --git a/SeeMeet/SeeMeet/Source/Extension/UICollectionView++.swift b/SeeMeet/SeeMeet/Source/Extension/UICollectionView++.swift new file mode 100644 index 0000000..2b376b6 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/UICollectionView++.swift @@ -0,0 +1,14 @@ +// +// UICollectionView++.swift +// SeeMeet_iOS +// +// Created by 박익범 on 2022/01/05. +// +import UIKit + +extension UICollectionView { + public func registerCustomXib(xibName: String){ + let xib = UINib(nibName: xibName, bundle: nil) + self.register(xib, forCellWithReuseIdentifier: xibName) + } +} diff --git a/SeeMeet/SeeMeet/Source/Extension/UIColor++.swift b/SeeMeet/SeeMeet/Source/Extension/UIColor++.swift new file mode 100644 index 0000000..1ffd6c9 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/UIColor++.swift @@ -0,0 +1,60 @@ +// +// UIColor++.swift +// SeeMeet_iOS +// +// Created by 박익범 on 2022/01/05. +// + +import UIKit + +extension UIColor { + @nonobjc class var white: UIColor { + return UIColor(white: 1.0, alpha: 1.0) + } + + @nonobjc class var orange: UIColor { + return UIColor(red: 255.0 / 255.0, green: 158.0 / 255.0, blue: 68.0 / 255.0, alpha: 1.0) + } + + @nonobjc class var pink01: UIColor { + return UIColor(red: 250.0 / 255.0, green: 85.0 / 255.0, blue: 92.0 / 255.0, alpha: 1.0) + } + + @nonobjc class var pink02: UIColor { + return UIColor(red: 254.0 / 255.0, green: 221.0 / 255.0, blue: 222.0 / 255.0, alpha: 1.0) + } + + @nonobjc class var grey01: UIColor { + return UIColor(red: 246.0 / 255.0, green: 246.0 / 255.0, blue: 246.0 / 255.0, alpha: 1.0) + } + + @nonobjc class var grey02: UIColor { + return UIColor(red: 231.0 / 255.0, green: 231.0 / 255.0, blue: 231.0 / 255.0, alpha: 1.0) + } + + @nonobjc class var grey03: UIColor { + return UIColor(red: 196.0 / 255.0, green: 196.0 / 255.0, blue: 196.0 / 255.0, alpha: 1.0) + } + + @nonobjc class var grey04: UIColor { + return UIColor(red: 163.0 / 255.0, green: 163.0 / 255.0, blue: 163.0 / 255.0, alpha: 1.0) + } + + @nonobjc class var grey05: UIColor { + return UIColor(red: 75.0 / 255.0, green: 75.0 / 255.0, blue: 75.0 / 255.0, alpha: 1.0) + } + + @nonobjc class var grey06: UIColor { + return UIColor(red: 28.0 / 255.0, green: 28.0 / 255.0, blue: 28.0 / 255.0, alpha: 1.0) + } + + @nonobjc class var kakaoyellow: UIColor { + return UIColor(red: 254.0 / 255.0, green: 229.0 / 255.0, blue: 28.0 / 255.0, alpha: 1.0) + } + + @nonobjc class var snslogoblack: UIColor { + return UIColor(red: 34.0 / 255.0, green: 34.0 / 255.0, blue: 34.0 / 255.0, alpha: 1.0) + } + + +} diff --git a/SeeMeet/SeeMeet/Source/Extension/UIFont++.swift b/SeeMeet/SeeMeet/Source/Extension/UIFont++.swift new file mode 100644 index 0000000..25e7796 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/UIFont++.swift @@ -0,0 +1,82 @@ +// +// UIFont++.swift +// SeeMeet_iOS +// +// Created by 박익범 on 2022/01/05. +// +import UIKit + +struct AppFontName { + static let DINEng = "DINEngschriftStd" + static let DINMittel = "DINMittelschriftStd" + static let DINBdCond = "DINNeuzeitGroteskStd-BdCond" + static let DINLight = "DINNeuzeitGroteskStd-Light" + static let DINProblack = "DINPro-Black" + static let DINProBold = "DINPro-Bold" + static let DINProLight = "DINPro-Light" + static let DINProMedium = "DINPro-Medium" + static let DINProRegular = "DINPro-Regular" + static let HanSansBold = "SpoqaHanSansNeo-Bold" + static let HanSansLight = "SpoqaHanSansNeo-Light" + static let HanSansMedium = "SpoqaHanSansNeo-Medium" + static let HanSansRegular = "SpoqaHanSansNeo-Regular" + static let HanSansThin = "SpoqaHanSansNeo-Thin" +} +extension UIFont{ + + @objc class func dinEngFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.DINEng, size: size)! + } + + @objc class func dinMittelFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.DINMittel, size: size)! + } + + @objc class func dinBdCondFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.DINBdCond, size: size)! + } + + @objc class func dinLightFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.DINLight, size: size)! + } + + @objc class func dinProBlackFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.DINProblack, size: size)! + } + + @objc class func dinProBoldFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.DINProBold, size: size)! + } + + @objc class func dinProLightFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.DINProLight, size: size)! + } + + @objc class func dinProMediumFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.DINProMedium, size: size)! + } + + @objc class func dinProRegularFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.DINProRegular, size: size)! + } + + @objc class func hanSansBoldFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.HanSansBold, size: size)! + } + + @objc class func hanSansLightFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.HanSansLight, size: size)! + } + + @objc class func hanSansMediumFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.HanSansMedium, size: size)! + } + + @objc class func hanSansRegularFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.HanSansRegular, size: size)! + } + + @objc class func hanSansThinFont(ofSize size: CGFloat) -> UIFont { + return UIFont(name: AppFontName.HanSansThin, size: size)! + } +} diff --git a/SeeMeet/SeeMeet/Source/Extension/UILabel++.swift b/SeeMeet/SeeMeet/Source/Extension/UILabel++.swift new file mode 100644 index 0000000..10f6420 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/UILabel++.swift @@ -0,0 +1,57 @@ +import UIKit + +extension UILabel { +// func setTextFontAttribute(defaultText: String, containText: String, changingFont: UIFont, color: UIColor) { +// let text: String = defaultText +// let changeText: NSMutableAttributedString = NSMutableAttributedString(string: text) +// changeText.addAttribute(.font, value: changingFont, range: (text as NSString).range(of: containText)) +// changeText.addAttribute(.foregroundColor, value: color, range: (text as NSString).range(of: containText)) +// +// attributedText = changeText +// } +// +// func setTextLineAttribute(defaultText: String, value: CGFloat) { +// let text: String = defaultText +// let changeText = NSMutableAttributedString(string: text) +// changeText.addAttribute(NSAttributedString.Key.kern, value: value, range: NSRange(0 ... changeText.length-1)) +// +// attributedText = changeText +// } +// +// func setTextFontColorSpacingAttribute(defaultText: String, value: CGFloat, containText: String, changingFont: UIFont, color: UIColor) { +// let text: String = defaultText +// let changeText = NSMutableAttributedString(string: text) +// changeText.addAttribute(NSAttributedString.Key.kern, value: value, range: NSRange(0 ... changeText.length-1)) +// changeText.addAttribute(.font, value: changingFont, range: (text as NSString).range(of: containText)) +// changeText.addAttribute(.foregroundColor, value: color, range: (text as NSString).range(of: containText)) +// +// attributedText = changeText +// } + + func setAttributedText(defaultText: String, containText: String? = nil, + font: UIFont? = nil, color: UIColor? = nil, + kernValue: CGFloat? = nil, lineSpacing: CGFloat? = nil) { + let mutableAttributedText = NSMutableAttributedString(string: defaultText) + + if let font = font { + mutableAttributedText.addAttribute(.font, value: font, range: (defaultText as NSString).range(of: containText ?? defaultText)) + } + + if let color = color { + mutableAttributedText.addAttribute(.foregroundColor, value: color, range: (defaultText as NSString).range(of: containText ?? defaultText)) + } + + if let kernValue = kernValue { + mutableAttributedText.addAttribute(.kern, value: kernValue, range: NSRange(0...defaultText.count-1)) + } + + if let lineSpacing = lineSpacing { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = lineSpacing + paragraphStyle.lineBreakMode = .byTruncatingTail + mutableAttributedText.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSRange(0...defaultText.count-1)) + } + + self.attributedText = mutableAttributedText + } +} diff --git a/SeeMeet/SeeMeet/Source/Extension/UIScreen++.swift b/SeeMeet/SeeMeet/Source/Extension/UIScreen++.swift new file mode 100644 index 0000000..dc188ea --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/UIScreen++.swift @@ -0,0 +1,72 @@ +// +// UIScreen++.swift +// SeeMeet_iOS +// +// Created by 박익범 on 2022/01/05. +// + +import UIKit + +public let widthRatio: CGFloat = UIScreen.main.bounds.width / 375 +public let heightRatio: CGFloat = UIScreen.main.bounds.height / 812 + +extension UIScreen { + + class public var hasNotch: Bool { + let deviceRatio + = UIScreen.main.bounds.width / UIScreen.main.bounds.height + if deviceRatio > 0.5 { + return false + } + else{ + return true + } + } + + class public func getNotchHeight() -> Int { + var topPadding: CGFloat? + if #available(iOS 11.0, *) { + let window = UIApplication.shared.keyWindow + topPadding = window?.safeAreaInsets.top + } + + if #available(iOS 13.0, *) { + let window = UIApplication.shared.windows[0] + topPadding = window.safeAreaInsets.top + } + return Int(topPadding!) + } + + + class public func getIndecatorHeight() -> Int { + var bottomPadding: CGFloat? + if #available(iOS 11.0, *) { + let window = UIApplication.shared.keyWindow + bottomPadding = window?.safeAreaInsets.bottom + } + + if #available(iOS 13.0, *) { + let window = UIApplication.shared.windows[0] + bottomPadding = window.safeAreaInsets.top + } + return Int(bottomPadding!) + } + + + class public func getDeviceHeight() -> Int{ + return Int(UIScreen.main.bounds.height) + } + + class public func getDeviceHeight() -> CGFloat{ + return UIScreen.main.bounds.height + } + + + class func getDeviceWidth() -> Int{ + return Int(UIScreen.main.bounds.width) + } + + class func getDeviceWidth() -> CGFloat{ + return UIScreen.main.bounds.width + } +} diff --git a/SeeMeet/SeeMeet/Source/Extension/UITabelView++.swift b/SeeMeet/SeeMeet/Source/Extension/UITabelView++.swift new file mode 100644 index 0000000..1308b3e --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/UITabelView++.swift @@ -0,0 +1,15 @@ +// +// UITabelView++.swift +// SeeMeet_iOS +// +// Created by 박익범 on 2022/01/05. +// +import UIKit + +extension UITableView { + + public func registerCustomXib(xibName: String){ + let xib = UINib(nibName: xibName, bundle: nil) + self.register(xib, forCellReuseIdentifier: xibName) + } +} diff --git a/SeeMeet/SeeMeet/Source/Extension/UITextField++.swift b/SeeMeet/SeeMeet/Source/Extension/UITextField++.swift new file mode 100644 index 0000000..7b3223c --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/UITextField++.swift @@ -0,0 +1,28 @@ +// +// UITextField++.swift +// SeeMeet +// +// Created by 이유진 on 2022/01/12. +// + +import UIKit + +extension UITextField { + func addLeftPadding(width : CGFloat = 10) { + let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.height)) + self.leftView = paddingView + self.leftViewMode = ViewMode.always + } + + func addRightPadding(width : CGFloat = 10) { + let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.height)) + self.rightView = paddingView + self.rightViewMode = ViewMode.always + } + + func setLeftPadding(width: CGFloat){ + self.leftView?.snp.updateConstraints{ make in + make.width.equalTo(width) + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Extension/UIView++.swift b/SeeMeet/SeeMeet/Source/Extension/UIView++.swift new file mode 100644 index 0000000..515f3fb --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/UIView++.swift @@ -0,0 +1,59 @@ +// +// UIView++.swift +// SeeMeet_iOS +// +// Created by 박익범 on 2022/01/05. +// + +import UIKit + +extension UIView { + func addSubviews(_ views: [UIView]) { + views.forEach { self.addSubview($0) } + } + + func removeAllSubViews() { + self.subviews.forEach { $0.removeFromSuperview() } + } + + func getDeviceHeight() -> Int { + return Int(UIScreen.main.bounds.height) + } + func getDeviceWidth() -> Int{ + return Int(UIScreen.main.bounds.width) + } + + func dismissKeyboardWhenTappedAround() { + let tap: UITapGestureRecognizer = + UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard)) + tap.cancelsTouchesInView = false + self.addGestureRecognizer(tap) + } + + @objc func dismissKeyboard() { + self.endEditing(true) + } + + func getShadowView(color : CGColor, masksToBounds : Bool, shadowOffset : CGSize, shadowRadius : Int, shadowOpacity : Float) { + layer.shadowColor = color + layer.masksToBounds = masksToBounds + layer.shadowOffset = shadowOffset + layer.shadowRadius = CGFloat(shadowRadius) + layer.shadowOpacity = shadowOpacity + } + func removeShadowView() { + layer.shadowOpacity = 0 + } + + func makeToastAnimation(message: String){ + let toastMessage = ToastMessageView(message: message) + UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.addSubview(toastMessage) + toastMessage.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.centerY.equalToSuperview() + } + UIView.animate(withDuration: 1.0, animations: { + toastMessage.alpha = 0 + }, completion: {_ in toastMessage.removeFromSuperview() }) + } +} diff --git a/SeeMeet/SeeMeet/Source/Extension/UIViewController++.swift b/SeeMeet/SeeMeet/Source/Extension/UIViewController++.swift new file mode 100644 index 0000000..3d79307 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/UIViewController++.swift @@ -0,0 +1,81 @@ +// +// UIViewController++.swift +// SeeMeet_iOS +// +// Created by 박익범 on 2022/01/05. +// + +import UIKit + +extension UIViewController { + + func makeRequestAlert(title : String, + message : String, + okAction : ((UIAlertAction) -> Void)?, + cancelAction : ((UIAlertAction) -> Void)? = nil, + completion : (() -> Void)? = nil){ + + let generator = UIImpactFeedbackGenerator(style: .medium) + generator.impactOccurred() + + let alertViewController = UIAlertController(title: title, message: message, + preferredStyle: .alert) + + let okAction = UIAlertAction(title: "예", style: .default, handler: okAction) + alertViewController.addAction(okAction) + + + let cancelAction = UIAlertAction(title: "아니오", style: .default, handler: cancelAction) + alertViewController.addAction(cancelAction) + + + self.present(alertViewController, animated: true, completion: completion) + } + + + func makeAlert(title : String, + message : String, + okAction : ((UIAlertAction) -> Void)? = nil, + completion : (() -> Void)? = nil){ + let generator = UIImpactFeedbackGenerator(style: .medium) + generator.impactOccurred() + + let alertViewController = UIAlertController(title: title, message: message, + preferredStyle: .alert) + + let okAction = UIAlertAction(title: "확인", style: .default, handler: okAction) + alertViewController.addAction(okAction) + + + self.present(alertViewController, animated: true, completion: completion) + } + + func makeAlert(title : String?, + message : String?, + okAction : ((UIAlertAction) -> Void)? = nil, + completion : (() -> Void)? = nil){ + let generator = UIImpactFeedbackGenerator(style: .medium) + generator.impactOccurred() + + let alertViewController = UIAlertController(title: "", message: message, + preferredStyle: .alert) + + + + let okAction = UIAlertAction(title: "확인", style: .default, handler: okAction) + alertViewController.addAction(okAction) + + + self.present(alertViewController, animated: true, completion: completion) + } + + func addBackButton() { + let backButton = UIBarButtonItem(image: UIImage(named: "btn_back"), style: .plain, target: self, action: #selector(popVC)) + backButton.tintColor = .grey06 + self.navigationItem.leftBarButtonItem = backButton + } + + @objc func popVC() { + self.navigationController?.popViewController(animated: true) + } +} diff --git a/SeeMeet/SeeMeet/Source/Extension/UserDefaults++.swift b/SeeMeet/SeeMeet/Source/Extension/UserDefaults++.swift new file mode 100644 index 0000000..0c688f1 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Extension/UserDefaults++.swift @@ -0,0 +1,37 @@ +// +// UserDefaults++.swift +// SeeMeet +// +// Created by 김인환 on 2022/03/27. +// + +import UIKit + +extension UserDefaults { + public static func isFirstLaunch() -> Bool { + let hasBeenLaunchedBeforeFlag = "hasBeenLaunchedBeforeFlag" + let isFirstLaunch = !UserDefaults.standard.bool(forKey: Constants.UserDefaultsKey.hasBeenLaunchedBeforeFlag) + if isFirstLaunch { + UserDefaults.standard.set(true, forKey: Constants.UserDefaultsKey.hasBeenLaunchedBeforeFlag) + UserDefaults.standard.synchronize() + } + return isFirstLaunch + } + + static func deleteUserValue() { + TokenUtils.shared.delete(account: "accessToken") + TokenUtils.shared.delete(account: "refreshToken") + //유저디폴츠 값 수정 및 삭제 + let ud = UserDefaults.standard + + UserDefaults.standard.do { + $0.set(false, forKey: Constants.UserDefaultsKey.isLogin) + $0.set(false,forKey: Constants.UserDefaultsKey.isAppleLogin) + $0.removeObject(forKey: Constants.UserDefaultsKey.userName) + $0.removeObject(forKey: Constants.UserDefaultsKey.userNickname) + $0.removeObject(forKey: Constants.UserDefaultsKey.userEmail) + } + guard let image = UIImage(named: "img_profile") else { return } + ImageManager.shared.saveImage(image: image, named: "profile.png") + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/Authentication/AccessTokenRefreshDataModel.swift b/SeeMeet/SeeMeet/Source/Models/Authentication/AccessTokenRefreshDataModel.swift new file mode 100644 index 0000000..a806287 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/Authentication/AccessTokenRefreshDataModel.swift @@ -0,0 +1,25 @@ +// +// AccessTokenRefreshDataModel.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/20. +// + +import Foundation + +struct AccessTokenRefreshDataModel: Codable { + let status: Int + let success: Bool + let message: String? + let data: TokenRefreshData? +} + +struct TokenRefreshData: Codable { + let accessToken: String + let refreshToken: String + + enum CodingKeys: String, CodingKey { + case accessToken = "accesstoken" + case refreshToken = "refreshtoken" + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/Authentication/EmailLoginResponseModel.swift b/SeeMeet/SeeMeet/Source/Models/Authentication/EmailLoginResponseModel.swift new file mode 100644 index 0000000..4b6ebb7 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/Authentication/EmailLoginResponseModel.swift @@ -0,0 +1,58 @@ +// +// EmailLoginResponseModel.swift +// SeeMeet +// +// Created by 이유진 on 2022/08/06. +// + +import Foundation + +// MARK: - Main +struct EmailLoginResponseModel: Codable { + let status: Int + let success: Bool + let message: String + let data: EmailLoginData? +} + +// MARK: - DataClass +struct EmailLoginData: Codable { + let user: UserData + let accesstoken: String + let refreshtoken: String +} + +// MARK: - User +struct UserData: Codable { + let id: Int + let email: String + let idFirebase: String? + let username: String? + let isNoticed: Bool + let createdAt, updatedAt: String + let isDeleted: Bool + let provider: String + let socialID: String? + let nickname: String? + let imgLink: String? + let push: Bool + let password: String + let imgName, fcm: String? + + enum CodingKeys: String, CodingKey { + case id, email + case idFirebase + case username + case isNoticed + case createdAt + case updatedAt + case isDeleted + case provider + case socialID + case nickname + case imgLink + case push, password + case imgName + case fcm + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/Authentication/PutNameAndIdResponseDataModel.swift b/SeeMeet/SeeMeet/Source/Models/Authentication/PutNameAndIdResponseDataModel.swift new file mode 100644 index 0000000..bc0806c --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/Authentication/PutNameAndIdResponseDataModel.swift @@ -0,0 +1,16 @@ +// +// PutNameAndIdDataModel.swift +// SeeMeet +// +// Created by 김인환 on 2022/04/23. +// + +import Foundation + +// MARK: - PutNameAndIdResponseDataModel +struct PutNameAndIdResponseDataModel: Codable { + let status: Int + let success: Bool + let message: String +} + diff --git a/SeeMeet/SeeMeet/Source/Models/Authentication/SocialLoginDataModel.swift b/SeeMeet/SeeMeet/Source/Models/Authentication/SocialLoginDataModel.swift new file mode 100644 index 0000000..e9371e3 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/Authentication/SocialLoginDataModel.swift @@ -0,0 +1,38 @@ +import Foundation + +// MARK: - SocialLoginDataModel +struct SocialLoginDataModel: Codable { + let status: Int + let success: Bool + let message: String + let data: SocialLoginResponseData? +} + +// MARK: - SocialLoginResponseData +struct SocialLoginResponseData: Codable { + let user: socialUser + let accesstoken: String + let refreshtoken: String +} + +// MARK: - socialUser +struct socialUser: Codable { + let id: Int + let email, idFirebase: String? + let username: String + let isNoticed: Bool + let createdAt, updatedAt: String + let isDeleted: Bool + let provider, socialID: String + let nickname: String? + let imgLink: String? + let push: Bool + let password, imgName: String? +// let fcm: String + + enum CodingKeys: String, CodingKey { + case id, email, idFirebase, username, isNoticed, createdAt, updatedAt, isDeleted, provider + case socialID = "socialId" + case nickname, imgLink, push, password, imgName + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/Authentication/WithdrawalDataModel.swift b/SeeMeet/SeeMeet/Source/Models/Authentication/WithdrawalDataModel.swift new file mode 100644 index 0000000..79cb0a2 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/Authentication/WithdrawalDataModel.swift @@ -0,0 +1,26 @@ +import Foundation + +// MARK: - WithdrawalDataModel +struct WithdrawalDataModel: Codable { + let status: Int + let success: Bool + let message: String + let data: WithdrawalResponseData? +} + +// MARK: - WithdrawalResponseData +struct WithdrawalResponseData: Codable { + let id: Int + let email, idFirebase: String? + let username: String + let isNoticed: Bool + let createdAt, updatedAt: String + let isDeleted: Bool + let provider: String + let socialId, nickname: String? + let imgLink: String? + let push: Bool + let password: String? + let imgName: String? + let fcm: String? +} diff --git a/SeeMeet/SeeMeet/Source/Models/CalendarScene/CanceledPlanDetailModel.swift b/SeeMeet/SeeMeet/Source/Models/CalendarScene/CanceledPlanDetailModel.swift new file mode 100644 index 0000000..2d9335e --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/CalendarScene/CanceledPlanDetailModel.swift @@ -0,0 +1,41 @@ +// +// CanceledPlanDetailModel.swift +// SeeMeet +// +// Created by 이유진 on 2022/08/13. +// + +import Foundation + +// MARK: - Main +struct CanceledPlanDetailModel: Codable { + let status: Int + let success: Bool + let message: String + let data: canceledData +} + +// MARK: - DataClass +struct canceledData: Codable { + let id, hostID: Int + let invitationTitle, invitationDesc: String + let isConfirmed, isCanceled: Bool + let createdAt: String + let isDeleted: Bool + let canceledAt: String + let isVisible: Bool + let hostName: String + let guest: [CanceledPlansGuest] + + enum CodingKeys: String, CodingKey { + case id + case hostID = "hostId" + case invitationTitle, invitationDesc, isConfirmed, isCanceled, createdAt, isDeleted, canceledAt, isVisible, hostName, guest + } +} + +// MARK: - Guest +struct CanceledPlansGuest: Codable { + let id: Int + let username: String +} diff --git a/SeeMeet/SeeMeet/Source/Models/CalendarScene/MonthlyPlansResponseModel.swift b/SeeMeet/SeeMeet/Source/Models/CalendarScene/MonthlyPlansResponseModel.swift new file mode 100644 index 0000000..485a368 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/CalendarScene/MonthlyPlansResponseModel.swift @@ -0,0 +1,38 @@ +import Foundation + +// MARK: - Response +struct MonthlyPlansResponseModel: Codable { + let status: Int + let success: Bool? + let data: [Plan]? + let message: String? +} + +// MARK: - Plan +struct Plan: Codable { + let planID: Int + let invitationTitle, date, start, end: String + let users: [User] + + enum CodingKeys: String, CodingKey { + case planID = "planId" + case invitationTitle, date, start, end, users + } +} + +// MARK: - User +struct User: Codable { + let userID: Int + let username: String + + enum CodingKeys: String, CodingKey { + case userID = "user_id" + case username + } +} + +extension Plan: Equatable { + static func == (lhs: Plan, rhs: Plan) -> Bool { + lhs.planID == rhs.planID + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/CalendarScene/PlanDetailResponseModel.swift b/SeeMeet/SeeMeet/Source/Models/CalendarScene/PlanDetailResponseModel.swift new file mode 100644 index 0000000..46ff8c3 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/CalendarScene/PlanDetailResponseModel.swift @@ -0,0 +1,35 @@ +import Foundation + +// MARK: - PlanDetailResponseModel +struct PlanDetailResponseModel: Codable { + let status: Int + let success: Bool? + let data: PlanDetailData? + let message: String? +} + +// MARK: - PlanDetailData +struct PlanDetailData: Codable { + let planid, invitationid: Int + let invitationTitle, invitationDesc, date, start: String + let end: String + let hostName: String + let impossible, possible: [PossibleUser] + + enum CodingKeys: String, CodingKey { + case planid, invitationid, invitationTitle, invitationDesc, date, start, end + case hostName = "hostname" + case impossible, possible + } +} + +// MARK: - PossibleUser +struct PossibleUser: Codable { + let userID: Int + let username: String? + + enum CodingKeys: String, CodingKey { + case userID = "user_id" + case username + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/CalendarScene/PlansDeleteResponseModel.swift b/SeeMeet/SeeMeet/Source/Models/CalendarScene/PlansDeleteResponseModel.swift new file mode 100644 index 0000000..6047795 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/CalendarScene/PlansDeleteResponseModel.swift @@ -0,0 +1,15 @@ +// +// PlansDeleteReponseModel.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/09. +// + +import Foundation + +// MARK: - PlansDeleteResponseModel +struct PlansDeleteResponseModel: Codable { + let status: Int + let success: Bool? + let message: String? +} diff --git a/SeeMeet/SeeMeet/Source/Models/FriendsScene/FriendsAddResponseModel.swift b/SeeMeet/SeeMeet/Source/Models/FriendsScene/FriendsAddResponseModel.swift new file mode 100644 index 0000000..b5b460a --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/FriendsScene/FriendsAddResponseModel.swift @@ -0,0 +1,17 @@ +import Foundation + +// MARK: - FriendsAddResponseModel +struct FriendsAddResponseModel: Codable { + let status: Int + let success: Bool? + let message: String? + let data: AddInfo? +} + +// MARK: - AddInfo +struct AddInfo: Codable { + let id, sender, receiver: Int + let isDeleted: Bool + let updatedAt: String + let receiverDeleted: Bool +} diff --git a/SeeMeet/SeeMeet/Source/Models/FriendsScene/FriendsListDataModel.swift b/SeeMeet/SeeMeet/Source/Models/FriendsScene/FriendsListDataModel.swift new file mode 100644 index 0000000..945ed0e --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/FriendsScene/FriendsListDataModel.swift @@ -0,0 +1,50 @@ +import Foundation + +// MARK: - friendsDataModel +struct FriendsDataModel: Codable { + let status: Int + let success: Bool + let message: String? + let data: [FriendsData]? +} + +// MARK: - Datum +struct FriendsData: Codable { + let id: Int + let username: String + let email: String? + + init(){ + self.id = 0 + self.username = "" + self.email = "" + } + +} + +extension FriendsData: Equatable { + static func == (lhs: FriendsData, rhs: FriendsData) -> Bool { + return lhs.id == rhs.id + } +} + +extension FriendsData: Comparable {//Comparable 이게 최선은 아닐듯 .. + static func < (lhs: FriendsData, rhs: FriendsData) -> Bool { + return lhs.username Bool { + return lhs.username<=lhs.username + } + + static func > (lhs: FriendsData, rhs: FriendsData) -> Bool { + return lhs.username>lhs.username + } + + static func >= (lhs: FriendsData, rhs: FriendsData) -> Bool { + return lhs.username>=lhs.username + } + + +} + diff --git a/SeeMeet/SeeMeet/Source/Models/FriendsScene/FriendsSearchResponseModel.swift b/SeeMeet/SeeMeet/Source/Models/FriendsScene/FriendsSearchResponseModel.swift new file mode 100644 index 0000000..c937192 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/FriendsScene/FriendsSearchResponseModel.swift @@ -0,0 +1,20 @@ +import Foundation + +// MARK: - Response +struct FriendsSearchResponseModel: Codable { + let status: Int + let success: Bool? + let message: String? + let data: FriendData? +} + +// MARK: - FriendData +struct FriendData: Codable { + let id: Int + let email, idFirebase, username: String? + let isNoticed: Bool + let createdAt, updatedAt: String + let isDeleted: Bool + let provider, socialId: String? + let nickname: String +} diff --git a/SeeMeet/SeeMeet/Source/Models/HomeScene/HomeDataModel.swift b/SeeMeet/SeeMeet/Source/Models/HomeScene/HomeDataModel.swift new file mode 100644 index 0000000..533b1df --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/HomeScene/HomeDataModel.swift @@ -0,0 +1,19 @@ +import Foundation + +// MARK: - HomeDataModel +struct HomeDataModel: Codable { + let status: Int + let success: Bool + let data: [HomeData] +} + +// MARK: - Datum +struct HomeData: Codable { + let planID: Int + let invitationTitle, date, count: String + + enum CodingKeys: String, CodingKey { + case planID = "planId" + case invitationTitle, date, count + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/HomeScene/LastDateDataModel.swift b/SeeMeet/SeeMeet/Source/Models/HomeScene/LastDateDataModel.swift new file mode 100644 index 0000000..3524e4f --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/HomeScene/LastDateDataModel.swift @@ -0,0 +1,14 @@ + +import Foundation + +// MARK: - HomeDataModel +struct LastDateDataModel: Codable { + let status: Int + let success: Bool + let data: DateModel +} + +// MARK: - DateModel +struct DateModel: Codable { + let date: String +} diff --git a/SeeMeet/SeeMeet/Source/Models/InvitationCancelDataModel.swift b/SeeMeet/SeeMeet/Source/Models/InvitationCancelDataModel.swift new file mode 100644 index 0000000..fa7e28c --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/InvitationCancelDataModel.swift @@ -0,0 +1,34 @@ +// This file was generated from JSON Schema using quicktype, do not modify it directly. +// To parse the JSON, add this file to your project and do: +// +// let plansDetailDataModel = try? newJSONDecoder().decode(PlansDetailDataModel.self, from: jsonData) + +import Foundation + +// MARK: - PlansDetailDataModel +struct InvitationCancelDataModel: Codable { + let status: Int + let success: Bool + let message: String + let data: InvitationCancelData +} + +// MARK: - InvitationCancelData +struct InvitationCancelData: Codable { + let invitation: [InvitationCancel] +} + +// MARK: - Invitation +struct InvitationCancel: Codable { + let invitationID, hostID: Int + let invitationTitle, invitationDesc: String + let isConfirmed, isCanceled: Bool + let createdAt: String + let isDeleted: Bool + + enum CodingKeys: String, CodingKey { + case invitationID = "invitationId" + case hostID = "hostId" + case invitationTitle, invitationDesc, isConfirmed, isCanceled, createdAt, isDeleted + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/InvitationListCancelDataModel.swift b/SeeMeet/SeeMeet/Source/Models/InvitationListCancelDataModel.swift new file mode 100644 index 0000000..4628daf --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/InvitationListCancelDataModel.swift @@ -0,0 +1,13 @@ +// This file was generated from JSON Schema using quicktype, do not modify it directly. +// To parse the JSON, add this file to your project and do: +// +// let plansDetailDataModel = try? newJSONDecoder().decode(PlansDetailDataModel.self, from: jsonData) + +import Foundation + +// MARK: - PlansDetailDataModel +struct InvitationListCancelDataModel: Codable { + let status: Int + let success: Bool + let message: String? +} diff --git a/SeeMeet/SeeMeet/Source/Models/InvitationPlansDataModel.swift b/SeeMeet/SeeMeet/Source/Models/InvitationPlansDataModel.swift new file mode 100644 index 0000000..d9c06f7 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/InvitationPlansDataModel.swift @@ -0,0 +1,37 @@ +// This file was generated from JSON Schema using quicktype, do not modify it directly. +// To parse the JSON, add this file to your project and do: +// +// let plansDetailDataModel = try? newJSONDecoder().decode(PlansDetailDataModel.self, from: jsonData) + +import Foundation + +// MARK: - PlansDetailDataModel +struct InvitationPlansDataModel: Codable { + let status: Int + let success: Bool + let message: String + let data: [InvitationPlansData] +} + +// MARK: - Datum +struct InvitationPlansData: Codable { + let responseID: Int + let invitationDate: InvitationDate + + enum CodingKeys: String, CodingKey { + case responseID = "responseId" + case invitationDate + } +} + +// MARK: - InvitationDate +struct InvitationDate: Codable { + let id, invitationID: Int + let date, start, end: String + + enum CodingKeys: String, CodingKey { + case id + case invitationID = "invitation_id" + case date, start, end + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/InvitationRejectDataModel.swift b/SeeMeet/SeeMeet/Source/Models/InvitationRejectDataModel.swift new file mode 100644 index 0000000..bd43076 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/InvitationRejectDataModel.swift @@ -0,0 +1,21 @@ +import Foundation + +// MARK: - PlansDetailDataModel +struct InvitationRejectDataModel: Codable { + let status: Int + let success: Bool + let message: String + let data: InvitationReject +} + +// MARK: - InvitationReject +struct InvitationReject: Codable { + let id, invitationID: Int + let impossible: Bool + + enum CodingKeys: String, CodingKey { + case id + case invitationID = "invitationId" + case impossible + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/MypageScene/ProfileImageResponseModel.swift b/SeeMeet/SeeMeet/Source/Models/MypageScene/ProfileImageResponseModel.swift new file mode 100644 index 0000000..8692cf8 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/MypageScene/ProfileImageResponseModel.swift @@ -0,0 +1,38 @@ +// +// ProfileImageResponseModel.swift +// SeeMeet +// +// Created by 이유진 on 2022/06/30. +// + +import Foundation + +// MARK: - Main +struct ProfileImageResponseModel: Codable { + let status: Int + let success: Bool + let data: ProfileImageResponseData? + let message: String? +} + +// MARK: - DataClass +struct ProfileImageResponseData: Codable { + let id: Int + let email, idFirebase, username: String + let isNoticed: Bool + let createdAt, updatedAt: String + let isDeleted: Bool + let provider: String + let socialID: String? + let nickname: String + let imgLink: String + let push: Bool + let password: String? + let imgName: String + + enum CodingKeys: String, CodingKey { + case id, email, idFirebase, username, isNoticed, createdAt, updatedAt, isDeleted, provider + case socialID = "socialId" + case nickname, imgLink, push, password, imgName + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/MypageScene/PutPasswordResponseModel.swift b/SeeMeet/SeeMeet/Source/Models/MypageScene/PutPasswordResponseModel.swift new file mode 100644 index 0000000..36b1ab0 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/MypageScene/PutPasswordResponseModel.swift @@ -0,0 +1,50 @@ +// +// PutPasswordResponseModel.swift +// SeeMeet +// +// Created by 이유진 on 2022/07/02. +// + +import Foundation + +// MARK: - Main +struct PasswordResponseModel: Codable { + let status: Int + let success: Bool? + let message: String? + let data : PasswordResponseData? +} + +// MARK: - Datum +struct PasswordResponseData: Codable { + let id: Int + let email, idFirebase: String? + let username: String + let isNoticed: Bool + let createdAt, updatedAt: String + let isDeleted: Bool + let provider: String + let socialID: String? + let nickname: String + let imgLink: String? + let push: Bool + let password: String + let imgName,fcm: String? + + enum CodingKeys: String, CodingKey { + case id, email + case idFirebase = "id_firebase" + case username + case isNoticed = "is_noticed" + case createdAt = "created_at" + case updatedAt = "updated_at" + case isDeleted = "is_deleted" + case provider + case socialID = "social_id" + case nickname + case imgLink = "img_link" + case push, password + case imgName = "img_name" + case fcm + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/NotificationSetResponseModel.swift b/SeeMeet/SeeMeet/Source/Models/NotificationSetResponseModel.swift new file mode 100644 index 0000000..116a2d2 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/NotificationSetResponseModel.swift @@ -0,0 +1,37 @@ +// +// NotificationSetResponseModel.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/11. +// + +import Foundation + +// MARK: - Welcome +struct NotificationSetResponseModel: Codable { + let status: Int + let success: Bool + let message: String + let data: NotificationSetData +} + +// MARK: - DataClass +struct NotificationSetData: Codable { + let id: Int + let email: String? + let idFirebase, username: String? + let isNoticed: Bool + let createdAt, updatedAt: String + let isDeleted: Bool + let provider: String + let socialID, nickname, imgLink: String? + let push: Bool + let password: String? + let imgName, fcm: String? + + enum CodingKeys: String, CodingKey { + case id, email, idFirebase, username, isNoticed, createdAt, updatedAt, isDeleted, provider + case socialID = "socialId" + case nickname, imgLink, push, password, imgName, fcm + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansDetailDataModel.swift b/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansDetailDataModel.swift new file mode 100644 index 0000000..d8a582f --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansDetailDataModel.swift @@ -0,0 +1,61 @@ + +import Foundation + +// MARK: - PlansDetailDataModel +struct PlansDetailDataModel: Codable { + let status: Int + let success: Bool + let message: String + let data: PlansDetailData +} + +// MARK: - PlansDetailData +struct PlansDetailData: Codable { + let isResponse: Bool? + let invitation: PlansInvitation + let invitationDates: [PlansInvitationDate] + let newGuests: [PlansGuest?]? + let rejectGuests: [PlansGuest?] +} + +// MARK: - Guest +struct PlansGuest: Codable { + let id: Int + let username: String +} + +// MARK: - Invitation +struct PlansInvitation: Codable { + let id, hostID: Int + let invitationTitle, invitationDesc: String + let isConfirmed, isCanceled: Bool + let createdAt: String + let isDeleted: Bool + let host: Host + + enum CodingKeys: String, CodingKey { + case id + case hostID = "host_id" + case invitationTitle = "invitation_title" + case invitationDesc = "invitation_desc" + case isConfirmed = "is_confirmed" + case isCanceled = "is_canceled" + case createdAt = "created_at" + case isDeleted = "is_deleted" + case host + } +} + +// MARK: - InvitationDate +struct PlansInvitationDate: Codable { + let id, invitationID: Int + let date, start, end: String + let isSelected: Bool + let respondent: [PlansGuest]? + + enum CodingKeys: String, CodingKey { + case id + case invitationID = "invitationId" + case date, start, end, isSelected, respondent + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansListDataModel.swift b/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansListDataModel.swift new file mode 100644 index 0000000..abfaeac --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansListDataModel.swift @@ -0,0 +1,81 @@ +// This file was generated from JSON Schema using quicktype, do not modify it directly. +// To parse the JSON, add this file to your project and do: +// +// let homeDataModel = try? newJSONDecoder().decode(HomeDataModel.self, from: jsonData) + +import Foundation + +// MARK: - HomeDataModel +struct PlansListDataModel: Codable { + let status: Int + let success: Bool? + let data: PlansListData? + let message: String? +} + +// MARK: - PlansListData +struct PlansListData: Codable { + let invitations: [Invitation] + let confirmedAndCanceld: [ConfirmedAndCanceld] +} + +// MARK: - ConfirmedAndCanceld +struct ConfirmedAndCanceld: Codable { + let id: Int + let invitationTitle: String + let isCanceled, isConfirmed: Bool + let guests: [Guest] + let planID: Int? + + enum CodingKeys: String, CodingKey { + case id + case invitationTitle = "invitation_title" + case isCanceled = "is_canceled" + case isConfirmed = "is_confirmed" + case guests + case planID = "planId" + } +} + +// MARK: - Guest +struct Guest: Codable { + let id: Int + let username: String +// let impossible: Bool? + let isResponse: Bool? +} + +// MARK: - Invitation +struct Invitation: Codable { + let id: Int + let hostID: Int? + let invitationTitle, invitationDesc: String + let isConfirmed, isCanceled: Bool + let createdAt: String + let isDeleted: Bool + let guests: [Guest]? + let isReceived, isResponse: Bool + let host: Host? + let canceled_at: String? + let days: Int + let isVisible: Bool + + enum CodingKeys: String, CodingKey { + case id + case hostID = "host_id" + case invitationTitle = "invitation_title" + case invitationDesc = "invitation_desc" + case isConfirmed = "is_confirmed" + case isCanceled = "is_canceled" + case createdAt = "created_at" + case isDeleted = "is_deleted" + case isVisible = "is_visible" + case guests, isReceived, isResponse, host, canceled_at, days + } +} + +// MARK: - Host +struct Host: Codable { + let id: Int + let username: String? +} diff --git a/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansRequestAcceptDataModel.swift b/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansRequestAcceptDataModel.swift new file mode 100644 index 0000000..d7059ba --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansRequestAcceptDataModel.swift @@ -0,0 +1,71 @@ +// This file was generated from JSON Schema using quicktype, do not modify it directly. +// To parse the JSON, add this file to your project and do: +// +// let plansDetailDataModel = try? newJSONDecoder().decode(PlansDetailDataModel.self, from: jsonData) + +import Foundation + +// MARK: - PlansDetailDataModel +struct PlansRequestAcceptDataModel: Codable { + let status: Int + let success: Bool + let message: String + let data: PlansRequestAcceptData +} + +// MARK: - PlansRequestAcceptData +struct PlansRequestAcceptData: Codable { + let invitation: PlansRequestAcceptInvitation + let invitationDate: PlansRequestAcceptInvitationDate + let plan: Plan +} + +// MARK: - Invitation +struct PlansRequestAcceptInvitation: Codable { + let id, hostID: Int + let invitationTitle, invitationDesc: String + let isConfirmed, isCanceled: Bool + let createdAt: String + let isDeleted: Bool + let host: Host + + enum CodingKeys: String, CodingKey { + case id + case hostID = "host_id" + case invitationTitle = "invitation_title" + case invitationDesc = "invitation_desc" + case isConfirmed = "is_confirmed" + case isCanceled = "is_canceled" + case createdAt = "created_at" + case isDeleted = "is_deleted" + case host + } +} + +// MARK: - InvitationDate +struct PlansRequestAcceptInvitationDate: Codable { + let id, invitationID: Int + let date, start, end: String + let guest: [SendGuest]? + + enum CodingKeys: String, CodingKey { + case id + case invitationID = "invitation_id" + case date, start, end, guest + } +} + +// MARK: - Plan +struct RequestAcceptPlan: Codable { + let id, invitationDateID: Int + let createdAt: String + let isDeleted: Bool + + enum CodingKeys: String, CodingKey { + case id + case invitationDateID = "invitation_date_id" + case createdAt = "created_at" + case isDeleted = "is_deleted" + } +} + diff --git a/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansSendDetailDataModel.swift b/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansSendDetailDataModel.swift new file mode 100644 index 0000000..bb8b883 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/PlansScene/PlansSendDetailDataModel.swift @@ -0,0 +1,58 @@ +import Foundation + +// MARK: - PlansDetailDataModel +struct PlansSendDetailDataModel: Codable { + let status: Int + let success: Bool + let message: String + let data: PlansSendDetailData +} + +// MARK: - PlansSendDetailData +struct PlansSendDetailData: Codable { + let invitation: PlansSendInvitation + let invitationDates: [PlansSendInvitationDate] +} + +// MARK: - Invitation +struct PlansSendInvitation: Codable { + let id, hostID: Int + let invitationTitle, invitationDesc: String + let isConfirmed, isCanceled: Bool + let createdAt: String + let isDeleted: Bool + let host: Host + let guests: [SendGuest] + + enum CodingKeys: String, CodingKey { + case id + case hostID = "host_id" + case invitationTitle = "invitation_title" + case invitationDesc = "invitation_desc" + case isConfirmed = "is_confirmed" + case isCanceled = "is_canceled" + case createdAt = "created_at" + case isDeleted = "is_deleted" + case host, guests + } +} + +// MARK: - Guest +struct SendGuest: Codable { + let id: Int + let username: String + let isResponse: Bool +} + +// MARK: - InvitationDate +struct PlansSendInvitationDate: Codable { + let id, invitationID: Int + let date, start, end: String + let respondent: [Host] + + enum CodingKeys: String, CodingKey { + case id + case invitationID = "invitation_id" + case date, start, end, respondent + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/PlansScene/ResponseDateDataModel.swift b/SeeMeet/SeeMeet/Source/Models/PlansScene/ResponseDateDataModel.swift new file mode 100644 index 0000000..5b23ace --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/PlansScene/ResponseDateDataModel.swift @@ -0,0 +1,26 @@ +import Foundation + +// MARK: - PlansDetailDataModel +struct ResponseDateDataModel: Codable { + let status: Int + let success: Bool + let data: [ResponseDateData] +} + +// MARK: - Datum +struct ResponseDateData: Codable { + let invitationTitle, date, start, end: String + let planid: Int + let users: [ResponseUser] +} + +// MARK: - User +struct ResponseUser: Codable { + let userID: Int + let username: String + + enum CodingKeys: String, CodingKey { + case userID = "user_id" + case username + } +} diff --git a/SeeMeet/SeeMeet/Source/Models/RegisterScene/RegisterDataModel.swift b/SeeMeet/SeeMeet/Source/Models/RegisterScene/RegisterDataModel.swift new file mode 100644 index 0000000..99ea294 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/RegisterScene/RegisterDataModel.swift @@ -0,0 +1,32 @@ +import Foundation + +// MARK: - RegisterModel +struct RegisterDataModel: Codable { + let status: Int + let success: Bool + let message: String + let data: RegisterData? +} + +// MARK: - RegisterData +struct RegisterData: Codable { + let newUser: NewUser + let accesstoken: String + let refreshtoken: String +} + +// MARK: - User +struct NewUser: Codable { + let id: Int + let email: String + let idFirebase, username: String? + let isNoticed: Bool + let createdAt, updatedAt: String + let isDeleted: Bool + let provider: String + let socialId, nickname, imgLink: String? + let push: Bool + let password: String + let imgName, fcm, refreshToken: String? + +} diff --git a/SeeMeet/SeeMeet/Source/Models/RequestScene/InvitationPlanDataModel.swift b/SeeMeet/SeeMeet/Source/Models/RequestScene/InvitationPlanDataModel.swift new file mode 100644 index 0000000..1d0166c --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/RequestScene/InvitationPlanDataModel.swift @@ -0,0 +1,73 @@ +// +// InvitationPlanDataModel.swift +// SeeMeet +// +// Created by 이유진 on 2022/01/21. +// + +import Foundation + +// MARK: - Main +struct InvitationPlanData: Codable { + let status: Int + let success: Bool? + let data: [ScheduleData]? +} + +// MARK: - Datum +struct ScheduleData: Codable { + let id: Int + let invitationTitle, date, start, end: String +} + +extension ScheduleData: Comparable { + static func < (lhs: ScheduleData, rhs: ScheduleData) -> Bool { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_KR") + formatter.timeZone = NSTimeZone(name: "ko_KR") as TimeZone? + formatter.dateFormat = "hh:mm:ss" + + let lh: Date = formatter.date(from: lhs.start) ?? Date() + let rh: Date = formatter.date(from: rhs.start) ?? Date() + + return lh < rh + } + + static func <= (lhs: ScheduleData, rhs: ScheduleData) -> Bool { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_KR") + formatter.timeZone = NSTimeZone(name: "ko_KR") as TimeZone? + formatter.dateFormat = "hh:mm:ss" + + let lh: Date = formatter.date(from: lhs.start) ?? Date() + let rh: Date = formatter.date(from: rhs.start) ?? Date() + + return lh <= rh + } + + static func > (lhs: ScheduleData, rhs: ScheduleData) -> Bool { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_KR") + formatter.timeZone = NSTimeZone(name: "ko_KR") as TimeZone? + formatter.dateFormat = "hh:mm:ss" + + let lh: Date = formatter.date(from: lhs.start) ?? Date() + let rh: Date = formatter.date(from: rhs.start) ?? Date() + + return lh > rh + } + + static func >= (lhs: ScheduleData, rhs: ScheduleData) -> Bool { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_KR") + formatter.timeZone = NSTimeZone(name: "ko_KR") as TimeZone? + formatter.dateFormat = "hh:mm:ss" + + let lh: Date = formatter.date(from: lhs.start) ?? Date() + let rh: Date = formatter.date(from: rhs.start) ?? Date() + + return lh >= rh + } + + +} diff --git a/SeeMeet/SeeMeet/Source/Models/RequestScene/PlanRequestDataModel.swift b/SeeMeet/SeeMeet/Source/Models/RequestScene/PlanRequestDataModel.swift new file mode 100644 index 0000000..386a8fa --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Models/RequestScene/PlanRequestDataModel.swift @@ -0,0 +1,56 @@ +// +// InvitationDataModel.swift +// SeeMeet +// +// Created by 이유진 on 2022/01/21. +// + +import Foundation + +// MARK: - PlanRequestData +struct PlanRequestData: Codable { + let status: Int + let success: Bool? + let message: String? + let data: PlanRequestResultData +} + +// MARK: - PlanRequestResultData +struct PlanRequestResultData: Codable { + let invitation: RequestInvitation + let guests: [RequestGuest] + let dates: [[RequestDateElement]] +} + +// MARK: - DateElement +struct RequestDateElement: Codable { + let id, invitationID: Int + let date, start, end: String + + enum CodingKeys: String, CodingKey { + case id + case invitationID = "invitationId" + case date, start, end + } +} + +// MARK: - Guest +struct RequestGuest: Codable { + let id: Int + let username: String +} + +// MARK: - Invitation +struct RequestInvitation: Codable { + let id, hostID: Int + let invitationTitle, invitationDesc: String + let isConfirmed, isCanceled: Bool + let createdAt: String + let isDeleted: Bool + + enum CodingKeys: String, CodingKey { + case id + case hostID = "hostId" + case invitationTitle, invitationDesc, isConfirmed, isCanceled, createdAt, isDeleted + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/Authentication/AccessInterceptor.swift b/SeeMeet/SeeMeet/Source/Services/Authentication/AccessInterceptor.swift new file mode 100644 index 0000000..96ed209 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/Authentication/AccessInterceptor.swift @@ -0,0 +1,46 @@ +// +// AccessInterceptor.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/20. +// + +import Foundation +import Alamofire + +class AccessInterceptor: RequestInterceptor { + + static let shared = AccessInterceptor() + + func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { + print(request.response) + print(request.response) + guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else { + completion(.doNotRetryWithError(error)) + return + } + + PostRefreshAccessTokenService.shared.tokenRefresh { response in + switch response { + case .success(let data): + guard let tokenData = data as? TokenRefreshData else { return } + TokenUtils.shared.create(account: "accessToken", value: tokenData.accessToken) + TokenUtils.shared.create(account: "refreshToken", value: tokenData.refreshToken) + completion(.retry) + case .pathErr: + print("pathErr") + completion(.doNotRetry) + case .networkFail: + print("networkFail") + completion(.doNotRetry) + case .serverErr: + print("serverErr") + completion(.doNotRetry) + case .requestErr(_): // 재 로그인 필요 + UserDefaults.deleteUserValue() + NotificationCenter.default.post(name: Notification.Name.RefreshTokenExpired, object: nil) + completion(.doNotRetry) + } + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/Authentication/AppleAuthService.swift b/SeeMeet/SeeMeet/Source/Services/Authentication/AppleAuthService.swift new file mode 100644 index 0000000..d4795ba --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/Authentication/AppleAuthService.swift @@ -0,0 +1,64 @@ +// +// AppleAuthService.swift +// SeeMeet +// +// Created by 이유진 on 2022/03/20. +// + +import Foundation +import AuthenticationServices +import Alamofire + +class AppleAuthService: NSObject { + + // MARK: - Properties + + static let shared = AppleAuthService() + + private let URL = Constants.URL.socialLoginURL + private let provider: String = "apple" + + func login(name: String,token: String,completion: @escaping (NetworkResult)->Void) { + // 키체인에 apple token 저장 + TokenUtils.shared.create(account: "AppleUserIdentifier", value: token) + + let headers: HTTPHeaders = ["Content-Type": "application/json"] + let parameters: Parameters = [ + "socialtoken": token, + "name": name, + "provider": provider, + "fcm": UserDefaults.standard.string(forKey: "fcmToken") ?? "" + ] + + let requestToAPI = AF.request(URL, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers) + requestToAPI.responseData { response in + dump(response) + switch response.result { + case .success: + guard let statusCode = response.response?.statusCode, + let value = response.value else { return } + + let networkResult = self.judgeStatus(by: statusCode, value) + UserDefaults.standard.set("apple", forKey: "loginBy") + completion(networkResult) + case .failure: + completion(.pathErr) + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(SocialLoginDataModel.self, from: data) else { return .pathErr } + switch statusCode { + case 200, 404: + return .success(decodedData) + case 400: + print(decodedData.message) + return .requestErr(decodedData.message) + case 500: + return .serverErr + default: return .networkFail + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/Authentication/KakaoAuthService.swift b/SeeMeet/SeeMeet/Source/Services/Authentication/KakaoAuthService.swift new file mode 100644 index 0000000..9152794 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/Authentication/KakaoAuthService.swift @@ -0,0 +1,128 @@ +// +// KakaoAuthService.swift +// SeeMeet +// +// Created by 김인환 on 2022/03/06. +// + +import Foundation +import KakaoSDKCommon +import KakaoSDKAuth +import KakaoSDKUser +import Alamofire + +struct KakaoAuthService { + static let shared = KakaoAuthService() + + private let URL = Constants.URL.socialLoginURL + private let provider: String = "kakao" + + static var isKakaoLogined: Bool = false + + func login(completion: @escaping (NetworkResult) -> Void) { + logout() + if (UserApi.isKakaoTalkLoginAvailable()) { // 1. 카카오톡 설치 여부를 확인한다. + UserApi.shared.loginWithKakaoTalk {(OAuthToken, error) in + if let error = error { + print(error) + return + } else { + print("loginWithKakaoTalk() success.") + guard let OAuthToken = OAuthToken else { return } + + TokenUtils.shared.create(account: "kakaoOAuthToken", value: OAuthToken.accessToken) + requestLoginToService(kakaoToken: OAuthToken, completion: completion) + } + } + } else { // 카카오톡 미설치시 사파리를 통해 카카오 계정으로 로그인한다. + UserApi.shared.loginWithKakaoAccount { (OAuthToken, error) in + if let error = error { + print(error) + return + } else { + print("loginWithKakaoAccount() success.") + guard let OAuthToken = OAuthToken else { return } + // 2. 소셜 토큰 받아서 키체인에 저장 + TokenUtils.shared.create(account: "kakaoOAuthToken", value: OAuthToken.accessToken) + requestLoginToService(kakaoToken: OAuthToken, completion: completion) + } + } + } + + func requestLoginToService(kakaoToken: OAuthToken, completion: @escaping (NetworkResult) -> Void) { // 3. 유저의 카카오 프로필정보를 조회해서 닉네임(유저이름)을 가져온다. + UserDefaults.standard.set("kakao", forKey: "loginBy") // 카카오로 로그인 했는지 여부 + + UserApi.shared.me { user, error in + if let error = error { + print(error) + } else { + if let user = user { + guard let nickname = user.properties?["nickname"] else { return } + + let headers: HTTPHeaders = ["Content-Type": "application/json"] + + let parameters: Parameters = [ + "socialtoken": kakaoToken.accessToken, + "name": nickname, + "provider": provider, + "fcm": UserDefaults.standard.string(forKey: "fcmToken") ?? "" + ] + + // 4. 씨밋 로그인 서버로 로그인 요청한다. + let request = AF.request(URL, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers) + request.responseData { response in + dump(response) + switch response.result { + case .success: + guard let statusCode = response.response?.statusCode, + let value = response.value else { return } + + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + case .failure: + completion(.pathErr) + } + } + } + } + } + } + } + + func logout() { + UserApi.shared.logout {(error) in + if let error = error { + print(error) + } + else { + print("logout() success.") + } + } + } + + func unlink() { // 카카오 계정과 연동 끊기, 로그아웃도 함께 이뤄진다. + UserApi.shared.unlink {(error) in + if let error = error { + print(error) + } + else { + print("unlink() success.") + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(SocialLoginDataModel.self, from: data) else { return .pathErr } + switch statusCode { + case 200, 404: + return .success(decodedData) + case 400: + print(decodedData.message) + return .requestErr(decodedData.message) + case 500: + return .serverErr + default: return .networkFail + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/Authentication/PostRefreshAccessTokenService.swift b/SeeMeet/SeeMeet/Source/Services/Authentication/PostRefreshAccessTokenService.swift new file mode 100644 index 0000000..a25876f --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/Authentication/PostRefreshAccessTokenService.swift @@ -0,0 +1,56 @@ +// +// PostRefreshAccessTokenService.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/20. +// + +import Foundation +import Alamofire + +class PostRefreshAccessTokenService { + + static let shared = PostRefreshAccessTokenService() + + func tokenRefresh(completion : @escaping (NetworkResult) -> Void) { + let URL = Constants.URL.refreshAccessTokenURL + + var headers = TokenUtils.shared.getAuthorizationHeader() + headers?.add(HTTPHeader(name: "refreshtoken", value: TokenUtils.shared.read(account: "refreshToken") ?? "")) + + let dataRequest = AF.request(URL, method: .post, parameters: nil, encoding: JSONEncoding.default, headers: headers) + + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else { return } + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure(let error): + print(error.localizedDescription) + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 401: return .requestErr(data) // 액세스 토큰이 만료 된 후 리프레시 토큰 또한 만료된 경우 + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + let decoder = JSONDecoder() + + guard let decodedData = try? decoder.decode(AccessTokenRefreshDataModel.self, from: data) else { + return .pathErr + } + + return .success(decodedData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/Authentication/PutNameAndIdService.swift b/SeeMeet/SeeMeet/Source/Services/Authentication/PutNameAndIdService.swift new file mode 100644 index 0000000..71346c1 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/Authentication/PutNameAndIdService.swift @@ -0,0 +1,79 @@ +// +// PutNameAndIdService.swift +// SeeMeet +// +// Created by 김인환 on 2022/04/23. +// + +import Foundation +import Alamofire + +struct PutNameAndIdService { + static let shared = PutNameAndIdService() + + private let URL = Constants.URL.putNameAndIdURL + + enum RequestError: Error { + case missingError // 값이 없을 경우 + case duplicateError // 중복 닉네임의 경우 + } + + func putNameAndId(name: String?, userId: String, + accessToken: String, completion: @escaping (NetworkResult) -> Void) { + let header : HTTPHeaders = ["Content-Type": "application/json", "accesstoken": accessToken] + + var requestBody: Parameters = [ + "nickname": userId + ] + + if let name { + requestBody["name"] = name + } + + let response = AF.request(URL, method: .put, parameters: requestBody, encoding: JSONEncoding.default, headers: header) + + response.responseData { responseData in + switch responseData.result { + case .success: + if let statusCode = responseData.response?.statusCode, + let value = responseData.value { + let networkResult = judgeStatus(by: statusCode, value) + completion(networkResult) + } + + case .failure(let error): + print(error.localizedDescription) + completion(.networkFail) + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidDecodableData(data: data) + case 400: return isValidDecodableData(data: data) + case 404: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + + private func isValidDecodableData(data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(PutNameAndIdResponseDataModel.self, from: data) else { return .pathErr } + if decodedData.status == 200 { + return .success(decodedData) + } else if decodedData.status == 400 { + let message = decodedData.message + if message == "이미 사용중인 닉네임입니다." { + return .requestErr(RequestError.duplicateError) + } else { + return .requestErr(RequestError.missingError) + } + } + else { + return .pathErr + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/Authentication/PutWithdrawalService.swift b/SeeMeet/SeeMeet/Source/Services/Authentication/PutWithdrawalService.swift new file mode 100644 index 0000000..70a9eaf --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/Authentication/PutWithdrawalService.swift @@ -0,0 +1,55 @@ +import Alamofire +import Foundation + +struct PutWithdrawalService { + static let shared = PutWithdrawalService() + + func putWithdrawal(authorizationCode: String?, completion: @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + var URL = Constants.URL.putWithdrawalURL + + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + if let authorizationCode { + URL = URL + "?code=" + authorizationCode + } + + let dataRequest = AF.request(URL, + method: .put, + parameters: nil, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + dump(dataResponse) + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else { return } + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(WithdrawalDataModel.self, from: data) + + else { return .pathErr } + + return .success(decodedData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/CalendarService.swift b/SeeMeet/SeeMeet/Source/Services/CalendarService.swift new file mode 100644 index 0000000..0dfd8b9 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/CalendarService.swift @@ -0,0 +1,93 @@ +import Foundation +import Alamofire + +struct CalendarService { + + // MARK: - properties + + static let shared = CalendarService() + + private var headers: HTTPHeaders? + + // MARK: - initializer + + init() { + guard let headers = TokenUtils().getAuthorizationHeader() else { return } + self.headers = headers + } + + // MARK: - Methods + + func getPlanDatas(year: String, + month: String, + completion: @escaping (NetworkResult) -> Void) { + let url = Constants.URL.calendarURL(year, month) + + let request = AF.request(url, + method: .get, + headers: headers, + interceptor: AccessInterceptor.shared) + + request.responseData { responseData in + switch responseData.result { + case .success: + if let statusCode = responseData.response?.statusCode, + let value = responseData.value { + + + let networkResult = judgeStatus(by: statusCode, value) + completion(networkResult) + } + case .failure(let error): + print(error.localizedDescription) + completion(.networkFail) + } + } + + } + + func getDetailPlanData(planID: Int, + completion: @escaping (NetworkResult) -> Void) { + let url = Constants.URL.baseURL + "/plan/detail/\(planID)" + + let request = AF.request(url, + method: .get, + headers: headers).validate() + + request.responseData { responseData in + switch responseData.result { + case .success: + if let statusCode = responseData.response?.statusCode, + let value = responseData.value { + let networkResult = judgeStatus(by: statusCode, value) + completion(networkResult) + } + case .failure(let error): + print(error.localizedDescription) + completion(.networkFail) + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidDecodableData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidDecodableData(data: Data) -> NetworkResult { + let decoder = JSONDecoder() + if let decodedData = try? decoder.decode(MonthlyPlansResponseModel.self, from: data){ + return .success(decodedData) + } else { + if let decodedData = try? decoder.decode(PlanDetailResponseModel.self, from: data) { + return .success(decodedData) + } else { + return .pathErr + } + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/Constants.swift b/SeeMeet/SeeMeet/Source/Services/Constants.swift new file mode 100644 index 0000000..b367916 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/Constants.swift @@ -0,0 +1,67 @@ +import Foundation + +struct Constants { + + struct URL{ + //baseURL + static let baseURL = "http://3.34.126.253:3000" + + //auth관련 + static let registerURL = baseURL + "/auth" + static let loginURL = baseURL + "/auth/login" + static let socialLoginURL = baseURL + "/auth/social" + static let putNameAndIdURL = baseURL + "/auth/" + static let putWithdrawalURL = baseURL + "/auth/withdrawal" + static let refreshAccessTokenURL = baseURL + "/auth/refresh" + + //home관련 + static let homeURL = { (year: String, month: String) -> String in + baseURL + "/plan/comeplan/\(year)/\(month)" + } + static let lastURL = { (year: String, month: String, day: String) -> String in + baseURL + "/plan/lastplan/\(year)/\(month)/\(day)" + } + + //Calendar + static var calendarURL = { (year: String, month: String) -> String in + baseURL + "/plan/month/\(year)/\(month)" + } + + //친구 관련 + static let friendsListURL = baseURL + "/friend/list" + static let searchFriendsURL = baseURL + "/friend/search" + static let addFriendsURL = baseURL + "/friend/addFriend" + + //약속 관련 + static let plansListURL = baseURL + "/invitation/list" + static let plansDetailURL = { (postID: String) in + baseURL + "/invitation/\(postID)" + } + static let plansCanceledDetailURL = { (postID: String) in + baseURL + "/invitation/canceled/\(postID)" + } + static let plansResponseURL = { (date: String) in + baseURL + "/plan/response/\(date)" + } + static let plansRequestURL = { (plansID: String) in + baseURL + "/invitation-response/\(plansID)" + } + static let plansRejectURL = { (plansID: String) in + baseURL + "/invitation-response/\(plansID)/reject" + } + static let plansDeleteURL = { (plansID: String) in + baseURL + "/plan/delete/\(plansID)" + } + + //약속신청 관련 + static let invitationURL = baseURL + "/invitation" + static let invitationPlanURL = { (year: String, month: String) in + baseURL + "/plan/invitationplan/\(year)/\(month)" + } + + //마이페이지 관련 + static let postProfileImageURL = baseURL + "/user/upload" + static let putPasswordUrl = baseURL + "/user/password" + static let postNotificationSetURL = baseURL + "/user/push" + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/FriendsAddService.swift b/SeeMeet/SeeMeet/Source/Services/FriendsAddService.swift new file mode 100644 index 0000000..27a57a4 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/FriendsAddService.swift @@ -0,0 +1,67 @@ +import Foundation +import Alamofire + +struct FriendsAddService { + + // MARK: - properties + + static let shared = FriendsAddService() + + private var headers: HTTPHeaders? + + // MARK: - initializer + + init() { + guard let headers = TokenUtils.shared.getAuthorizationHeader() else { return } + self.headers = headers + } + + // MARK: - methods + + func addFriends(nickname: String, + completion: @escaping (NetworkResult) -> Void) { + let url = Constants.URL.addFriendsURL + + let requestBody: Parameters = ["nickname": nickname] + + let request = AF.request(url, + method: .post, + parameters: requestBody, + encoding: JSONEncoding.default, + headers: headers, + interceptor: AccessInterceptor.shared) + + request.responseData { responseData in + switch responseData.result { + case .success: + if let statusCode = responseData.response?.statusCode, + let value = responseData.value { + let networkResult = judgeStatus(by: statusCode, value) + completion(networkResult) + } + case .failure(let error): + print(error.localizedDescription) + completion(.networkFail) + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200, 400, 404: return isValidDecodableData(data: data) + case 500: return .serverErr + default: return.networkFail + } + } + + private func isValidDecodableData(data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(FriendsAddResponseModel.self, from: data) else { return .pathErr } + if decodedData.status == 200 { + return .success(decodedData) + } else { + return .requestErr(decodedData) + } + } + +} diff --git a/SeeMeet/SeeMeet/Source/Services/FriendsSearchService.swift b/SeeMeet/SeeMeet/Source/Services/FriendsSearchService.swift new file mode 100644 index 0000000..1c4ff3d --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/FriendsSearchService.swift @@ -0,0 +1,66 @@ +import Foundation +import Alamofire + +struct FriendsSearchService { + + // MARK: - properties + + static let shared = FriendsSearchService() + + private var headers: HTTPHeaders? + + // MARK: - initializer + + init() { + guard let headers = TokenUtils.shared.getAuthorizationHeader() else { return } + self.headers = headers + } + + // MARK: - methods + + func searchFriends(nickname: String, + completion: @escaping (NetworkResult) -> Void) { + let url = Constants.URL.searchFriendsURL + + let requestBody: Parameters = ["nickname": nickname] + + let request = AF.request(url, + method: .post, + parameters: requestBody, + encoding: JSONEncoding.default, + headers: headers, + interceptor: AccessInterceptor.shared) + + request.responseData { responseData in + switch responseData.result { + case .success: + if let statusCode = responseData.response?.statusCode, + let value = responseData.value { + let networkResult = judgeStatus(by: statusCode, value) + completion(networkResult) + } + case .failure(let error): + print(error.localizedDescription) + completion(.networkFail) + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200, 400, 404: return isValidDecodableData(data: data) + case 500: return .serverErr + default: return.networkFail + } + } + + private func isValidDecodableData(data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(FriendsSearchResponseModel.self, from: data) else { return .pathErr } + if decodedData.status == 200 { + return .success(decodedData) + } else { + return .requestErr(decodedData) + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/GetCanceldPlansDetailService.swift b/SeeMeet/SeeMeet/Source/Services/GetCanceldPlansDetailService.swift new file mode 100644 index 0000000..2da3326 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/GetCanceldPlansDetailService.swift @@ -0,0 +1,51 @@ +import Alamofire +import Foundation + +struct GetCanceldPlansDetailService { + static let shared = GetCanceldPlansDetailService() + + func getCanceledPlans(plansId: String, completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.plansCanceledDetailURL(plansId) + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + let dataRequest = AF.request(URL, + method: .get, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + dump(dataResponse) + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else {return} + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + + let decoder = JSONDecoder() + var decodedData = try! decoder.decode(CanceledPlanDetailModel.self, from: data) + guard let decodedData = try? decoder.decode(CanceledPlanDetailModel.self, from: data) else { + return .pathErr} + + return .success(decodedData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/GetFriendsListService.swift b/SeeMeet/SeeMeet/Source/Services/GetFriendsListService.swift new file mode 100644 index 0000000..0f3433d --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/GetFriendsListService.swift @@ -0,0 +1,52 @@ +import Alamofire +import Foundation + +struct GetFriendsListService { + static let shared = GetFriendsListService() + + func getFriendsList(completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.friendsListURL + + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + + let dataRequest = AF.request(URL, + method: .get, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else {return} + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + + } + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200, 304: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(FriendsDataModel.self, from: data) + else {return .pathErr} + + return .success(decodedData) + + } + +} diff --git a/SeeMeet/SeeMeet/Source/Services/GetHomeDataService.swift b/SeeMeet/SeeMeet/Source/Services/GetHomeDataService.swift new file mode 100644 index 0000000..dc37a69 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/GetHomeDataService.swift @@ -0,0 +1,48 @@ +import Alamofire +import Foundation + +struct GetHomeDataService { + static let shared = GetHomeDataService() + func getHomeData(year: String, month: String, + completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.homeURL(year, month) + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + let dataRequest = AF.request(URL, + method: .get, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + dump(dataResponse) + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else {return} + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(HomeDataModel.self, from: data) else { return.pathErr } + + return .success(decodedData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/GetLastDateService.swift b/SeeMeet/SeeMeet/Source/Services/GetLastDateService.swift new file mode 100644 index 0000000..ab68d8d --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/GetLastDateService.swift @@ -0,0 +1,52 @@ +import Alamofire +import Foundation + +struct GetLastDateService { + static let shared = GetLastDateService() + + func getLastPlans(year: String, month: String, day: String, completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.lastURL(year, month, day) + + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + let dataRequest = AF.request(URL, + method: .get, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + dump(dataResponse) + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else {return} + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + + } + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(LastDateDataModel.self, from: data) + else {return .pathErr} + + return .success(decodedData) + + } + +} diff --git a/SeeMeet/SeeMeet/Source/Services/GetPlansDetailService.swift b/SeeMeet/SeeMeet/Source/Services/GetPlansDetailService.swift new file mode 100644 index 0000000..7127d90 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/GetPlansDetailService.swift @@ -0,0 +1,50 @@ +import Alamofire +import Foundation + +struct GetPlansDetailDataService { + static let shared = GetPlansDetailDataService() + func getPlansDetail(postID: String, completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.plansDetailURL(postID) + + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + + let dataRequest = AF.request(URL, + method: .get, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + dump(dataResponse) + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else {return} + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(PlansDetailDataModel.self, from: data) + else {return .pathErr} + + return .success(decodedData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/GetPlansListDataService.swift b/SeeMeet/SeeMeet/Source/Services/GetPlansListDataService.swift new file mode 100644 index 0000000..3fefdad --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/GetPlansListDataService.swift @@ -0,0 +1,50 @@ +import Alamofire +import Foundation + +struct GetPlansListDataService { + static let shared = GetPlansListDataService() + + func getPlansList(completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.plansListURL + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + let dataRequest = AF.request(URL, + method: .get, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + dump(dataResponse) + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else {return} + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(PlansListDataModel.self, from: data) + else {return .pathErr} + + return .success(decodedData) + + } + +} diff --git a/SeeMeet/SeeMeet/Source/Services/GetPlansSendDetailService.swift b/SeeMeet/SeeMeet/Source/Services/GetPlansSendDetailService.swift new file mode 100644 index 0000000..5bad53f --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/GetPlansSendDetailService.swift @@ -0,0 +1,48 @@ +import Alamofire +import Foundation + +struct GetPlansSendDetailDataService { + static let shared = GetPlansSendDetailDataService() + + func getSendDetail(plansId: String, completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.plansDetailURL(plansId) + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + let dataRequest = AF.request(URL, + method: .get, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else {return} + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(PlansSendDetailDataModel.self, from: data) else { return .pathErr } + + return .success(decodedData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/GetResponseDateDataService.swift b/SeeMeet/SeeMeet/Source/Services/GetResponseDateDataService.swift new file mode 100644 index 0000000..4fa08cd --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/GetResponseDateDataService.swift @@ -0,0 +1,49 @@ +import Alamofire +import Foundation + +struct GetResponseDateDataService { + static let shared = GetResponseDateDataService() + + func getResponseDate(date: String, completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.plansResponseURL(date) + + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + let dataRequest = AF.request(URL, + method: .get, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else {return} + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(ResponseDateDataModel.self, from: data) + else {return .pathErr} + + return .success(decodedData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/ImageManager.swift b/SeeMeet/SeeMeet/Source/Services/ImageManager.swift new file mode 100644 index 0000000..e50e97f --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/ImageManager.swift @@ -0,0 +1,35 @@ +// +// ImageManager.swift +// SeeMeet +// +// Created by 이유진 on 2022/07/01. +// + +import UIKit +class ImageManager { + static let shared = ImageManager() + + @discardableResult + func saveImage(image: UIImage,named: String) -> Bool { + guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else { + return false + } + guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else { + return false + } + do { + try data.write(to: directory.appendingPathComponent(named)!) + return true + } catch { + print(error.localizedDescription) + return false + } + } + + func getSavedImage(named: String) -> UIImage? { + if let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) { + return UIImage(contentsOfFile: URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(named).path) + } + return nil + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/NetworkRechability.swift b/SeeMeet/SeeMeet/Source/Services/NetworkRechability.swift new file mode 100644 index 0000000..da638a2 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/NetworkRechability.swift @@ -0,0 +1,8 @@ +// +// NetworkRechability.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/10. +// + +import Foundation diff --git a/SeeMeet/SeeMeet/Source/Services/NetworkRechabilityService.swift b/SeeMeet/SeeMeet/Source/Services/NetworkRechabilityService.swift new file mode 100644 index 0000000..789ddae --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/NetworkRechabilityService.swift @@ -0,0 +1,45 @@ +// +// NetworkRechabilityService.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/10. +// + +import Foundation +import SystemConfiguration + +public class NetworkReachabilityService { /// https://stackoverflow.com/questions/30743408/check-for-internet-connection-with-swift + + class func isConnectedToNetwork() -> Bool { + + var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) + zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) + zeroAddress.sin_family = sa_family_t(AF_INET) + + let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in + SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress) + } + } + + var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0) + if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false { + return false + } + + /* Only Working for WIFI + let isReachable = flags == .reachable + let needsConnection = flags == .connectionRequired + + return isReachable && !needsConnection + */ + + // Working for Cellular and WIFI + let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0 + let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0 + let ret = (isReachable && !needsConnection) + + return ret + + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/NetworkResult.swift b/SeeMeet/SeeMeet/Source/Services/NetworkResult.swift new file mode 100644 index 0000000..f5ef666 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/NetworkResult.swift @@ -0,0 +1,9 @@ +import Foundation + +enum NetworkResult { + case success(T) + case requestErr(T) + case pathErr + case serverErr + case networkFail +} diff --git a/SeeMeet/SeeMeet/Source/Services/PostInvitationRejectService.swift b/SeeMeet/SeeMeet/Source/Services/PostInvitationRejectService.swift new file mode 100644 index 0000000..aec45c8 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/PostInvitationRejectService.swift @@ -0,0 +1,46 @@ +import Foundation +import Alamofire + +struct PostInvitationRejectService { + + static let shared = PostInvitationRejectService() + + func postRejectInvitation(plansId: String, completion : @escaping (NetworkResult) -> Void) { + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + let URL = Constants.URL.plansRejectURL(plansId) + let dataRequest = AF.request(URL, + method: .post, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else { return } + guard let value = dataResponse.value else { return } + + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(InvitationRejectDataModel.self, from: data) else { + return .pathErr + } + switch statusCode { + case 200: + return .success(decodedData) + case 400: + print(decodedData.message) + return .requestErr(decodedData.message) + case 500: + return .serverErr + default: return .networkFail + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/PostInvitationService.swift b/SeeMeet/SeeMeet/Source/Services/PostInvitationService.swift new file mode 100644 index 0000000..023725c --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/PostInvitationService.swift @@ -0,0 +1,52 @@ +import Foundation +import Alamofire + +struct PostInvitationService { + + static let shared = PostInvitationService() + + private func makeParameter(invitationDateIds: [Int]) -> Parameters { + return ["invitationDateIds": invitationDateIds] + } + + func postInvitation(plansId: String, invitationDateIds: [Int], + completion : @escaping (NetworkResult) -> Void) { + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + let URL = Constants.URL.plansRequestURL(plansId) + let dataRequest = AF.request(URL, + method: .post, + parameters: makeParameter(invitationDateIds: invitationDateIds), + encoding: JSONEncoding.default, + headers: header) + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else { return } + guard let value = dataResponse.value else { return } + + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(InvitationPlansDataModel.self, from: data) else { + return .pathErr + } + switch statusCode { + case 200: + return .success(decodedData) + case 400: + print(decodedData.message) + return .requestErr(decodedData.message) + case 500: + return .serverErr + default: return .networkFail + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/PostLoginService.swift b/SeeMeet/SeeMeet/Source/Services/PostLoginService.swift new file mode 100644 index 0000000..6514bd2 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/PostLoginService.swift @@ -0,0 +1,55 @@ +import Foundation +import Alamofire + +struct PostLoginService { + + static let shared = PostLoginService() + + private func makeParameter(email: String, + password: String) -> Parameters { + return ["email": email, "password": password] + } + + func login(email: String, + password: String, + completion : @escaping (NetworkResult) -> Void) { + let header : HTTPHeaders = ["Content-Type": "application/json"] + let dataRequest = AF.request(Constants.URL.loginURL, + method: .post, + parameters: makeParameter(email: email, + password: password), + encoding: JSONEncoding.default, + headers: header) + dataRequest.responseData { dataResponse in + dump(dataResponse) + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else { return } + guard let value = dataResponse.value else { return } + + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(EmailLoginResponseModel.self, from: data) else { + return .pathErr + } + switch statusCode { + case 200, 404: + return .success(decodedData) + case 400: + print(decodedData.message) + return .requestErr(decodedData.message) + case 500: + return .serverErr + default: return .networkFail + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/PostPlansRequestAcceptService.swift b/SeeMeet/SeeMeet/Source/Services/PostPlansRequestAcceptService.swift new file mode 100644 index 0000000..ebaf1a4 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/PostPlansRequestAcceptService.swift @@ -0,0 +1,52 @@ +import Foundation +import Alamofire + +struct PostPlansRequestAcceptService { + + static let shared = PostPlansRequestAcceptService() + + private func makeParameter(dateId: Int) -> Parameters { + return ["dateId": dateId] + } + + func postPlansRequestAccept(plansId: String, dateId: Int, + completion : @escaping (NetworkResult) -> Void) { + + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + let dataRequest = AF.request(Constants.URL.plansDetailURL(plansId), + method: .post, + parameters: makeParameter(dateId: dateId), + encoding: JSONEncoding.default, + headers: header) + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else { return } + guard let value = dataResponse.value else { return } + + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(PlansRequestAcceptDataModel.self, from: data) else { + return .pathErr + } + switch statusCode { + case 200: + return .success(decodedData) + case 400: + print(decodedData.message) + return .requestErr(decodedData.message) + case 500: + return .serverErr + default: return .networkFail + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/PostProfileImageService.swift b/SeeMeet/SeeMeet/Source/Services/PostProfileImageService.swift new file mode 100644 index 0000000..8a88a77 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/PostProfileImageService.swift @@ -0,0 +1,35 @@ +// +// PostProfileImageService.swift +// SeeMeet +// +// Created by 이유진 on 2022/06/29. +// + +import Foundation +import Alamofire + +struct PostProfileImageService { + + static let shared = PostProfileImageService() + + func postProfileImage(imageData: UIImage?,accessToken: String, completion: @escaping (NetworkResult) -> Void) { + + let URL = Constants.URL.postProfileImageURL + let header : HTTPHeaders = [ + "Content-Type" : "multipart/form-data", + "accesstoken" : accessToken ] + + AF.upload(multipartFormData: { multipartFormData in + if let image = imageData?.pngData() { + multipartFormData.append(image, withName: "file", fileName: "profileImage.png", mimeType: "image/png") + } + }, to: URL, usingThreshold: UInt64.init(), method: .post, headers: header).response { dataResponse in + guard let statusCode = dataResponse.response?.statusCode, + statusCode == 200 + else { return } + completion(.success(statusCode)) + } + + } + +} diff --git a/SeeMeet/SeeMeet/Source/Services/PostPushNotificationSetService.swift b/SeeMeet/SeeMeet/Source/Services/PostPushNotificationSetService.swift new file mode 100644 index 0000000..7619085 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/PostPushNotificationSetService.swift @@ -0,0 +1,61 @@ +// +// PostPushSetService.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/11. +// + +import Foundation +import Alamofire + +struct PostPushNotificationSetService { + static let shared = PostPushNotificationSetService() + + func pushSet(isNotificationOn: Bool, completion : @escaping (NetworkResult) -> Void) { + let URL = Constants.URL.postNotificationSetURL + guard let fcmToken = UserDefaults.standard.string(forKey: "fcmToken"), + let headers = TokenUtils.shared.getAuthorizationHeader() else { return } + + let parameters: Parameters = [ + "push": isNotificationOn, + "fcm": fcmToken + ] + + let dataRequest = AF.request(URL, + method: .post, + parameters: parameters, + encoding: JSONEncoding.default, + headers: headers) + + dataRequest.responseData { responseData in + dump(responseData) + switch responseData.result { + case .success(let data): + guard let statusCode = responseData.response?.statusCode else { return } + guard let value = responseData.value else { return } + + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + case .failure(let err): + print(err.localizedDescription) + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(NotificationSetResponseModel.self, from: data) else { + return .pathErr + } + switch statusCode { + case 200...399: + return .success(decodedData) + case 400...404: + print(decodedData.message) + return .requestErr(decodedData.message) + case 500: + return .serverErr + default: return .networkFail + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/PostRegisterService.swift b/SeeMeet/SeeMeet/Source/Services/PostRegisterService.swift new file mode 100644 index 0000000..8be08c2 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/PostRegisterService.swift @@ -0,0 +1,62 @@ +import Foundation +import Alamofire + +struct PostRegisterService { + + static let shared = PostRegisterService() + + private func makeParameter(email: String, + password: String, + passwordConfirm: String) -> Parameters + { + return ["email": email, "password": password, "passwordConfirm": passwordConfirm] + } + + func register(email: String, + password: String, + passwordConfirm: String, + completion : @escaping (NetworkResult) -> Void) + { + let header : HTTPHeaders = ["Content-Type": "application/json"] + let dataRequest = AF.request(Constants.URL.registerURL, + method: .post, + parameters: makeParameter(email: email, + password: password, + passwordConfirm: passwordConfirm), + encoding: JSONEncoding.default, + headers: header) + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else { return } + guard let value = dataResponse.value else { return } + + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + + } + } + + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + let decoder = JSONDecoder() + print(data) + guard let decodedData = try? decoder.decode(RegisterDataModel.self, from: data) + else { + return .pathErr + } + switch statusCode { + case 200, 404: + //404가 이제 중복된 이메일 있을때 + return .success(decodedData) + case 400: + return .requestErr(decodedData.message) + case 500: + return .serverErr + default: return .networkFail + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/PutInvitationCancelService.swift b/SeeMeet/SeeMeet/Source/Services/PutInvitationCancelService.swift new file mode 100644 index 0000000..3dd1718 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/PutInvitationCancelService.swift @@ -0,0 +1,50 @@ +import Alamofire +import Foundation + +struct PutInvitationCancelService { + static let shared = PutInvitationCancelService() + + func putInvitationCancel(plansId: String, completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.plansDetailURL(plansId) + + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + let dataRequest = AF.request(URL, + method: .put, + encoding: JSONEncoding.default, + headers: header) + + + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else {return} + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(InvitationCancelDataModel.self, from: data) + else { return .pathErr } + + return .success(decodedData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/PutInvitationListService.swift b/SeeMeet/SeeMeet/Source/Services/PutInvitationListService.swift new file mode 100644 index 0000000..f969492 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/PutInvitationListService.swift @@ -0,0 +1,56 @@ +// +// PutInvitationListService.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/10. +// + +import Alamofire +import Foundation + +struct PutInvitationListService { + static let shared = PutInvitationListService() + + func putInvitationListCancel(invitationId: String, completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.plansListURL + "/" + "\(invitationId)" + + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + let dataRequest = AF.request(URL, + method: .put, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else {return} + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(InvitationListCancelDataModel.self, from: data) + else { return .pathErr } + + return .success(decodedData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/PutPasswordService.swift b/SeeMeet/SeeMeet/Source/Services/PutPasswordService.swift new file mode 100644 index 0000000..2acb0ba --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/PutPasswordService.swift @@ -0,0 +1,51 @@ +import Alamofire +import Foundation + +struct PutPasswordService { + static let shared = PutPasswordService() + + func putPassword(password: String, completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.putPasswordUrl + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + let requestBody: Parameters = ["password": password, "passwordConfirm": password] + + let dataRequest = AF.request(URL, + method: .put, + parameters: requestBody, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else {return} + guard let value = dataResponse.value else {return} + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패 사유") + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(PasswordResponseModel.self, from: data) + + else {print("안됨"); return .pathErr } + print(decodedData) + return .success(decodedData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/PutPlanDeleteService.swift b/SeeMeet/SeeMeet/Source/Services/PutPlanDeleteService.swift new file mode 100644 index 0000000..97e4ec7 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/PutPlanDeleteService.swift @@ -0,0 +1,57 @@ +// +// PutPlanDeleteService.swift +// SeeMeet +// +// Created by 김인환 on 2022/08/09. +// + +import Alamofire +import Foundation + +struct PutPlanDeleteService { + static let shared = PutPlanDeleteService() + + func putPlanDelete(planId: String, completion : @escaping (NetworkResult) -> Void) { + // completion 클로저를 @escaping closure로 정의합니다. + let URL = Constants.URL.plansDeleteURL(planId) + let header : HTTPHeaders = TokenUtils.shared.getAuthorizationHeader() ?? ["Content-Type": "application/json"] + + let dataRequest = AF.request(URL, + method: .put, + encoding: JSONEncoding.default, + headers: header) + + dataRequest.responseData { dataResponse in + switch dataResponse.result { + case .success: + guard let statusCode = dataResponse.response?.statusCode else { return } + guard let value = dataResponse.value else { return } + let networkResult = self.judgeStatus(by: statusCode, value) + completion(networkResult) + + case .failure: completion(.pathErr) + print("실패") + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidData(data : Data) -> NetworkResult { + + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(PlansDeleteResponseModel.self, from: data) else { print("약속 삭제 실패"); return .pathErr } + if decodedData.status == 200 { + return .success(decodedData) + } else { + return .requestErr(decodedData) + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/RequestScene/GetScheduleService.swift b/SeeMeet/SeeMeet/Source/Services/RequestScene/GetScheduleService.swift new file mode 100644 index 0000000..dfcf876 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/RequestScene/GetScheduleService.swift @@ -0,0 +1,70 @@ +// +// GetScheduleService.swift +// SeeMeet +// +// Created by 이유진 on 2022/01/21. +// + +import Alamofire +import Foundation + +struct GetScheduleService { + + // MARK: - properties + + static let shared = GetScheduleService() + + private var headers: HTTPHeaders? + + // MARK: - initializer + + init() { + guard let headers = TokenUtils.shared.getAuthorizationHeader() else { return } + self.headers = headers + } + + // MARK: - Methods + + func getScheduleData(year: String, + month: String, + completion: @escaping (NetworkResult) -> Void) { + let url = Constants.URL.invitationPlanURL(year, month) + + let request = AF.request(url, + method: .get, + headers: headers) + + request.responseData { responseData in + switch responseData.result { + case .success: + if let statusCode = responseData.response?.statusCode, + let value = responseData.value { + let networkResult = judgeStatus(by: statusCode, value) + completion(networkResult) + } + case .failure(let error): + print(error.localizedDescription) + completion(.networkFail) + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200: return isValidDecodableData(data: data) + case 400: return .pathErr + case 500: return .serverErr + default: return .networkFail + } + } + + private func isValidDecodableData(data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(InvitationPlanData.self, from: data) else { return .pathErr } + if decodedData.status == 200 { + return .success(decodedData) + } else { + return .pathErr + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Services/RequestScene/PostRequestPlansService.swift b/SeeMeet/SeeMeet/Source/Services/RequestScene/PostRequestPlansService.swift new file mode 100644 index 0000000..3611b1f --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/RequestScene/PostRequestPlansService.swift @@ -0,0 +1,146 @@ +import Foundation +import Alamofire +import RxSwift + +class RequestPlansParameter { + var guests: [[String: Any]] = [] + var title: String? + var contents: String? + var date: [String] = [] + var start: [String] = [] + var end: [String] = [] + + func isAnyPropertyNotNil() -> Bool { + !guests.isEmpty + && title != nil + && contents != nil + && !date.isEmpty + && !start.isEmpty + && !end.isEmpty + } + + func removeTimeData(at index: Int) { + guard !date.isEmpty && !start.isEmpty && !end.isEmpty else { return } // 배열이 비었는지 검증한다. + + date.remove(at: index) + start.remove(at: index) + end.remove(at: index) + } + + func removeAllData() { + guests.removeAll() + title?.removeAll() + contents?.removeAll() + date.removeAll() + start.removeAll() + end.removeAll() + } +} + +struct PostRequestPlansService { + + // MARK: - properties + + static let shared = PostRequestPlansService() + + private var headers: HTTPHeaders? + + /// 싱글턴 객체로 참조할 수 있게 한다. 약속 신청에 관한 데이터는 여기로 모은다. + static let sharedParameterData = RequestPlansParameter() + + + + // MARK: - initializer + + init() { + guard let headers = TokenUtils().getAuthorizationHeader() else { return } + self.headers = headers + } + + // MARK: - methods + + func requestPlans(completion: @escaping (NetworkResult) -> Void) { + + guard PostRequestPlansService.sharedParameterData.isAnyPropertyNotNil() else { return } // 값이 모두 채워져 있는지 검증한다. + + let url = Constants.URL.invitationURL + + let requestBody: Parameters = ["guests": PostRequestPlansService.sharedParameterData.guests, + "invitationTitle": PostRequestPlansService.sharedParameterData.title ?? "", + "invitationDesc": PostRequestPlansService.sharedParameterData.contents ?? "", + "date": PostRequestPlansService.sharedParameterData.date, + "start": PostRequestPlansService.sharedParameterData.start, + "end": PostRequestPlansService.sharedParameterData.end] + + let request = AF.request(url, + method: .post, + parameters: requestBody, + encoding: JSONEncoding.default, + headers: headers) + + request.responseData { responseData in + switch responseData.result { + case .success: + if let statusCode = responseData.response?.statusCode, + let value = responseData.value { + let networkResult = judgeStatus(by: statusCode, value) + completion(networkResult) + } + case .failure(let error): + print(error.localizedDescription) + completion(.networkFail) + } + } + } + + func requestPlans(guests: [[String: Any]], title: String, contents: String, date: [String],start: [String], end:[String], + completion: @escaping (NetworkResult) -> Void) { + let url = Constants.URL.invitationURL + + let requestBody: Parameters = ["guests": guests, + "invitationTitle": title, + "invitationDesc": contents, + "date": date, + "start": start, + "end": end] + + let request = AF.request(url, + method: .post, + parameters: requestBody, + encoding: JSONEncoding.default, + headers: headers) + + request.responseData { responseData in + switch responseData.result { + case .success: + if let statusCode = responseData.response?.statusCode, + let value = responseData.value { + let networkResult = judgeStatus(by: statusCode, value) + completion(networkResult) + } + case .failure(let error): + print(error.localizedDescription) + completion(.networkFail) + } + } + } + + private func judgeStatus(by statusCode: Int, _ data: Data) -> NetworkResult { + switch statusCode { + case 200, 400, 404: return isValidDecodableData(data: data) + case 500: return .serverErr + default: return.networkFail + } + } + + private func isValidDecodableData(data: Data) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(PlanRequestData.self, from: data) else { return .pathErr } + if decodedData.status == 200 { + return .success(decodedData) + } else { + return .requestErr(decodedData) + } + } +} + diff --git a/SeeMeet/SeeMeet/Source/Services/TokenUtils.swift b/SeeMeet/SeeMeet/Source/Services/TokenUtils.swift new file mode 100644 index 0000000..f79ac0f --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Services/TokenUtils.swift @@ -0,0 +1,79 @@ +import Security +import Alamofire + +class TokenUtils { + + // 간편한 호출을 위한 싱글턴 객체 + static let shared = TokenUtils() + + private let serviceIdentifier: String = Bundle.main.bundleIdentifier ?? "SeeMeet.iOS" + + // Create + func create(account: String, value: String) { + + // 1. query작성 + let keyChainQuery: NSDictionary = [ + kSecClass : kSecClassGenericPassword, + kSecAttrService: serviceIdentifier, + kSecAttrAccount: account, + kSecValueData: value.data(using: .utf8, allowLossyConversion: false)! + ] + // allowLossyConversion은 인코딩 과정에서 손실이 되는 것을 허용할 것인지 설정 + + // 2. Delete + // Key Chain은 Key값에 중복이 생기면 저장할 수 없기때문에 먼저 Delete + SecItemDelete(keyChainQuery) + + // 3. Create + let status: OSStatus = SecItemAdd(keyChainQuery, nil) + assert(status == noErr, "failed to saving Token") + } + + // Read + func read(account: String) -> String? { + let KeyChainQuery: NSDictionary = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: serviceIdentifier, + kSecAttrAccount: account, + kSecReturnData: kCFBooleanTrue, // CFData타입으로 불러오라는 의미 + kSecMatchLimit: kSecMatchLimitOne // 중복되는 경우 하나의 값만 가져오라는 의미 + ] + // CFData 타입 -> AnyObject로 받고, Data로 타입변환해서 사용하면됨 + + // Read + var dataTypeRef: AnyObject? + let status = SecItemCopyMatching(KeyChainQuery, &dataTypeRef) + + // Read 성공 및 실패한 경우 + if(status == errSecSuccess) { + let retrievedData = dataTypeRef as! Data + let value = String(data: retrievedData, encoding: String.Encoding.utf8) + return value + } else { + print("failed to loading, status code = \(status)") + return nil + } + } + + // Delete + func delete(account: String) { + let keyChainQuery: NSDictionary = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: serviceIdentifier, + kSecAttrAccount: account + ] + + let status = SecItemDelete(keyChainQuery) + assert(status == noErr, "failed to delete the value, status code = \(status)") + } + + // HTTPHeaders 구성 + func getAuthorizationHeader() -> HTTPHeaders? { + if let accessToken = self.read(account: "accessToken") { + return ["accesstoken" : "\(accessToken)" , "Content-Type": "application/json"] as HTTPHeaders + } else { + return nil + } + } +} + diff --git a/SeeMeet/SeeMeet/Source/Support/AppDelegate.swift b/SeeMeet/SeeMeet/Source/Support/AppDelegate.swift new file mode 100644 index 0000000..f11f914 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Support/AppDelegate.swift @@ -0,0 +1,154 @@ +import AuthenticationServices +import UIKit +import Firebase +import KakaoSDKCommon +import UserNotifications +import FirebaseCore +import FirebaseMessaging + + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + sleep(1) + KakaoSDK.initSDK(appKey: "") + FirebaseApp.configure() + removeKeychainAtFirstLaunch() + + if UserDefaults.standard.bool(forKey: Constants.UserDefaultsKey.isLogin) { + if UserDefaults.standard.bool(forKey: Constants.UserDefaultsKey.isAppleLogin) { + // 애플 로그인으로 연동되어 있을 때, -> 애플 ID와의 연동상태 확인 로직 + let appleIDProvider = ASAuthorizationAppleIDProvider() + appleIDProvider.getCredentialState(forUserID: UserDefaults.standard.string(forKey: Constants.UserDefaultsKey.userAppleID) ?? "") { (credentialState, error) in + switch credentialState { + case .authorized: + print("해당 ID는 연동되어있습니다.") + UserDefaults.standard.set(true, forKey: Constants.UserDefaultsKey.isLogin) + case .revoked: + print("해당 ID는 연동되어있지않습니다.") + UserDefaults.standard.set(false, forKey: Constants.UserDefaultsKey.isLogin) + case .notFound: + print("해당 ID를 찾을 수 없습니다.") + UserDefaults.standard.set(false, forKey: Constants.UserDefaultsKey.isLogin) + default: + break + } + } + } + + PostRefreshAccessTokenService.shared.tokenRefresh { response in + switch response { + case .success(let data): + guard let tokenData = data as? TokenRefreshData else { return } + TokenUtils.shared.create(account: "accessToken", value: tokenData.accessToken) + TokenUtils.shared.create(account: "refreshToken", value: tokenData.refreshToken) + case .requestErr: + UserDefaults.deleteUserValue() // 만료시켜서 재 로그인 유도 + NotificationCenter.default.post(name: NSNotification.Name.DidLogout, object: nil) // 마이페이지 뷰에서 받는다 + case .serverErr: + print("serverError") + case .networkFail: + print("networkFail") + case .pathErr: + print("path error: tokenRefresh") + } + } + } + + // 앱 실행 중 애플 ID 강제로 연결 취소 시 + NotificationCenter.default.addObserver(forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification, object: nil, queue: nil) { (Notification) in + print("Revoked Notification") + UserDefaults.standard.set(false, forKey: Constants.UserDefaultsKey.isLogin) + } + + // Register for remote notifications. + if #available(iOS 10.0, *) { + // For iOS 10 display notification (sent via APNS) + UNUserNotificationCenter.current().delegate = self + + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { granted, error in + PostPushNotificationSetService.shared.pushSet(isNotificationOn: granted) { result in + print("푸시 알림 설정 완료") + NotificationCenter.default.post(name: NSNotification.Name.PushNotificationDidSet, object: granted) + UserDefaults.standard.set(granted, forKey: Constants.UserDefaultsKey.isPushNotificationOn) + } + } + } else { + let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) + application.registerUserNotificationSettings(settings) + } + Messaging.messaging().delegate = self + application.registerForRemoteNotifications() + + return true + } + + private func removeKeychainAtFirstLaunch() { + guard UserDefaults.isFirstLaunch() else { return } + TokenUtils.shared.delete(account: "accessToken") + TokenUtils.shared.delete(account: "refreshToken") + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { + + // 세로방향 고정 + return UIInterfaceOrientationMask.portrait + } + + +} + +extension AppDelegate: UNUserNotificationCenterDelegate { + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + Messaging.messaging().apnsToken = deviceToken + } + + func userNotificationCenter(_ center: UNUserNotificationCenter,willPresent notification: UNNotification,withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler([.alert, .badge, .sound]) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter,didReceive response: UNNotificationResponse,withCompletionHandler completionHandler: @escaping () -> Void) { + if let window = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window { + if let rootViewController = window.rootViewController { + if let tabBarController = rootViewController as? TabbarVC { + tabBarController.selectedIndex = 1 + if let navigationVC = tabBarController.selectedViewController as? UINavigationController, + let calendarVC = navigationVC.topViewController as? CalendarVC { + calendarVC.calendar.select(Date().nextDate()) + calendarVC.displayPlansCollectionView(at: Date().nextDate()) + } + window.rootViewController = tabBarController + window.makeKeyAndVisible() + } + } + } + + completionHandler() + } +} + +extension AppDelegate: MessagingDelegate { + func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { + //서버로 보낼 fcmToken값 + print("FCM Token: \(fcmToken)") + guard let fcmToken = fcmToken else { return } + + UserDefaults.standard.set(fcmToken, forKey: "fcmToken") //우선 userdefaults에 담아두고 로그인시 꺼내서 사용 + } +} diff --git a/SeeMeet/SeeMeet/Source/Support/SceneDelegate.swift b/SeeMeet/SeeMeet/Source/Support/SceneDelegate.swift new file mode 100644 index 0000000..87af71c --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Support/SceneDelegate.swift @@ -0,0 +1,56 @@ +import UIKit +import KakaoSDKAuth + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = (scene as? UIWindowScene) else { return } + let window = UIWindow(windowScene: windowScene) + self.window = window + + let coordinator = AppCoordinator(window) + coordinator.start() + } + + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + if let url = URLContexts.first?.url { + if AuthApi.isKakaoTalkLoginUrl(url) { + _ = AuthController.handleOpenUrl(url: url) + } + } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/CVC/CalendarScene/CalendarPlansCVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/CalendarScene/CalendarPlansCVC.swift new file mode 100644 index 0000000..4f89487 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/CalendarScene/CalendarPlansCVC.swift @@ -0,0 +1,157 @@ +import UIKit + +class CalendarPlansCVC: UICollectionViewCell { + + // MARK: - properties + + static let identifier: String = "CalendarPlansCVC" + + var isSchedule: Bool = false { + didSet { + if isSchedule { + nameLabelStackView = nil + headerView.backgroundColor = UIColor.grey04 + } + } + } + + private let headerView: UIView = UIView().then { + $0.layer.cornerRadius = CGFloat(10.0) + $0.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] + $0.backgroundColor = UIColor.grey06 + } + + let headerTitle: UILabel = UILabel().then { + $0.textColor = .white + $0.font = UIFont.hanSansBoldFont(ofSize: 14) + $0.lineBreakMode = .byTruncatingTail + } + + let hourLabel: UILabel = UILabel().then { + $0.textColor = .grey06 + $0.font = UIFont.dinProRegularFont(ofSize: 14) + $0.text = "오전 11:00" + } + + private lazy var nameLabelStackView: UIStackView? = UIStackView().then { + $0.axis = .horizontal + $0.alignment = .fill + $0.distribution = .fillEqually + $0.spacing = 6 * widthRatio + $0.layoutMargins = UIEdgeInsets(top: 10 * heightRatio, left: 0, bottom: 10 * heightRatio, right: 0) + $0.isLayoutMarginsRelativeArrangement = true + } + + var namesToShow: [String]? { + didSet { + setNameStackViewLayout() + } + } + + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + getShadowView(color: UIColor.black.cgColor, masksToBounds: false, shadowOffset: CGSize(width: 0, height: 4), shadowRadius: 3, shadowOpacity: 0.25) + setBaseViewLayouts() + setContentViewLayouts() + } + + override init(frame: CGRect) { + super.init(frame: frame) + getShadowView(color: UIColor.black.cgColor, masksToBounds: false, shadowOffset: CGSize(width: 0, height: 4), shadowRadius: 3, shadowOpacity: 0.25) + setBaseViewLayouts() + setContentViewLayouts() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + getShadowView(color: UIColor.black.cgColor, masksToBounds: false, shadowOffset: CGSize(width: 0, height: 4), shadowRadius: 3, shadowOpacity: 0.25) + setBaseViewLayouts() + setContentViewLayouts() + } + + override func prepareForReuse() { + namesToShow?.removeAll() + } + + // MARK: - layout + + private func setBaseViewLayouts() { + //shadow 추가 필요 + layer.cornerRadius = CGFloat(10.0) + backgroundColor = UIColor.white + + addSubview(headerView) + headerView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(52 * heightRatio) + } + + headerView.addSubview(headerTitle) + headerTitle.snp.makeConstraints { + $0.leading.equalToSuperview().offset(22 * widthRatio) + $0.top.equalToSuperview().offset(17 * heightRatio) + $0.trailing.equalToSuperview().offset(-12 * widthRatio) + } + } + + private func setContentViewLayouts() { + + addSubview(hourLabel) + hourLabel.snp.makeConstraints { + if isSchedule { + $0.leading.equalToSuperview().offset(37 * widthRatio) + $0.top.equalTo(headerView.snp.bottom).offset(31 * heightRatio) + } else { + $0.leading.equalToSuperview().offset(22 * widthRatio) + $0.top.equalTo(headerView.snp.bottom).offset(17 * heightRatio) + } + } + + if isSchedule { + hourLabel.font = UIFont.dinProRegularFont(ofSize: 16) + } + else { + + guard let nameLabelStackView = nameLabelStackView else { return } + + addSubview(nameLabelStackView) + nameLabelStackView.snp.makeConstraints { + $0.leading.equalToSuperview().offset(22 * widthRatio) + $0.top.equalTo(hourLabel.snp.bottom).offset(4 * heightRatio) + $0.width.equalTo(62 * widthRatio) + $0.height.equalTo(45 * heightRatio) + } + } + } + + func setNameStackViewLayout() { + + guard let nameLabelStackView = nameLabelStackView else { return } + + nameLabelStackView.snp.updateConstraints { + $0.width.equalTo(56 * widthRatio * CGFloat(namesToShow?.count ?? 0) + 6 * CGFloat((namesToShow?.count ?? 1) - 1)) + } + + nameLabelStackView.arrangedSubviews.forEach { // 표시 데이터 초기화 안하면 쌓임쌓임 + $0.removeFromSuperview() + } + + namesToShow?.forEach { + let nameLabel: UILabel = UILabel() + nameLabel.font = UIFont.hanSansMediumFont(ofSize: 12) + nameLabel.textColor = UIColor.pink01 + nameLabel.text = $0 + nameLabel.sizeToFit() + nameLabel.clipsToBounds = true + nameLabel.layer.cornerRadius = 13 * heightRatio + nameLabel.layer.borderWidth = 1 + nameLabel.layer.borderColor = UIColor.pink01.cgColor + nameLabel.textAlignment = .center + nameLabelStackView.addArrangedSubview(nameLabel) + } + setNeedsLayout() + layoutIfNeeded() + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/CVC/HomeScene/HomeEventCVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/HomeScene/HomeEventCVC.swift new file mode 100644 index 0000000..2ac1563 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/HomeScene/HomeEventCVC.swift @@ -0,0 +1,100 @@ +// +// HomeEventCVC.swift +// SeeMeet +// +// Created by 박익범 on 2022/01/08. +// + +import UIKit +import SnapKit +import Then + +class HomeEventCVC: UICollectionViewCell { + + static let identifier: String = "HomeEventCVC" + var userHeight: CGFloat = UIScreen.getDeviceHeight() - 88 + +//MARK: Componnents + private let dDayView = UIView().then{ + $0.backgroundColor = UIColor.pink01 + $0.clipsToBounds = true + $0.layer.cornerRadius = 14 + } + private let dDayLabel = UILabel().then{ + $0.textColor = UIColor.white + $0.textAlignment = .center + } + private let eventImageView = UIImageView().then{ + $0.image = UIImage(named: "Ellipse_dummy") + } + private let eventNameLabel = UILabel().then{ + $0.font = UIFont.hanSansBoldFont(ofSize: 14) + $0.textColor = UIColor.grey06 + $0.textAlignment = .center + } + private let eventDateLabel = UILabel().then{ + $0.font = UIFont.hanSansRegularFont(ofSize: 14) + $0.textColor = UIColor.grey06 + $0.textAlignment = .center + } +//MARK: Layout + func setBackgroundViewLayout() { + getShadowView(color: UIColor.black.cgColor, masksToBounds: false, shadowOffset: CGSize(width: 0, height: 4), shadowRadius: 3, shadowOpacity: 0.25) + contentView.clipsToBounds = true + contentView.backgroundColor = UIColor.white + contentView.layer.cornerRadius = 18 + } + func setLayout() { + addSubviews([dDayView, eventImageView, eventNameLabel, eventDateLabel]) + dDayView.addSubview(dDayLabel) + let cellRatio = userHeight / 724 + + dDayView.snp.makeConstraints{ + $0.top.equalToSuperview().offset(11 * cellRatio) + $0.width.equalTo(53 * cellRatio) + $0.height.equalTo(29 * cellRatio) + $0.centerX.equalToSuperview() + dDayView.layer.cornerRadius = 14 * cellRatio + } + dDayLabel.snp.makeConstraints{ + $0.centerX.equalToSuperview() + $0.centerY.equalToSuperview() + dDayLabel.font = UIFont.dinProBoldFont(ofSize: 14 * cellRatio) + } + eventImageView.snp.makeConstraints{ + $0.top.equalTo(dDayView.snp.bottom).offset(9 * cellRatio) + $0.width.height.equalTo(82 * cellRatio) + $0.centerX.equalToSuperview() + } + + eventNameLabel.snp.makeConstraints{ + $0.top.equalTo(eventImageView.snp.bottom).offset(16 * cellRatio) + $0.leading.equalToSuperview().offset(19 * cellRatio) + $0.trailing.equalToSuperview().offset(-19 * cellRatio) + $0.height.equalTo(18 * cellRatio) + } + eventDateLabel.snp.makeConstraints{ + $0.top.equalTo(eventNameLabel.snp.bottom).offset(8 * cellRatio) + $0.leading.equalToSuperview().offset(10 * cellRatio) + $0.trailing.equalToSuperview().offset(-10 * cellRatio) + $0.height.equalTo(18 * cellRatio) + } + + } + +//MARK: Function + func setData(dDay: String, image: String, eventName: String, eventData: String){ + dDayLabel.text = dDay + eventImageView.image = UIImage(named: image) + eventNameLabel.text = eventName + eventDateLabel.text = eventData + } + + override func awakeFromNib() { + super.awakeFromNib() + setBackgroundViewLayout() + setLayout() + // Initialization code + } + +} diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/CompletePlansCVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/CompletePlansCVC.swift new file mode 100644 index 0000000..7832e49 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/CompletePlansCVC.swift @@ -0,0 +1,155 @@ +import UIKit + +class CompletePlansCVC: UICollectionViewCell { + + //MARK: - UI Components + + private let dateAgoLabel = UILabel().then { + $0.font = UIFont.dinProMediumFont(ofSize: 14) + $0.textColor = UIColor.grey04 + } + + private let cancelPlansButton = UIButton().then { + $0.setTitleColor(UIColor.pink01, for: .normal) + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.setAttributedTitle(String.getAttributedText(text: "약속 취소", letterSpacing: -0.6, lineSpacing: nil), for: .normal) + } + + let closeButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_close"), for: .normal) + } + + private let planNameLabel = UILabel().then { + $0.font = UIFont.hanSansBoldFont(ofSize: 18) + $0.attributedText = String.getAttributedText(text: "강화도 여행", letterSpacing: -0.6, lineSpacing: nil) + $0.textColor = UIColor.black + } + + private let plansNameButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_plansName"), for: .normal) + } + + private let nameButtonStackView = UIStackView().then { + $0.axis = .horizontal + $0.alignment = .fill + $0.distribution = .fillEqually + $0.spacing = 6 + $0.isLayoutMarginsRelativeArrangement = true + } + + private let bottomView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + // MARK: - Properties + static let identifier: String = "CompletePlansCVC" + + var namesToShow: [String: Bool]? { + didSet { + setNameButtonStack() + } + } + + var isCanceled: Bool = false { + willSet { + cancelPlansButton.setAttributedTitle(String.getAttributedText(text: newValue ? "약속 취소" : "약속 확정", letterSpacing: -0.6, lineSpacing: nil), for: .normal) + } + } + var dayAgoText: String? { + willSet { + dateAgoLabel.attributedText = String.getAttributedText(text: newValue ?? "" + "일전", letterSpacing: -0.6, lineSpacing: nil) + } + } + var planTitle: String? { + willSet { + planNameLabel.attributedText = String.getAttributedText(text: newValue ?? "", letterSpacing: -0.6, lineSpacing: nil) + } + } + + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + setupAutoLayouts() + } + + override func prepareForReuse() { + nameButtonStackView.removeAllSubViews() + } + + // MARK: - Layout + + private func setupAutoLayouts() { + addSubviews([dateAgoLabel, cancelPlansButton, planNameLabel, plansNameButton, nameButtonStackView, bottomView, closeButton]) + + dateAgoLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(6 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + + cancelPlansButton.snp.makeConstraints { + $0.centerY.equalTo(dateAgoLabel) + $0.leading.equalTo(dateAgoLabel.snp.trailing).offset(6 * widthRatio) + } + + closeButton.snp.makeConstraints { + $0.top.equalToSuperview() + $0.trailing.equalToSuperview() + } + + planNameLabel.snp.makeConstraints { + $0.top.equalTo(cancelPlansButton.snp.bottom).offset(5 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + + plansNameButton.snp.makeConstraints{ + $0.top.equalTo(cancelPlansButton.snp.bottom).offset(5 * heightRatio) + $0.leading.equalTo(planNameLabel.snp.trailing) + } + + nameButtonStackView.snp.makeConstraints{ + $0.top.equalTo(planNameLabel.snp.bottom).offset(11 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.height.equalTo(26 * heightRatio) + } + + bottomView.snp.makeConstraints{ + $0.bottom.equalToSuperview().offset(-8 * heightRatio) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(1 * heightRatio) + } + } + + // MARK: - Custom Methods + + private func setNameButtonStack() { + namesToShow?.forEach { (key, value) in + + let nameLabel = UILabel() + nameLabel.font = UIFont.hanSansMediumFont(ofSize: 14) + nameLabel.text = key + nameLabel.textColor = value ? UIColor.grey06 : UIColor.grey04 + nameLabel.backgroundColor = value ? UIColor.grey02 : UIColor.white + nameLabel.clipsToBounds = true + nameLabel.layer.borderWidth = 1 + nameLabel.layer.borderColor = UIColor.grey02.cgColor + nameLabel.layer.cornerRadius = 26 * heightRatio / 2 + nameLabel.lineBreakMode = .byTruncatingTail + nameLabel.textAlignment = .center + + nameLabel.numberOfLines = 1 + nameLabel.snp.makeConstraints{ + $0.width.equalTo(63 * widthRatio) + } + nameButtonStackView.addArrangedSubview(nameLabel) + + } + } + + func setData(namesToShow: [String: Bool], dayAgoText: String, planName: String, isCanceled: Bool) { + self.namesToShow = namesToShow + self.isCanceled = isCanceled + self.dayAgoText = dayAgoText + planTitle = planName + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/PlansReceiveCVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/PlansReceiveCVC.swift new file mode 100644 index 0000000..f058b77 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/PlansReceiveCVC.swift @@ -0,0 +1,154 @@ +import UIKit +import SnapKit +import Then + + +protocol PlansReceiveCVCDelegate { + func plansReceiveCVCDidTap(cell: PlansReceiveCVC) +} + +class PlansReceiveCVC: UICollectionViewCell { + + // MARK: - UI Components + + private let titleLabel = UILabel().then { + $0.font = UIFont.hanSansBoldFont(ofSize: 16) + $0.textColor = UIColor.grey06 + } + + private let bottomView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private let sideView = UIView().then { + $0.backgroundColor = UIColor.grey06 + $0.clipsToBounds = true + $0.layer.cornerRadius = 12 + $0.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner] + } + + private let timeLabel = UILabel().then { + $0.font = UIFont.dinProRegularFont(ofSize: 13) + $0.textColor = UIColor.grey06 + $0.text = "오전 11:00 - 오후 2:00" + } + + private let nameTagButtonStackView = UIStackView().then { + $0.axis = .horizontal + $0.alignment = .fill + $0.distribution = .fillEqually + $0.spacing = 6 * widthRatio + $0.layoutMargins = UIEdgeInsets(top: 4*heightRatio , left: 0 , bottom: 4*heightRatio, right: 10*widthRatio) + $0.isLayoutMarginsRelativeArrangement = true + } + + // MARK: - Properties + + static let identifier: String = "PlansReceiveCVC" + + var delegate: PlansReceiveCVCDelegate? + + var namesToShow: [String]? + + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + setupAutoLayouts() + setGestureRecognizer() + } + + override func prepareForReuse() { + nameTagButtonStackView.removeAllSubViews() + } + + // MARK: - Layout + + private func setupAutoLayouts() { + addSubviews([titleLabel, bottomView, sideView, timeLabel, nameTagButtonStackView]) + + contentView.clipsToBounds = false + contentView.layer.cornerRadius = 10 + contentView.backgroundColor = UIColor.white + getShadowView(color: UIColor.black.cgColor, masksToBounds: false, shadowOffset: CGSize(width: 0, height: 0), shadowRadius: 1, shadowOpacity: 0.1) + + sideView.snp.makeConstraints { + $0.leading.top.bottom.equalToSuperview() + $0.width.equalTo(12 * widthRatio) + } + + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(15 * heightRatio) + $0.leading.equalTo(sideView.snp.trailing).offset(15 * widthRatio) + $0.height.equalTo(20 * heightRatio) + } + + bottomView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(13 * heightRatio) + $0.leading.equalTo(sideView.snp.trailing).offset(14 * widthRatio) + $0.trailing.equalToSuperview().offset(-16 * widthRatio) + $0.height.equalTo(1 * heightRatio) + } + + timeLabel.snp.makeConstraints { + $0.top.equalTo(bottomView.snp.bottom).offset(11 * heightRatio) + $0.leading.equalTo(sideView.snp.trailing).offset(15 * widthRatio) + $0.height.equalTo(17 * heightRatio) + } + nameTagButtonStackView.snp.makeConstraints { + $0.top.equalTo(timeLabel.snp.bottom).offset(12 * heightRatio) + $0.leading.equalTo(sideView.snp.trailing).offset(15 * widthRatio) + $0.width.equalTo(180 * widthRatio) + $0.height.equalTo(32 * heightRatio) + } + + } + + private func setStackButton() { + guard let namesToShow = namesToShow else { return } + + nameTagButtonStackView.snp.updateConstraints { + $0.width.equalTo(60 * widthRatio * CGFloat(namesToShow.count ?? 0) + 6 * CGFloat((namesToShow.count ?? 1) - 1)) + } + + nameTagButtonStackView.arrangedSubviews.forEach { + $0.removeFromSuperview() + } + + namesToShow.forEach { name in + let nameButton: UIButton = UIButton().then { + $0.titleLabel?.font = UIFont.hanSansRegularFont(ofSize: 13) + $0.setTitle(name, for: .normal) + $0.setTitleColor(UIColor.pink01, for: .normal) + $0.backgroundColor = UIColor.white + $0.clipsToBounds = true + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor.pink01.cgColor + $0.layer.cornerRadius = 12 + } + nameTagButtonStackView.addArrangedSubview(nameButton) + } + setNeedsLayout() + } + + // MARK: - Custom Methods + + private func setGestureRecognizer() { + let gesture = UITapGestureRecognizer(target: self, action: #selector(didTap(_:))) + addGestureRecognizer(gesture) + } + + func setData(title: String, time: String, namesToShow: [String]) { + titleLabel.attributedText = String.getAttributedText(text: title, letterSpacing: -0.6, lineSpacing: nil) + timeLabel.attributedText = String.getAttributedText(text: time, letterSpacing: -0.6, lineSpacing: nil) + + self.namesToShow = namesToShow + setStackButton() + } + + // MARK: - Actions + + @objc private func didTap(_ sender: UITapGestureRecognizer) { + delegate?.plansReceiveCVCDidTap(cell: self) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/ProgressReceiveCVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/ProgressReceiveCVC.swift new file mode 100644 index 0000000..cac155b --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/ProgressReceiveCVC.swift @@ -0,0 +1,110 @@ +import UIKit +import SnapKit +import Then + + +class ProgressReceiveCVC: UICollectionViewCell { + + // MARK: - UI Components + + private let cellHeadLabel = UILabel().then { + $0.font = UIFont.hanSansBoldFont(ofSize: 18) + $0.textColor = UIColor.black + $0.textAlignment = .center + $0.text = "받은 요청" + } + + private let dateAgoLabel = UILabel().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 13) + $0.textColor = UIColor.grey04 + } + + private let nameLabel = UILabel().then { + $0.backgroundColor = UIColor.black + $0.clipsToBounds = true + $0.layer.cornerRadius = 26 * heightRatio / 2 + $0.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.textColor = UIColor.white + $0.lineBreakMode = .byTruncatingTail + $0.textAlignment = .center + $0.numberOfLines = 1 + + } + + private let receiveLabel = UILabel().then { + $0.text = "친구의 요청에 답해보세요!" + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + } + + // MARK: - Properties + + static let identifier: String = "ProgressReceiveCVC" + + var hostNameText: String = "" + var dayAgoText: String = "" + + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + setupAutoLayout() + setButtonTitle() + } + + // MARK: - Layout + + func setupAutoLayout() { + addSubviews([cellHeadLabel, dateAgoLabel, nameLabel, receiveLabel]) + + getShadowView(color: UIColor.grey04.cgColor, masksToBounds: false, shadowOffset: CGSize(width: 0, height: 0), shadowRadius: 3, shadowOpacity: 0.4) + contentView.clipsToBounds = true + contentView.layer.cornerRadius = 10 + contentView.backgroundColor = UIColor.white + + cellHeadLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(18 * heightRatio) + $0.leading.equalToSuperview().offset(24 * widthRatio) + } + + dateAgoLabel.snp.makeConstraints { + $0.centerY.equalTo(cellHeadLabel) + $0.trailing.equalToSuperview().offset(-24 * widthRatio) + } + + nameLabel.snp.makeConstraints { + $0.top.equalTo(cellHeadLabel.snp.bottom).offset(12 * heightRatio) + $0.leading.equalToSuperview().offset(24 * widthRatio) + $0.width.equalTo(63 * widthRatio) + $0.height.equalTo(26 * heightRatio) + } + + receiveLabel.snp.makeConstraints { + $0.top.equalTo(nameLabel.snp.bottom).offset(8 * heightRatio) + $0.leading.equalToSuperview().offset(24 * widthRatio) + } + + } + + // MARK: - Custom Methods + + private func setButtonTitle() { + nameLabel.text = hostNameText + if dayAgoText == "0" { + dateAgoLabel.setAttributedText(defaultText: "방금 전", + font: UIFont.hanSansRegularFont(ofSize: 13), + color: UIColor.grey04, + kernValue: -0.6) + } else { + dateAgoLabel.setAttributedText(defaultText: dayAgoText + "일전", + font: UIFont.hanSansRegularFont(ofSize: 13), + color: UIColor.grey04, + kernValue: -0.6) + } + } + + func setData(dayAgo: String, hostName: String) { + hostNameText = hostName + dayAgoText = dayAgo + setButtonTitle() + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/ProgressSendCVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/ProgressSendCVC.swift new file mode 100644 index 0000000..bed2bc7 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/PlansScene/ProgressSendCVC.swift @@ -0,0 +1,123 @@ +import UIKit + +class ProgressSendCVC: UICollectionViewCell { + + // MARK: - UI Component + + private let cellHeadLabel = UILabel().then { + $0.font = UIFont.hanSansBoldFont(ofSize: 18) + $0.textColor = UIColor.pink01 + $0.textAlignment = .center + $0.text = "보낸 요청" + } + + private let dateAgoLabel = UILabel().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 13) + $0.textColor = UIColor.grey04 + $0.text = "1일 전" + } + + private let sendLabel = UILabel().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.textColor = UIColor.black + } + + private let nameTagButtonStackView = UIStackView().then { + $0.axis = .horizontal + $0.alignment = .fill + $0.distribution = .fillProportionally + $0.spacing = 8 + } + + // MARK: - Properties + + static let identifier: String = "ProgressSendCVC" + + var dateAgoText: String? + var namesToShow: [String: Bool]? + + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + setupAutoLayout() + } + + override func prepareForReuse() { + nameTagButtonStackView.removeAllSubViews() + } + + //MARK: - Layout + + private func setupAutoLayout() { + addSubviews([cellHeadLabel, dateAgoLabel, nameTagButtonStackView, sendLabel]) + + getShadowView(color: UIColor.grey04.cgColor, masksToBounds: false, shadowOffset: CGSize.zero, shadowRadius: 3, shadowOpacity: 0.4) + contentView.clipsToBounds = true + contentView.layer.cornerRadius = 10 + contentView.backgroundColor = UIColor.white + + cellHeadLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(18 * heightRatio) + $0.leading.equalToSuperview().offset(24 * widthRatio) + } + + dateAgoLabel.snp.makeConstraints { + $0.centerY.equalTo(cellHeadLabel) + $0.trailing.equalToSuperview().offset(-24 * widthRatio) + } + + sendLabel.snp.makeConstraints { + $0.top.equalTo(cellHeadLabel.snp.bottom).offset(8 * heightRatio) + $0.leading.equalToSuperview().offset(24 * widthRatio) + } + + nameTagButtonStackView.snp.makeConstraints { + $0.top.equalTo(sendLabel.snp.bottom).offset(6 * heightRatio) + $0.leading.equalToSuperview().offset(24 * widthRatio) + $0.height.equalTo(26 * heightRatio) + } + } + + // MARK: - Custom Method + + func setData(dateAgo: String, namesToShow: [String: Bool]) { // 외부로부터 데이터를 입력받는 메서드 + dateAgo == "0" ? dateAgoLabel.setAttributedText(defaultText: "방금 전", kernValue: -0.6) : dateAgoLabel.setAttributedText(defaultText: dateAgo + "일전", kernValue: -0.6) + + let waitCount = namesToShow.values.filter { $0 == false }.count + sendLabel.setAttributedText(defaultText: waitCount != 0 + ? "친구 \(waitCount)명의 답변을 기다리고 있어요!" + : "친구가 답변을 모두 완료하였어요!", + containText: "\(waitCount)", + font: UIFont.hanSansBoldFont(ofSize: 16), + color: UIColor.pink01) + + self.namesToShow = namesToShow + setNameButtonStack() + } + + private func setNameButtonStack() { + namesToShow?.forEach { (key, value) in + let nameLabel = UILabel() + nameLabel.font = UIFont.hanSansMediumFont(ofSize: 14) + nameLabel.text = key + nameLabel.textColor = value ? UIColor.white : UIColor.pink01 + nameLabel.backgroundColor = value ? UIColor.pink01 : UIColor.white + nameLabel.clipsToBounds = true + nameLabel.layer.borderWidth = 1 + nameLabel.layer.borderColor = UIColor.pink01.cgColor + nameLabel.layer.cornerRadius = 26 * heightRatio / 2 + nameLabel.lineBreakMode = .byTruncatingTail + nameLabel.textAlignment = .center + + nameLabel.numberOfLines = 1 + nameLabel.snp.makeConstraints{ + $0.width.equalTo(63 * widthRatio) + } + if key == "" { + nameLabel.layer.borderWidth = 0 + } + nameTagButtonStackView.addArrangedSubview(nameLabel) + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/CVC/RequestScene/SearchFieldTokenCVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/RequestScene/SearchFieldTokenCVC.swift new file mode 100644 index 0000000..ae1d31b --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/RequestScene/SearchFieldTokenCVC.swift @@ -0,0 +1,102 @@ +// +// ChipView.swift +// SeeMeet +// +// Created by 이유진 on 2022/01/15. +// + +import UIKit +import Then +import SnapKit +import RxSwift +import RxCocoa + +protocol SearchFieldTokenCVCDelegate{ + func removeButtonTap(cell: SearchFieldTokenCVC) +} + +class SearchFieldTokenCVC: UICollectionViewCell { + + // MARK: - UI Components + + private let nameLabel = UILabel().then { + $0.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.textColor = UIColor.white + $0.numberOfLines = 1 + $0.lineBreakMode = .byTruncatingTail + } + + let removeButton = UIButton().then { + $0.setImage(UIImage(named: "property1White"), for: .normal) + } + + // MARK: - Properties + + static let identifier = "SearchFieldTokenCVC" + + let disposeBag = DisposeBag() + + var friendsData = FriendsData() + var delegate: SearchFieldTokenCVCDelegate? + + // MARK: - Life Cycle + + override init(frame: CGRect) { + super.init(frame: frame) + configUI() + setLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Layout + + private func configUI() { + backgroundColor = UIColor.pink01 + layer.cornerRadius = 13 * heightRatio + nameLabel.text = friendsData.username + + removeButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + guard let self = self else { return } + self.delegate?.removeButtonTap(cell: self) + }) + .disposed(by: disposeBag) + } + + private func setLayout() { + addSubviews([nameLabel,removeButton]) + + self.snp.makeConstraints({ + $0.width.equalTo(82 * widthRatio) + $0.height.equalTo(26 * heightRatio) + }) + + removeButton.snp.makeConstraints{ + $0.trailing.equalToSuperview().offset(15 * widthRatio) + $0.centerY.equalToSuperview() + } + + nameLabel.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(13 * widthRatio) + $0.centerY.equalToSuperview() + $0.width.equalTo(39 * widthRatio) + } + } + + // MARK: - Custom Methods + + func setFriendsData(friendsData: FriendsData) { + self.friendsData = friendsData + self.nameLabel.text = friendsData.username + + if friendsData.username.count > 3 { + nameLabel.snp.updateConstraints { + $0.width.equalTo(53 * widthRatio) + } + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/CVC/RequestScene/SearchInputFieldCVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/RequestScene/SearchInputFieldCVC.swift new file mode 100644 index 0000000..3fb54ef --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/CVC/RequestScene/SearchInputFieldCVC.swift @@ -0,0 +1,44 @@ +// +// SearchInputFieldCVC.swift +// SeeMeet +// +// Created by 김인환 on 2022/07/08. +// + +import UIKit +import SnapKit + +class SearchInputFieldCVC: UICollectionViewCell { + + // MARK: - UI Components + + let inputField = UITextField().then { + $0.backgroundColor = .clear + $0.font = UIFont.hanSansRegularFont(ofSize: 14) + } + + // MARK: - Properties + + static let identifier = "SearchInputFieldCVC" + + // MARK: - Life Cycle + + override init(frame: CGRect) { + super.init(frame: frame) + setAutoLayouts() + isUserInteractionEnabled = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Layout + + private func setAutoLayouts() { + self.contentView.addSubview(inputField) + inputField.snp.makeConstraints { + $0.width.height.equalToSuperview() + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/TVC/FriendsScene/FriendsAddTVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/TVC/FriendsScene/FriendsAddTVC.swift new file mode 100644 index 0000000..9515311 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/TVC/FriendsScene/FriendsAddTVC.swift @@ -0,0 +1,127 @@ +import UIKit + +protocol FriendsAddTVCDelegate { + func friendsAddTVC(cell: FriendsAddTVC, resultMessage: String) +} + +class FriendsAddTVC: UITableViewCell { + + // MARK: - UI Components + + let profileImage: UIImageView = UIImageView().then { + $0.image = UIImage(named: "img_illust_2") + } + + let nameLabel: UILabel = UILabel().then { + $0.font = UIFont.hanSansMediumFont(ofSize: 16) + $0.textColor = UIColor.black + } + + let emailLabel: UILabel = UILabel().then { + $0.font = UIFont.hanSansMediumFont(ofSize: 13) + $0.textColor = UIColor.grey04 + } + + private let addButton: UIButton = UIButton().then { + $0.setImage(UIImage(named: "btn_add-friends_circle"), for: .normal) + } + + // MARK: - Properties + + static let identifier: String = "FriendsAddTVC" + + var delegate: FriendsAddTVCDelegate? + + var nickname: String? + + // MARK: - Initializer + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setAutoLayouts() + configUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setAutoLayouts() + configUI() + } + + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + setAutoLayouts() + configUI() + } + + override func prepareForReuse() { + addButton.setImage(UIImage(named: "btn_add-friends_circle"), for: .normal) + } + + // MARK: - Layout + + private func setAutoLayouts() { + selectionStyle = .none + + addSubviews([profileImage, nameLabel, emailLabel]) + contentView.addSubview(addButton) + profileImage.snp.makeConstraints { + $0.leading.equalToSuperview() + $0.width.height.equalTo(42 * heightRatio) + $0.top.equalToSuperview().offset(5 * heightRatio) + } + + nameLabel.snp.makeConstraints { + $0.leading.equalTo(profileImage.snp.trailing).offset(24 * widthRatio) + $0.top.equalToSuperview().offset(6 * heightRatio) + } + + emailLabel.snp.makeConstraints { + $0.leading.equalTo(nameLabel.snp.leading) + $0.top.equalTo(nameLabel.snp.bottom).offset(3 * heightRatio) + } + + addButton.snp.makeConstraints { + $0.trailing.equalToSuperview() + $0.width.height.equalTo(48 * widthRatio) + $0.top.equalToSuperview().offset(2 * heightRatio) + } + } + + // MARK: - Custom Methods + + private func configUI() { + addButton.addTarget(self, action: #selector(addButtonDidTap(_:)), for: .touchUpInside) + } + + // MARK: - Network + + private func requestAddFriend() { + FriendsAddService.shared.addFriends(nickname: nickname ?? "") { responseData in + switch responseData { + case .success(let response): + self.addButton.setImage(UIImage(named: "btn_add-friends_fin"), for: .normal) + case .requestErr(let response): + guard let response = response as? FriendsAddResponseModel else { return } + if response.message != "" { + self.delegate?.friendsAddTVC(cell: self, resultMessage: response.message ?? "잘못된 요청입니다.") + } + case .pathErr: + self.delegate?.friendsAddTVC(cell: self, resultMessage: "잘못된 요청입니다.") + print("Path Error") + case .serverErr: + print("Server Error") + case .networkFail: + print("Network Fail") + } + } + } + + // MARK: - Actions + + @objc private func addButtonDidTap(_ sender: UIButton) { + requestAddFriend() + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/TVC/FriendsScene/FriendsListTVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/TVC/FriendsScene/FriendsListTVC.swift new file mode 100644 index 0000000..b25053d --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/TVC/FriendsScene/FriendsListTVC.swift @@ -0,0 +1,93 @@ +import UIKit + + +protocol FriendsListTVCDelegate { + func messageButtonDidTap(friendData: FriendsData) +} + +class FriendsListTVC: UITableViewCell { + + // MARK: - UI Components + + private let profileIcon: UIImageView = UIImageView().then { + $0.image = UIImage(named: "img_profile") + } + + let nameLabel: UILabel = UILabel().then { + $0.textColor = UIColor.black + $0.font = UIFont.hanSansMediumFont(ofSize: 16) + $0.text = "김준희" + } + + private let messageButton: UIButton = UIButton().then { + $0.setImage(UIImage(named: "btn_add-message"), for: .normal) + } + + // MARK: - Properties + + static let identifier: String = "FriendsListTVC" + var friendData: FriendsData? + + var delegate: FriendsListTVCDelegate? + + // MARK: - initializer + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setAutoLayouts() + configUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setAutoLayouts() + configUI() + } + + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + setAutoLayouts() + configUI() + } + + // MARK: - Layout + + private func setAutoLayouts() { + selectionStyle = .none + addSubviews([profileIcon, nameLabel]) + contentView.addSubview(messageButton) + + profileIcon.snp.makeConstraints { + $0.width.height.equalTo(42 * heightRatio) + $0.leading.centerY.equalToSuperview() + } + + nameLabel.snp.makeConstraints { + $0.leading.equalTo(profileIcon.snp.trailing).offset(18 * widthRatio) + $0.centerY.equalToSuperview() + } + + messageButton.snp.makeConstraints { + $0.trailing.equalToSuperview() + $0.width.height.equalTo(48 * widthRatio) + $0.centerY.equalToSuperview() + } + } + + // MARK: - Custom Methods + + private func configUI() { + messageButton.addTarget(self, action: #selector(messageButtonDidTap(_:)), for: .touchUpInside) + } + + // MARK: - Actions + + @objc private func messageButtonDidTap(_ sender: UIButton) { + guard let friendData = friendData else { + return + } + delegate?.messageButtonDidTap(friendData: friendData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/TVC/RequestScene/ScheduleTVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/TVC/RequestScene/ScheduleTVC.swift new file mode 100644 index 0000000..b782113 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/TVC/RequestScene/ScheduleTVC.swift @@ -0,0 +1,91 @@ +// +// ScheduleTVC.swift +// SeeMeet +// +// Created by 이유진 on 2022/01/17. +// +import UIKit + +class ScheduleTVC: UITableViewCell { + + // MARK: - UI Components + + private let dotView = UIView().then{ + $0.backgroundColor = UIColor.pink01 + } + + private let plansTimeLabel = UILabel().then { + $0.textColor = UIColor.grey06 + $0.font = UIFont.dinProMediumFont(ofSize: 18) + } + + private let separateLineView = UIView().then{ + $0.backgroundColor = UIColor.grey04 + } + + private let plansTitleLabel = UILabel().then{ + $0.font = UIFont.hanSansMediumFont(ofSize: 16) + $0.textColor = UIColor.grey05 + } + + // MARK: - Properties + + static let identifier: String = "ScheduleTVC" + + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + addContentView() + setAutoLayouts() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + // Configure the view for the selected state + } + + // MARK: - Layouts + + private func addContentView(){ + addSubviews([dotView,plansTimeLabel,separateLineView,plansTitleLabel]) + } + + func setAutoLayouts() { + dotView.snp.makeConstraints { + $0.leading.equalToSuperview() + $0.centerY.equalToSuperview() + $0.width.height.equalTo(5 * widthRatio) + } + plansTimeLabel.snp.makeConstraints { + $0.leading.equalTo(dotView.snp.trailing).offset(21 * widthRatio) + $0.centerY.equalToSuperview() + } + separateLineView.snp.makeConstraints{ + $0.leading.equalTo(plansTimeLabel.snp.trailing).offset(14 * widthRatio) + $0.centerY.equalToSuperview() + $0.width.equalTo(1 * widthRatio) + $0.height.equalTo(19 * heightRatio) + } + plansTitleLabel.snp.makeConstraints{ + $0.leading.equalTo(separateLineView.snp.trailing).offset(14 * widthRatio) + $0.centerY.equalToSuperview() + } + + backgroundColor = UIColor.grey01 + dotView.layer.cornerRadius = 5/2 + } + + func setData(time: String, plansTitle: String) { + plansTimeLabel.text = time + plansTitleLabel.text = plansTitle + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Cells/TVC/RequestScene/SearchTVC.swift b/SeeMeet/SeeMeet/Source/Views/Cells/TVC/RequestScene/SearchTVC.swift new file mode 100644 index 0000000..6dae2b4 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Cells/TVC/RequestScene/SearchTVC.swift @@ -0,0 +1,79 @@ +// +// searchTVC.swift +// SeeMeet +// +// Created by 이유진 on 2022/01/14. +// + +import UIKit +import Then +import SnapKit + +class SearchTVC: UITableViewCell { + + // MARK: - UI Components + + private let profileImageView: UIImageView = UIImageView().then{ + $0.image = UIImage(named: "img_profile") + } + private let nameLabel: UILabel = UILabel().then{ + $0.font = UIFont.hanSansMediumFont(ofSize: 14) + } + + // MARK: - Properties + + static let identifier: String = "SearchTVC" + + private var name: String = "" + var data: FriendsData? + + // MARK: - Life Cycle + + override func awakeFromNib() { + super.awakeFromNib() + addContentView() + setAutolayouts() + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + addContentView() + setAutolayouts() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + // Configure the view for the selected state + } + + // MARK: - Layouts + + private func addContentView() { + addSubviews([profileImageView,nameLabel]) + } + + private func setAutolayouts() { + profileImageView.snp.makeConstraints{ + $0.leading.centerY.equalToSuperview() + $0.width.height.equalTo(40 * widthRatio) + + } + nameLabel.snp.makeConstraints { + $0.leading.equalTo(profileImageView.snp.trailing).offset(18 * widthRatio) + $0.centerY.equalToSuperview() + } + + } + + // MARK: - Interface + + func setData(data: FriendsData) { + self.data = data + nameLabel.text = data.username + } + +} diff --git a/SeeMeet/SeeMeet/Source/Views/Coordinators/AppCoordinator.swift b/SeeMeet/SeeMeet/Source/Views/Coordinators/AppCoordinator.swift new file mode 100644 index 0000000..6724808 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Coordinators/AppCoordinator.swift @@ -0,0 +1,102 @@ +// +// AppCoordinator.swift +// SeeMeet +// +// Created by 김인환 on 2022/05/21. +// + +import UIKit +import RxSwift + +class AppCoordinator: Coordinator { + + var coordinators: [Coordinator] = [] + + let window: UIWindow? + + var navigationController = UINavigationController().then { + $0.modalTransitionStyle = .crossDissolve + $0.modalPresentationStyle = .overFullScreen + } + + private let disposeBag = DisposeBag() + + init(_ window: UIWindow?) { // SceneDelegate에서 UIWindow의 의존성 주입받는다. + self.window = window + window?.makeKeyAndVisible() + } + + func start() { + startTabbarViewController() + } + + private func startTabbarViewController() { + let tabBarController = TabbarVC() + tabBarController.tabBarDelegate = self + + let homeCoordinator = HomeCoordinator() + homeCoordinator.parentCoordinator = self + coordinators.append(homeCoordinator) + let homeVC = homeCoordinator.startHomeVC() + homeVC.tabBarItem = UITabBarItem(title: "", image: UIImage(named: "home_ic"), selectedImage: UIImage(named: "home_ic_clicked")) + + let calendarCoordinator = CalendarCoordinator() + calendarCoordinator.parentCoordinator = self + coordinators.append(calendarCoordinator) + let calendarVC = calendarCoordinator.startCalendarVC() + calendarVC.tabBarItem = UITabBarItem(title: "", image: UIImage(named: "calendar_ic"), selectedImage: UIImage(named: "calendar_ic_clicked")) + + tabBarController.setViewControllers([homeVC, calendarVC], animated: false) + tabBarController.selectedViewController = homeVC + + [homeVC, calendarVC].forEach { + $0.tabBarItem.imageInsets = UIEdgeInsets(top: UIScreen.hasNotch ? -5 * heightRatio : -25 * heightRatio, left: 0, bottom: 0, right: 0) + } + + self.window?.rootViewController = tabBarController + } + + func startRequestSceneFromFriendsList(friendData: FriendsData) { + gotoRequestScene(friendData: friendData) + } +} + +extension AppCoordinator: TabbarVCDelegate { + func needLogin() { + if self.navigationController.isBeingPresented { + return + } + let loginCoordinator = LoginCoordinator(navigationController: self.navigationController) // AppCoordinator의 경우엔 로그인 완료시 그냥 dismiss + loginCoordinator.parentCoordinator = self + coordinators.append(loginCoordinator) + loginCoordinator.start() + self.window?.rootViewController?.present(navigationController, animated: true) { + self.window?.rootViewController?.view.makeToastAnimation(message: "로그인이 필요합니다.") + } + } + + func dismissAlert() { + self.window?.rootViewController?.presentedViewController?.dismiss(animated: false) + } + + func gotoRequestScene(friendData: FriendsData?) { + let requestCoordinator = RequestCoordinator(navigationController: self.navigationController) + requestCoordinator.parentCoordinator = self + requestCoordinator.friendData = friendData + self.coordinators.append(requestCoordinator) + requestCoordinator.start() + self.window?.rootViewController?.present(self.navigationController, animated: false) + } + + func presentAlert() { + let alertVC = SMPopUpVC(withType: .needLogin).then { + $0.modalPresentationStyle = .overFullScreen + $0.pinkButtonCompletion = { [weak self] in + guard let self = self else { return } + self.window?.rootViewController?.presentedViewController?.dismiss(animated: false) + self.needLogin() + } + } + self.window?.rootViewController?.present(alertVC, animated: false) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Coordinators/CalendarCoordinator.swift b/SeeMeet/SeeMeet/Source/Views/Coordinators/CalendarCoordinator.swift new file mode 100644 index 0000000..c8f3a25 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Coordinators/CalendarCoordinator.swift @@ -0,0 +1,55 @@ +// +// CalendarCoordinator.swift +// SeeMeet +// +// Created by 김인환 on 2022/05/24. +// + +import UIKit + +class CalendarCoordinator: Coordinator { + weak var parentCoordinator: Coordinator? + var coordinators: [Coordinator] = [] + var navigationController: UINavigationController + + init() { + navigationController = UINavigationController() + } + + init(navigationController: UINavigationController) { // HomeCoordinator로부터 의존성을 주입받는다. + self.navigationController = navigationController + } + + func start() { + startCalendarVC() + } + + func startCalendarVC() -> UINavigationController { + let vc = CalendarVC() + vc.coordinator = self + vc.delegate = self + navigationController.setViewControllers([vc], animated: false) + return navigationController + } + + func showCalendarDetailVC(planID: Int?,isCanceled: Bool) { + guard let detailVC = UIStoryboard(name: "CalendarDetail", bundle: nil).instantiateViewController(withIdentifier: CalendarDetailVC.identifier) as? CalendarDetailVC else { return } + detailVC.delegate = self + detailVC.planID = planID + detailVC.isCanceled = isCanceled + navigationController.pushViewController(detailVC, animated: true) + + } +} + +extension CalendarCoordinator: CalendarVCDelegate{ + func plansDidTap(plansID: Int?) { + showCalendarDetailVC(planID: plansID, isCanceled: false) + } +} + +extension CalendarCoordinator: CalendarDetailVCDelegate{ + func backButtonDidTap() { + navigationController.popViewController(animated: true) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Coordinators/Coordinator.swift b/SeeMeet/SeeMeet/Source/Views/Coordinators/Coordinator.swift new file mode 100644 index 0000000..59b8f3a --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Coordinators/Coordinator.swift @@ -0,0 +1,14 @@ +// +// Coordinator.swift +// SeeMeet +// +// Created by 김인환 on 2022/05/21. +// + +import Foundation +import UIKit + +protocol Coordinator: AnyObject { + var coordinators: [Coordinator] { get set } // 하위 코디네이터들을 관리하는 프로퍼티 + func start() // 해당 코디네이터의 root VC를 띄우는 메서드를 지정한다. +} diff --git a/SeeMeet/SeeMeet/Source/Views/Coordinators/FriendsCoordinator.swift b/SeeMeet/SeeMeet/Source/Views/Coordinators/FriendsCoordinator.swift new file mode 100644 index 0000000..63f361c --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Coordinators/FriendsCoordinator.swift @@ -0,0 +1,67 @@ +// +// FriendsCoordinator.swift +// SeeMeet +// +// Created by 김인환 on 2022/06/12. +// + +import Foundation +import UIKit + +class FriendsCoordinator: Coordinator { + weak var parentCoordinator: Coordinator? + var coordinators: [Coordinator] = [] + var navigationController: UINavigationController + + private var friendNames: [String] + + init(navigationController: UINavigationController, friendNames: [String]) { // HomeCoordinator로부터 주입받는다. + self.navigationController = navigationController + self.friendNames = friendNames + } + + func start() { + startFriendsListVC() + } + + func startFriendsListVC() { + guard let vc = UIStoryboard(name: "FriendsList", bundle: nil).instantiateViewController(withIdentifier: "FriendsListVC") as? FriendsListVC else { return } + vc.coordinator = self + vc.delegate = self + navigationController.pushViewController(vc, animated: true) + } + + func startFriendsAddVC() { + guard let vc = UIStoryboard(name: "FriendsAdd", bundle: nil).instantiateViewController(withIdentifier: FriendsAddVC.identifier) as? FriendsAddVC else { return } + vc.modalPresentationStyle = .fullScreen + vc.coordinator = self + navigationController.present(vc, animated: true) + } + + +} + +extension FriendsCoordinator: FriendsListVCDelegate{ + + func backButtonDidTap() { + navigationController.popViewController(animated: true) + } + + func addFriendsButtonDidTap() { + startFriendsAddVC() + } + + func messageButtonDidTap(friendData: FriendsData) { + coordinators.removeAll() + navigationController.popToRootViewController(animated: true) + + guard let homeCoordinator = parentCoordinator as? HomeCoordinator, let appCoordinator = homeCoordinator.parentCoordinator as? AppCoordinator else { return } // 이럴 바에 notificationCenter 쓰는게 나을듯... + appCoordinator.startRequestSceneFromFriendsList(friendData: friendData) + parentCoordinator = nil + } +} + + + + + diff --git a/SeeMeet/SeeMeet/Source/Views/Coordinators/HomeCoordinator.swift b/SeeMeet/SeeMeet/Source/Views/Coordinators/HomeCoordinator.swift new file mode 100644 index 0000000..fc4ec57 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Coordinators/HomeCoordinator.swift @@ -0,0 +1,91 @@ +// +// TabbarCoordinator.swift +// SeeMeet +// +// Created by 김인환 on 2022/05/21. +// + +import Foundation +import UIKit + +class HomeCoordinator: Coordinator { + weak var parentCoordinator: Coordinator? + var coordinators: [Coordinator] = [] + var navigationController: UINavigationController + + init() { + self.navigationController = UINavigationController() + } + + func start() { + _ = startHomeVC() + } + + func startHomeVC() -> UINavigationController { + let vc = HomeVC() + vc.coordinator = self + vc.delegate = self + navigationController.setViewControllers([vc], animated: true) + return navigationController + } + + func startPlansScenes() { + let coordinator = PlansCoordinator(navigationController: navigationController) + coordinator.parentCoordinator = self + coordinators.append(coordinator) + coordinator.start() + } + + func startFriendScenes(friendNames: [String]) { + let coordinator = FriendsCoordinator(navigationController: navigationController, friendNames: friendNames) + coordinator.parentCoordinator = self + coordinators.append(coordinator) + coordinator.start() + } + + func startLoginScene(){ + let coordinator = LoginCoordinator(navigationController: navigationController) // home의 경우에는 로그인 완료 시 start하면 될듯 + coordinator.parentCoordinator = self + coordinators.append(coordinator) + coordinator.start() + } + + func startMyPageScene(){ + let coordinator = MyPageCoordinator(navigationController: navigationController) + coordinator.parentCoordinator = self + coordinators.append(coordinator) + coordinator.start() + } + + func startProfileRevise(){ + let coordinator = MyPageCoordinator(navigationController: navigationController) + coordinator.parentCoordinator = self + coordinators.append(coordinator) + coordinator.startUserInfoVCRevisingStatus() + } +} + +extension HomeCoordinator: HomeVCDelegate { + func notificationButtonDidTap() { + startPlansScenes() + } + + func friendsButtonDidTap(friendNames: [String]) { + startFriendScenes(friendNames: friendNames) + } + + func nameButtonDidTap() { + startMyPageScene() + } + + func loginButtonDidTap(){ + startLoginScene() + } + + func upcomingPalnDidTap() { + } + + func goToProfileRevise() { + startProfileRevise() + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Coordinators/LoginCoordinator.swift b/SeeMeet/SeeMeet/Source/Views/Coordinators/LoginCoordinator.swift new file mode 100644 index 0000000..d49ce40 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Coordinators/LoginCoordinator.swift @@ -0,0 +1,98 @@ +// +// LoginCoordinator.swift +// SeeMeet +// +// Created by 김인환 on 2022/06/12. +// + +import UIKit + +class LoginCoordinator: Coordinator { + weak var parentCoordinator: Coordinator? + var coordinators: [Coordinator] = [] + var navigationController: UINavigationController + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + func start() { + startMainLoginVC() + } + + func startMainLoginVC() { + guard let vc = UIStoryboard(name: "MainLogin", bundle: nil).instantiateViewController(withIdentifier: "MainLoginVC") as? MainLoginVC else {return} + vc.coordinator = self + vc.delegate = self + navigationController.setViewControllers([vc], animated: true) + } + + func startEmailLoginVC(){ + guard let vc = UIStoryboard(name: "EmailLogin", bundle: nil).instantiateViewController(withIdentifier: "EmailLoginVC") as? EmailLoginVC else {return} + vc.delegate = self + vc.coordinator = self + navigationController.pushViewController(vc, animated: true) + + } + func startProfileRegisterVC(accessToken: String, refreshToken: String, name: String?, isAppleLogin: Bool) { + guard let vc = UIStoryboard(name: "ProfileRegister", bundle: Bundle.main).instantiateViewController(withIdentifier: "ProfileRegisterVC") as? ProfileRegisterVC else { return } + vc.do { + $0.accessTokenToSet = accessToken + $0.refreshTokenToSet = refreshToken + $0.nameToSet = name + $0.isAppleLogin = isAppleLogin + } + vc.delegate = self + navigationController.pushViewController(vc, animated: true) + } + + func startRegisterScene(){ + let coordinator = RegisterCoordinator(navigationController: navigationController) + coordinator.parentCoordinator = self.parentCoordinator + parentCoordinator?.coordinators.append(coordinator) + coordinator.start() + parentCoordinator?.coordinators.removeAll(where: { $0 === self }) + } + + +} + +extension LoginCoordinator: MainLoginVCDelegate, EmailLoginVCDelegate, ProfileRegisterVCDelegate { + func needRegister(accessToken: String, refreshToken: String, name: String?, isAppleLogin: Bool) { + startProfileRegisterVC(accessToken: accessToken, refreshToken: refreshToken, name: name, isAppleLogin: isAppleLogin) + } + + func backButtonDidTap() { + if self.navigationController.visibleViewController is MainLoginVC { + loginCompleted() + } else { + navigationController.popViewController(animated: true) + } + } + + func emailRegisterDidTap() { + startRegisterScene() + } + + func emailLoginDidTap() { + startEmailLoginVC() + } + + func closeButtonDidTap() { + loginCompleted() + } + + func loginCompleted() { + if parentCoordinator is AppCoordinator { + self.navigationController.presentingViewController?.dismiss(animated: true) + self.navigationController.viewControllers.removeAll() + } else if parentCoordinator is HomeCoordinator { + parentCoordinator?.start() + } + parentCoordinator?.coordinators.removeAll(where: { $0 === self }) + } + + func registerBackButtonDidTap() { + backButtonDidTap() + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/Coordinators/MyPageCoordinator.swift b/SeeMeet/SeeMeet/Source/Views/Coordinators/MyPageCoordinator.swift new file mode 100644 index 0000000..0f38ca1 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Coordinators/MyPageCoordinator.swift @@ -0,0 +1,70 @@ +// +// MyPageCoordinator.swift +// SeeMeet +// +// Created by 이유진 on 2022/08/02. +// + +import UIKit + +class MyPageCoordinator: Coordinator { + weak var parentCoordinator: Coordinator? + var coordinators: [Coordinator] = [] + var navigationController: UINavigationController + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + func start() { + startUserInfoVC() + } + + func startUserInfoVC() { + guard let vc = UIStoryboard(name: "UserInfo", bundle: nil).instantiateViewController(withIdentifier: "UserInfoVC") as? UserInfoVC else {return} + vc.coordinator = self + vc.delegate = self + navigationController.pushViewController(vc, animated: true) + } + + func startChangePasswordVC() { + guard let vc = UIStoryboard(name: "ChangePassword", bundle: nil).instantiateViewController(withIdentifier: "ChangePasswordVC") as? ChangePasswordVC else {return} + vc.delegate = self + vc.coordinator = self + navigationController.pushViewController(vc, animated: true) + } + func startUserInfoVCRevisingStatus(){ + guard let vc = UIStoryboard(name: "UserInfo", bundle: nil).instantiateViewController(withIdentifier: "UserInfoVC") as? UserInfoVC else {return} + vc.coordinator = self + vc.delegate = self + vc.isInfoEditing = true + navigationController.pushViewController(vc, animated: true) + } +} + +extension MyPageCoordinator: UserInfoVCDelegate{ + func backButtonDidTap() { + navigationController.popViewController(animated: true) + } + + func changePasswordButtonDidTap() { + startChangePasswordVC() + } + + func withdrawalOKButtonDidTap() { + parentCoordinator?.start() + parentCoordinator?.coordinators.removeAll(where: { $0 === self }) + } + + func logoutOKButtonDidTap() { + withdrawalOKButtonDidTap() + } + +} + +extension MyPageCoordinator: ChangePasswordVCDelegate{ + func changePasswordCompleted() { + navigationController.popViewController(animated: true) + } +} + diff --git a/SeeMeet/SeeMeet/Source/Views/Coordinators/PlansCoordinator.swift b/SeeMeet/SeeMeet/Source/Views/Coordinators/PlansCoordinator.swift new file mode 100644 index 0000000..ad23443 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Coordinators/PlansCoordinator.swift @@ -0,0 +1,83 @@ +// +// PlansCoordinator.swift +// SeeMeet +// +// Created by 김인환 on 2022/06/01. +// + +import UIKit +import Then + +class PlansCoordinator: Coordinator { + weak var parentCoordinator: Coordinator? + var coordinators: [Coordinator] = [] + var navigationController: UINavigationController + + init(navigationController: UINavigationController) { // HomeCoordinator로부터 의존성을 주입받는다. + self.navigationController = navigationController + } + + func start() { + startPlansListVC() + } + + private func startPlansListVC() { + let vc = PlansListVC() + vc.coordinator = self + vc.delegate = self + navigationController.pushViewController(vc, animated: true) + } + + func startPlansSendListVC(plansID: String) { + + guard let vc = UIStoryboard(name: "PlansSendList", bundle: nil).instantiateViewController(withIdentifier: "PlansSendListVC") as? PlansSendListVC else { return } + vc.coordinator = self + vc.delegate = self + vc.plansId = plansID + navigationController.pushViewController(vc, animated: true) + } + + func startPlansReceiveVC(plansID: String) { + guard let vc = UIStoryboard(name: "PlansReceiveList", bundle: nil).instantiateViewController(withIdentifier: "PlansReceiveVC") as? PlansReceiveVC else { return } + vc.coordinator = self + vc.delegate = self + vc.plansId = plansID + navigationController.pushViewController(vc, animated: true) + } + + func startCalendarDetailVC(planID: Int?,isCanceled: Bool){ + let coordinator = CalendarCoordinator(navigationController: navigationController) + coordinator.parentCoordinator = self + coordinators.append(coordinator) + coordinator.showCalendarDetailVC(planID: planID,isCanceled: isCanceled) + + } +} + + +extension PlansCoordinator: PlansListVCDelegate { + func sendPlansDidTap(plansID: String) { + startPlansSendListVC(plansID: plansID) + } + + func receivePlansDidTap(plansID: String) { + startPlansReceiveVC(plansID: plansID) + } + + func completedPlansDidTap(plansID: Int?, isCanceled: Bool) { + startCalendarDetailVC(planID: plansID,isCanceled: isCanceled) + } + + func backButtonDidTap() { + navigationController.popViewController(animated: true) + } +} + +extension PlansCoordinator: PlansReceiveVCDelegate{ + + +} + +extension PlansCoordinator: PlansSendListVCDelegate{ + +} diff --git a/SeeMeet/SeeMeet/Source/Views/Coordinators/RegisterCoordinator.swift b/SeeMeet/SeeMeet/Source/Views/Coordinators/RegisterCoordinator.swift new file mode 100644 index 0000000..a18fd9b --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Coordinators/RegisterCoordinator.swift @@ -0,0 +1,70 @@ +// +// RegisterCoordinator.swift +// SeeMeet +// +// Created by 김인환 on 2022/06/12. +// + +import UIKit + +class RegisterCoordinator: Coordinator { + weak var parentCoordinator: Coordinator? + var coordinators: [Coordinator] = [] + var navigationController: UINavigationController + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + func start() { + startEmailRegisterVC() + } + + func startEmailRegisterVC(){ + guard let vc = UIStoryboard(name: "EmailRegister", bundle: nil).instantiateViewController(withIdentifier: "EmailRegisterVC") as? EmailRegisterVC else {return} + vc.delegate = self + vc.coordinator = self + navigationController.pushViewController(vc, animated: true) + } + + func startProfileRegisterVC(accessToken: String, refreshToken: String ,email: String){ + guard let vc = UIStoryboard(name: "ProfileRegister", bundle: Bundle.main).instantiateViewController(withIdentifier: "ProfileRegisterVC") as? ProfileRegisterVC else { return } + vc.do { + $0.accessTokenToSet = accessToken + $0.email = email + $0.refreshTokenToSet = refreshToken + $0.delegate = self + } + navigationController.pushViewController(vc, animated: true) + } +} + +extension RegisterCoordinator: EmailRegisterVCDelegate{ + func backButtonDidTap() { + navigationController.popViewController(animated: true) + } + + func closeButtonDidTap() { + self.navigationController.presentingViewController?.dismiss(animated: true) + self.navigationController.viewControllers.removeAll() + parentCoordinator?.start() + parentCoordinator?.coordinators.removeAll(where: { $0 === self }) + } + + func nextButtonDidTap(accessToken: String, refreshToken: String, email: String) { + startProfileRegisterVC(accessToken: accessToken, refreshToken: refreshToken, email: email) + } +} + + +extension RegisterCoordinator: ProfileRegisterVCDelegate{ + func registerBackButtonDidTap(){ + let viewControllerStack = navigationController.viewControllers + for viewController in viewControllerStack { + if let loginView = viewController as? MainLoginVC { + navigationController.popToViewController(loginView, animated: true) + } + } + } +} + diff --git a/SeeMeet/SeeMeet/Source/Views/Coordinators/RequestCoordinator.swift b/SeeMeet/SeeMeet/Source/Views/Coordinators/RequestCoordinator.swift new file mode 100644 index 0000000..11be86b --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/Coordinators/RequestCoordinator.swift @@ -0,0 +1,58 @@ +// +// RequestCoordinator.swift +// SeeMeet +// +// Created by 김인환 on 2022/06/13. +// + +import UIKit + +class RequestCoordinator: Coordinator { + weak var parentCoordinator: Coordinator? + var coordinators: [Coordinator] = [] + var navigationController: UINavigationController + + var friendData: FriendsData? + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + func start() { + startRequestPlansContentsVC() + } + + func startRequestPlansContentsVC() { + let vc = RequestPlansContentsVC() + vc.coordinator = self + vc.delegate = self + vc.friendDataToSet = friendData + navigationController.setViewControllers([vc], animated: false) + } + + func startRequestPlansDateVC() { + let vc = RequestPlansDateVC() + vc.coordinator = self + vc.delegate = self + navigationController.pushViewController(vc, animated: true) + } +} + +extension RequestCoordinator: RequestPlansContentsVCDelegate, RequestPlansDateVCDelegate { + func backButtonDidTap() { + self.navigationController.popViewController(animated: true) + } + + func exitButtonDidTap() { // RequestPlansContentsVC와 RequestPlansDateVC 에서 공통으로 사용된다. + self.navigationController.presentingViewController?.dismiss(animated: true) + self.navigationController.viewControllers.removeAll() + parentCoordinator?.coordinators.removeAll(where: { $0 === self }) + + PostRequestPlansService.sharedParameterData.removeAllData() + } + + func nextButtonDidTap() { + self.startRequestPlansDateVC() + } + +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/CalendarScene/CalendarDetailVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/CalendarScene/CalendarDetailVC.swift new file mode 100644 index 0000000..c084a7a --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/CalendarScene/CalendarDetailVC.swift @@ -0,0 +1,530 @@ +import UIKit + +protocol CalendarDetailVCDelegate{ + func backButtonDidTap() +} +class CalendarDetailVC: UIViewController { + + // MARK: - UI Components + + static let identifier: String = "CalendarDetailVC" + + private let topView: UIView = UIView().then { + $0.backgroundColor = UIColor.grey06 + } + + private let backButton: UIButton = UIButton().then { + $0.setImage(UIImage(named: "btn_back_white"), for: .normal) + } + + private let navigationTitleLabel: UILabel = UILabel().then { + $0.font = UIFont(name: "SpoqaHanSansNeo-Medium", size: 18.0) + $0.textAlignment = .center + $0.textColor = UIColor.white + } + + private let eventTitleLabel: UILabel = UILabel().then { + $0.font = UIFont(name: "SpoqaHanSansNeo-Bold", size: 22.0) + $0.textColor = UIColor.grey06 + $0.textAlignment = .left + $0.numberOfLines = 0 + $0.lineBreakMode = .byCharWrapping + } + + let timeLabel: UILabel = UILabel().then { + $0.font = UIFont(name: "DINPro-Regular", size: 18.0) + $0.textColor = UIColor.grey06 + } + + // private let nameTagStackView: UIStackView = UIStackView().then { + // $0.axis = .horizontal + // $0.alignment = .fill + // $0.distribution = .fillEqually + // $0.spacing = 10 + // $0.layoutMargins = UIEdgeInsets(top: 10 * heightRatio, left: 10 * widthRatio, bottom: 10 * heightRatio, right: 10 * widthRatio) + // $0.isLayoutMarginsRelativeArrangement = true + // } + + private var nameTagCollectionView: UICollectionView = { + let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewLayout()) + let flowlayout: UICollectionViewFlowLayout = UICollectionViewFlowLayout().then { + $0.scrollDirection = .vertical + $0.minimumInteritemSpacing = CGFloat(0) + $0.itemSize = CGSize(width: 100 * widthRatio, height: 23 * heightRatio) + + } + + collectionView.bounces = false + collectionView.setCollectionViewLayout(flowlayout, animated: false) + collectionView.showsVerticalScrollIndicator = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.backgroundColor = .white + collectionView.register(NameChipCVC.self, forCellWithReuseIdentifier: NameChipCVC.identifier) + + return collectionView + }() + + private let separator: UIView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private let letterView: UIView = UIView().then { + $0.backgroundColor = UIColor.grey01 + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + } + + private let letterTitleLabel: UILabel = UILabel().then { + $0.font = UIFont(name: "SpoqaHanSansNeo-Bold", size: 14) + $0.textColor = UIColor.grey06 + $0.lineBreakMode = .byTruncatingTail + } + + private let letterSeparator: UIView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private let letterContentView: UITextView = UITextView().then { + $0.isEditable = false + $0.backgroundColor = UIColor.clear + $0.textColor = UIColor.grey06 + $0.font = UIFont(name: "SpoqaHanSansNeo-Regular", size: 14) + + $0.textContainerInset = UIEdgeInsets(top: 10 * heightRatio, left: 10 * widthRatio, bottom: 10 * heightRatio, right: 10 * widthRatio) + } + + private let organizerLabel: UILabel = UILabel().then { + $0.text = "약속 주최자" + $0.font = UIFont(name: "SpoqaHanSansNeo-Bold", size: 14) + $0.textColor = UIColor.grey04 + } + + private let divider: UIView = UIView().then { + $0.backgroundColor = UIColor.grey04 + } + + private let organizerNameLabel: UILabel = UILabel().then { + $0.font = UIFont(name: "SpoqaHanSansNeo-Bold", size: 15) + } + + private let bottomSeparator: UIView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private let deleteButton: UIButton = UIButton().then { + $0.backgroundColor = UIColor.grey04 + $0.setTitle("약속 삭제", for: .normal) + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + } + + // MARK: - Properties + + var planID: Int? // 외부에서 반드시 입력받아야 함 + var isCanceled = false + weak var coordinator: Coordinator? + + private var possibleNameList = [PossibleUser]() { + didSet { + // setNameTag(nameList: possibleNameList?.map { $0.username ?? "탈퇴 유저" } ) + } + } + private var impossibleNameList = [PossibleUser]() { + didSet { + // setNameTag(nameList: impossibleNameList?.map { $0.username ?? "탈퇴 유저" } ) + } + } +// private var nameList = ["안뇽","랄라라"] + private var nameList = [CanceledPlansGuest]() + var delegate: CalendarDetailVCDelegate? + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setAutoLayouts() + configUI() + switch isCanceled{ + case true: + getCanceledPlansData() + deleteButton.isHidden = true + bottomSeparator.isHidden = true + case false: + requestPlanDetail() + deleteButton.isHidden = false + bottomSeparator.isHidden = false + } + setDelegate() + } + + // MARK: - setLayouts + + private func setAutoLayouts() { + view.addSubview(topView) + topView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + if UIScreen.hasNotch { + $0.height.equalTo(102 * heightRatio) + } else { + $0.height.equalTo(CGFloat(58 + UIScreen.getIndecatorHeight()) * heightRatio) + } + } + + topView.addSubview(backButton) + backButton.snp.makeConstraints { + $0.height.width.equalTo(48 * heightRatio) + $0.leading.equalToSuperview().offset(2 * heightRatio) + $0.bottom.equalToSuperview().offset(-5 * heightRatio) + } + + topView.addSubview(navigationTitleLabel) + navigationTitleLabel.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.height.equalTo(32 * heightRatio) + $0.bottom.equalToSuperview().offset(-11 * heightRatio) + } + + view.addSubview(eventTitleLabel) + eventTitleLabel.snp.makeConstraints { + $0.top.equalTo(topView.snp.bottom).offset(33 * heightRatio) + $0.leading.equalToSuperview().offset(21 * widthRatio) + $0.trailing.equalToSuperview().offset(-37 * widthRatio) + } + + view.addSubview(timeLabel) + timeLabel.snp.makeConstraints { + $0.leading.equalTo(eventTitleLabel.snp.leading) + $0.top.equalTo(eventTitleLabel.snp.bottom).offset(6 * heightRatio) + } + + view.addSubview(nameTagCollectionView) + // nameTagStackView.snp.makeConstraints { + // $0.leading.equalToSuperview().offset(10 * widthRatio) + // $0.top.equalTo(timeLabel.snp.bottom).offset(20 * heightRatio) + // $0.height.equalTo(46 * heightRatio) + // } + nameTagCollectionView.snp.makeConstraints { + $0.top.equalTo(timeLabel.snp.bottom).offset(20 * heightRatio) + $0.leading.trailing.equalToSuperview().inset(10 * widthRatio) + $0.height.equalTo(100 * heightRatio) + } + + view.addSubview(separator) + separator.snp.makeConstraints { + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(1) + $0.top.equalTo(nameTagCollectionView.snp.bottom).offset(18.5 * heightRatio) + } + + view.addSubview(letterView) + letterView.snp.makeConstraints { + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.top.equalTo(separator.snp.bottom).offset(25 * heightRatio) + $0.height.equalTo(225 * heightRatio) + } + + letterView.addSubview(letterTitleLabel) + letterTitleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(15 * heightRatio) + $0.leading.equalToSuperview().offset(19 * widthRatio) + $0.trailing.equalToSuperview().offset(-15 * widthRatio) + $0.height.equalTo(32 * heightRatio) + } + + letterView.addSubview(letterSeparator) + letterSeparator.snp.makeConstraints { + $0.leading.equalToSuperview().offset(15 * widthRatio) + $0.trailing.equalToSuperview().offset(-15 * widthRatio) + $0.height.equalTo(1) + $0.top.equalTo(letterTitleLabel.snp.bottom).offset(9 * heightRatio) + } + + letterView.addSubview(letterContentView) + letterContentView.snp.makeConstraints { + $0.leading.equalToSuperview().offset(9 * widthRatio) + $0.top.equalTo(letterSeparator.snp.bottom).offset(3 * heightRatio) + $0.trailing.equalToSuperview().offset(-5 * widthRatio) + $0.bottom.equalToSuperview().offset(-8 * heightRatio) + } + + view.addSubview(organizerLabel) + organizerLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(25 * widthRatio) + $0.top.equalTo(letterView.snp.bottom).offset(10 * heightRatio) + } + + view.addSubview(divider) + divider.snp.makeConstraints { + $0.leading.equalTo(organizerLabel.snp.trailing).offset(10 * widthRatio) + $0.top.equalTo(letterView.snp.bottom).offset(10 * heightRatio) + $0.width.equalTo(1) + $0.height.equalTo(16 * heightRatio) + } + + view.addSubview(organizerNameLabel) + organizerNameLabel.snp.makeConstraints { + $0.top.equalTo(letterView.snp.bottom).offset(8 * heightRatio) + $0.leading.equalTo(divider.snp.trailing).offset(15.5 * widthRatio) + } + + view.addSubview(bottomSeparator) + bottomSeparator.snp.makeConstraints { + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(1) + $0.bottom.equalTo(-112 * heightRatio) + } + + view.addSubview(deleteButton) + deleteButton.snp.makeConstraints { + $0.top.equalTo(bottomSeparator.snp.bottom).offset(16 * heightRatio) + $0.leading.equalTo(20 * widthRatio) + $0.trailing.equalTo(-20 * widthRatio) + $0.height.equalTo(54 * heightRatio) + } + } + + // MARK: - Custom Methods + + private func configUI() { + backButton.addTarget(self, action: #selector(backButtonDidTap(_:)), for: .touchUpInside) + deleteButton.addTarget(self, action: #selector(deleteButtonDidTap(_:)), for: .touchUpInside) + } + + private func setDelegate(){ + nameTagCollectionView.delegate = self + nameTagCollectionView.dataSource = self + } + + private func displayDateLabel(at date: String) { + + let formatter = DateFormatter().then { + $0.dateFormat = "yyyy-MM-dd" + $0.timeZone = NSTimeZone(name: "UTC") as TimeZone? + $0.locale = Locale(identifier: "ko_KR") + } + + if let date: Date = formatter.date(from: date) { + formatter.dateFormat = "yyyy월 M월 d일 E요일" + self.navigationTitleLabel.text = formatter.string(from: date) + } + } + + // MARK: - Networks + + private func getCanceledPlansData() { + guard let plansId = planID else { return } + GetCanceldPlansDetailService.shared.getCanceledPlans(plansId: String(plansId)) { (response) in + switch response { + case .success(let data): + if let response = data as? CanceledPlanDetailModel { + + self.eventTitleLabel.text = response.data.invitationTitle + self.letterTitleLabel.text = response.data.invitationTitle + self.letterContentView.text = response.data.invitationDesc + self.nameList = response.data.guest + self.organizerNameLabel .text = response.data.hostName + + + DispatchQueue.main.async { + self.nameTagCollectionView.reloadData() + self.timeLabel.text = "취소된 약속" + self.timeLabel.textColor = UIColor.pink01 + } + + + } + case .requestErr(let message): + print("requestERR", message) + case .pathErr: + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + private func requestPlanDetail() { // UI 코드를 분리 시켜야 할듯... 일단 나중에ㅠㅠ + guard let planID = planID else { + view.makeToastAnimation(message: "약속 조회 오류! 다시 시도해주세요.") + return + } + + CalendarService.shared.getDetailPlanData(planID: planID) { [weak self] responseData in + switch responseData { + case .success(let response): + guard let response = response as? PlanDetailResponseModel else { return } + self?.eventTitleLabel.text = response.data?.invitationTitle + self?.letterTitleLabel.text = response.data?.invitationTitle + self?.letterContentView.text = response.data?.invitationDesc + self?.organizerNameLabel.text = response.data?.hostName + self?.possibleNameList = response.data?.possible ?? [] + self?.impossibleNameList = response.data?.impossible ?? [] + self?.displayDateLabel(at: response.data?.date ?? "2022-01-01") + + self?.nameTagCollectionView.reloadData() + + var hourString = "" + let startHourComponents = response.data?.start.components(separatedBy: ":") + let endHourComponents = response.data?.end.components(separatedBy: ":") + + if let startHourString = startHourComponents?.first, + let startMinuteString = startHourComponents?[1], + let endHourString = endHourComponents?.first, + let endMinuteString = endHourComponents?[1], + let startHour = Int(startHourString), + let endHour = Int(endHourString) + { + + hourString = startHour < 12 ? "오전 \(startHour):\(startMinuteString)" : "오후 \(startHour):\(startMinuteString)" + hourString += endHour < 12 ? " - 오전 \(endHour):\(endMinuteString)" : " - 오후 \(endHour):\(endMinuteString)" + + self?.timeLabel.text = hourString + } + + case .requestErr(let response): + guard let response = response as? PlanDetailResponseModel, + let message = response.message else { return } + self?.view.makeToastAnimation(message: message) + + case .pathErr: + print("Path Err") + self?.view.makeToastAnimation(message: "요청 오류! 다시 시도하십시오.") + + case .serverErr: + print("Server Err") + self?.view.makeToastAnimation(message: "서버 에러! 다시 시도하십시오.") + + case .networkFail: + print("Network Err") + self?.view.makeToastAnimation(message: "통신 오류! 다시 시도하십시오.") + } + } + } + + private func requestPlanDelete() { + guard let planID = planID else { + return + } + + PutPlanDeleteService.shared.putPlanDelete(planId: "\(planID)") { responseData in + switch responseData { + case .success(let response): + // guard response is PlansDeleteResponseModel else { return } + self.navigationController?.popViewController(animated: true) + self.view.makeToastAnimation(message: "약속 삭제 되었습니다.") + default: + self.view.makeToastAnimation(message: "약속 삭제 실패. 다시 시도하십시오.") + } + } + + } + + // MARK: - Actions + + @objc private func backButtonDidTap(_ sender: UIButton) { + delegate?.backButtonDidTap() + } + + @objc private func deleteButtonDidTap(_ sender: UIButton) { + let nextVC = SMPopUpVC(withType: .deletePlans) + nextVC.modalPresentationStyle = .overCurrentContext + nextVC.pinkButtonCompletion = { [weak self] in + self?.requestPlanDelete() + self?.dismiss(animated: false) + } + present(nextVC, animated: false) + } +} + +extension CalendarDetailVC: UICollectionViewDelegate{ + +} + + +extension CalendarDetailVC: UICollectionViewDataSource{ + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + switch isCanceled{ + case true: + return nameList.count + case false: + let possibleCount = possibleNameList.count + let impossibleCount = impossibleNameList.count + + return possibleCount + impossibleCount + } + + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + switch isCanceled{ + case true: + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NameChipCVC.identifier, for: indexPath) as? NameChipCVC else {return UICollectionViewCell()} + + + cell.setData(name: nameList[indexPath.row].username) + cell.setCanceledLayout() + + + return cell + case false: + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NameChipCVC.identifier, for: indexPath) as? NameChipCVC else {return UICollectionViewCell()} + + let possibleCount = possibleNameList.count + if (indexPath.row CGSize { + switch isCanceled{ + case false: + let possibleCount = possibleNameList.count + if (indexPath.row UIEdgeInsets { + + return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 10 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return 10 + } + + + +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/CalendarScene/CalendarVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/CalendarScene/CalendarVC.swift new file mode 100644 index 0000000..f150c5b --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/CalendarScene/CalendarVC.swift @@ -0,0 +1,326 @@ +import UIKit +import Then +import SnapKit +import FSCalendar + +protocol CalendarVCDelegate{ + func plansDidTap(plansID: Int?) +} +class CalendarVC: UIViewController { + + // MARK: - UI Components + + static let identifier: String = "CalendarVC" + + private let calendarHeaderLabel: UILabel = UILabel().then { + $0.textColor = UIColor.pink01 + $0.font = UIFont.dinProBoldFont(ofSize: 22) + } + + let calendar: FSCalendar = FSCalendar().then { + $0.select($0.today) + $0.scope = .month + $0.locale = Locale(identifier: "ko_KR") + $0.scrollDirection = .horizontal + $0.allowsMultipleSelection = false + $0.calendarHeaderView.isHidden = true + $0.weekdayHeight = CGFloat(55.0 * heightRatio) + + $0.headerHeight = CGFloat.zero + $0.appearance.titleFont = UIFont.dinProRegularFont(ofSize: 16) + $0.appearance.weekdayFont = UIFont.dinProRegularFont(ofSize: 16) + $0.appearance.subtitleFont = UIFont.dinProRegularFont(ofSize: 16) + $0.appearance.weekdayTextColor = UIColor.grey05 + $0.appearance.titleDefaultColor = UIColor.grey06 + $0.appearance.todayColor = UIColor.pink01 + $0.appearance.eventDefaultColor = UIColor.pink01 + $0.appearance.selectionColor = UIColor.grey06 + $0.appearance.eventSelectionColor = UIColor.pink01 + } + + private let bottomCollectionContainerView: UIView = UIView().then { + $0.backgroundColor = UIColor.grey01 + } + + private let dateAndDayLabel: UILabel = UILabel().then { + $0.font = UIFont.hanSansBoldFont(ofSize: 18) + $0.textColor = UIColor.grey06 + + let nowDate = Date() + let currentMonth = Calendar.current.component(.month, from: nowDate) + let currentDate = Calendar.current.component(.day, from: nowDate) + $0.text = "\(currentMonth)월 \(currentDate)일 \(Date.getCurrentKoreanWeekDay())요일" + } + + private var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewLayout()) + let flowlayout: UICollectionViewFlowLayout = UICollectionViewFlowLayout().then { + $0.scrollDirection = .horizontal + $0.minimumInteritemSpacing = CGFloat(16) + $0.sectionInset = UIEdgeInsets(top: 0, left: 20.0 * widthRatio, bottom: 11 * heightRatio, right: 20 * widthRatio) + $0.itemSize = CGSize(width: 223.0 * widthRatio, height: 134.0 * heightRatio) + } + + collectionView.setCollectionViewLayout(flowlayout, animated: false) + collectionView.showsVerticalScrollIndicator = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.bounces = true + collectionView.backgroundColor = .none + collectionView.register(CalendarPlansCVC.self, forCellWithReuseIdentifier: CalendarPlansCVC.identifier) + + return collectionView + }() + + // MARK: - Properties + + weak var coordinator: Coordinator? + + private var planDatas: [Plan] = [] + private var selectedDatas: [Plan]? + var delegate: CalendarVCDelegate? + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + setAutoLayouts() + configUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + requestCalendarData(year: Date.getCurrentYear(), month: Date.getCurrentMonth()) + tabBarController?.tabBar.isHidden = false + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + tabBarController?.tabBar.isHidden = false + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + tabBarController?.tabBar.isHidden = true + } + + // MARK: - Layouts + + private func setAutoLayouts() { + navigationController?.navigationBar.isHidden = true + + layoutCalendarWeekdaySeparator() + + addSubviewAndConstraints(add: calendarHeaderLabel, to: view) { + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.top.equalTo(view.safeAreaLayoutGuide).offset(25 * heightRatio) + } + + // 화면 작은 애들은 추후 수정 필요...? + addSubviewAndConstraints(add: calendar, to: view) { + $0.top.equalTo(calendarHeaderLabel.snp.bottom).offset(9 * heightRatio) + $0.centerX.equalTo(view.snp.centerX) + $0.width.equalTo(378 * widthRatio) + $0.height.equalTo(418 * heightRatio) + } + + addSubviewAndConstraints(add: bottomCollectionContainerView, to: view) { + $0.top.equalTo(calendar.snp.bottom) + $0.leading.trailing.equalToSuperview() + if UIScreen.hasNotch { + $0.bottom.equalTo(view.snp.bottom).offset(88 * widthRatio) + } else { + $0.bottom.equalTo(view.snp.bottom).offset(77 * widthRatio) + } + } + + addSubviewAndConstraints(add: dateAndDayLabel, to: bottomCollectionContainerView) { + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.top.equalToSuperview().offset(16 * heightRatio) + } + + addSubviewAndConstraints(add: collectionView, to: bottomCollectionContainerView) { + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(157 * heightRatio) + $0.top.equalTo(dateAndDayLabel.snp.bottom).offset(16 * heightRatio) + } + } + + private func layoutCalendarWeekdaySeparator() { + let separator: UIView = UIView() + separator.backgroundColor = UIColor.grey01 + calendar.addSubview(separator) + separator.snp.makeConstraints { + $0.top.equalTo(calendar.calendarWeekdayView.snp.bottom) + $0.leading.equalTo(20 * widthRatio) + $0.trailing.equalTo(-20 * widthRatio) + $0.height.equalTo(1 * heightRatio) + } + } + + // MARK: - Networks + + private func requestCalendarData(year: String, month: String) { + CalendarService.shared.getPlanDatas(year: year, month: month) { [weak self] responseData in + switch responseData { + case .success(let response): + guard let response = response as? MonthlyPlansResponseModel, + let data = response.data, + let self = self else { return } + + if let datasToAppend = response.data?.filter({ + !self.planDatas.contains($0) + }) { + self.planDatas.append(contentsOf: datasToAppend) + } + + self.displayPlansCollectionView() + self.calendar.reloadData() + case .requestErr(let msg): + print(msg) + case .pathErr: + print("path error") + case .serverErr: + print("server error") + case .networkFail: + print("network Fail") + } + } + } + + // MARK: - Custom Methods + + private func configUI() { + calendar.delegate = self + calendar.dataSource = self + + collectionView.dataSource = self + collectionView.delegate = self + + view.backgroundColor = UIColor.white + } + + private func addSubviewAndConstraints(add subView: UIView, to superView: UIView, snapkitClosure closure: (ConstraintMaker) -> Void) { + superView.addSubview(subView) + subView.snp.makeConstraints(closure) + } + + func displayPlansCollectionView(at date: Date = Date()) { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + let cellDay = formatter.string(from: date) + selectedDatas = planDatas.filter { + $0.date.components(separatedBy: "T").first == cellDay + } + collectionView.reloadData() + } +} + +// MARK: - Extension + +extension CalendarVC: FSCalendarDataSource { + func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + guard let cellDay: String = formatter.string(from: date).components(separatedBy: "T").first else { return 0} + + let cellPlans = planDatas.filter { + $0.date.components(separatedBy: "T").first == cellDay + } + + if cellPlans != nil && cellPlans.count != 0 { + return 1 + } else { + return 0 + } + } +} + +// MARK: - Extensions + +extension CalendarVC: FSCalendarDelegate { + func calendar(_ calendar: FSCalendar, willDisplay cell: FSCalendarCell, for date: Date, at monthPosition: FSCalendarMonthPosition) { + let currentPage = calendar.currentPage + let currentYear = Calendar.current.component(.year, from: currentPage) + let currentMonth = Calendar.current.component(.month, from: currentPage) + + calendarHeaderLabel.text = "\(currentYear)년 \(currentMonth)월" + } + + func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) { + let selectedMonth = Calendar.current.component(.month, from: date) + let selectedDate = Calendar.current.component(.day, from: date) + + dateAndDayLabel.text = "\(selectedMonth)월 \(selectedDate)일 \(Date.getKoreanWeekDay(from: date))요일" + + // 선택된 날의 약속 목록 설정 + if monthPosition == .current { + displayPlansCollectionView(at: date) + } + } + + func calendar(_ calendar: FSCalendar, shouldSelect date: Date, at monthPosition: FSCalendarMonthPosition) -> Bool { + // 현재 달과 선택된 날이 다르면 캘린더 페이지 바뀌게 하는 로직 + if monthPosition != .current { + calendar.setCurrentPage(date, animated: true) + return false + } else { + return true + } + } + + func calendarCurrentPageDidChange(_ calendar: FSCalendar) { // 페이지 바뀌면 바뀐 페이지의 일정 다시 요청 + let currentPage = calendar.currentPage + let currentYear = String(Calendar.current.component(.year, from: currentPage)) + let currentMonth = String(Calendar.current.component(.month, from: currentPage)) + requestCalendarData(year: currentYear, month: currentMonth) + } +} + +extension CalendarVC: UICollectionViewDataSource { + func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return selectedDatas?.count ?? 0 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CalendarPlansCVC", for: indexPath) as? CalendarPlansCVC else { return UICollectionViewCell() } + + // 약속 데이터 받아와 넣기 + let planData = selectedDatas?[indexPath.row] + cell.headerTitle.text = planData?.invitationTitle + + var hourString = "" + let startHourComponents = planData?.start.components(separatedBy: ":") + let endHourComponents = planData?.end.components(separatedBy: ":") + + guard let startHourString = startHourComponents?.first, + let startMinuteString = startHourComponents?[1], + let endHourString = endHourComponents?.first, + let endMinuteString = endHourComponents?[1], + let startHour = Int(startHourString), + let endHour = Int(endHourString) + else { return UICollectionViewCell() } + + hourString = startHour < 12 ? "오전 \(startHour):\(startMinuteString)" : "오후 \(startHour):\(startMinuteString)" + hourString += endHour < 12 ? " - 오전 \(endHour):\(endMinuteString)" : " - 오후 \(endHour):\(endMinuteString)" + + cell.hourLabel.text = hourString + var namesToShow: [String] = planData?.users.map { $0.username } ?? [] + if namesToShow.count > 3 { // 일단 최대 3개만 보여줍시다. + namesToShow.removeSubrange(3...namesToShow.count-1) + } + cell.namesToShow = namesToShow + return cell + } +} + +extension CalendarVC: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let planData = selectedDatas?[indexPath.row] + guard let cell = collectionView.cellForItem(at: indexPath) as? CalendarPlansCVC else { return } + delegate?.plansDidTap(plansID: planData?.planID) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/CalendarScene/Components/NameChipCVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/CalendarScene/Components/NameChipCVC.swift new file mode 100644 index 0000000..806a54d --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/CalendarScene/Components/NameChipCVC.swift @@ -0,0 +1,65 @@ +// +// nameChipCVC.swift +// SeeMeet +// +// Created by 이유진 on 2022/08/11. +// + +import UIKit + +class NameChipCVC: UICollectionViewCell { + + private let nameLabel = UILabel().then { + $0.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.textColor = UIColor.white + } + + // MARK: - Properties + + static let identifier: String = "nameChipCVC" + + // MARK: - initializer + + override init(frame: CGRect) { + super.init(frame: frame) + setLayout() + setAutoLayouts() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setLayout() + + setAutoLayouts() + } + + private func setLayout(){ + clipsToBounds = true + layer.cornerRadius = 30 * heightRatio / 2 + backgroundColor = UIColor.pink01 + } + private func setAutoLayouts() { + + addSubview(nameLabel) + + nameLabel.snp.makeConstraints{ + $0.centerX.centerY.equalToSuperview() + } + } + func setData(name: String){ + nameLabel.text = name + } + + func setPossibleLayout(){ + backgroundColor = UIColor.pink01 + } + + func setImpossibleLayout(){ + backgroundColor = UIColor.grey04 + } + + func setCanceledLayout(){ + backgroundColor = UIColor.grey04 + + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/FriendsScene/Components/SMSearchBar.swift b/SeeMeet/SeeMeet/Source/Views/VCs/FriendsScene/Components/SMSearchBar.swift new file mode 100644 index 0000000..84cf3b7 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/FriendsScene/Components/SMSearchBar.swift @@ -0,0 +1,53 @@ +// +// SMSearchBar.swift +// SeeMeet +// +// Created by 김인환 on 2022/01/13. +// + +import UIKit +import SnapKit + +class SMSearchBar: UISearchBar { + + // MARK: - initializer + + override init(frame: CGRect) { + super.init(frame: frame) + configUI() + setAutoLayouts() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + configUI() + setAutoLayouts() + } + + // MARK: - Layout + + private func setAutoLayouts() { + searchTextField.snp.makeConstraints { + $0.leading.trailing.top.bottom.equalToSuperview() + } + } + + // MARK: - Custom Methods + + private func configUI() { + setImage(UIImage(named: "ic_search"), for: .search, state: .normal) + setImage(UIImage(named: "btn_remove"), for: .clear, state: .normal) + backgroundColor = UIColor.grey01 + backgroundImage = UIImage() + searchBarStyle = .minimal + isTranslucent = false + layer.cornerRadius = 10 + + if let textfield = value(forKey: "searchField") as? UITextField { + textfield.attributedPlaceholder = NSAttributedString(string: textfield.placeholder ?? "", attributes: [NSAttributedString.Key.foregroundColor: UIColor.grey04, NSAttributedString.Key.font: UIFont.hanSansRegularFont(ofSize: 14)]) + textfield.textColor = UIColor.grey06 + textfield.font = UIFont.hanSansRegularFont(ofSize: 14) + } + + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/FriendsScene/FriendsAddVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/FriendsScene/FriendsAddVC.swift new file mode 100644 index 0000000..eb4233e --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/FriendsScene/FriendsAddVC.swift @@ -0,0 +1,213 @@ +import UIKit + + +class FriendsAddVC: UIViewController { + + // MARK: - UI Components + + private let topView: UIView = UIView().then { + $0.backgroundColor = UIColor.grey06 + } + + private let navigationTitleLabel: UILabel = UILabel().then { + $0.text = "친구 추가" + $0.font = UIFont.hanSansMediumFont(ofSize: 18) + $0.textColor = UIColor.white + } + + private let closeButton: UIButton = UIButton().then { + $0.setImage(UIImage(named: "btn_close_white"), for: .normal) + } + + private let searchBar: SMSearchBar = SMSearchBar().then { + $0.placeholder = "친구 아이디" + } + + private lazy var tableView: UITableView = UITableView().then { + $0.separatorStyle = .none + $0.register(FriendsAddTVC.self, forCellReuseIdentifier: FriendsAddTVC.identifier) + } + + private let undefinedLabel: UILabel = UILabel().then { + $0.text = "검색 결과가 없습니다." + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.textColor = UIColor.grey03 + $0.isHidden = true + } + + // MARK: - Properties + + static let identifier: String = "FriendsAddVC" + + weak var coordinator: Coordinator? + + private var searchResults: FriendData? { + didSet { + tableView.reloadData() + } + } + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + configUI() + setAutoLayouts() + } + + // MARK: - Layout + + private func setAutoLayouts() { + view.dismissKeyboardWhenTappedAround() + view.addSubviews([topView, searchBar, tableView, undefinedLabel]) + topView.addSubviews([navigationTitleLabel, closeButton]) + + topView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + if UIScreen.hasNotch { + $0.height.equalTo(102 * heightRatio) + } else { + $0.height.equalTo((58 + CGFloat(UIScreen.getIndecatorHeight())) * heightRatio) + } + } + + navigationTitleLabel.snp.makeConstraints { + $0.bottom.equalToSuperview().offset(-15 * heightRatio) + $0.leading.equalToSuperview().offset(152 * widthRatio) + } + + closeButton.snp.makeConstraints { + $0.width.height.equalTo(48 * heightRatio) + $0.trailing.equalToSuperview().offset(-5 * widthRatio) + $0.bottom.equalToSuperview().offset(-4 * heightRatio) + } + + searchBar.snp.makeConstraints { + $0.top.equalTo(topView.snp.bottom).offset(14 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.height.equalTo(50 * heightRatio) + } + + tableView.snp.makeConstraints { + $0.leading.equalTo(20 * widthRatio) + $0.trailing.equalTo(-14 * widthRatio) + $0.bottom.equalToSuperview() + $0.top.equalTo(searchBar.snp.bottom).offset(30 * heightRatio) + } + + undefinedLabel.snp.makeConstraints { + $0.top.equalTo(searchBar.snp.bottom).offset(47 * heightRatio) + $0.centerX.equalToSuperview() + } + } + + // MARK: - Custom Methods + + private func configUI() { + closeButton.addTarget(self, action: #selector(closeButtonDidTap(_:)), for: .touchUpInside) + searchBar.delegate = self + tableView.delegate = self + tableView.dataSource = self + } + + // MARK: - Network + + private func requestFriendsSearchResults(nickname: String) { + FriendsSearchService.shared.searchFriends(nickname: nickname) { responseData in + switch responseData { + case .success(let response): + guard let response = response as? FriendsSearchResponseModel else { return } + self.searchResults = response.data + self.undefinedLabel.isHidden = true + self.tableView.isHidden = false + + case .requestErr(_): + self.undefinedLabel.isHidden = false + self.tableView.isHidden = true + case .pathErr: + print("Path Error") + self.undefinedLabel.isHidden = false + self.tableView.isHidden = true + case .serverErr: + self.undefinedLabel.isHidden = false + self.tableView.isHidden = true + print("Server Error") + case .networkFail: + self.undefinedLabel.isHidden = false + self.tableView.isHidden = true + print("Network Fail") + } + } + } + + // MARK: - Actions + + @objc private func closeButtonDidTap(_ sender: UIButton) { + dismiss(animated: true, completion: nil) + } + +} + +// MARK: - Extensions + +extension FriendsAddVC: UISearchBarDelegate { + func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { + searchBar.layer.borderColor = UIColor.pink01.cgColor + searchBar.layer.borderWidth = 1 + undefinedLabel.isHidden = true + tableView.isHidden = false + } + + func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { + searchBar.layer.borderColor = nil + searchBar.layer.borderWidth = 0 + } + + func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { // 키보드 완료 + searchBar.endEditing(true) + guard let searchEmail = searchBar.text else { return } + requestFriendsSearchResults(nickname: searchEmail) + } + + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + if searchText == "" { + tableView.isHidden = true + } + } +} + +extension FriendsAddVC: UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if searchResults != nil { + return 1 + } else { + return 0 + } + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell: FriendsAddTVC = tableView.dequeueReusableCell(withIdentifier: FriendsAddTVC.identifier) as? FriendsAddTVC else { return UITableViewCell() } + cell.nameLabel.text = searchResults?.username + cell.emailLabel.text = searchResults?.email + cell.nickname = searchResults?.nickname + cell.delegate = self + return cell + } +} + +extension FriendsAddVC: UITableViewDelegate { + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 53 * heightRatio + } +} + +extension FriendsAddVC: FriendsAddTVCDelegate { + func friendsAddTVC(cell: FriendsAddTVC, resultMessage: String) { + view.makeToastAnimation(message: resultMessage) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/FriendsScene/FriendsListVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/FriendsScene/FriendsListVC.swift new file mode 100644 index 0000000..654f303 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/FriendsScene/FriendsListVC.swift @@ -0,0 +1,268 @@ +import UIKit + +protocol FriendsListVCDelegate { + func backButtonDidTap() + func messageButtonDidTap(friendData: FriendsData) + func addFriendsButtonDidTap() +} + +class FriendsListVC: UIViewController { + + // MARK: - UI Components + + private let topView: UIView = UIView().then { + $0.backgroundColor = UIColor.clear + } + + private let navigationTitleLabel: UILabel = UILabel().then { + $0.text = "친구" + $0.textColor = UIColor.grey06 + $0.font = UIFont.hanSansMediumFont(ofSize: 18) + } + + private let backButton: UIButton = UIButton().then { + $0.setImage(UIImage(named: "btn_back"), for: .normal) + } + + private let addFriendsButton: UIButton = UIButton().then { + $0.setImage(UIImage(named: "btn_add-friends"), for: .normal) + } + + private let searchBar: SMSearchBar = SMSearchBar().then { + $0.placeholder = "친구 검색" + } + + private let separator: UIView = UIView().then { + $0.backgroundColor = UIColor.clear + } + + private let tableView: UITableView = UITableView().then { + $0.register(FriendsListTVC.self, forCellReuseIdentifier: FriendsListTVC.identifier) + } + + private lazy var emptyImage: UIImageView = UIImageView().then { + $0.image = UIImage(named: "img_illust_9") + } + + private lazy var emptyMessageLabel1: UILabel = UILabel().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.textColor = UIColor.grey05 + $0.text = "등록된 친구가 없어요!" + } + + private lazy var emptyMessageLabel2: UILabel = UILabel().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.textColor = UIColor.grey05 + $0.text = "친구를 추가해 주세요" + } + + // MARK: - Properties + + static let identifier: String = "FriendsListVC" + + weak var coordinator: Coordinator? + private var filteredFriendsData: [FriendsData] = [] + var delegate: FriendsListVCDelegate? + + private var friendsData: [FriendsData]? + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setAutoLayouts() + configUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + getFriendsList() + } + + // MARK: - setLayouts + + private func setAutoLayouts() { + view.dismissKeyboardWhenTappedAround() + view.addSubviews([topView, searchBar, separator,tableView]) + topView.addSubviews([navigationTitleLabel, backButton, addFriendsButton]) + + topView.snp.makeConstraints { + $0.leading.trailing.top.equalToSuperview() + $0.height.equalTo(102 * heightRatio) + } + + backButton.snp.makeConstraints { + $0.width.height.equalTo(48 * heightRatio) + $0.leading.equalTo(2 * widthRatio) + $0.bottom.equalTo(5 * heightRatio) + } + + navigationTitleLabel.snp.makeConstraints { + $0.bottom.equalToSuperview().offset(-11 * heightRatio) + $0.leading.equalTo(backButton.snp.trailing).offset(120 * widthRatio) + } + + addFriendsButton.snp.makeConstraints { + $0.width.height.equalTo(48 * heightRatio) + $0.centerY.equalTo(backButton.snp.centerY) + $0.trailing.equalToSuperview().offset(-11 * widthRatio) + } + + searchBar.delegate = self + searchBar.snp.makeConstraints { + $0.width.equalTo(335 * widthRatio) + $0.height.equalTo(50 * heightRatio) + $0.top.equalTo(topView.snp.bottom).offset(14 * heightRatio) + $0.centerX.equalToSuperview() + } + + separator.snp.makeConstraints { + $0.leading.trailing.equalToSuperview() + $0.top.equalTo(searchBar.snp.bottom).offset(14 * heightRatio) + $0.height.equalTo(1) + } + } + + private func setAutoLayoutsIfEmptyTable() { + view.addSubviews([emptyImage, emptyMessageLabel1, emptyMessageLabel2]) + emptyImage.snp.makeConstraints { + $0.width.height.equalTo(164 * heightRatio) + $0.top.equalTo(searchBar.snp.bottom).offset(135 * heightRatio) + $0.centerX.equalToSuperview() + } + + emptyMessageLabel1.snp.makeConstraints { + $0.top.equalTo(emptyImage.snp.bottom).offset(17 * heightRatio) + $0.centerX.equalToSuperview() + } + + emptyMessageLabel2.snp.makeConstraints { + $0.top.equalTo(emptyMessageLabel1.snp.bottom).offset(4 * heightRatio) + $0.centerX.equalToSuperview() + } + } + + // MARK: - Custom Methods + + private func configUI() { + backButton.addTarget(self, action: #selector(backButtonDidTap(_:)), for: .touchUpInside) + addFriendsButton.addTarget(self, action: #selector(addFriendsButtonDidTap(_:)), for: .touchUpInside) + + tableView.dataSource = self + tableView.delegate = self + tableView.separatorStyle = .none + } + + // MARK: - Network + + func getFriendsList() { + GetFriendsListService.shared.getFriendsList(){ [weak self] (response) in + guard let self = self else { return } + switch response { + case .success(let data): + if let response = data as? FriendsDataModel { + self.friendsData = response.data + if let friendsData = self.friendsData, friendsData.count > 0 { + self.tableView.snp.makeConstraints { + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalToSuperview().offset(-11 * widthRatio) + $0.bottom.equalToSuperview() + $0.top.equalTo(self.separator.snp.bottom) + } + self.tableView.reloadData() + } else { + self.setAutoLayoutsIfEmptyTable() + } + } + case .requestErr(let message) : + print("requestERR") + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + // MARK: - Actions + + @objc private func backButtonDidTap(_ sender: UIButton) { + delegate?.backButtonDidTap() + } + + @objc private func addFriendsButtonDidTap(_ sender: UIButton) { + delegate?.addFriendsButtonDidTap() + } + + // MARK: - tableview Delegate + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !scrollView.contentOffset.y.isZero { + separator.backgroundColor = UIColor.grey02 + } else { + separator.backgroundColor = UIColor.clear + } + } +} + +// MARK: - Extensions + +extension FriendsListVC: UISearchBarDelegate { + func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { + searchBar.layer.borderColor = UIColor.pink01.cgColor + searchBar.layer.borderWidth = 1 + } + + func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { + searchBar.layer.borderColor = nil + searchBar.layer.borderWidth = 0 + } + + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + if !searchText.isEmpty { + filteredFriendsData = friendsData?.filter { $0.username.lowercased().prefix(searchText.count) == searchText.lowercased() } ?? [] + } else { + filteredFriendsData.removeAll() + } + tableView.reloadData() // 일단은 음절단위로 갑시다! + } +} + +extension FriendsListVC: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if filteredFriendsData.isEmpty { + return friendsData?.count ?? 0 + } else { + return filteredFriendsData.count + } + } + + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: FriendsListTVC.identifier, for: indexPath) as? FriendsListTVC else { return UITableViewCell() } + if filteredFriendsData.isEmpty { + cell.nameLabel.text = friendsData?.map({$0.username})[indexPath.row] ?? "" + cell.friendData = friendsData?[indexPath.row] + } else { + cell.nameLabel.text = filteredFriendsData.map({$0.username})[indexPath.row] + cell.friendData = filteredFriendsData[indexPath.row] + } + cell.delegate = self + return cell + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 68 * heightRatio + } +} + +extension FriendsListVC: FriendsListTVCDelegate { + func messageButtonDidTap(friendData: FriendsData) { + delegate?.messageButtonDidTap(friendData: friendData) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/GrayTextField.swift b/SeeMeet/SeeMeet/Source/Views/VCs/GrayTextField.swift new file mode 100644 index 0000000..7cedd94 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/GrayTextField.swift @@ -0,0 +1,35 @@ +import UIKit + +enum textFieldType { + case email + case password +} + +class GrayTextField: UITextField{ + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + init(type: textFieldType, placeHolder: String) { + super.init(frame: .zero) + self.placeholder = "" + self.font = UIFont.hanSansRegularFont(ofSize: 14) + self.tintColor = UIColor.pink01 + self.contentVerticalAlignment = .center + if #available(iOS 12.0, *) { + self.textContentType = .oneTimeCode + } + + switch type{ + case .email: + self.placeholder = placeHolder + case .password: + self.isSecureTextEntry = true + self.placeholder = placeHolder + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/GrayTextView.swift b/SeeMeet/SeeMeet/Source/Views/VCs/GrayTextView.swift new file mode 100644 index 0000000..ce61653 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/GrayTextView.swift @@ -0,0 +1,52 @@ +import UIKit + +enum textViewType{ + case loginEmail + case loginPassword + case register + +} +class GrayTextView: UIView{ + var isNotSee: Bool = false + var notSeeButtonCompletion: ((Bool) -> Bool)? + + let emailTextIamgeView = UIImageView().then{ + $0.image = UIImage(named: "ic_e-mail") + } + let pwdTextImageView = UIImageView().then{ + $0.image = UIImage(named: "ic_password") + } + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + init(type: textViewType) { + super.init(frame: .zero) + self.backgroundColor = UIColor.grey01 + self.clipsToBounds = true + self.layer.cornerRadius = 10 + + switch type { + case .loginEmail: + addSubview(emailTextIamgeView) + emailTextIamgeView.snp.makeConstraints{ + $0.top.leading.bottom.equalToSuperview().offset(0) + $0.width.equalTo(48) + } + case .loginPassword: + addSubviews([pwdTextImageView]) + pwdTextImageView.snp.makeConstraints{ + $0.top.leading.bottom.equalToSuperview().offset(0) + $0.width.equalTo(48) + } + default: + print("defultView") + } + } + + +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/HomeScene/HomeVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/HomeScene/HomeVC.swift new file mode 100644 index 0000000..382fd23 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/HomeScene/HomeVC.swift @@ -0,0 +1,599 @@ +import UIKit +import SnapKit +import Then +import RxSwift +import RxCocoa +import SafariServices + +protocol HomeVCDelegate { + func notificationButtonDidTap() + func friendsButtonDidTap(friendNames: [String]) + func nameButtonDidTap() + func loginButtonDidTap() + func upcomingPalnDidTap() + func goToProfileRevise() +} + +final class HomeVC: UIViewController { + + // MARK: - UI Components + + private let homeBackgroundView = UIView().then { + $0.backgroundColor = UIColor.grey01 + } + + private let topView = UIView().then{ + $0.backgroundColor = UIColor.pink01 + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner] + $0.getShadowView(color: UIColor.black.cgColor, masksToBounds: false, shadowOffset: CGSize(width: 0, height: 4), shadowRadius: 3, shadowOpacity: 0.25) + } + + private let menuButton = UIButton().then{ + $0.setBackgroundImage(UIImage(named: "btn_menu"), for: .normal) + } + + private let friendsButton = UIButton().then{ + $0.setBackgroundImage(UIImage(named: "btn_friends"), for: .normal) + } + + private let notificationButton = UIButton().then{ + $0.setBackgroundImage(UIImage(named: "ic_noti"), for: .normal) + } + + private let dDayLabel = UILabel().then{ + $0.font = UIFont.hanSansRegularFont(ofSize: 22) + $0.textColor = UIColor.black + $0.numberOfLines = 2 + $0.setAttributedText(defaultText: "씨밋과 함께 약속을 잡아볼까요?", containText: "약속", font: UIFont.hanSansBoldFont(ofSize: 26), color: UIColor.white, kernValue: -0.6) + //가운데 일수는 26/bold/white + } + + private let characterImageView = UIImageView().then{ + $0.image = UIImage(named: "img_illust_5") + } + + private let collectionViewHeadLabel = UILabel().then{ + $0.text = "다가오는 약속" + $0.font = UIFont.hanSansBoldFont(ofSize: 20) + $0.textColor = UIColor.black + } + + private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: .init()).then { + let flowLayout = UICollectionViewFlowLayout().then { layout in + layout.minimumLineSpacing = 0 + layout.minimumInteritemSpacing = 0 + layout.scrollDirection = .horizontal + } + $0.setCollectionViewLayout(flowLayout, animated: false) + $0.backgroundColor = .none + $0.bounces = true + $0.showsHorizontalScrollIndicator = false + } + + private let noEventImageView = UIImageView().then{ + $0.image = UIImage(named: "img_illust_10") + } + + private let noEventLabel = UILabel().then{ + $0.text = "일정이 없어요!" + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.textColor = UIColor.grey04 + $0.textAlignment = .center + } + + private let myPageView = MyPageView() + + // MARK: - Properties + weak var coordinator: HomeCoordinator? + var delegate: HomeVCDelegate? + + private var lastEventDate: String = "" + private var userWidth: CGFloat = UIScreen.getDeviceWidth() + private var userHeight: CGFloat = UIScreen.getDeviceHeight() - 88 * heightRatio + + private var homeData: [HomeData]? { + didSet { + homeData = homeData?.filter { !isPrevious(date: $0.date) } + } + } + private var friendsListData: [FriendsData]? + + let disposeBag = DisposeBag() + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setAutoLayouts() + configUI() + setDelegate() + setCollectionViewLayout() + isNoEventLayout() + setMainillust() + setupMyPageViewButtons() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + tabBarController?.tabBar.isHidden = false + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + tabBarController?.tabBar.isHidden = false + getHomeData() + getLastPlansCount() + NotificationCenter.default.addObserver(self, selector: #selector(makeToast(_:)), name: NSNotification.Name(rawValue: "toastMessage"), object: nil) + setMyPageData() + + if let accessToken = TokenUtils.shared.read(account: "accessToken") { + print(accessToken) + } + var userName = UserDefaults.standard.string(forKey: Constants.UserDefaultsKey.userName) + var userId = UserDefaults.standard.string(forKey: Constants.UserDefaultsKey.userNickname) + var isLogin = UserDefaults.standard.bool(forKey: Constants.UserDefaultsKey.isLogin) + if (userName == nil)&&(userId == nil)&&(isLogin==true){ + let alertVC = SMPopUpVC(withType: .profile) + alertVC.modalPresentationStyle = .overFullScreen + self.present(alertVC, animated: false, completion: nil) + + alertVC.pinkButtonCompletion = { + self.dismiss(animated: false) + self.delegate?.goToProfileRevise() + } + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + tabBarController?.tabBar.isHidden = true + } + + // MARK: - setLayouts + + private func setAutoLayouts() { + self.navigationController?.isNavigationBarHidden = true + + view.addSubview(homeBackgroundView) + homeBackgroundView.addSubviews([topView, collectionViewHeadLabel]) + topView.addSubviews([menuButton, friendsButton, notificationButton, dDayLabel, characterImageView]) + + homeBackgroundView.snp.makeConstraints{ + $0.top.bottom.leading.trailing.equalToSuperview() + } + topView.snp.makeConstraints{ + let topViewRatio: CGFloat = 0.6 + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(userHeight * topViewRatio) + } + menuButton.snp.makeConstraints{ + $0.top.equalToSuperview().offset(48 * heightRatio) + $0.leading.equalToSuperview().offset(7 * widthRatio) + $0.height.width.equalTo(48 * heightRatio) + } + notificationButton.snp.makeConstraints{ + $0.centerY.equalTo(menuButton) + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.width.height.equalTo(48 * widthRatio) + } + friendsButton.snp.makeConstraints{ + $0.centerY.equalTo(menuButton) + $0.trailing.equalTo(notificationButton.snp.leading) + $0.width.height.equalTo(48 * widthRatio) + } + dDayLabel.snp.makeConstraints{ + $0.top.equalTo(menuButton.snp.bottom).offset(35 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) +// $0.width.equalTo(250 * widthRatio) +// $0.height.equalTo(63 * heightRatio) + } + characterImageView.snp.makeConstraints{ + $0.bottom.equalTo(topView.snp.bottom).offset(-11 * heightRatio) + $0.trailing.equalToSuperview().offset(-24 * heightRatio) + $0.width.equalTo(317 * heightRatio) + $0.height.equalTo(210 * heightRatio) + } + collectionViewHeadLabel.snp.makeConstraints{ + $0.top.equalTo(topView.snp.bottom).offset(14 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.width.equalTo(116 * widthRatio) + $0.height.equalTo(32 * heightRatio) + } + + view.addSubview(myPageView) + + myPageView.frame = CGRect(x: -userWidth * 0.84, y: 0, width: userWidth * 0.84, height: userHeight + 88) + myPageView.isHidden = true + } + + // MARK: - Custom Methods + + private func configUI() { + menuButton.addTarget(self, action: #selector(menuButtonClicked(_:)), for: .touchUpInside) + friendsButton.addTarget(self, action: #selector(friendsButtonDidTap(_:)), for: .touchUpInside) + notificationButton.addTarget(self, action: #selector(notiButtonClicked(_:)), for: .touchUpInside) + } + + private func setupMyPageViewButtons() { + guard let noticeURL = URL(string: "https://be-simon.notion.site/Seemeet-4b19c31ed34b4429ae8348d8c2950437"), + let termOfServiceURL = URL(string: "https://be-simon.notion.site/Seemeet-107c957d37b745858a4da44498dd4b7a"), + let privacyPolicyURL = URL(string: "https://be-simon.notion.site/Seemeet-6fbe99c20f0f47f8a2a23cb4c601bd12"), + let openSourceURL = URL(string: "https://be-simon.notion.site/Seemeet-30d0b45f3a2d40f9b6d2ca65094cf955") else { return } + myPageView.noticeButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + let webView = SFSafariViewController(url: noticeURL) + self?.present(webView, animated: true) + }) + .disposed(by: disposeBag) + + myPageView.termOfServiceButton.rx.tap // 이용약관 + .asDriver() + .drive(onNext: { [weak self] in + let webView = SFSafariViewController(url: termOfServiceURL) + webView.modalPresentationStyle = .pageSheet + webView.dismissButtonStyle = .close + self?.present(webView, animated: true) + }) + .disposed(by: disposeBag) + + myPageView.privacyPolicyButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + let webView = SFSafariViewController(url: privacyPolicyURL) + webView.modalPresentationStyle = .pageSheet + webView.dismissButtonStyle = .close + self?.present(webView, animated: true) + }) + .disposed(by: disposeBag) + + myPageView.openSourceButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + let webView = SFSafariViewController(url: openSourceURL) + webView.modalPresentationStyle = .pageSheet + webView.dismissButtonStyle = .close + self?.present(webView, animated: true) + }) + .disposed(by: disposeBag) + } + + private func setDelegate(){ + myPageView.nameButtonTappedDelegate = self + } + + private func setCollectionViewLayout() { + homeBackgroundView.addSubview(collectionView) + collectionView.do { + $0.delegate = self + $0.dataSource = self + $0.registerCustomXib(xibName: "HomeEventCVC") + } + + collectionView.snp.makeConstraints { + let collectionViewRatio: CGFloat = 0.32 + $0.top.equalTo(collectionViewHeadLabel.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(userHeight * collectionViewRatio) + } + } + + private func isNoEventLayout() { + if homeData == nil { + homeBackgroundView.addSubviews([noEventImageView, noEventLabel]) + noEventImageView.snp.makeConstraints{ + $0.top.equalTo(collectionViewHeadLabel.snp.bottom).offset(10 * heightRatio) + $0.centerX.equalToSuperview() + $0.width.equalTo(249 * widthRatio) + $0.height.equalTo(128 * heightRatio) + } + noEventLabel.snp.makeConstraints{ + $0.top.equalTo(noEventImageView.snp.bottom).offset(12 * heightRatio) + $0.centerX.equalToSuperview() + $0.width.equalTo(110 * widthRatio) + } + } + } + + private func setMyPageData() { + if UserDefaults.standard.bool(forKey: Constants.UserDefaultsKey.isLogin) { + myPageView.do { + $0.nameButton.setTitle(UserDefaults.standard.string(forKey: Constants.UserDefaultsKey.userName) ?? "로그인", for: .normal) + $0.nicknameLabel.text = UserDefaults.standard.string(forKey: Constants.UserDefaultsKey.userNickname) ?? "SeeMeet에서 친구와 약속을 잡아보세요!" + guard let image = ImageManager.shared.getSavedImage(named: "profile.png") ?? UIImage(named: "img_profile") else {return} + $0.profileImageView.image = image + } + } + } + + func isPrevious(date: String) -> Bool { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + + let startDate = dateFormatter.date(from: Date.getCurrentYear() + "-" + Date.getCurrentMonth() + "-" + Date.getCurrentDate()) ?? Date() + let endDate = dateFormatter.date(from: date) ?? Date() + + let interval = endDate.timeIntervalSince(startDate) + let days = Int(interval / 86400) + + if days <= 0 { + return true + } + else { + return false + } + + } + + private func setMainillust() { + let elapsedDays = String.getTimeInterval(from: lastEventDate, by: "yyyy-MM-dd") + + enum MainText: String { + case firstComeIn = "씨밋과 함께 약속을 잡아볼까요?" + case firstFriendAdded = "친구가 당신의 약속 신청을 기다리고 있어요!" + case todayMeet = "아싸 오늘은 친구 만나는 날이다!" + case twoWeek = "약속잡기에 딱 좋은\n 시기에요!" + case threeWeek = "친구와 만난지 벌써 \n%@일이 지났어요" + case overThreeWeek = "친구를 언제 만났는지 기억도 안나요.." + } + + let randomMessageList: [MainText] = [.firstFriendAdded, .twoWeek, .overThreeWeek] + var mainText: MainText = .firstComeIn + var mainCharacterImage: UIImage? + var containText: String { + switch mainText { + case .firstComeIn: + return "약속" + case .firstFriendAdded: + return "약속 신청" + case .todayMeet: + return "친구" + case .twoWeek: + return "딱 좋은" + case .threeWeek: + return "\(abs(elapsedDays))일" + case .overThreeWeek: + return "기억도" + default: + return "" + } + } + + if friendsListData?.count ?? 0 == 0 && homeData?.count ?? 0 == 0 { // 사용자 초기 화면: 친구목록도 아직 없고, 약속 데이터도 없음 + mainCharacterImage = UIImage(named: "img_illust_5") + mainText = .firstComeIn + + } else if friendsListData?.count ?? 0 > 0 && homeData?.count ?? 0 == 0 { // 친구는 있으나, 약속 데이터 없음 + switch randomMessageList.randomElement() { + case .firstFriendAdded: + mainCharacterImage = UIImage(named: "img_illust_4") + mainText = .firstFriendAdded + case .twoWeek: + mainCharacterImage = UIImage(named: "img_illust_8") + mainText = .twoWeek + case .overThreeWeek: + mainCharacterImage = UIImage(named: "img_illust_7") + mainText = .overThreeWeek + default: + print("error") + } + + } else if elapsedDays <= 0 { + if elapsedDays > -14 { + mainCharacterImage = UIImage(named: "img_illust_8") + mainText = .twoWeek + } else if elapsedDays < -14 && elapsedDays > -21 { + mainCharacterImage = UIImage(named: "img_illust_6") + mainText = .threeWeek + } else if elapsedDays <= -21 { + mainCharacterImage = UIImage(named: "img_illust_6") + mainText = .overThreeWeek + } + + } else if homeData?.contains(where: { + $0.date == Date.getCurrentYear() + "-" + Date.getCurrentMonth() + "-" + Date.getCurrentDate() + }) ?? false { + mainText = .todayMeet + mainCharacterImage = UIImage(named: "img_illust_1") + } + + let defaultText = mainText != .threeWeek ? mainText.rawValue : String(format: mainText.rawValue, containText) + + dDayLabel.setAttributedText(defaultText: defaultText, containText: containText, + font: UIFont.hanSansBoldFont(ofSize: 26), color: UIColor.white, + kernValue: -0.6 * widthRatio) + characterImageView.image = mainCharacterImage + } + + // MARK: - Actions + + @objc private func notiButtonClicked(_ sender: UIButton) { + delegate?.notificationButtonDidTap() + } + + @objc private func friendsButtonDidTap(_ sender: UIButton) { + if UserDefaults.standard.bool(forKey: Constants.UserDefaultsKey.isLogin) { + delegate?.friendsButtonDidTap(friendNames: friendsListData?.map { $0.username } ?? []) + + } else { + let alertVC = SMPopUpVC(withType: .needLogin).then { + $0.modalPresentationStyle = .overFullScreen + $0.pinkButtonCompletion = { [weak self] in + self?.dismiss(animated: false, completion: nil) + self?.delegate?.loginButtonDidTap() + } + } + present(alertVC, animated: false, completion: nil) + } + } + + @objc private func menuButtonClicked(_ sender: UIButton) { + myPageView.isHidden = false + myPageView.pushAlarmSwitch.isOn = UserDefaults.standard.bool(forKey: Constants.UserDefaultsKey.isPushNotificationOn) + UIView.animate(withDuration: 0.3) { + let yFrame = CGAffineTransform(translationX: self.userWidth * 0.84, y: 0) + self.myPageView.transform = yFrame + } + } + + @objc private func makeToast(_ notification: NSNotification){ + view.makeToastAnimation(message: notification.object as? String ?? "") + } + + // MARK: - Networks + + private func getHomeData() { + GetHomeDataService.shared.getHomeData(year: Date.getCurrentYear(), month: Date.getCurrentMonth()){ [weak self] (response) in + switch response { + case .success(let data) : + if let response = data as? HomeDataModel { + self?.homeData = response.data + self?.setMainillust() + if response.data.isEmpty { + self?.isNoEventLayout() + self?.noEventLabel.isHidden = false + self?.noEventImageView.isHidden = false + } else { + self?.noEventLabel.isHidden = true + self?.noEventImageView.isHidden = true + } + self?.collectionView.reloadData() + } + case .requestErr(let message) : + print("requestERR") + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + private func getLastPlansCount() { + GetLastDateService.shared.getLastPlans(year: Date.getCurrentYear(), month: Date.getCurrentMonth(), day: Date.getCurrentDate()) { [weak self](response) in + switch response { + case .success(let data) : + if let response = data as? LastDateDataModel { + self?.lastEventDate = response.data.date + self?.setMainillust() + } + case .requestErr(let message) : + print("requestERR") + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + // private func requestFriendsList() { + // GetFriendsListService.shared.getFriendsList() { response in + // switch response { + // case .success(let data) : + // if let response = data as? FriendsDataModel { + // self.friendsListData = response.data + // self.setMainillust() + // } + // case .requestErr(let message) : + // print("requestERR") + // case .pathErr : + // print("pathERR") + // case .serverErr: + // print("serverERR") + // case .networkFail: + // print("networkFail") + // } + // } + // } +} + +// MARK: - Extensions + +extension HomeVC: NameButtonTappedDelegate{ + func nameButtonTapped() { + if !UserDefaults.standard.bool(forKey: Constants.UserDefaultsKey.isLogin) { + let popUp = SMPopUpVC(withType: .needLogin).then { + $0.modalPresentationStyle = .overFullScreen + $0.pinkButtonCompletion = { [weak self] in + self?.dismiss(animated: false, completion: nil) + self?.tabBarController?.tabBar.isHidden = true + self?.delegate?.loginButtonDidTap() + } + } + present(popUp, animated: false, completion: nil) + }else{ + delegate?.nameButtonDidTap() + } + } +} + +extension HomeVC: UICollectionViewDelegate{ + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let cellHeight = userHeight * 0.3 + let cellWidth = userWidth * 0.4 + return CGSize(width: cellWidth, height: cellHeight) + } +} + +extension HomeVC: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + if let homeData = homeData { + if homeData.isEmpty { + return 0 + } else { + if !isPrevious(date: homeData[section].date) { + return homeData.count + } else { + isNoEventLayout() + return 0 + } + } + } else { + return 0 + } + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeEventCVC.identifier, for: indexPath) as? HomeEventCVC, + let homeData = homeData else { return UICollectionViewCell() } + + let dDayDate = String.getTimeIntervalString(from: homeData[indexPath.row].date, by: "yyyy-MM-dd") + let eventDate = homeData[indexPath.row].date.components(separatedBy: "-") + let event = eventDate[1] + "-" + eventDate[2] + var imageName = "" + + if Int(homeData[indexPath.row].count) ?? 0 - 1 > 2 { + imageName = "img_illust_3" + } else { + imageName = "img_illust_2" + } + cell.setData(dDay: "D-" + dDayDate, image: imageName, eventName: homeData[indexPath.row].invitationTitle, eventData: event) + + return cell + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + + } +} + +extension HomeVC: UICollectionViewDelegateFlowLayout{ + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + 10 * widthRatio + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + 0 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + UIEdgeInsets(top: 0, left: 20 * widthRatio, bottom: 0, right: 0) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/LoginScene/EmailLoginVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/LoginScene/EmailLoginVC.swift new file mode 100644 index 0000000..c115906 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/LoginScene/EmailLoginVC.swift @@ -0,0 +1,300 @@ +import UIKit +import SnapKit +import Then +import SwiftUI + +protocol EmailLoginVCDelegate{ + func backButtonDidTap() + func emailRegisterDidTap() + func closeButtonDidTap() + func loginCompleted() +} +class EmailLoginVC: UIViewController { + + // MARK: - UI Components + + private let navigationBarView = UIView() + + private let navigationTitleLabel = UILabel().then{ + $0.textColor = .black + $0.font = UIFont.hanSansBoldFont(ofSize: 18) + $0.text = "이메일로 로그인" + } + + private let backButton = UIButton().then{ + $0.setImage(UIImage(named: "btn_back"), for: .normal) + } + + private let closeButton = UIButton().then{ + $0.setImage(UIImage(named: "btn_close_bold"), for: .normal) + + } + + private let emailTextView = GrayTextView(type: .loginEmail) + + private let emailTextField = GrayTextField(type: .email, placeHolder: "이메일").then{ + $0.tag = 1 + } + + private let passwordTextView = GrayTextView(type: .loginPassword) + + private let passwordTextField = GrayTextField(type: .password, placeHolder: "비밀번호").then{ + $0.tag = 2 + } + + private let passwordSeeButton = UIButton().then{ + $0.setBackgroundImage(UIImage(named: "ic_password_notsee"), for: .normal) + $0.isHidden = true + } + + private let loginButton = UIButton().then{ + $0.backgroundColor = UIColor.grey04 + $0.setTitle("로그인", for: .normal) + $0.setTitleColor(UIColor.white, for: .normal) + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.isEnabled = false + } + + private let registerLabel = UILabel().then{ + $0.text = "아직 회원이 아니신가요?" + $0.font = UIFont.hanSansRegularFont(ofSize: 14) + $0.textColor = UIColor.grey05 + } + + private let registerButton = UIButton().then{ + $0.titleLabel?.font = UIFont.hanSansRegularFont(ofSize: 14) + $0.setTitleColor(UIColor.grey05, for: .normal) + let attribute = [NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue] + let attributedString = NSAttributedString(string: "회원가입", attributes: attribute) + $0.setAttributedTitle(attributedString, for: .normal) + } + + // MARK: - Properties + + weak var coordinator: Coordinator? + + //원래 비밀번호 상태 true가 디폴트 + private var isNotSee: Bool = true + private var isFull: Bool = false + var delegate: EmailLoginVCDelegate? + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setLoginLayout() + configUI() + } + + // MARK: - Layout + + private func setLoginLayout(){ + self.navigationController?.isNavigationBarHidden = true + view.backgroundColor = UIColor.white + view.addSubviews([navigationBarView, + emailTextView, + passwordTextView, + loginButton, + registerLabel, + registerButton]) + navigationBarView.addSubviews([backButton, + navigationTitleLabel, + closeButton]) + emailTextView.addSubview(emailTextField) + passwordTextView.addSubviews([passwordTextField, + passwordSeeButton]) + + passwordTextField.delegate = self + emailTextField.delegate = self + + view.dismissKeyboardWhenTappedAround() + + navigationBarView.snp.makeConstraints{ + $0.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + $0.height.equalTo(58 * heightRatio) + } + + backButton.snp.makeConstraints{ + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(2 * widthRatio) + $0.width.height.equalTo(48 * widthRatio) + } + + closeButton.snp.makeConstraints{ + $0.centerY.equalToSuperview() + $0.trailing.equalToSuperview().offset(-4 * widthRatio) + $0.width.height.equalTo(48 * widthRatio) + } + + navigationTitleLabel.snp.makeConstraints{ + $0.centerX.centerY.equalToSuperview() + } + + emailTextView.snp.makeConstraints{ + $0.top.equalTo(navigationBarView.snp.bottom).offset(32 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.height.equalTo(48 * heightRatio) + } + + emailTextField.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(52 * widthRatio) + $0.top.bottom.equalTo(0) + $0.trailing.equalToSuperview().offset(-5 * widthRatio) + } + + passwordSeeButton.snp.makeConstraints{ + $0.trailing.top.bottom.equalToSuperview() + $0.width.equalTo(48 * widthRatio) + } + + passwordTextView.snp.makeConstraints{ + $0.top.equalTo(emailTextView.snp.bottom).offset(14 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.height.equalTo(48 * heightRatio) + } + + passwordTextField.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(52 * widthRatio) + $0.top.bottom.equalTo(0) + $0.trailing.equalToSuperview().offset(-5 * widthRatio) + } + + loginButton.snp.makeConstraints{ + $0.top.equalTo(passwordTextView.snp.bottom).offset(40 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.height.equalTo(54 * heightRatio) + } + registerLabel.snp.makeConstraints{ + $0.top.equalTo(loginButton.snp.bottom).offset(32 * heightRatio) + $0.centerX.equalToSuperview().offset(-30 * widthRatio) + } + registerButton.snp.makeConstraints{ + $0.leading.equalTo(registerLabel.snp.trailing).offset(14 * widthRatio) + $0.centerY.equalTo(registerLabel) + } + } + + // MARK: - Custom Methods + + private func isValidEmail(testStr:String) -> Bool { + let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,50}" + let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx) + return emailTest.evaluate(with: testStr) + } + + private func configUI() { + backButton.addTarget(self, action: #selector(backButtonClicked(_:)), for: .touchUpInside) + closeButton.addTarget(self, action: #selector(closeButtonClicked(_:)), for: .touchUpInside) + emailTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) + passwordTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) + passwordSeeButton.addTarget(self, action: #selector(notSeeButtonClicked(_:)), for: .touchUpInside) + loginButton.addTarget(self, action: #selector(loginButtonClicked(_:)), for: .touchUpInside) + registerButton.addTarget(self, action: #selector(registerButtonClicked(_:)), for: .touchUpInside) + } + + // MARK: - Actions + + @objc func notSeeButtonClicked(_ sender: UIButton) { + if isNotSee == true { + isNotSee = false + passwordSeeButton.setBackgroundImage(UIImage(named: "ic_password_see"), for: .normal) + passwordTextField.isSecureTextEntry = isNotSee + } + else { + isNotSee = true + passwordSeeButton.setBackgroundImage(UIImage(named: "ic_password_notsee"), for: .normal) + passwordTextField.isSecureTextEntry = isNotSee + } + } + + //이메일로그인 서버 완성된 후 전체적인 함수 점검하기 + @objc private func loginButtonClicked(_ sender: UIButton){ + if isFull == true { + PostLoginService.shared.login(email: emailTextField.text ?? "", password: passwordTextField.text ?? ""){ (response) in + switch(response) { + case .success(let success): + if let success = success as? EmailLoginResponseModel { + if success.status == 404 { + self.view.makeToastAnimation(message: success.message) + } + else { + UserDefaults.standard.set(true, forKey: Constants.UserDefaultsKey.isLogin) + UserDefaults.standard.set(success.data?.user.username, forKey: Constants.UserDefaultsKey.userName) + UserDefaults.standard.set(success.data?.user.email, forKey: Constants.UserDefaultsKey.userEmail) + UserDefaults.standard.set(success.data?.user.nickname, forKey: Constants.UserDefaultsKey.userNickname) + NotificationCenter.default.post(name: NSNotification.Name.PushNotificationDidSet, object: success.data?.user.push) + guard let accessToken = success.data?.accesstoken as? String, let refreshToken = success.data?.refreshtoken as? String else { return } + TokenUtils.shared.create(account: "accessToken", value: accessToken) + TokenUtils.shared.create(account: "refreshToken", value: refreshToken) + UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.makeToastAnimation(message: "로그인이 완료되었습니다.") + self.delegate?.loginCompleted() + } + } + case .requestErr(let message) : + + if message as? String == "이메일이 유효하지 않습니다."{ + self.view.makeToastAnimation(message: "등록되지 않은 유저입니다.") + } + if message as? String == "로그인 실패"{ + self.view.makeToastAnimation(message: "비밀번호가 틀렸습니다.") + } + + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + } + + @objc func backButtonClicked(_ sender: UIButton) { + self.navigationController?.popViewController(animated: true) + } + + @objc func closeButtonClicked(_ sender: UIButton){ + delegate?.closeButtonDidTap() + } + + @objc private func registerButtonClicked(_ sender: UIButton){ + delegate?.emailRegisterDidTap() + } +} + +// MARK: - Extensions + +extension EmailLoginVC: UITextFieldDelegate{ + func textFieldDidBeginEditing(_ textField: UITextField) { + if textField.tag == 2{ + passwordSeeButton.isHidden = false + } + + } + func textFieldDidEndEditing(_ textField: UITextField) { + if textField.tag == 2{ + if textField.text?.isEmpty == true{ + passwordSeeButton.isHidden = true + } + } + } + @objc func textFieldDidChange(_ sender: Any?) { + if passwordTextField.text?.isEmpty == false && isValidEmail(testStr: emailTextField.text ?? ""){ + if passwordTextField.text?.count ?? 0 >= 8 { + loginButton.backgroundColor = UIColor.pink01 + loginButton.isEnabled = true + isFull = true + } + } + else { + isFull = false + loginButton.isEnabled = false + loginButton.backgroundColor = UIColor.grey04 + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/LoginScene/MainLoginVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/LoginScene/MainLoginVC.swift new file mode 100644 index 0000000..5f60d11 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/LoginScene/MainLoginVC.swift @@ -0,0 +1,338 @@ +// +// MainLoginVC.swift +// SeeMeet +// +// Created by 이유진 on 2022/03/06. +// + +import UIKit +import SnapKit +import AuthenticationServices +import Kingfisher + +protocol MainLoginVCDelegate { + func loginCompleted() + func needRegister(accessToken: String, refreshToken: String, name: String?, isAppleLogin: Bool) + func backButtonDidTap() + func emailRegisterDidTap() + func emailLoginDidTap() +} + +class MainLoginVC: UIViewController { + + // MARK: - UI Components + + private let navigationBarView = UIView() + + private let backButton = UIButton().then{ + $0.setImage(UIImage(named: "btn_back"), for: .normal) + } + + private let logoImageView = UIImageView().then { + $0.image = UIImage(named: "img_seemeet_logo") + } + + private let kakaoLoginButton = UIButton().then{ + $0.setImage(UIImage(named: "img_logo_kakao"), for: .normal) + $0.backgroundColor = UIColor.kakaoyellow + $0.setTitle("카카오로 계속하기", for: .normal) + $0.setTitleColor(UIColor.snslogoblack, for: .normal) + $0.titleLabel?.font = UIFont.hanSansBoldFont(ofSize: 16) + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.imageEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: 10 * widthRatio) + $0.titleEdgeInsets = .init(top: 0, left: 30 * widthRatio, bottom: 0, right: 20 * widthRatio) + } + + private let appleLoginButton = UIButton().then{ + $0.setImage(UIImage(named: "img_logo_apple"), for: .normal) + $0.backgroundColor = UIColor.white + $0.setTitle("Apple로 계속하기", for: .normal) + $0.setTitleColor(UIColor.snslogoblack, for: .normal) + $0.titleLabel?.font = UIFont.hanSansBoldFont(ofSize: 16) + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.layer.borderWidth = 1.0 + $0.layer.borderColor = UIColor.black.cgColor + $0.imageEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: 5) + $0.adjustsImageWhenHighlighted = false // 버튼 클릭하면 이미지의 하얀부분 회색되는거 없애기 + } + + private let emailLoginButton = UIButton().then{ + $0.setTitleColor(UIColor.grey05, for: .normal) + $0.titleLabel?.font = UIFont.hanSansRegularFont(ofSize: 14) + let underlineAttribute = [NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue] + let underlineAttributedString = NSAttributedString(string: "이메일로 로그인", attributes: underlineAttribute) + $0.setAttributedTitle(underlineAttributedString, for: .normal) + } + + private let emailJoinButton = UIButton().then{ + $0.setTitleColor(UIColor.grey05, for: .normal) + $0.titleLabel?.font = UIFont.hanSansRegularFont(ofSize: 14) + let underlineAttribute = [NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue] + let underlineAttributedString = NSAttributedString(string: "이메일로 가입", attributes: underlineAttribute) + $0.setAttributedTitle(underlineAttributedString, for: .normal) + } + + // MARK: Properties + + weak var coordinator: Coordinator? + var delegate: MainLoginVCDelegate? + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setAutoLayouts() + configUI() + } + + // MARK: - Layout + + private func setAutoLayouts() { + self.navigationController?.isNavigationBarHidden = true + view.backgroundColor = UIColor.white + view.addSubviews([navigationBarView, + logoImageView, + kakaoLoginButton, + appleLoginButton, + emailLoginButton, + emailJoinButton]) + navigationBarView.addSubview(backButton) + + navigationBarView.snp.makeConstraints { + $0.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + $0.height.equalTo(58) + } + + backButton.snp.makeConstraints { + $0.top.equalToSuperview().offset(5 * heightRatio) + $0.leading.equalToSuperview().offset(2 * widthRatio) + $0.width.height.equalTo(48 * widthRatio) + } + + logoImageView.snp.makeConstraints { + $0.top.equalTo(navigationBarView.snp.bottom).offset(48 * heightRatio) + $0.centerX.equalToSuperview() + $0.width.height.equalTo(158 * widthRatio) + } + + kakaoLoginButton.snp.makeConstraints { + $0.top.equalTo(logoImageView.snp.bottom).offset(48 * heightRatio) + $0.centerX.equalToSuperview() + $0.width.equalTo(335 * widthRatio) + $0.height.equalTo(54 * heightRatio) + } + + appleLoginButton.snp.makeConstraints { + $0.top.equalTo(kakaoLoginButton.snp.bottom).offset(12 * heightRatio) + $0.centerX.equalToSuperview() + $0.width.equalTo(335 * widthRatio) + $0.height.equalTo(54 * heightRatio) + } + + emailJoinButton.snp.makeConstraints{ + $0.top.equalTo(appleLoginButton.snp.bottom).offset(24 * heightRatio) + $0.centerX.equalToSuperview().offset(-60 * widthRatio) + } + + emailLoginButton.snp.makeConstraints{ + $0.leading.equalTo(emailJoinButton.snp.trailing).offset(32 * widthRatio) + $0.centerY.equalTo(emailJoinButton) + } + } + + private func configUI() { + backButton.addTarget(self, action: #selector(backButtonClicked(_:)), for: .touchUpInside) + appleLoginButton.addTarget(self, action: #selector(appleLoginButtonClicked(_:)), for: .touchUpInside) + emailLoginButton.addTarget(self, action: #selector(emailLoginButtonClicked(_:)), for: .touchUpInside) + emailJoinButton.addTarget(self, action: #selector(emailJoinButtonClicked(_:)), for: .touchUpInside) + kakaoLoginButton.addTarget(self, action: #selector(kakaoButtonDidTap(_:)), for: .touchUpInside) + } + + // MARK: - Custom Method + + private func getAuthFromApple(){ + let appleIDProvider = ASAuthorizationAppleIDProvider() + let requestToApple = appleIDProvider.createRequest() + requestToApple.requestedScopes = [.fullName, .email] + + let authorizationController = ASAuthorizationController(authorizationRequests: [requestToApple]) + + authorizationController.delegate = self + authorizationController.presentationContextProvider = self + authorizationController.performRequests() + } + + // MARK: - Actions + + @objc private func backButtonClicked(_ sender: UIButton) { + delegate?.backButtonDidTap() + } + + @available(iOS 13.0, *) + @objc private func appleLoginButtonClicked(_ sender: UIButton){ + getAuthFromApple() + } + + @objc private func emailLoginButtonClicked(_ sender: UIButton){ + delegate?.emailLoginDidTap() + } + + @objc private func emailJoinButtonClicked(_ sender: UIButton) { + delegate?.emailRegisterDidTap() + } + + // MARK: - Network + + @objc private func kakaoButtonDidTap(_ sender: UIButton) { + KakaoAuthService.shared.login { responseData in + switch responseData { + case .success(let success): + if let success = success as? SocialLoginDataModel { + if 400...499 ~= success.status { + self.view.makeToastAnimation(message: success.message) + } else { + guard let userData = success.data?.user, + let accessToken = success.data?.accesstoken, + let refreshToken = success.data?.refreshtoken else { return } + + if let nickName = userData.nickname { // nickname값이 있으면 기존에 가입된 아이디 -> 바로 로그인 처리 + UserDefaults.standard.do { + $0.set(userData.email, forKey: Constants.UserDefaultsKey.userEmail) + $0.set(userData.username, forKey: Constants.UserDefaultsKey.userName) + $0.set(userData.nickname, forKey: Constants.UserDefaultsKey.userNickname) + $0.set(true, forKey: Constants.UserDefaultsKey.isLogin) + } + + NotificationCenter.default.post(name: NSNotification.Name.PushNotificationDidSet, object: userData.push) + + TokenUtils.shared.create(account: "accessToken", value: accessToken) + TokenUtils.shared.create(account: "refreshToken", value: refreshToken) + + + if let url = userData.imgLink{ + if let url = URL(string: url){ + let resource = ImageResource(downloadURL: url) + KingfisherManager.shared.retrieveImage(with: resource){ result in + switch result{ + case .success(let value): + ImageManager.shared.saveImage(image: value.image, named: "profile.png") + case .failure(let error): + print(error) + } + } + } + } + print("accessToken: ", accessToken) + self.delegate?.loginCompleted() + } else { // nickname값이 없으면 최초 가입시켜야 한다 + self.delegate?.needRegister(accessToken: accessToken, refreshToken: refreshToken, name: userData.username, isAppleLogin: false) +// self.putNameAndId(userData: userData, accessToken: accessToken, refreshToken: refreshToken) + } + } + } + case .pathErr: + print("pathErr") + case .requestErr(let message): + print("requestERR", message) + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + } + } + } +} + +// MARK: - Extension + +extension MainLoginVC: ASAuthorizationControllerDelegate { + + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + + if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { + let userIdentifier = appleIDCredential.user + dump(appleIDCredential) + let fullName = appleIDCredential.fullName + var token = String() + var name = String() + if let identityToken = appleIDCredential.identityToken, + let authorizationToken = appleIDCredential.authorizationCode, + let tokenString = String(data: identityToken, encoding: .utf8) { + token = tokenString + print("authorizationToken: \(String(data: authorizationToken, encoding: .utf8))") + print("tokenString: \(tokenString)") } + + if let lastName = fullName?.familyName, + let firstName = fullName?.givenName { + name = "\(lastName)\(firstName)" + } + AppleAuthService.shared.login(name: name, token: token) { responseData in + switch responseData { + case .success(let success): + if let success = success as? SocialLoginDataModel { + if success.status == 404 { + self.view.makeToastAnimation(message: success.message) + } else { + guard let userData = success.data?.user, + let accessToken = success.data?.accesstoken, + let refreshToken = success.data?.refreshtoken else { return } + + if let nickName = userData.nickname { // nickname값이 있으면 기존에 가입된 아이디 -> 바로 로그인 처리 + UserDefaults.standard.do { + $0.set(userData.email, forKey: Constants.UserDefaultsKey.userEmail) + $0.set(userData.username, forKey: Constants.UserDefaultsKey.userName) + $0.set(userData.nickname, forKey: Constants.UserDefaultsKey.userNickname) + $0.set(true, forKey: Constants.UserDefaultsKey.isLogin) + $0.set(true,forKey: Constants.UserDefaultsKey.isAppleLogin) + $0.set(userIdentifier,forKey: Constants.UserDefaultsKey.userAppleID) + } + NotificationCenter.default.post(name: NSNotification.Name.PushNotificationDidSet, object: userData.push) + + TokenUtils.shared.create(account: "accessToken", value: accessToken) + TokenUtils.shared.create(account: "refreshToken", value: refreshToken) + if let url = userData.imgLink{ + if let url = URL(string: url){ + let resource = ImageResource(downloadURL: url) + KingfisherManager.shared.retrieveImage(with: resource){ result in + switch result{ + case .success(let value): + ImageManager.shared.saveImage(image: value.image, named: "profile.png") + case .failure(let error): + print(error) + } + } + } + } + self.delegate?.loginCompleted() + + } else { // nickname값이 없으면 최초 가입시켜야 한다 + self.delegate?.needRegister(accessToken: accessToken, refreshToken: refreshToken, name: userData.username, isAppleLogin: true) + } + } + } + case .pathErr: + print("pathErr") + case .requestErr(let message): + print("requestERR", message) + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + } + } + } + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { + print("error \(error)") + } +} + +extension MainLoginVC: ASAuthorizationControllerPresentationContextProviding { // 애플계정 인증 창을 띄울 화면 설정 + @available(iOS 13.0, *) + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return self.view.window! + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/MyPageScene/ChangePasswordVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/MyPageScene/ChangePasswordVC.swift new file mode 100644 index 0000000..3af25d2 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/MyPageScene/ChangePasswordVC.swift @@ -0,0 +1,342 @@ +// +// ChangePasswordVC.swift +// SeeMeet +// +// Created by 이유진 on 2022/06/05. +// + +import UIKit + +protocol ChangePasswordVCDelegate { + func backButtonDidTap() + func changePasswordCompleted() +} + +class ChangePasswordVC: UIViewController { + + weak var coordinator: Coordinator? + var delegate: ChangePasswordVCDelegate? + + private let navigationBarView = UIView().then{ + $0.backgroundColor = .white + } + + private let backButton = UIButton().then{ + $0.setImage(UIImage(named: "btn_back"), for: .normal) + } + + private let newPasswordLabel = UILabel().then{ + $0.textColor = .black + $0.font = UIFont.hanSansBoldFont(ofSize: 16) + $0.text = "새 비밀번호" + } + + private let newPasswordTextField = UITextField().then{ + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.isSecureTextEntry = true + $0.addRightPadding(width: 50) + } + private let seePasswordButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "ic_password_notsee"), for: .normal) + } + private let newPasswordUnderLineView = UIView().then{ + $0.backgroundColor = UIColor.grey02 + } + private let passwordWarningLabel = UILabel().then { + $0.setAttributedText(defaultText: " ", font: UIFont.hanSansRegularFont(ofSize: 12), color: UIColor.red, kernValue: -0.6) + } + private let passwordCheckLabel = UILabel().then{ + $0.textColor = .black + $0.font = UIFont.hanSansBoldFont(ofSize: 16) + $0.text = "비밀번호 확인" + } + + private let passwordCheckTextField = UITextField().then{ + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.isSecureTextEntry = true + $0.addRightPadding(width: 50) + + } + private let seePasswordCheckButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "ic_password_notsee"), for: .normal) + } + + private let passwordCheckUnderLineView = UIView().then{ + $0.backgroundColor = UIColor.grey02 + } + + private let passwordCheckWarningLabel = UILabel().then { + $0.setAttributedText(defaultText: " ", font: UIFont.hanSansRegularFont(ofSize: 12), color: UIColor.red, kernValue: -0.6) + } + + private let saveButton = UIButton().then{ + $0.setTitle("저장", for: .normal) + $0.titleLabel?.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.setTitleColor(UIColor.grey06, for: .normal) + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.layer.borderColor = UIColor.grey06.cgColor + $0.layer.borderWidth = 1 + $0.isEnabled = false + } + + private let cancelButton = UIButton().then{ + $0.setTitle("취소", for: .normal) + $0.titleLabel?.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.setTitleColor(UIColor.grey03, for: .normal) + $0.isHidden = true + } + + override func viewDidLoad() { + super.viewDidLoad() + setLayout() + setGesture() + setDelegate() + } + + private func setDelegate() { + newPasswordTextField.delegate = self + passwordCheckTextField.delegate = self + } + private func setGesture(){ + backButton.addTarget(self, action: #selector(backButtonDidTap(_:)), for: .touchUpInside) + seePasswordButton.addTarget(self, action: #selector(seePasswordButtonClicked(_:)), for: .touchUpInside) + seePasswordCheckButton.addTarget(self, action: #selector(seePasswordCheckButtonClicked(_:)), for: .touchUpInside) + newPasswordTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) + passwordCheckTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) + saveButton.addTarget(self, action: #selector(saveButtonDidTap(_:)), for: .touchUpInside) + } + + @objc private func backButtonDidTap(_ sender: UIButton){ + self.navigationController?.popViewController(animated: true) + } + @objc private func saveButtonDidTap(_ sender: UIButton){ + putPassword() + } + @objc func seePasswordButtonClicked(_ sender: UIButton) { + if !newPasswordTextField.isSecureTextEntry{ + seePasswordButton.setBackgroundImage(UIImage(named: "ic_password_notsee"), for: .normal) + newPasswordTextField.isSecureTextEntry.toggle() + } + else{ + seePasswordButton.setBackgroundImage(UIImage(named: "ic_password_see"), for: .normal) + newPasswordTextField.isSecureTextEntry.toggle() + } + } + @objc func seePasswordCheckButtonClicked(_ sender: UIButton) { + if !passwordCheckTextField.isSecureTextEntry{ + seePasswordCheckButton.setBackgroundImage(UIImage(named: "ic_password_notsee"), for: .normal) + passwordCheckTextField.isSecureTextEntry.toggle() + } + else{ + seePasswordCheckButton.setBackgroundImage(UIImage(named: "ic_password_see"), for: .normal) + passwordCheckTextField.isSecureTextEntry.toggle() + } + } + + private func isValidPassword(testStr:String, regEx: [String]) -> [Bool]{ + var passwordStatus: [Bool] = [] + for reg in regEx{ + let passwordRegEx = reg + let passwordTest = NSPredicate(format:"SELF MATCHES %@", passwordRegEx) + passwordStatus.append(passwordTest.evaluate(with: testStr)) + } + return passwordStatus + } + private func setLayout() { + self.navigationController?.isNavigationBarHidden = true +// self.tabBarController?.tabBar.isHidden = true + + view.addSubviews([navigationBarView,newPasswordLabel,newPasswordTextField,newPasswordUnderLineView,passwordWarningLabel,passwordCheckLabel,passwordCheckTextField,passwordCheckUnderLineView,passwordCheckWarningLabel,saveButton,cancelButton]) + navigationBarView.addSubview(backButton) + newPasswordTextField.addSubview(seePasswordButton) + passwordCheckTextField.addSubview(seePasswordCheckButton) + + navigationBarView.snp.makeConstraints{ + $0.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + $0.height.equalTo(58) + } + backButton.snp.makeConstraints{ + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(2) + } + newPasswordLabel.snp.makeConstraints{ + $0.top.equalTo(navigationBarView.snp.bottom).offset(39) + $0.leading.equalToSuperview().offset(20) + $0.width.equalTo(92) + } + newPasswordTextField.snp.makeConstraints{ + $0.leading.equalTo(newPasswordLabel.snp.trailing).offset(18) + $0.trailing.equalToSuperview().offset(-20) + $0.centerY.equalTo(newPasswordLabel) + } + seePasswordButton.snp.makeConstraints{ + $0.trailing.equalToSuperview() + $0.centerY.equalToSuperview() + $0.width.height.equalTo(48) + } + newPasswordUnderLineView.snp.makeConstraints{ + $0.top.equalTo(newPasswordTextField.snp.bottom) + $0.leading.trailing.equalTo(newPasswordTextField) + $0.height.equalTo(1) + } + passwordWarningLabel.snp.makeConstraints{ + $0.top.equalTo(newPasswordUnderLineView.snp.bottom).offset(3) + $0.leading.equalTo(newPasswordUnderLineView) + } + passwordCheckLabel.snp.makeConstraints{ + $0.top.equalTo(newPasswordLabel.snp.bottom).offset(32) + $0.leading.equalToSuperview().offset(20) + $0.width.equalTo(92) + } + passwordCheckTextField.snp.makeConstraints{ + $0.leading.equalTo(passwordCheckLabel.snp.trailing).offset(18) + $0.trailing.equalToSuperview().offset(-20) + $0.centerY.equalTo(passwordCheckLabel) + } + seePasswordCheckButton.snp.makeConstraints{ + $0.trailing.equalToSuperview() + $0.centerY.equalToSuperview() + $0.width.height.equalTo(48) + } + passwordCheckUnderLineView.snp.makeConstraints{ + $0.top.equalTo(passwordCheckTextField.snp.bottom) + $0.leading.trailing.equalTo(passwordCheckTextField) + $0.height.equalTo(1) + } + passwordCheckWarningLabel.snp.makeConstraints{ + $0.top.equalTo(passwordCheckUnderLineView.snp.bottom).offset(3) + $0.leading.equalTo(passwordCheckUnderLineView) + } + saveButton.snp.makeConstraints{ + $0.trailing.equalToSuperview().offset(-20) + $0.top.equalTo(passwordCheckUnderLineView.snp.bottom).offset(18) + $0.width.equalTo(60) + $0.height.equalTo(40) + } + cancelButton.snp.makeConstraints{ + $0.trailing.equalTo(saveButton.snp.leading).offset(-20) + $0.centerY.equalTo(saveButton) + } + } + + private func putPassword(){ + guard let accessToken = TokenUtils.shared.read(account: "accessToken"), + let password = passwordCheckTextField.text + else{return} + + PutPasswordService.shared.putPassword(password: password) { [weak self] result in + + switch result { + case .success(let success): + print("비밀번호 변경 성공") + UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.makeToastAnimation(message: "비밀번호가 변경되었습니다.") + self?.delegate?.changePasswordCompleted() + case .requestErr(let error): + print("requestErr") + case .pathErr: + print("pathErr") + case .networkFail: + print("networkFail") + case .serverErr: + print("serverErr") + } + } + + } + + +} + +extension ChangePasswordVC: UITextFieldDelegate{ + + func textFieldDidEndEditing(_ textField: UITextField) { + switch textField{ + case newPasswordTextField: + let regList: [String] = ["^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[!@#$%^&*()_+=-]).{8,16}", "^(?=.*[A-Za-z])(?=.*[0-9]).{8,16}", "^(?=.*[A-Za-z])(?=.*[!@#$%^&*()_+=-]).{8,16}", "^(?=.*[0-9])(?=.*[!@#$%^&*()_+=-]).{8,16}"] + var regiBoolList: [Bool] = [] + var regiCount = 0 + regiBoolList = isValidPassword(testStr: newPasswordTextField.text ?? "", regEx: regList) + for valid in regiBoolList{ + if valid { + regiCount += 1 + } + } + if newPasswordTextField.text?.count ?? 0 < 8 { + passwordWarningLabel.text = "8자 이상의 비밀번호를 입력해주세요." + + }else if regiCount < 1{ + passwordWarningLabel.text = "영문, 숫자, 특수문자 중 2가지 이상을 사용해주세요." + + } else{ + passwordWarningLabel.text = "" + saveButton.isEnabled = true + } + case passwordCheckTextField: + if newPasswordTextField.text != passwordCheckTextField.text { + passwordCheckWarningLabel.text = "비밀번호가 일치하지 않아요." + saveButton.isEnabled = false + } + else{ + passwordCheckWarningLabel.text = "" + saveButton.isEnabled = true + } + default: + print("default") + } + + } + + @objc func textFieldDidChange(_ sender: Any?) { + + let regList: [String] = ["^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[!@#$%^&*()_+=-]).{8,16}", "^(?=.*[A-Za-z])(?=.*[0-9]).{8,16}", "^(?=.*[A-Za-z])(?=.*[!@#$%^&*()_+=-]).{8,16}", "^(?=.*[0-9])(?=.*[!@#$%^&*()_+=-]).{8,16}"] + var regiBoolList: [Bool] = [] + var regiCount = 0 + regiBoolList = isValidPassword(testStr: newPasswordTextField.text ?? "", regEx: regList) + for valid in regiBoolList{ + if valid { + regiCount += 1 + } + } + + if newPasswordTextField.text?.count ?? 0 < 8 { + passwordWarningLabel.text = "8자 이상의 비밀번호를 입력해주세요." + }else if regiCount < 1{ + passwordWarningLabel.text = "영문, 숫자, 특수문자 중 2가지 이상을 사용해주세요." + + } else{ + passwordWarningLabel.text = "" + saveButton.isEnabled = true + } + + if newPasswordTextField.text != passwordCheckTextField.text { + passwordCheckWarningLabel.text = "비밀번호가 일치하지 않아요." + saveButton.isEnabled = false + } + else{ + passwordCheckWarningLabel.text = "" + saveButton.isEnabled = true + } + + + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + switch textField{ + case newPasswordTextField, passwordCheckTextField: + if let char = string.cString(using: String.Encoding.utf8) { + let isBackSpace = strcmp(char, "\\b") + if isBackSpace == -92 { + return true + } + } + guard textField.text!.count < 16 else { return false } + default: + return true + + } + return true + } +} + + diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/MyPageScene/MyPageView.swift b/SeeMeet/SeeMeet/Source/Views/VCs/MyPageScene/MyPageView.swift new file mode 100644 index 0000000..68745dc --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/MyPageScene/MyPageView.swift @@ -0,0 +1,237 @@ +import UIKit +import RxSwift +import RxCocoa + +protocol NameButtonTappedDelegate{ + func nameButtonTapped() +} + +class MyPageView: UIView { + + // MARK: - UI Components + + let profileImageView = UIImageView().then{ + guard let image = ImageManager.shared.getSavedImage(named: "profile.png") ?? UIImage(named: "img_profile") else {return} + $0.image = image + $0.layer.cornerRadius = 46/2 + $0.clipsToBounds = true + $0.contentMode = .scaleAspectFill + } + private let closeButton = UIButton().then{ + $0.setBackgroundImage(UIImage(named: "btn_close_white"), for: .normal) + } + let nameButton = UIButton().then{ + $0.setTitle("로그인", for: .normal) + $0.titleLabel?.font = UIFont.hanSansBoldFont(ofSize: 20) + $0.setImage(UIImage(named: "ic_mypage_login"), for: .normal) + $0.setTitleColor(UIColor.white, for: .normal) + $0.semanticContentAttribute = .forceRightToLeft + + } + let nicknameLabel = UILabel().then{ + $0.text = "SeeMeet에서 친구와 약속을 잡아보세요!" + $0.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.textColor = UIColor.white + } + private let menuView = UIView().then{ + $0.backgroundColor = .white + } + let noticeButton = UIButton().then{ + $0.setTitle("공지사항", for: .normal) + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 16) + $0.setTitleColor(UIColor.black, for: .normal) + } + private let separateLineView = UIView().then{ + $0.backgroundColor = .grey02 + } + private let pushNotificationLabel = UILabel().then{ + $0.text = "푸시알림" + $0.font = UIFont.hanSansMediumFont(ofSize: 16) + } + + let pushAlarmSwitch = UISwitch().then { + $0.isOn = UserDefaults.standard.bool(forKey: Constants.UserDefaultsKey.isPushNotificationOn) + } + + private let separateLineView2 = UIView().then{ + $0.backgroundColor = .grey02 + } + let termOfServiceButton = UIButton().then{ + $0.setTitle("이용약관", for: .normal) + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.setTitleColor(UIColor.black, for: .normal) + } + let privacyPolicyButton = UIButton().then{ + $0.setTitle("개인정보 정책", for: .normal) + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.setTitleColor(UIColor.black, for: .normal) + } + let openSourceButton = UIButton().then{ + $0.setTitle("오픈소스", for: .normal) + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.setTitleColor(UIColor.black, for: .normal) + } + + // MARK: - Properties + + var nameButtonTappedDelegate: NameButtonTappedDelegate? + + let disposeBag = DisposeBag() + + + // MARK: - Life Cycle + + override init(frame: CGRect) { + super.init(frame: frame) + setupViews() + setupAutoLayouts() + + NotificationCenter.default.rx.notification(Notification.Name.RefreshTokenExpired) + .asDriver(onErrorJustReturn: Notification(name: Notification.Name("Error"), object: nil, userInfo: nil)) + .drive(onNext: { [weak self] noti in + guard let isPushOn = noti.object as? Bool else { return } + self?.pushAlarmSwitch.isOn = isPushOn + }) + .disposed(by: disposeBag) + + NotificationCenter.default.rx.notification(Notification.Name.DidLogout) + .asDriver(onErrorJustReturn: Notification(name: Notification.Name("Error"), object: nil, userInfo: nil)) + .drive(onNext: { [weak self] noti in + guard let image = UIImage(named: "img_profile") else { return } + ImageManager.shared.saveImage(image: image, named: "profile_png") + self?.profileImageView.image = image + self?.nameButton.setTitle("로그인", for: .normal) + self?.nicknameLabel.text = "SeeMeet에서 친구와 약속을 잡아보세요!" + }) + .disposed(by: disposeBag) + + NotificationCenter.default.rx.notification(Notification.Name.PushNotificationDidSet) + .asDriver(onErrorJustReturn: Notification(name: Notification.Name("Error"), object: nil, userInfo: nil)) + .drive(onNext: { [weak self] noti in + guard let isPushOn = noti.object as? Bool else { return } + self?.pushAlarmSwitch.isOn = isPushOn + }) + .disposed(by: disposeBag) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - setLayouts + + private func setupAutoLayouts(){ + addSubviews([profileImageView, closeButton, nameButton, nicknameLabel,menuView]) + menuView.addSubviews([noticeButton,separateLineView,pushNotificationLabel,pushAlarmSwitch,separateLineView2,termOfServiceButton,privacyPolicyButton,openSourceButton]) + self.backgroundColor = UIColor.black + profileImageView.snp.makeConstraints{ + $0.top.equalToSuperview().offset(94 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.width.height.equalTo(46 * heightRatio) + } + closeButton.snp.makeConstraints{ + $0.top.equalToSuperview().offset(48 * heightRatio) + $0.trailing.equalToSuperview().offset(-4 * widthRatio) + $0.width.height.equalTo(48 * widthRatio) + } + nameButton.snp.makeConstraints{ + $0.top.equalTo(profileImageView.snp.bottom).offset(13 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.height.equalTo(48 * heightRatio) + + } + nicknameLabel.snp.makeConstraints{ + $0.top.equalTo(nameButton.snp.bottom).offset(11 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.width.equalTo(300 * widthRatio) + $0.height.equalTo(20 * heightRatio) + } + menuView.snp.makeConstraints{ + $0.top.equalTo(nicknameLabel.snp.bottom).offset(33 * heightRatio) + $0.leading.bottom.trailing.equalToSuperview() + } + noticeButton.snp.makeConstraints{ + $0.top.equalToSuperview().offset(47 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + + } + separateLineView.snp.makeConstraints{ + $0.top.equalTo(noticeButton.titleLabel!.snp.bottom).offset(12 * heightRatio) + $0.leading.trailing.equalToSuperview().inset(20 * widthRatio) + $0.height.equalTo(1) + } + pushNotificationLabel.snp.makeConstraints{ + $0.top.equalTo(separateLineView.snp.bottom).offset(24 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + pushAlarmSwitch.snp.makeConstraints{ + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.centerY.equalTo(pushNotificationLabel) + } + separateLineView2.snp.makeConstraints{ + $0.top.equalTo(pushNotificationLabel.snp.bottom).offset(12 * heightRatio) + $0.leading.trailing.equalToSuperview().inset(20 * widthRatio) + $0.height.equalTo(1) + } + termOfServiceButton.snp.makeConstraints{ + $0.top.equalTo(separateLineView2.snp.bottom).offset(36 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + privacyPolicyButton.snp.makeConstraints{ + $0.top.equalTo(termOfServiceButton.snp.bottom).offset(12 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + openSourceButton.snp.makeConstraints{ + $0.top.equalTo(privacyPolicyButton.snp.bottom).offset(12 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + } + + private func setupViews() { + closeButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + UIView.animate(withDuration: 0.5) { + let yFrame = CGAffineTransform(translationX: -UIScreen.getDeviceWidth() * 0.84, y: 0) + self?.transform = yFrame + } + }) + .disposed(by: disposeBag) + + nameButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + self?.nameButtonTappedDelegate?.nameButtonTapped() + }) + .disposed(by: disposeBag) + + pushAlarmSwitch.rx.isOn + .asDriver() + .drive(onNext: { [weak self] isOn in + self?.postPushNotificationSet(isNotificationOn: isOn) + }) + .disposed(by: disposeBag) + } + + // MARK: - Network + + private func postPushNotificationSet(isNotificationOn: Bool) { + PostPushNotificationSetService.shared.pushSet(isNotificationOn: isNotificationOn) { response in + switch response { + case .success(let data): + if let response = data as? NotificationSetResponseModel { + print("푸시 알림 설정 완료") + } + UserDefaults.standard.set(isNotificationOn, forKey: Constants.UserDefaultsKey.isPushNotificationOn) + case .pathErr: + print("pathError") + case .networkFail: + print("networkFail") + case .requestErr(let err): + print("requestErr") + case .serverErr: + print("serverErr") + } + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/MyPageScene/UserInfoVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/MyPageScene/UserInfoVC.swift new file mode 100644 index 0000000..b269789 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/MyPageScene/UserInfoVC.swift @@ -0,0 +1,624 @@ +// +// UserInfoViewController.swift +// SeeMeet +// +// Created by 이유진 on 2022/06/05. +// + +import UIKit +import AuthenticationServices + +protocol UserInfoVCDelegate{ + func backButtonDidTap() + func changePasswordButtonDidTap() + func withdrawalOKButtonDidTap() + func logoutOKButtonDidTap() +} +class UserInfoVC: UIViewController { + + private var isImageEditing = false + var isInfoEditing = false //이름,아이디 수정 중인 상태인지 flag값 + private var userName = UserDefaults.standard.string(forKey: Constants.UserDefaultsKey.userName) + private var userId = UserDefaults.standard.string(forKey: Constants.UserDefaultsKey.userNickname) + private var isDuplicatedID = false + let imagePicker = UIImagePickerController() + weak var coordinator: Coordinator? + var delegate: MyPageCoordinator? + + private let navigationBarView = UIView().then{ + $0.backgroundColor = .white + } + + private let profileView = UIView().then{ + $0.backgroundColor = .white + $0.getShadowView(color: UIColor.black.cgColor, masksToBounds: false, shadowOffset: CGSize(width: 0, height: 1), shadowRadius: 1, shadowOpacity: 0.25) + } + + + private let backButton = UIButton().then{ + $0.setImage(UIImage(named: "btn_back"), for: .normal) + } + + private let profileImageView = UIImageView().then { + guard let image = ImageManager.shared.getSavedImage(named: "profile.png") ?? UIImage(named: "img_profile") else {return} + $0.image = image + $0.layer.cornerRadius = 50 + $0.clipsToBounds = true + $0.contentMode = .scaleAspectFill + + } + + private let profileImageEditButton = UIButton().then{ + $0.setTitle("프로필사진 편집", for: .normal) + $0.titleLabel?.font = UIFont.hanSansRegularFont(ofSize: 10) + $0.setTitleColor(UIColor.grey06, for: .normal) + $0.layer.cornerRadius = 6 + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor.grey06.cgColor + } + + private let cameraButton = UIButton().then{ + $0.setImage(UIImage(named: "ic_camera"), for: .normal) + $0.isHidden = true + } + + private let nameLabel = UILabel().then{ + $0.textColor = .black + $0.font = UIFont.hanSansBoldFont(ofSize: 16) + $0.text = "이름" + } + + private let nameTextField = UITextField().then{ + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.isEnabled = false + } + private let nameUnderLineView = UIView().then{ + $0.backgroundColor = UIColor.grey02 + $0.isHidden = true + } + + private let idLabel = UILabel().then{ + $0.textColor = .black + $0.font = UIFont.hanSansBoldFont(ofSize: 16) + $0.text = "아이디" + } + + private let idTextField = UITextField().then{ + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.isEnabled = false + } + + private let idUnderLineView = UIView().then{ + $0.backgroundColor = UIColor.grey02 + $0.isHidden = true + } + + private let reviseButton = UIButton().then{ + $0.setTitle("수정", for: .normal) + $0.titleLabel?.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.setTitleColor(UIColor.grey06, for: .normal) + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.layer.borderColor = UIColor.grey06.cgColor + $0.layer.borderWidth = 1 + } + + private let reviseCancelButton = UIButton().then{ + $0.setTitle("취소", for: .normal) + $0.titleLabel?.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.setTitleColor(UIColor.grey03, for: .normal) + $0.isHidden = true + } + + private let changePasswordView = UIView().then{ + $0.backgroundColor = .white + $0.getShadowView(color: UIColor.black.cgColor, masksToBounds: false, shadowOffset: CGSize(width: 0, height: 1), shadowRadius: 1, shadowOpacity: 0.25) + } + + private let changePasswordLabel = UILabel().then{ + $0.textColor = .black + $0.font = UIFont.hanSansMediumFont(ofSize: 16) + $0.text = "비밀번호 변경" + } + + private let changePasswordButton = UIButton().then{ + $0.setImage(UIImage(named: "ic_calendar_right"), for: .normal) + $0.isUserInteractionEnabled = false + } + + private let accountWithdrawalButton = UIButton().then{ + $0.setTitle("회원탈퇴", for: .normal) + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 16) + $0.setTitleColor(UIColor.grey04, for: .normal) + } + + private let logoutButton = UIButton().then{ + $0.setTitle("로그아웃", for: .normal) + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 16) + $0.setTitleColor(UIColor.grey04, for: .normal) + } + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setLayout() + setDelegate() + setGesture() + setTextField() + if(isInfoEditing){ + layoutInfoRevisingOn() + }else{ + layoutInfoRevisingOff() + } + } + override func viewDidAppear(_ animated: Bool) { + if(isInfoEditing){ + layoutInfoRevisingOn() + }else{ + layoutInfoRevisingOff() + } + } + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + self.view.endEditing(true) + } + // MARK: - Custom Method + + + private func setDelegate() { + imagePicker.delegate = self + } + private func setGesture(){ + profileImageEditButton.addTarget(self, action: #selector(profileImageEditButtonDidTap(_:)), for: .touchUpInside) + cameraButton.addTarget(self, action: #selector(cameraButtonDidTap(_:)), for: .touchUpInside) + nameTextField.addTarget(self, action: #selector(nameTextFieldDidChange(_:)), for: .editingChanged) + idTextField.addTarget(self, action: #selector(self.idTextFieldDidChange(_:)), for: .editingChanged) + reviseButton.addTarget(self, action: #selector(reviseButtonDidTap(_:)), for: .touchUpInside) + reviseCancelButton.addTarget(self, action: #selector(reviseCancelButtonDidTap(_:)), for: .touchUpInside) + accountWithdrawalButton.addTarget(self, action: #selector(accountWithdrawalButtonDidTap(_:)), for: .touchUpInside) + logoutButton.addTarget(self, action: #selector(logoutButtonDidTap(_:)), for: .touchUpInside) + backButton.addTarget(self, action: #selector(backButtonDidTap(_:)), for: .touchUpInside) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(changePasswordViewDidTap(_:))) + changePasswordView.addGestureRecognizer(tapGesture) + changePasswordView.isUserInteractionEnabled = true + } + + private func putNameAndID(){ + guard let accessToken = TokenUtils.shared.read(account: "accessToken"), + let name = nameTextField.text, + let id = idTextField.text else { return } + + PutNameAndIdService.shared.putNameAndId(name: name, userId: id, accessToken: accessToken) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let success): + if let data = success as? PutNameAndIdResponseDataModel { + UserDefaults.standard.do { + $0.set(name, forKey: Constants.UserDefaultsKey.userName) + $0.set(id, forKey: Constants.UserDefaultsKey.userNickname) + } + self.toggleInfoRevisingState() +// guard let image = self.profileImageView.image else { return } +// ImageManager.shared.saveImage(image: image, named: "profile.png") + } + case .requestErr(let error): + print("닉네임중복") + self.isDuplicatedID = true + self.view.makeToastAnimation(message: "이미 사용 중이에요") + case .pathErr: + print("pathErr") + case .networkFail: + print("networkFail") + case .serverErr: + print("serverErr") + } + } + + } + + private func postProfileImage(){ + guard let accessToken = TokenUtils.shared.read(account: "accessToken") else {return} + PostProfileImageService.shared.postProfileImage(imageData: profileImageView.image, accessToken: accessToken){ result in + switch result { + case .success(let msg): + guard let image = self.profileImageView.image else {return} + ImageManager.shared.saveImage(image: image, named: "profile.png") + print("success", msg) + DispatchQueue.main.async { + self.cameraButton.isHidden = true + self.profileImageEditButton.setTitle("프로필사진 편집", for: .normal) + } + + + case .requestErr(let msg): + print("requestERR", msg) + case .pathErr: + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + private func setTextField(){ + nameTextField.text = userName + idTextField.text = userId + } + private func toggleImageRevisingState(){ + isImageEditing.toggle() + switch isImageEditing{ + case true: + cameraButton.isHidden = false + profileImageEditButton.setTitle("프로필사진 저장", for: .normal) + + case false: + + postProfileImage() + + cameraButton.isHidden = true + profileImageEditButton.setTitle("프로필사진 편집", for: .normal) + + } + } + private func toggleInfoRevisingState(){ + isInfoEditing.toggle() + switch isInfoEditing{ + case true: + layoutInfoRevisingOn() + case false: + layoutInfoRevisingOff() + } + } + private func layoutInfoRevisingOn(){ + reviseCancelButton.isHidden = false + nameTextField.isEnabled = true + nameUnderLineView.isHidden = false + idTextField.isEnabled = true + idUnderLineView.isHidden = false + reviseButton.setTitle("저장", for: .normal) + } + + private func layoutInfoRevisingOff(){ + reviseCancelButton.isHidden = true + nameTextField.isEnabled = false + nameUnderLineView.isHidden = true + idTextField.isEnabled = false + idUnderLineView.isHidden = true + reviseButton.setTitle("수정", for: .normal) + } + + + private func removeUserDefaults() { + UserDefaults.deleteUserValue() + } + + private func appleAccountWithdrawal() { + let appleIDProvider = ASAuthorizationAppleIDProvider() + let requestToApple = appleIDProvider.createRequest() + requestToApple.requestedScopes = [] + + let authorizationController = ASAuthorizationController(authorizationRequests: [requestToApple]) + + authorizationController.delegate = self +// authorizationController.presentationContextProvider = self + authorizationController.performRequests() + } + // MARK: - Layout + + private func setLayout() { + self.navigationController?.isNavigationBarHidden = true + self.view.backgroundColor = UIColor.grey01 + self.tabBarController?.tabBar.isHidden = true + + view.addSubviews([navigationBarView,profileView,changePasswordView,accountWithdrawalButton,logoutButton]) + navigationBarView.addSubview(backButton) + profileView.addSubviews([profileImageView,profileImageEditButton,cameraButton,nameLabel,nameTextField,nameUnderLineView,idLabel,idTextField,idUnderLineView,reviseButton,reviseCancelButton]) + changePasswordView.addSubviews([changePasswordLabel,changePasswordButton]) + + navigationBarView.snp.makeConstraints{ + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(102) + } + backButton.snp.makeConstraints{ + $0.top.equalToSuperview().offset(49) + $0.leading.equalToSuperview().offset(2) + } + profileView.snp.makeConstraints{ + $0.top.equalTo(navigationBarView.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(309) + } + + profileImageView.snp.makeConstraints{ + $0.top.equalToSuperview().offset(10) + $0.centerX.equalToSuperview() + $0.width.height.equalTo(99) + } + + profileImageEditButton.snp.makeConstraints{ + $0.top.equalTo(profileImageView.snp.bottom).offset(10) + $0.centerX.equalTo(profileImageView.snp.centerX) + $0.height.equalTo(20) + $0.width.equalTo(84) + } + cameraButton.snp.makeConstraints{ + $0.top.equalTo(profileImageView.snp.centerY).offset(25) + $0.leading.equalTo(profileImageView.snp.centerX).offset(25) + } + nameLabel.snp.makeConstraints{ + $0.top.equalToSuperview().offset(155) + $0.leading.equalToSuperview().offset(20) + $0.width.equalTo(60) + } + nameTextField.snp.makeConstraints{ + $0.leading.equalTo(nameLabel.snp.trailing).offset(58) + $0.trailing.equalToSuperview().offset(-20) + $0.centerY.equalTo(nameLabel) + } + nameUnderLineView.snp.makeConstraints{ + $0.top.equalTo(nameTextField.snp.bottom) + $0.leading.trailing.equalTo(nameTextField) + $0.height.equalTo(1) + } + idLabel.snp.makeConstraints{ + $0.top.equalTo(nameLabel.snp.bottom).offset(32) + $0.leading.equalToSuperview().offset(20) + $0.width.equalTo(60) + } + idTextField.snp.makeConstraints{ + $0.leading.equalTo(idLabel.snp.trailing).offset(58) + $0.trailing.equalToSuperview().offset(-20) + $0.centerY.equalTo(idLabel) + } + idUnderLineView.snp.makeConstraints{ + $0.top.equalTo(idTextField.snp.bottom) + $0.leading.trailing.equalTo(idTextField) + $0.height.equalTo(1) + } + reviseButton.snp.makeConstraints{ + $0.trailing.equalToSuperview().offset(-20) + $0.bottom.equalToSuperview().offset(-32) + $0.width.equalTo(60) + $0.height.equalTo(40) + } + reviseCancelButton.snp.makeConstraints{ + $0.trailing.equalTo(reviseButton.snp.leading).offset(-20) + $0.centerY.equalTo(reviseButton) + } + changePasswordView.snp.makeConstraints{ + $0.top.equalTo(profileView.snp.bottom).offset(12) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(52) + } + changePasswordLabel.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(20) + $0.centerY.equalToSuperview() + } + changePasswordButton.snp.makeConstraints{ + $0.trailing.equalToSuperview().offset(-9) + $0.centerY.equalToSuperview() + } + if let useremail = UserDefaults.standard.string(forKey: Constants.UserDefaultsKey.userEmail){ + changePasswordView.isHidden = false//SNS로그인 아닌 경우 + accountWithdrawalButton.snp.makeConstraints{ + $0.top.equalTo(changePasswordView.snp.bottom).offset(56) + $0.centerX.equalToSuperview().offset(-88) + } + + }else{ + changePasswordView.isHidden = true + accountWithdrawalButton.snp.makeConstraints{//SNS로그인인 경우 + $0.top.equalTo(profileView.snp.bottom).offset(56) + $0.centerX.equalToSuperview().offset(-88) + } + } + + + logoutButton.snp.makeConstraints{ + $0.leading.equalTo(accountWithdrawalButton.snp.trailing).offset(113) + $0.centerY.equalTo(accountWithdrawalButton) + } + } + + // MARK: - Network + + private func putWithdrawal(authorizationCode: String? = nil) { + print("authToken: \(authorizationCode)") + PutWithdrawalService.shared.putWithdrawal(authorizationCode: authorizationCode) { response in + switch response { + case .success(let data): + if let response = data as? WithdrawalDataModel { + print(response.status, response.message) + if UserDefaults.standard.string(forKey: "loginBy") == "kakao" { + KakaoAuthService.shared.logout() // 언링크 + } + UserDefaults.standard.removeObject(forKey: "loginBy") + UserDefaults.deleteUserValue() + self.removeUserDefaults() + self.dismiss(animated: false) + self.delegate?.withdrawalOKButtonDidTap() + } + case .requestErr(_): + print("requestERR") + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + + + //MARK: - Action + + @objc private func backButtonDidTap(_ sender: UIButton) { + delegate?.backButtonDidTap() + } + @objc private func profileImageEditButtonDidTap(_ sender: UIButton) { + toggleImageRevisingState() + } + @objc private func cameraButtonDidTap(_ sender: UIButton){ + imagePicker.sourceType = .photoLibrary + present(imagePicker, animated: false, completion: nil) + + } + @objc private func reviseButtonDidTap(_ sender: UIButton) {//이름,아이디 수정 버튼 + let pattern = "^[a-zA-Z0-9_\\.]*$" + + if (isInfoEditing){//수정중인 상태일 때 수정(저장)버튼을 눌렀을 경우 + + if let text = nameTextField.text,text.isEmpty { + self.view.makeToastAnimation(message: "이름을 입력해주세요") + return} + + if let text = idTextField.text,text.isEmpty{ + self.view.makeToastAnimation(message: "아이디를 입력해주세요") + return + } + + if idTextField.text!.count < 7 { + self.view.makeToastAnimation(message: "아이디는 7자 이상 입력해주세요") + return + + } + guard let id = idTextField.text else {return} + guard let _ = id.range(of: pattern, options: .regularExpression) else { + self.view.makeToastAnimation(message: "아이디는 알파벳, 숫자, 밑줄, 마침표만 사용 가능해요") + return + } + if id.allSatisfy({$0.isNumber}){ + self.view.makeToastAnimation(message: "숫자로만은 만들 수 없어요") + return + } + + + putNameAndID() + }else{ + toggleInfoRevisingState() + } + + + } + + @objc private func reviseCancelButtonDidTap(_ sender: UIButton) { + toggleInfoRevisingState() + nameTextField.text = userName + idTextField.text = userId + + } + + @objc private func changePasswordViewDidTap(_ sender: UIButton) { + delegate?.changePasswordButtonDidTap() + } + + @objc private func accountWithdrawalButtonDidTap(_ sender: UIButton) { + let alertVC = SMPopUpVC(withType: .accountWithdrawal) + alertVC.modalPresentationStyle = .overFullScreen + self.present(alertVC, animated: false, completion: nil) + + alertVC.greyButtonCompletion = { + if UserDefaults.standard.bool(forKey: Constants.UserDefaultsKey.isAppleLogin) { + self.appleAccountWithdrawal() + } else { + self.putWithdrawal() + } + } + + alertVC.pinkButtonCompletion = { + self.dismiss(animated: false) + } + } + + @objc private func logoutButtonDidTap(_ sender: UIButton) { + let alertVC = SMPopUpVC(withType: .logout) + alertVC.modalPresentationStyle = .overFullScreen + self.present(alertVC, animated: false, completion: nil) + + alertVC.greyButtonCompletion = { + self.removeUserDefaults() + self.dismiss(animated: false) + self.delegate?.logoutOKButtonDidTap() + } + + alertVC.pinkButtonCompletion = { + self.dismiss(animated: false) + } + } + + @objc func nameTextFieldDidChange(_ sender: Any?) { + guard let nameText = nameTextField.text else {return} + if nameText.count > 5 { + nameTextField.deleteBackward() + } + } + + @objc func idTextFieldDidChange(_ sender: Any?) { + let currentText = idTextField.text ?? "" + idTextField.text = currentText.lowercased() + + } + +} + + +extension UserInfoVC: UIImagePickerControllerDelegate,UINavigationControllerDelegate{ + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + + if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage{ + profileImageView.image = image + } + dismiss(animated: true, completion: nil) + } + +} +extension UserInfoVC: UITextFieldDelegate { + // 한 글자 입력시 호출 + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + if textField == nameTextField{ + let utf8Char = string.cString(using: .utf8) + let isBackSpace = strcmp(utf8Char, "\\b") + if string.hasCharacters() || isBackSpace == -92{ + return true + } + return false + } + + return true + } +} + +extension UserInfoVC: ASAuthorizationControllerDelegate { + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + + if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { // 일단 재로그인 시켜서 authorization code를 받아온 후 넘겨준다. + let userIdentifier = appleIDCredential.user + + dump(authorization) + + if let authorizationToken = appleIDCredential.authorizationCode, + let encodedAuthorizationToken = String(data: authorizationToken, encoding: .utf8) { + print("authorizationToken: \(encodedAuthorizationToken)") + self.putWithdrawal(authorizationCode: encodedAuthorizationToken) + } else { + self.view.makeToastAnimation(message: "탈퇴 오류! 잠시 후에 다시 시도해주세요.") + } + + } + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { + print("error: \(error)") + } +} + +extension UserInfoVC: ASAuthorizationControllerPresentationContextProviding { // 애플계정 인증 창을 띄울 화면 설정 + @available(iOS 13.0, *) + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return self.view.window! + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/Components/AvailTimeOptionView.swift b/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/Components/AvailTimeOptionView.swift new file mode 100644 index 0000000..2edbe26 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/Components/AvailTimeOptionView.swift @@ -0,0 +1,128 @@ +// +// AvailTimeOptionView.swift +// SeeMeet +// +// Created by 김인환 on 2022/03/08. +// + +import UIKit + +protocol AvailTimeOptionViewDelegate { + func checkBoxDidTap(view: AvailTimeOptionView) + func availTimeOptionViewDidTap(view: AvailTimeOptionView) +} + +class AvailTimeOptionView: UIView { + + // MARK: - UI Components + + let dateLabel: UILabel = UILabel().then { + $0.font = UIFont.dinProBoldFont(ofSize: 16) + $0.textColor = UIColor.grey06 + } + + let timeLabel: UILabel = UILabel().then { + $0.font = UIFont.dinProRegularFont(ofSize: 14) + $0.textColor = UIColor.grey06 + } + + let checkButton: UIButton = UIButton(type: .system).then { + $0.setBackgroundImage(UIImage(named: "ic_check_grey"), for: .normal) + $0.setBackgroundImage(UIImage(named: "ic_check_active"), for: .selected) + } + + private let cellBottomView = UIView().then { + $0.backgroundColor = UIColor.grey01 + } + + // MARK: - Properties + static let identifier: String = "AvailTimeOptionView" + + var isResponsed: Bool = true { // 이미 응답을 보낸 약속에서 인지 여부. 버튼 상호작용 여부 결정한다. + willSet { + checkButton.isEnabled = !newValue + } + } + + var delegate: AvailTimeOptionViewDelegate? + + // MARK: - Intializer + + override init(frame: CGRect) { + super.init(frame: frame) + setAutoLayouts() + configUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setAutoLayouts() + configUI() + } + + // MARK: - Life Cycle + + override func awakeFromNib() { + setAutoLayouts() + configUI() + } + + // MARK: - Layout + + private func setAutoLayouts() { + addSubviews([dateLabel, timeLabel, checkButton, cellBottomView]) + bringSubviewToFront(checkButton) + + snp.makeConstraints { + $0.width.equalTo(344 * widthRatio) + $0.height.equalTo(82 * heightRatio) + } + + dateLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(14 * heightRatio) + $0.leading.equalToSuperview().offset(40 * widthRatio) + } + + timeLabel.snp.makeConstraints { + $0.top.equalTo(dateLabel.snp.bottom).offset(7 * heightRatio) + $0.leading.equalToSuperview().offset(40 * widthRatio) + } + + checkButton.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalTo(snp.trailing).offset(-11 * widthRatio) + $0.width.height.equalTo(48 * heightRatio) + } + + cellBottomView.snp.makeConstraints { + $0.bottom.equalToSuperview() + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalTo(snp.trailing).offset(-20 * widthRatio) + $0.height.equalTo(1) + } + } + + // MARK: - Custom Methods + + private func configUI() { + checkButton.addTarget(self, action: #selector(checkButtonDidTap(_:)), for: .touchUpInside) + setGestureRecognizer() + } + + private func setGestureRecognizer() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(selfDidTap(_:))) + tapGesture.cancelsTouchesInView = false + addGestureRecognizer(tapGesture) + } + + // MARK: - Actions + + @objc private func checkButtonDidTap(_ sender: UIButton) { + sender.isSelected.toggle() + delegate?.checkBoxDidTap(view: self) + } + + @objc private func selfDidTap(_ sender: UIGestureRecognizer) { // 이 뷰 자체를 터치했을 경우 + delegate?.availTimeOptionViewDidTap(view: self) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/Components/ConfirmTimeOptionView.swift b/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/Components/ConfirmTimeOptionView.swift new file mode 100644 index 0000000..4a9a3e1 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/Components/ConfirmTimeOptionView.swift @@ -0,0 +1,181 @@ +import UIKit + +protocol TimeOptionViewDelegate { + func timeOptionViewDidTap(view: ConfirmTimeOptionView, tag: Int) +} + +class ConfirmTimeOptionView: UIButton { + + // MARK: - UI Components + + let yearLabel = UILabel().then { + $0.font = UIFont.dinProBoldFont(ofSize: 18) + $0.textColor = UIColor.grey06 + } + + let timeLabel = UILabel().then { + $0.font = UIFont.dinProMediumFont(ofSize: 16) + $0.textColor = UIColor.grey06 + $0.textAlignment = .left + } + + private let separator = UIView().then { + $0.backgroundColor = UIColor.grey04 + } + + private let cellbottomView = UIView().then { + $0.backgroundColor = UIColor.white + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner] + } + + private let cellBackgroundView = UIView().then { + $0.isUserInteractionEnabled = true + $0.backgroundColor = UIColor.grey02 + $0.clipsToBounds = true + $0.layer.cornerRadius = 12 + } + + private let peopleImageView = UIImageView().then { + $0.image = UIImage(named: "ic_friend") + } + + let profileCountLabel = UILabel().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 13) + $0.textColor = UIColor.grey03 + $0.text = "0" + } + + private let nameStackView = UIStackView(frame: .zero).then { + $0.axis = .horizontal + $0.spacing = 8 * widthRatio + $0.distribution = .fillProportionally + $0.alignment = .fill + } + + // MARK: - Properties + + static let identifier: String = "TimeOptionView" + + var delegate: TimeOptionViewDelegate? + var namesToShow: [String]? { + didSet { + setNameLabels() + } + } + var invitationDateID: Int? + + override var isSelected: Bool { + willSet { + super.isSelected = newValue + toggleSelectedState() + } + } + + // MARK: - initializer + + override init(frame: CGRect) { + super.init(frame: frame) + setupAutoLayout() + addTapGesture() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - setLayout + + private func setupAutoLayout() { + addSubview(cellBackgroundView) + cellBackgroundView.addSubviews([yearLabel, separator, timeLabel, cellbottomView]) + cellbottomView.addSubviews([peopleImageView, profileCountLabel, nameStackView]) + + cellBackgroundView.snp.makeConstraints { + $0.leading.equalToSuperview() + $0.trailing.equalToSuperview() + $0.height.equalTo(99 * heightRatio) + } + + yearLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(17 * heightRatio) + $0.leading.equalToSuperview().offset(22 * widthRatio) + } + + separator.snp.makeConstraints { + $0.top.equalToSuperview().offset(18 * heightRatio) + $0.leading.equalTo(yearLabel.snp.trailing).offset(18 * widthRatio) + $0.width.equalTo(1) + $0.height.equalTo(22 * heightRatio) + } + + timeLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(18 * heightRatio) + $0.trailing.equalToSuperview().offset(-22 * widthRatio) + } + + cellbottomView.snp.makeConstraints { + $0.bottom.leading.trailing.equalToSuperview() + $0.height.equalTo(41 * heightRatio) + } + + nameStackView.snp.makeConstraints { + $0.trailing.equalToSuperview().offset(-22 * widthRatio) + $0.bottom.equalToSuperview().offset(-12 * heightRatio) + $0.height.equalTo(16 * heightRatio) + } + + profileCountLabel.snp.makeConstraints { + if namesToShow?.count != 0 { + $0.trailing.equalTo(nameStackView.snp.leading).offset(-17 * widthRatio) + } else { + $0.trailing.equalToSuperview().offset(-22 * widthRatio) + } + $0.bottom.equalToSuperview().offset(-12 * heightRatio) + } + + peopleImageView.snp.makeConstraints { + $0.trailing.equalTo(profileCountLabel.snp.leading).offset(-4 * widthRatio) + $0.top.equalToSuperview().offset(13 * heightRatio) + $0.height.width.equalTo(13 * widthRatio) + } + } + + // MARK: - Custom Method + + private func setNameLabels() { + if let namesToShow = namesToShow { + namesToShow.forEach { name in + let nameLabel = UILabel().then { + $0.setAttributedText(defaultText: name, + font: UIFont.hanSansRegularFont(ofSize: 13), + color: UIColor.grey05, + kernValue: -0.6) + } + nameStackView.addArrangedSubview(nameLabel) + } + profileCountLabel.text = "\(namesToShow.count)" + } + } + + private func addTapGesture() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(timeOptionViewDidTap(_:))) + addGestureRecognizer(tapGesture) + isUserInteractionEnabled = true + } + + private func toggleSelectedState() { + cellBackgroundView.backgroundColor = isSelected ? UIColor.black : UIColor.grey02 + cellBackgroundView.layer.borderWidth = isSelected ? 1 : 0 + cellBackgroundView.layer.borderColor = isSelected ? UIColor.black.cgColor : UIColor.grey02.cgColor + yearLabel.textColor = isSelected ? UIColor.white : UIColor.grey06 + timeLabel.textColor = isSelected ? UIColor.white : UIColor.grey06 + } + + // MARK: - Actions + + @objc private func timeOptionViewDidTap(_ sender: UITapGestureRecognizer) { + delegate?.timeOptionViewDidTap(view: self, tag: tag) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/PlansListVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/PlansListVC.swift new file mode 100644 index 0000000..85cbf51 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/PlansListVC.swift @@ -0,0 +1,669 @@ +import UIKit +import SnapKit +import Then +import RxSwift +import RxCocoa +import SwiftUI + +protocol PlansListVCDelegate { + func backButtonDidTap() + func sendPlansDidTap(plansID: String) + func receivePlansDidTap(plansID: String) + func completedPlansDidTap(plansID: Int?,isCanceled: Bool) +} + +class PlansListVC: UIViewController { + + // MARK: UI Components + + //headerView + private let plansListBackgroundView = UIView().then { + $0.backgroundColor = UIColor.white + } + + private let headerView = UIView().then { + $0.backgroundColor = UIColor.grey01 + } + + private let backButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_back"), for: .normal) + } + + private let headerLabel = UILabel().then { + $0.text = "약속 내역" + $0.font = UIFont.hanSansBoldFont(ofSize: 18) + $0.textColor = UIColor.grey06 + $0.textAlignment = .center + } + + //customTabbar + private let progressView = UIView().then { + $0.backgroundColor = .none + $0.isUserInteractionEnabled = true + } + + private let completeView = UIView().then { + $0.backgroundColor = .none + $0.isUserInteractionEnabled = true + } + + private let progressHeadView = UIView().then { + $0.backgroundColor = .none + } + + private let completeHeadView = UIView().then { + $0.backgroundColor = .none + } + + private let progressLabel = UILabel().then { + $0.text = "진행중" + $0.font = UIFont.hanSansBoldFont(ofSize: 16) + $0.textColor = UIColor.grey06 + $0.textAlignment = .center + } + + private let completeLabel = UILabel().then { + $0.text = "완료" + $0.font = UIFont.hanSansMediumFont(ofSize: 16) + $0.textColor = UIColor.grey06 + $0.textAlignment = .center + } + + private let bottomView = UIView().then { + $0.backgroundColor = UIColor.grey06 + } + + //collectionViewHeader + private let progressHeadLabel = UILabel().then { + $0.font = UIFont.hanSansMediumFont(ofSize: 24) + $0.setAttributedText(defaultText: "진행 중이에요", containText: "진행 중", font: UIFont.hanSansBoldFont(ofSize: 24), color: UIColor.grey06) + } + + private let completeHeadLabel = UILabel().then { + $0.font = UIFont.hanSansMediumFont(ofSize: 24) + $0.setAttributedText(defaultText: "완료되었어요", containText: "완료", font: UIFont.hanSansBoldFont(ofSize: 24), color: UIColor.grey06) + $0.textColor = UIColor.grey06 + } + + private let progressHeadCountLabel = UILabel().then { + $0.font = UIFont.dinProRegularFont(ofSize: 24) + $0.textColor = UIColor.grey06 + } + + private let completeHeadCountLabel = UILabel().then { + $0.font = UIFont.dinProRegularFont(ofSize: 24) + $0.textColor = UIColor.grey06 + } + + private let pagerScrollView = UIScrollView().then { + $0.isPagingEnabled = true + $0.bounces = false + $0.backgroundColor = .none + $0.showsHorizontalScrollIndicator = false + $0.showsVerticalScrollIndicator = false + } + + //progressCollectionView + private let progressCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()).then { + let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + $0.tag = 1 + $0.setCollectionViewLayout(layout, animated: false) + $0.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + $0.backgroundColor = .none + $0.bounces = true + $0.showsVerticalScrollIndicator = false + } + + //completeCollectionView + private let completeCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()).then { + let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + $0.tag = 2 + $0.setCollectionViewLayout(layout, animated: false) + $0.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + $0.backgroundColor = .none + $0.bounces = true + $0.showsVerticalScrollIndicator = false + } + + private let networkFailImage1 = UIImageView(image: UIImage(named: "img_network_fail")).then { + $0.contentMode = .scaleAspectFit + } + private let networkFailImage2 = UIImageView(image: UIImage(named: "img_network_fail")).then { + $0.contentMode = .scaleAspectFit + } + + private let networkFailLabel1 = UILabel().then { + $0.text = "인터넷 연결을 확인해주세요" + $0.font = UIFont.hanSansMediumFont(ofSize: 16) + } + + private let networkFailLabel2 = UILabel().then { + $0.text = "인터넷 연결을 확인해주세요" + $0.font = UIFont.hanSansMediumFont(ofSize: 16) + } + + // MARK: - Properties + + static let identifier: String = "PlansListVC" + + weak var coordinator: PlansCoordinator? + var delegate: PlansListVCDelegate? + + private var disposeBag: DisposeBag? = DisposeBag() + + private var userWidth: CGFloat = UIScreen.getDeviceWidth() + private var userHeight: CGFloat = UIScreen.getDeviceHeight() + + private var invitationData: [Invitation] = [] // 진행 중 약속들 + private var confirmData: [ConfirmedAndCanceld?] = [] // 완료 된 약속들 + + private var progressPlansCount: Int { + invitationData.count + } + + private var completePlansCount: Int { + confirmData.count + } + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + configUI() + setupAutoLayout() + } + + override func viewWillAppear(_ animated: Bool) { + if NetworkReachabilityService.isConnectedToNetwork() { + showNetworkFailViews(show: false) + getPlansData() + } else { + showNetworkFailViews(show: true) + } + } + + // MARK: - Layout + + private func configUI() { + backButton.addTarget(self, action: #selector(backButtonDidTap(_:)), for: .touchUpInside) + + let tapGestureProgressView = UITapGestureRecognizer(target: self, action: #selector(tabbarDidTap(_:))) + let tapGestureCompleteView = UITapGestureRecognizer(target: self, action: #selector(tabbarDidTap(_:))) + progressView.addGestureRecognizer(tapGestureProgressView) + completeView.addGestureRecognizer(tapGestureCompleteView) + + [progressCollectionView, completeCollectionView].forEach { + $0.dataSource = self + $0.delegate = self + } + pagerScrollView.delegate = self + + progressCollectionView.registerCustomXib(xibName: "ProgressSendCVC") + progressCollectionView.registerCustomXib(xibName: "ProgressReceiveCVC") + completeCollectionView.registerCustomXib(xibName: "CompletePlansCVC") + } + + private func setupAutoLayout() { + setHeaderLayout() + setScrollViewLayout() + } + + private func setHeaderLayout() { + view.addSubview(plansListBackgroundView) + plansListBackgroundView.addSubview(headerView) + headerView.addSubviews([backButton, headerLabel, progressView, completeView, bottomView]) + progressView.addSubview(progressLabel) + completeView.addSubview(completeLabel) + + plansListBackgroundView.snp.makeConstraints{ + $0.top.bottom.leading.trailing.equalToSuperview() + } + + headerView.snp.makeConstraints{ + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(152 * heightRatio) + } + + backButton.snp.makeConstraints{ + $0.top.equalTo(self.view.safeAreaLayoutGuide.snp.top).offset(5 * heightRatio) + $0.leading.equalToSuperview().offset(2 * widthRatio) + $0.width.height.equalTo(48 * widthRatio) + } + + headerLabel.snp.makeConstraints{ + $0.top.equalTo(self.view.safeAreaLayoutGuide.snp.top).offset(15 * heightRatio) + $0.centerX.equalToSuperview() + $0.width.equalTo(80 * widthRatio) + } + + progressView.snp.makeConstraints{ + $0.bottom.equalToSuperview() + $0.leading.equalToSuperview() + $0.width.equalTo(userWidth * 0.5) + $0.height.equalTo(50 * heightRatio) + } + + progressLabel.snp.makeConstraints{ + $0.centerX.equalToSuperview() + $0.centerY.equalToSuperview() + } + + completeView.snp.makeConstraints{ + $0.bottom.equalToSuperview() + $0.trailing.equalToSuperview() + $0.width.equalTo(userWidth * 0.5) + $0.height.equalTo(50 * heightRatio) + } + + completeLabel.snp.makeConstraints{ + $0.centerX.equalToSuperview() + $0.centerY.equalToSuperview() + } + + bottomView.snp.makeConstraints{ + $0.leading.equalToSuperview() + $0.bottom.equalToSuperview() + $0.width.equalTo(userWidth * 0.5) + $0.height.equalTo(3 * heightRatio) + } + } + + private func setScrollViewLayout() { + plansListBackgroundView.addSubview(pagerScrollView) + pagerScrollView.addSubviews([progressHeadView, completeHeadView, progressCollectionView, completeCollectionView]) + progressHeadView.addSubviews([progressHeadLabel, progressHeadCountLabel]) + completeHeadView.addSubviews([completeHeadLabel, completeHeadCountLabel]) + progressCollectionView.addSubview(networkFailImage1) + progressCollectionView.addSubview(networkFailLabel1) + completeCollectionView.addSubview(networkFailImage2) + completeCollectionView.addSubview(networkFailLabel2) + + pagerScrollView.contentSize = CGSize(width: userWidth * 2, height: userHeight - 152) + + pagerScrollView.snp.makeConstraints { + $0.top.equalTo(headerView.snp.bottom) + $0.leading.trailing.bottom.equalToSuperview() + } + + progressHeadView.snp.makeConstraints { + $0.top.equalTo(headerView.snp.bottom) + $0.leading.equalToSuperview() + $0.trailing.equalTo(completeHeadView.snp.leading) + $0.bottom.equalTo(progressCollectionView.snp.top) + $0.height.equalTo(74 * heightRatio) + $0.width.equalTo(userWidth) + } + + progressHeadLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.width.equalTo(145 * widthRatio) + } + + progressHeadCountLabel.snp.makeConstraints { + $0.centerY.equalTo(progressHeadLabel) + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.width.equalTo(50 * widthRatio) + } + + completeHeadView.snp.makeConstraints { + $0.top.equalTo(headerView.snp.bottom) + $0.leading.equalTo(progressHeadView.snp.trailing) + $0.trailing.equalToSuperview() + $0.bottom.equalTo(completeCollectionView.snp.top) + $0.height.equalTo(74 * heightRatio) + $0.width.equalTo(userWidth) + } + + completeHeadLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.width.equalTo(145 * widthRatio) + } + + completeHeadCountLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.width.equalTo(50 * widthRatio) + } + + progressCollectionView.snp.makeConstraints { + $0.top.equalTo(progressHeadView.snp.bottom) + $0.bottom.leading.equalToSuperview() + $0.trailing.equalTo(completeCollectionView.snp.leading) + $0.width.equalTo(userWidth) + $0.height.equalTo(userHeight-242 * heightRatio) + } + + completeCollectionView.snp.makeConstraints { + $0.top.equalTo(completeHeadView.snp.bottom) + $0.bottom.trailing.equalToSuperview() + $0.leading.equalTo(progressCollectionView.snp.trailing) + $0.width.equalTo(userWidth) + $0.height.equalTo(userHeight-242) + } + + networkFailImage1.snp.makeConstraints { + $0.width.equalTo(275 * widthRatio) + $0.height.equalTo(205.32 * heightRatio) + $0.centerX.equalToSuperview() + $0.top.equalTo(headerView.snp.bottom).offset(164 * heightRatio) + } + + networkFailImage2.snp.makeConstraints { + $0.width.equalTo(275 * widthRatio) + $0.height.equalTo(205.32 * heightRatio) + $0.centerX.equalToSuperview() + $0.top.equalTo(headerView.snp.bottom).offset(164 * heightRatio) + } + + networkFailLabel1.snp.makeConstraints { + $0.top.equalTo(networkFailImage1.snp.bottom) + $0.centerX.equalToSuperview() + } + + networkFailLabel2.snp.makeConstraints { + $0.top.equalTo(networkFailImage2.snp.bottom) + $0.centerX.equalToSuperview() + } + + setCountLabel() + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + moveBottomView(offsetX: pagerScrollView.contentOffset.x / 2) + setTabbarTitle(offsetX: pagerScrollView.contentOffset.x) + + func moveBottomView(offsetX: CGFloat) { + bottomView.snp.remakeConstraints { + $0.leading.equalToSuperview().offset(offsetX) + $0.height.equalTo(3 * heightRatio) + $0.width.equalTo(userWidth/2) + $0.bottom.equalToSuperview() + } + } + + func setTabbarTitle(offsetX: CGFloat) { + if offsetX > userWidth / 2 { + progressLabel.font = UIFont.hanSansMediumFont(ofSize: 16) + completeLabel.font = UIFont.hanSansBoldFont(ofSize: 16) + } else{ + progressLabel.font = UIFont.hanSansBoldFont(ofSize: 16) + completeLabel.font = UIFont.hanSansMediumFont(ofSize: 16) + } + } + } + + // MARK: - Custom Method + + private func setCountLabel() { + progressHeadCountLabel.setAttributedText(defaultText: "\(progressPlansCount)건", containText: "\(progressPlansCount)", font: UIFont.dinProBoldFont(ofSize: 24), color: UIColor.pink01) + completeHeadCountLabel.setAttributedText(defaultText: "\(completePlansCount)건", containText: "\(completePlansCount)", font: UIFont.dinProBoldFont(ofSize: 24), color: UIColor.pink01) + } + + private func setCompleteEmptyView() { + let emptyImage = UIImageView().then { + $0.image = UIImage(named: "img_illust_9") + } + let emptyLabel = UILabel().then{ + $0.text = "완료된 약속이 없어요!" + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.textColor = UIColor.grey05 + $0.textAlignment = .center + } + if completePlansCount == 0 { + completeCollectionView.addSubviews([emptyImage, emptyLabel]) + emptyImage.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalToSuperview().offset(110) + $0.width.height.equalTo(164) + } + emptyLabel.snp.makeConstraints { + $0.top.equalTo(emptyImage.snp.bottom).offset(15) + $0.centerX.equalToSuperview() + $0.height.equalTo(30) + } + } + else { + completeCollectionView.removeAllSubViews() + } + } + + private func setProgressEmptyView() { + let emptyImage = UIImageView().then { + $0.image = UIImage(named: "img_illust_9") + } + + let emptyLabel = UILabel().then{ + $0.text = "진행 중인 약속이 없어요!" + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.textColor = UIColor.grey05 + $0.textAlignment = .center + } + + if progressPlansCount == 0 { + progressCollectionView.addSubviews([emptyImage, emptyLabel]) + emptyImage.snp.makeConstraints{ + $0.centerX.equalToSuperview() + $0.top.equalToSuperview().offset(110 * heightRatio) + $0.width.height.equalTo(164 * widthRatio) + } + emptyLabel.snp.makeConstraints{ + $0.top.equalTo(emptyImage.snp.bottom).offset(15 * heightRatio) + $0.centerX.equalToSuperview() + $0.height.equalTo(30 * heightRatio) + } + } else { + progressCollectionView.removeAllSubViews() + } + + } + + private func showNetworkFailViews(show: Bool) { + [networkFailImage1,networkFailImage2,networkFailLabel1,networkFailLabel2] + .forEach { $0.isHidden = !show } + } + + //MARK: - Network + + private func getPlansData() { + setCompleteEmptyView() + setProgressEmptyView() + GetPlansListDataService.shared.getPlansList { (response) in + switch response { + case .success(let data) : + if let response = data as? PlansListDataModel { + self.invitationData = response.data?.invitations.sorted(by: { + + guard let timeInterval1 = TimeInterval(String.getTimeIntervalString(from: $0.createdAt)), + let timeInterval2 = TimeInterval(String.getTimeIntervalString(from: $1.createdAt)) else { return $0.id < $1.id} + return timeInterval1 < timeInterval2 + }) ?? [] + + self.confirmData = response.data?.confirmedAndCanceld.sorted(by: { $0.id > $1.id }) ?? [] + + DispatchQueue.main.async { + self.setCountLabel() + self.setCompleteEmptyView() + self.setProgressEmptyView() + self.progressCollectionView.reloadData() + self.completeCollectionView.reloadData() + } + } + case .requestErr(let message) : + print("requestERR") + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + private func putInvitationListDelete(id: String) { + PutInvitationListService.shared.putInvitationListCancel(invitationId: id) { [weak self] response in + switch response { + case .success(_): + self?.getPlansData() + case .networkFail: + self?.view.makeToastAnimation(message: "통신오류! 다시 시도하십시오.") + default: + self?.view.makeToastAnimation(message: "요청 실패") + } + } + } + + // MARK: - Actions + + @objc private func tabbarDidTap(_ sender: UIView) { + var contentOffsetX = pagerScrollView.contentOffset.x + if contentOffsetX >= userWidth { + pagerScrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true) + } else{ + pagerScrollView.setContentOffset(CGPoint(x: userWidth, y: 0), animated: true) + } + } + + @objc private func backButtonDidTap(_ sender: UIButton) { + delegate?.backButtonDidTap() + } +} + +//MARK: - Extension + +extension PlansListVC: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + if collectionView.tag == 1 { + return CGSize(width: userWidth-40, height: 144.0 * heightRatio) + } + else{ + return CGSize(width: userWidth, height: 146.0 * heightRatio) + } + } +} + +extension PlansListVC: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + switch collectionView.tag { + case 1: // progressCollectionView + return progressPlansCount + case 2: // completeCollectionView + return completePlansCount + default: + return 0 + } + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let day: Int = 1 + + switch collectionView.tag { + case 1: // progressCollectionView + let data = invitationData[indexPath.row] + + if data.isReceived { // 받은요청 + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ProgressReceiveCVC.identifier, for: indexPath) as? ProgressReceiveCVC else { return UICollectionViewCell() } + + cell.setData(dayAgo: String.getTimeIntervalString(from: data.createdAt), + hostName: data.host?.username ?? "탈퇴") + + return cell + + } else { // 보낸요청 + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ProgressSendCVC.identifier, for: indexPath) as? ProgressSendCVC else { return UICollectionViewCell() } + + cell.setData(dateAgo: String.getTimeIntervalString(from: data.createdAt), + namesToShow: data.guests?.reduce([String: Bool]()) { dict, guest in + var dict = dict + dict[guest.username] = guest.isResponse + return dict + } ?? [:]) + + return cell + } + + case 2: // completeCollectionView + let data = confirmData[indexPath.row] + + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CompletePlansCVC.identifier, for: indexPath) as? CompletePlansCVC else { return UICollectionViewCell() } + + cell.setData(namesToShow: data?.guests.reduce([String: Bool]()) { dict, guest in + var dict = dict + dict[guest.username] = guest.isResponse + return dict + } ?? ["":false], dayAgoText: "\(day + indexPath.row)일전", planName: data?.invitationTitle ?? "", isCanceled: data?.isCanceled ?? false) + + guard let id = data?.id else { return UICollectionViewCell() } + + cell.closeButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + self?.disposeBag = nil + self?.disposeBag = DisposeBag() + self?.putInvitationListDelete(id: "\(id)") + }) + .disposed(by: disposeBag!) + + return cell + + default: + return UICollectionViewCell() + } + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + switch collectionView.tag { + case 1: // progressCollectionView + if invitationData[indexPath.row].isReceived { + let plansID = String(invitationData[indexPath.row].id) + delegate?.receivePlansDidTap(plansID: plansID) + + } else { + let plansID = String(invitationData[indexPath.row].id) + delegate?.sendPlansDidTap(plansID: plansID) + } + case 2: + if confirmData[indexPath.row]?.isConfirmed == true && confirmData[indexPath.row]?.isCanceled == false { + let plansID = confirmData[indexPath.row]?.planID + delegate?.completedPlansDidTap(plansID: plansID,isCanceled: false) + }else { + let ID = confirmData[indexPath.row]?.id + delegate?.completedPlansDidTap(plansID: ID, isCanceled: true) + } + default: + break + } + } +} + +extension PlansListVC: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + switch collectionView.tag { + case 1: + return 20 * heightRatio + case 2: + return 0 + default: + return 0 + } + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return 0 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + switch collectionView.tag { + case 1: + return UIEdgeInsets(top: 20 * heightRatio, left: 0, bottom: 0, right: 0) + case 2: + return UIEdgeInsets.zero + default: + return UIEdgeInsets.zero + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/PlansReceiveVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/PlansReceiveVC.swift new file mode 100644 index 0000000..dabfb29 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/PlansReceiveVC.swift @@ -0,0 +1,671 @@ +import UIKit +import SnapKit +import Then + +protocol PlansReceiveVCDelegate{ + func backButtonDidTap() +} + +final class PlansReceiveVC: UIViewController { + + //MARK: UI Components + + private let backgroundView = UIView().then { + $0.backgroundColor = UIColor.white + } + + private let backGroundScrollView = UIScrollView().then { + $0.backgroundColor = UIColor.white + $0.isPagingEnabled = false + $0.bounces = true + $0.showsVerticalScrollIndicator = true +// $0.contentInsetAdjustmentBehavior = .never + $0.isScrollEnabled = true + } + + private let headerBackgroundView = UIView().then { + $0.backgroundColor = .none + } + + private let backButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_back"), for: .normal) + } + + private let titleLabel = UILabel().then { + $0.setAttributedText(defaultText: "새로운 초대장", font: UIFont.hanSansBoldFont(ofSize: 14), color: UIColor.orange, kernValue: -0.6) + } + + private let nameTitleLabel = UILabel().then { + $0.textColor = UIColor.grey06 + $0.font = UIFont.hanSansRegularFont(ofSize: 24) + $0.lineBreakMode = .byWordWrapping + $0.numberOfLines = 2 + } + + private let titleImageView = UIImageView().then { + $0.image = UIImage(named: "img_illust_11") + } + + private let textBackGroundView = UIView().then { + $0.backgroundColor = UIColor.grey01 + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + } + + private let plansTitleLabel = UILabel().then { + $0.font = UIFont.hanSansBoldFont(ofSize: 14) + $0.textColor = UIColor.grey06 + $0.textAlignment = .left + } + + private let textBottomView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private let plansDetailTextView = UITextView().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 14) + $0.textColor = UIColor.grey06 + $0.backgroundColor = .none + $0.textAlignment = .left + $0.isEditable = false + $0.isScrollEnabled = true + } + + private let withReceiveLabel = UILabel().then { + $0.setAttributedText(defaultText: "나와 함께 받은 사람", font: UIFont.hanSansMediumFont(ofSize: 16), color: UIColor.grey06, kernValue: -0.6) + } + + private let nameButtonStackView = UIStackView().then { + $0.axis = .horizontal + $0.alignment = .fill + $0.distribution = .fillProportionally + $0.spacing = 6 * widthRatio + $0.layoutMargins = UIEdgeInsets.zero + $0.isLayoutMarginsRelativeArrangement = true + } + + private let separatorLineView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private let timeOptionStackView = UIStackView().then { + $0.backgroundColor = UIColor.white + $0.axis = .vertical + } + + private let selectDateHeadLabel = UILabel().then { + $0.numberOfLines = 2 + $0.setAttributedText(defaultText: "내 일정과 비교하며\n날짜를 선택해보세요!", containText: "날짜를 선택", font: UIFont.hanSansBoldFont(ofSize: 20), color: UIColor.pink01, kernValue: -0.6) + } + + private let sideIndicator = UIView().then { + $0.backgroundColor = UIColor.grey06 + $0.clipsToBounds = true + $0.layer.cornerRadius = 2 + } + + private var checkButton = UIButton() + + private let scheduleBackgroundView = UIView().then { + $0.backgroundColor = UIColor.grey01 + } + + private let collectionViewHeadLabel = UILabel().then { + $0.font = UIFont.dinProBoldFont(ofSize: 20) + } + + private lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()).then { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + $0.setCollectionViewLayout(layout, animated: false) + $0.contentInset = UIEdgeInsets.zero + $0.backgroundColor = .none + $0.isPagingEnabled = false + $0.bounces = true + $0.showsHorizontalScrollIndicator = false + + $0.delegate = self + $0.dataSource = self + $0.registerCustomXib(xibName: "PlansReceiveCVC") + } + //bottomButtonView + private let bottomButtonBackgroundView = UIView().then{ + $0.backgroundColor = UIColor.white + } + + private let rejectButton = UIButton().then{ + $0.backgroundColor = UIColor.grey04 + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.setTitle("거절", for: .normal) + } + + private let confirmButton = UIButton().then{ + $0.backgroundColor = UIColor.grey04 + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.setTitle("답변", for: .normal) + } + + //emptyView + private let emptyLabel = UILabel().then{ + $0.attributedText = String.getAttributedText(text: "일정이 없어요!", letterSpacing: -0.6, lineSpacing: nil) + $0.textColor = UIColor.grey04 + } + + //MARK: - Properties + + static let identifier: String = "PlansReceiveVC" + + var plansId: String? // 외부로부터 입력받는다. + weak var coordinator: Coordinator? + + private var plansData: PlansDetailData? + private var collectionViewData: [ResponseDateData]? + private var isSelectedOptionViewDictionary: [Int: Bool] = [:] + var delegate: PlansReceiveVCDelegate? + + private var selectedTimeOptionIDs: [Int] { // 체크박스 체크된 옵션의 invitationID들을 모은 리스트 + guard let plansData = plansData else { return [] } + return isSelectedOptionViewDictionary + .filter { $0.value } + .reduce([Int]()) { list, element in + var list = list + list.append(plansData.invitationDates[element.key].id) + return list + } + } + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setupAutoLayout() + configUI() + getPlansData() + } + + // MARK: - Layout + + private func setupAutoLayout() { + setViewsHierarchy() + setHeadLayout() + setTextViewLayout() + } + + private func setViewsHierarchy() { + view.addSubviews([backGroundScrollView, headerBackgroundView]) + headerBackgroundView.addSubview(backButton) + backGroundScrollView.addSubviews([titleLabel, nameTitleLabel, titleImageView, textBackGroundView, withReceiveLabel, nameButtonStackView, separatorLineView,timeOptionStackView, scheduleBackgroundView, selectDateHeadLabel, bottomButtonBackgroundView]) + textBackGroundView.addSubviews([plansTitleLabel, textBottomView, plansDetailTextView]) + scheduleBackgroundView.addSubviews([collectionViewHeadLabel, collectionView]) + bottomButtonBackgroundView.addSubviews([rejectButton, confirmButton]) + } + + private func setHeadLayout() { + headerBackgroundView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(102 * heightRatio) + } + + backButton.snp.makeConstraints { + $0.top.equalToSuperview().offset(49 * heightRatio) + $0.leading.equalToSuperview().offset(2 * widthRatio) + $0.width.height.equalTo(48 * widthRatio) + } + + backGroundScrollView.snp.makeConstraints { + $0.top.equalTo(headerBackgroundView.snp.bottom) + $0.leading.bottom.trailing.equalToSuperview() + } + } + + private func setTextViewLayout() { + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(29 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + + nameTitleLabel.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(8 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.lessThanOrEqualTo(titleImageView.snp.leading) + } + + titleImageView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.trailing.equalTo(view.snp.trailing).offset(-31 * widthRatio) + $0.width.equalTo(107 * widthRatio) + $0.height.equalTo(81 * heightRatio) + } + + textBackGroundView.snp.makeConstraints { + $0.top.equalTo(nameTitleLabel.snp.bottom).offset(18 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalTo(view.snp.trailing).offset(-20 * widthRatio) + $0.height.equalTo(255 * heightRatio) + } + + plansTitleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(15 * heightRatio) + $0.leading.equalToSuperview().offset(25 * widthRatio) + } + + textBottomView.snp.makeConstraints { + $0.top.equalTo(plansTitleLabel.snp.bottom).offset(8 * heightRatio) + $0.leading.equalToSuperview().offset(15 * widthRatio) + $0.trailing.equalTo(view.snp.trailing).offset(-15 * widthRatio) + $0.height.equalTo(1 * heightRatio) + } + + plansDetailTextView.snp.makeConstraints { + $0.top.equalTo(textBottomView.snp.bottom).offset(10 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalTo(view.snp.trailing).offset(-20 * widthRatio) + $0.bottom.equalToSuperview().offset(-18 * heightRatio) + } + + withReceiveLabel.snp.makeConstraints { + $0.top.equalTo(textBackGroundView.snp.bottom).offset(22 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + + nameButtonStackView.snp.makeConstraints { + $0.top.equalTo(withReceiveLabel.snp.bottom).offset(11 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.height.equalTo(32 * heightRatio) + } + + separatorLineView.snp.makeConstraints { + $0.top.equalTo(nameButtonStackView.snp.bottom).offset(42 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalTo(view.snp.trailing).offset(-20 * widthRatio) + $0.height.equalTo(1 * heightRatio) + } + } + + private func setSelectData() { + guard let plansData = plansData else { return } + timeOptionStackView.snp.makeConstraints { + $0.top.equalTo(selectDateHeadLabel.snp.bottom).offset(32 * heightRatio) + $0.leading.equalToSuperview() + $0.trailing.equalToSuperview() + $0.height.equalTo(82 * heightRatio * CGFloat(plansData.invitationDates.count)) + } + + selectDateHeadLabel.snp.makeConstraints { + $0.top.equalTo(separatorLineView.snp.bottom).offset(37 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + + if !(plansData.isResponse ?? false) { + setCollectionViewData() + } else { + timeOptionStackView.snp.makeConstraints { + $0.bottom.equalToSuperview() + } + } + + func setCollectionViewData() { + scheduleBackgroundView.snp.makeConstraints { + $0.top.equalTo(timeOptionStackView.snp.bottom) + $0.leading.width.equalToSuperview() + $0.height.equalTo(261 * heightRatio) + } + + scheduleBackgroundView.addSubview(emptyLabel) + + emptyLabel.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.centerY.equalToSuperview() + } + + emptyLabel.isHidden = true + collectionViewHeadLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(24 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + + collectionView.snp.makeConstraints { + $0.top.equalTo(collectionViewHeadLabel.snp.bottom).offset(24 * heightRatio) + $0.leading.equalToSuperview() + $0.trailing.equalTo(view.snp.trailing) + $0.height.equalTo(135 * heightRatio) + } + + if plansData.isResponse == false { + setBottomButtonViewLayout() + } + + func setBottomButtonViewLayout() { + bottomButtonBackgroundView.snp.makeConstraints { + $0.top.equalTo(scheduleBackgroundView.snp.bottom) + $0.leading.bottom.equalToSuperview() + $0.trailing.equalTo(view.snp.trailing) + $0.height.equalTo(112 * heightRatio) + } + + rejectButton.snp.makeConstraints { + $0.top.equalToSuperview().offset(15 * heightRatio) + $0.leading.equalTo(20 * widthRatio) + $0.height.equalTo(54 * heightRatio) + $0.width.equalTo(160 * widthRatio) + } + + confirmButton.snp.makeConstraints { + $0.centerY.equalTo(rejectButton) + $0.trailing.equalTo(view.snp.trailing).offset(-20 * widthRatio) + $0.height.equalTo(54 * heightRatio) + $0.width.equalTo(160 * widthRatio) + } + } + } + } + + private func setTimeOptionStackViewLayout() { + guard let plansData = plansData else { return } + + plansData.invitationDates.enumerated().forEach { index, dateData in + let optionView = AvailTimeOptionView().then { + $0.isResponsed = plansData.isResponse ?? false + $0.dateLabel.text = dateData.date + do { + $0.timeLabel.text = try String.getAMPMTimeString(from: dateData.start) + " ~ " + String.getAMPMTimeString(from: dateData.end) + } catch { + print(error.localizedDescription) + } + + $0.tag = index // tag를 이용해 몇번째 옵션인지 구별하고 몇번째 데이터 인지 식별한다. + + if plansData.isResponse ?? false && dateData.isSelected ?? false { // 이미 응답한 약속이면서 골랐던 시간대이면 버튼 아이콘 바꾼다. + $0.checkButton.setBackgroundImage(UIImage(named: "ic_finish_check_inactive"), for: .normal) + } + } + + optionView.delegate = self + timeOptionStackView.addArrangedSubview(optionView) + } + + if plansData.isResponse == false { + timeOptionStackView.addSubview(sideIndicator) + sideIndicator.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.width.equalTo(4 * widthRatio) + $0.height.equalTo(82 * heightRatio) + } + } + } + + // MARK: - Custom Method + + private func configUI() { + backButton.addTarget(self, action: #selector(backButtonDidTap(_:)), for: .touchUpInside) + rejectButton.addTarget(self, action: #selector(rejectButtonDidTap(_:)), for: .touchUpInside) + confirmButton.addTarget(self, action: #selector(confirmButtonDidTap(_:)), for: .touchUpInside) + } + + private func setStackButton() { + plansData?.newGuests?.forEach { guest in + let nameButton: UIButton = UIButton(type: .system).then { + $0.titleLabel?.font = UIFont.hanSansRegularFont(ofSize: 13) + $0.setTitle(guest?.username, for: .normal) + $0.setTitleColor(UIColor.pink01, for: .normal) + $0.backgroundColor = UIColor.white + $0.clipsToBounds = true + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor.pink01.cgColor + $0.layer.cornerRadius = 32 * heightRatio / 2 + if ((guest?.username.isEmpty) != nil) { + $0.layer.borderWidth = 0 + } + } + + if #available(iOS 15.0, *) { + var configuration = UIButton.Configuration.plain() + configuration.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 12 * widthRatio, bottom: 0, trailing: 12 * widthRatio) + nameButton.configuration = configuration + } else { + nameButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 12 * widthRatio, bottom: 0, right: 12 * widthRatio) + } + nameButtonStackView.addArrangedSubview(nameButton) + } + } + + private func setTextData() { + let hostName: String = plansData?.invitation.host.username ?? "탈퇴한 유저" + nameTitleLabel.setAttributedText(defaultText: hostName + "님이 보냈어요", + containText: hostName + "님", + font: UIFont.hanSansBoldFont(ofSize: 24), + color: UIColor.grey06, kernValue: -0.6) + + plansTitleLabel.setAttributedText(defaultText: plansData?.invitation.invitationTitle ?? "", kernValue: -0.6) + plansDetailTextView.attributedText = String.getAttributedText(text: plansData?.invitation.invitationDesc ?? "", letterSpacing: -0.6, lineSpacing: nil) + } + + // MARK: - Network + + private func getPlansData() { + guard let plansId = plansId else { return } + GetPlansDetailDataService.shared.getPlansDetail(postID: plansId) { (response) in + switch response { + case .success(let data): + if let response = data as? PlansDetailDataModel { + print(response.data) + self.plansData = response.data + + DispatchQueue.main.async { + self.setSelectData() + self.setTimeOptionStackViewLayout() + self.setTextData() + self.setStackButton() + self.collectionView.reloadData() + } + + for index in 0...response.data.invitationDates.count { + self.isSelectedOptionViewDictionary[index] = false + } + } + case .requestErr(let message): + print("requestERR", message) + case .pathErr: + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + private func getSelectedOptionScheduleData(dateId: String) { + GetResponseDateDataService.shared.getResponseDate(date: dateId) { (response) in + switch response { + case .success(let data) : + if let response = data as? ResponseDateDataModel { + self.collectionViewData = response.data + DispatchQueue.main.async { + self.collectionView.reloadData() + self.emptyLabel.isHidden = !response.data.isEmpty + } + } + case .requestErr(_): + print("requestERR") + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + private func postComfirmPlans() { + guard let plansData = plansData else { return } + PostInvitationService.shared.postInvitation(plansId: String(plansData.invitation.id), invitationDateIds: selectedTimeOptionIDs) { (response) in + switch(response) { + case .success(let success): + if let success = success as? InvitationPlansDataModel { + print(success.message, success.status, success.data) + } + case .requestErr(let message) : + print("requestERR", message) + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + private func postRejectPlans() { + guard let plansData = plansData else { return } + PostInvitationRejectService.shared.postRejectInvitation(plansId: String(plansData.invitation.id)) { (response) in + switch(response) { + case .success(let success): + if let success = success as? InvitationRejectDataModel { + print(success.message, success.status, success.data) + } + case .requestErr(let message): + print("requestERR", message) + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + // MARK: - Actions + + @objc private func confirmButtonDidTap(_ sender: UIButton) { + guard let plansData = plansData else { return } + + var yearList: [String] = [] + var timeList: [String] = [] + isSelectedOptionViewDictionary.forEach { key, bool in + if bool { + yearList.append(plansData.invitationDates[key].date) + do { + timeList.append(try String.getAMPMTimeString(from: plansData.invitationDates[key].start) + + " ~ " + String.getAMPMTimeString(from: plansData.invitationDates[key].end)) + } catch { + print(error.localizedDescription) + } + } + } + + let requestAlertVC = SMRequestPopUpVC(withType: .recieveConfirm).then { + $0.yearText = yearList + $0.dateText = timeList + $0.modalPresentationStyle = .overFullScreen + $0.pinkButtonCompletion = { + self.postComfirmPlans() + self.dismiss(animated: false, completion: nil) + let viewControllers : [UIViewController] = self.navigationController!.viewControllers as [UIViewController] + self.navigationController?.popToViewController(viewControllers[viewControllers.count - 3 ], animated: false) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "toastMessage"), object: "답변을 완료했어요.") + } + } + self.present(requestAlertVC, animated: false, completion: nil) + } + + @objc private func rejectButtonDidTap(_ sender: UIButton) { + let AlertVC = SMPopUpVC(withType: .refusePlans) + AlertVC.modalPresentationStyle = .overFullScreen + self.present(AlertVC, animated: false, completion: nil) + + AlertVC.pinkButtonCompletion = { + self.postRejectPlans() + self.dismiss(animated: true, completion: nil) + let viewControllers : [UIViewController] = self.navigationController!.viewControllers as [UIViewController] + self.navigationController?.popToViewController(viewControllers[viewControllers.count - 3 ], animated: false) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "toastMessage"), object: "답변을 완료했어요.") + } + } + + @objc private func backButtonDidTap(_ sender: UIButton){ +// self.navigationController?.popViewController(animated: true) + delegate?.backButtonDidTap() + } +} + +// MARK: - Extensions + +extension PlansReceiveVC: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: 224 * widthRatio, height: 129 * heightRatio) + } +} + +extension PlansReceiveVC: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return collectionViewData?.count ?? 0 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PlansReceiveCVC.identifier, for: indexPath) as? PlansReceiveCVC else { return UICollectionViewCell() } + + guard let data = collectionViewData?[indexPath.row] else { return UICollectionViewCell() } + + var timeText: String = "" + do { + timeText = try String.getAMPMTimeString(from: data.start) + " ~ " + String.getAMPMTimeString(from: data.end) + } catch { + print(error.localizedDescription) + } + + cell.setData(title: data.invitationTitle, time: timeText, + namesToShow: data.users.map { $0.username }) + + return cell + } +} + +extension PlansReceiveVC: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 20 * widthRatio + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return 0 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsets(top: 0, left: 20 * widthRatio, bottom: 0, right: 0) + } +} + +extension PlansReceiveVC: AvailTimeOptionViewDelegate { + func checkBoxDidTap(view: AvailTimeOptionView) { + isSelectedOptionViewDictionary[view.tag] = view.checkButton.isSelected + + if isSelectedOptionViewDictionary.values.contains(true) { + confirmButton.backgroundColor = UIColor.pink01 + confirmButton.isEnabled = true + } else { + confirmButton.backgroundColor = UIColor.grey04 + confirmButton.isEnabled = false + } + } + + func availTimeOptionViewDidTap(view: AvailTimeOptionView) { + guard let plansData = plansData else { return } + + if plansData.isResponse == false { // 좌측 인디케이터 움직임 + UIView.animate(withDuration: 0.5) { + let yFrame = CGAffineTransform(translationX: 0, y: CGFloat(82 * (view.tag)) * heightRatio) + self.sideIndicator.transform = yFrame + } + } + + collectionViewHeadLabel.setAttributedText(defaultText: plansData.invitationDates[view.tag].date, kernValue: -0.6) + getSelectedOptionScheduleData(dateId: String(plansData.invitationDates[view.tag].id)) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/PlansSendListVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/PlansSendListVC.swift new file mode 100644 index 0000000..8b9090d --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/PlansScene/PlansSendListVC.swift @@ -0,0 +1,480 @@ +import UIKit + +protocol PlansSendListVCDelegate{ + func backButtonDidTap() +} +class PlansSendListVC: UIViewController { + + // MARK: - UI Components + + private let backGroundScrollView = UIScrollView().then { + $0.isPagingEnabled = false + $0.bounces = true + $0.contentSize = CGSize(width: UIScreen.getDeviceWidth(), height: 1000) + $0.backgroundColor = UIColor.grey01 + $0.showsVerticalScrollIndicator = true + $0.contentInsetAdjustmentBehavior = .never + } + + private let headerView = UIView().then { + $0.backgroundColor = UIColor.grey01 + } + + private let backButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_back"), for: .normal) + } + + private let headLabel = UILabel().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 24) + $0.textColor = UIColor.grey06 + $0.setAttributedText(defaultText: "약속을 확정해 주세요", containText: "약속을 확정", font: UIFont.hanSansBoldFont(ofSize: 24), color: UIColor.grey06) + } + + private let nameTagStackView: UIStackView = UIStackView().then { + $0.axis = .horizontal + $0.alignment = .fill + $0.distribution = .fillProportionally + $0.spacing = 10 + $0.layoutMargins = UIEdgeInsets.zero + $0.isLayoutMarginsRelativeArrangement = true + } + + private let confirmCountLabel = UILabel().then { + $0.font = UIFont.dinProRegularFont(ofSize: 30) + $0.textColor = UIColor.grey06 + $0.text = "3/3" + } + + private var dateSelectStackView = UIStackView().then { + $0.backgroundColor = UIColor.grey01 + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.axis = .vertical + $0.spacing = 16 * heightRatio + $0.distribution = .fillEqually + } + + //보낸 신청 내용 + private let sendPlansDetailLabel = UILabel().then { + $0.font = UIFont.hanSansBoldFont(ofSize: 18) + $0.text = "보낸 신청 내용" + $0.textColor = UIColor.grey06 + } + + private let textView = UIView().then { + $0.backgroundColor = UIColor.white + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + } + + private let titleLabel = UILabel().then { + $0.font = UIFont.hanSansBoldFont(ofSize: 14) + $0.textColor = UIColor.grey06 + } + + private let titleBottomView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private let detailTextView = UITextView().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 14) + $0.textColor = UIColor.grey06 + $0.isEditable = false + } + + private let bottomView = UIView().then { + $0.backgroundColor = UIColor.white + } + + private let cancelButton = UIButton().then { + $0.backgroundColor = UIColor.grey04 + $0.setTitle("취소", for: .normal) + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + } + + private let confirmButton = UIButton().then { + $0.backgroundColor = UIColor.grey02 + $0.setTitle("확정", for: .normal) + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + } + + // MARK: - Properties + + var plansId: String? + weak var coordinator: Coordinator? + private var checkedOptionIndex: Int? + private var checkedOptionDateId: Int? + private var plansData: PlansSendDetailData? { + didSet { + setData() + setHeadLayout() + setTimeOptionViewLayout() + setButtonStack() + } + } + var delegate: PlansSendListVCDelegate? + + private var responsedGuestCount: Int { + plansData?.invitation.guests.filter { $0.isResponse == true }.count ?? 0 + } + private var guestCount: Int { + plansData?.invitation.guests.count ?? 0 + } + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + configUI() + setupAutoLayout() + getPlansData() + } + + // MARK: - setLayout + + private func setupAutoLayout() { + setHeadLayout() + setTextViewLayout() + setBottomButtonView() + } + + // MARK: - Custom Methods + + private func configUI() { + backButton.addTarget(self, action: #selector(backButtonDidTap(_:)), for: .touchUpInside) + cancelButton.addTarget(self, action: #selector(cancelButtonDidTap(_:)), for: .touchUpInside) + confirmButton.addTarget(self, action: #selector(confirmButtonDidTap(_:)), for: .touchUpInside) + } + + private func setHeadLayout() { + view.addSubviews([headerView, backGroundScrollView]) + headerView.addSubview(backButton) + backGroundScrollView.addSubviews([headLabel, nameTagStackView, confirmCountLabel, sendPlansDetailLabel, textView, bottomView, dateSelectStackView]) + + headerView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(102) + } + + backButton.snp.makeConstraints { + $0.leading.equalToSuperview().offset(2 * widthRatio) + $0.top.equalToSuperview().offset(49 * heightRatio) + } + + backGroundScrollView.snp.makeConstraints{ + $0.top.equalTo(headerView.snp.bottom) + $0.leading.trailing.bottom.equalToSuperview() + } + + headLabel.snp.makeConstraints{ + $0.top.equalToSuperview().offset(30 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + + nameTagStackView.snp.makeConstraints { + $0.top.equalTo(headLabel.snp.bottom).offset(22 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.height.equalTo(26 * heightRatio) + } + + confirmCountLabel.snp.makeConstraints { + $0.centerY.equalTo(nameTagStackView) + $0.trailing.equalTo(view.snp.trailing).offset(-20 * widthRatio) + } + + dateSelectStackView.snp.makeConstraints { + $0.top.equalTo(nameTagStackView.snp.bottom).offset(32 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalTo(view.snp.trailing).offset(-20 * widthRatio) + } + } + + private func setTimeOptionViewLayout() { + guard let datesData = plansData?.invitationDates else { return } + + for (index, data) in datesData.enumerated() { + let optionView = ConfirmTimeOptionView().then { + $0.namesToShow = data.respondent.map { $0.username ?? "탈퇴" } + $0.yearLabel.text = data.date + do { + $0.timeLabel.text = try String.getAMPMTimeString(from: data.start) + " ~ " + String.getAMPMTimeString(from: data.end) + } catch { + print("Error: Cannot Convert AM/PM Date From String") + } + $0.tag = index + $0.invitationDateID = data.id + $0.delegate = self + } + dateSelectStackView.addArrangedSubview(optionView) + } + + dateSelectStackView.snp.remakeConstraints { + $0.top.equalTo(nameTagStackView.snp.bottom).offset(32 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalTo(view.snp.trailing).offset(-20 * widthRatio) + $0.height.equalTo(CGFloat((99 * datesData.count + (16 * datesData.count - 1))) * heightRatio) + } + } + + private func setTextViewLayout() { + textView.addSubviews([titleLabel, titleBottomView, detailTextView]) + + sendPlansDetailLabel.snp.makeConstraints { + $0.top.equalTo(dateSelectStackView.snp.bottom).offset(40 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + + textView.snp.makeConstraints { + $0.top.equalTo(sendPlansDetailLabel.snp.bottom).offset(8 * heightRatio) + $0.centerX.equalToSuperview() + $0.height.equalTo(255 * heightRatio) + $0.width.equalTo(335 * widthRatio) + } + + titleLabel.snp.makeConstraints{ + $0.top.equalToSuperview().offset(15 * heightRatio) + $0.leading.equalToSuperview().offset(25 * widthRatio) + $0.height.equalTo(32) + $0.width.equalTo(295) + } + + titleBottomView.snp.makeConstraints{ + $0.top.equalTo(titleLabel.snp.bottom).offset(8) + $0.leading.equalToSuperview().offset(15 * widthRatio) + $0.trailing.equalToSuperview().offset(-15 * widthRatio) + $0.height.equalTo(1 * heightRatio) + } + detailTextView.snp.makeConstraints{ + $0.top.equalTo(titleBottomView.snp.bottom).offset(3 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalToSuperview().offset(-22 * widthRatio) + $0.bottom.equalToSuperview().offset(18 * heightRatio) + } + } + + private func setBottomButtonView() { + bottomView.addSubviews([cancelButton, confirmButton]) + bottomView.snp.makeConstraints{ + $0.top.equalTo(textView.snp.bottom).offset(60 * heightRatio) + $0.leading.equalToSuperview() + $0.trailing.equalTo(view.snp.trailing) + $0.bottom.equalToSuperview() + $0.height.equalTo(112 * heightRatio) + } + cancelButton.snp.makeConstraints{ + $0.top.equalToSuperview().offset(16 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.width.equalTo(160 * widthRatio) + $0.height.equalTo(54 * heightRatio) + + } + confirmButton.snp.makeConstraints{ + $0.top.equalToSuperview().offset(16 * heightRatio) + $0.width.equalTo(160 * widthRatio) + $0.trailing.equalTo(view.snp.trailing).offset(-20 * widthRatio) + $0.height.equalTo(54 * heightRatio) + } + } + + private func setButtonStack() { + plansData?.invitation.guests.forEach { guest in + let nameButton: UIButton = UIButton(type: .system).then { + $0.titleLabel?.font = UIFont.hanSansRegularFont(ofSize: 13) + $0.setTitle(guest.username, for: .normal) + $0.setTitleColor(guest.isResponse ? UIColor.white : UIColor.pink01, for: .normal) + $0.backgroundColor = guest.isResponse ? UIColor.pink01 : UIColor.white + $0.clipsToBounds = true + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor.pink01.cgColor + $0.layer.cornerRadius = 26 * heightRatio / 2 + } + + if #available(iOS 15.0, *) { + var buttonConfiguration = UIButton.Configuration.plain() + buttonConfiguration.contentInsets = NSDirectionalEdgeInsets(top: 1 * heightRatio, leading: 13 * widthRatio, bottom: 0, trailing: 11 * widthRatio) + nameButton.configuration = buttonConfiguration + + } else { + nameButton.titleEdgeInsets = UIEdgeInsets(top: 1 * heightRatio, left: 13 * widthRatio, bottom: 0, right: 11 * widthRatio) + } + + nameTagStackView.addArrangedSubview(nameButton) + } + } + + private func setData() { + titleLabel.text = plansData?.invitation.invitationTitle + detailTextView.text = plansData?.invitation.invitationDesc + + guard let responsedGuestCount = plansData?.invitation.guests.filter({ $0.isResponse == true }).count, + let guestCount = plansData?.invitation.guests.count else { return } + + confirmCountLabel.setAttributedText(defaultText: "\(responsedGuestCount)/\(guestCount)", + containText: "\(responsedGuestCount)", + font: UIFont.dinProBoldFont(ofSize: 30), + color: UIColor.pink01, kernValue: -0.6) + if responsedGuestCount == guestCount { + headLabel.setAttributedText(defaultText: "약속을 확정해 주세요", containText: "약속을 확정", + font: UIFont.hanSansBoldFont(ofSize: 24), + color: UIColor.grey06, kernValue: -0.6) + } + else { + headLabel.setAttributedText(defaultText: "답변을 기다리고 있어요", containText: "답변", + font: UIFont.hanSansBoldFont(ofSize: 24), + color: UIColor.grey06, kernValue: -0.6) + } + } + + // MARK: - Network + + private func getPlansData() { // 처음 데이터 불러오기 + guard let plansId = plansId else { return } + GetPlansSendDetailDataService.shared.getSendDetail(plansId: plansId) { (response) in + switch response { + case .success(let data) : + if let responseData = data as? PlansSendDetailDataModel { + self.plansData = responseData.data + } + case .requestErr(_) : + print("requestERR") + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + private func postAcceptRequest() { // 약속 확정 + guard let plansId = plansId, + let checkedOptionDateId = checkedOptionDateId else { return } + PostPlansRequestAcceptService.shared.postPlansRequestAccept(plansId: plansId, dateId: checkedOptionDateId) { (response) in + switch response { + case .success(let data) : + if let response = data as? PlansRequestAcceptDataModel { + print(response.status, response.message) + } + case .requestErr(_) : + print("requestERR") + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + private func postCancelRequest() { // 약속 취소 + guard let plansId = plansId else { return } + PutInvitationCancelService.shared.putInvitationCancel(plansId: plansId) { (response) in + switch response { + case .success(let data): + if let response = data as? PlansRequestAcceptDataModel{ + print(response.status, response.message) + } + case .requestErr(_): + print("requestERR") + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + + // MARK: - Actions + + @objc private func backButtonDidTap(_ sender: UIButton) { +// self.navigationController?.popViewController(animated: true) + delegate?.backButtonDidTap() + } + + @objc private func cancelButtonDidTap(_ sender: UIButton) { + let alertVC = SMPopUpVC(withType: .cancelPlans) + alertVC.modalPresentationStyle = .overFullScreen + self.present(alertVC, animated: false, completion: nil) + + alertVC.pinkButtonCompletion = { + self.postCancelRequest() + self.dismiss(animated: true, completion: nil) + let viewControllers : [UIViewController] = self.navigationController!.viewControllers as [UIViewController] + self.navigationController?.popToViewController(viewControllers[viewControllers.count - 3 ], animated: false) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "toastMessage"), object: "약속을 취소했어요") + } + } + + @objc private func confirmButtonDidTap(_ sender: UIButton) { + var alertVC: SMRequestPopUpVC + var yearList: [String] = [] + var dateList: [String] = [] + + guard let checkedOptionIndex = checkedOptionIndex, + let plansData = plansData else { return } + let plansDate = plansData.invitationDates[checkedOptionIndex] //선택한 옵션뷰 정보 + + yearList.append(plansDate.date) + do { + dateList.append(try String.getAMPMTimeString(from: plansDate.start) + " ~ " + String.getAMPMTimeString(from: plansDate.end)) + } catch let error { + print(error.localizedDescription) + return + } + + if plansData.invitation.guests.filter({ $0.isResponse == false }).count == 0 { // 모두 응답한 경우 + if plansDate.respondent.count == plansData.invitation.guests.count { // 모든 응답자들이 선택한 옵션에 있는 경우 + alertVC = SMRequestPopUpVC(withType: .sendConfirm) + } else { // 모든 응답자들이 선택한 옵션에 있지는 않은 경우 + alertVC = SMRequestPopUpVC(withType: .sendNotSelectConfirm) + } + } else { // 미응답자가 있는 경우 + alertVC = SMRequestPopUpVC(withType: .sendNotRequestConfirm) + } + + alertVC.modalPresentationStyle = .overFullScreen + alertVC.yearText = yearList + alertVC.dateText = dateList + self.present(alertVC, animated: false, completion: nil) + + alertVC.pinkButtonCompletion = { + self.postAcceptRequest() + self.dismiss(animated: false, completion: nil) + let viewControllers : [UIViewController] = self.navigationController!.viewControllers as [UIViewController] + self.navigationController?.popToViewController(viewControllers[viewControllers.count - 3], animated: false) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "toastMessage"), object: "약속을 확정했어요") + } + } +} + +// MARK: - Extensions + +extension PlansSendListVC: TimeOptionViewDelegate { + func timeOptionViewDidTap(view: ConfirmTimeOptionView, tag: Int) { + if checkedOptionIndex == tag { // 선택된 옵션을 또다시 터치할경우 취소처리 + view.isSelected = false + checkedOptionIndex = nil + confirmButton.isEnabled = false + confirmButton.backgroundColor = UIColor.grey02 + + checkedOptionDateId = nil + } else { + view.isSelected = true + checkedOptionIndex = tag + dateSelectStackView.arrangedSubviews.filter { $0.tag != tag }.forEach { // 선택한 옵션 이외는 취소처리 + guard let optionView: ConfirmTimeOptionView = $0 as? ConfirmTimeOptionView else { return } + optionView.isSelected = false + } + confirmButton.isEnabled = true + confirmButton.backgroundColor = UIColor.pink01 + + checkedOptionDateId = view.invitationDateID + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/RegisterScene/EmailRegisterVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/RegisterScene/EmailRegisterVC.swift new file mode 100644 index 0000000..8e46ec3 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/RegisterScene/EmailRegisterVC.swift @@ -0,0 +1,514 @@ +// +// RegisterVC.swift +// SeeMeet +// +// Created by 박익범 on 2022/01/18. +// +protocol EmailRegisterVCDelegate{ + func backButtonDidTap() + func closeButtonDidTap() + func nextButtonDidTap(accessToken: String, refreshToken: String, email: String) +} + +import UIKit +import SnapKit +import Then + +class EmailRegisterVC: UIViewController { + + // MARK: - UI Components + + private let headerView = UIView().then { + $0.backgroundColor = UIColor.white + } + + private let backButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_back"), for: .normal) + } + + private let closeButton = UIButton().then { + $0.setImage(UIImage(named: "btn_close_bold"), for: .normal) + } + + private let headLabel = UILabel().then{ + $0.setAttributedText(defaultText: "회원가입", font: UIFont.hanSansBoldFont(ofSize: 24), color: UIColor.grey06, kernValue: -0.6) + } + //name + private let nameView = UIView().then{ + $0.backgroundColor = .none + } + + private let nameViewheadLabel = UILabel().then{ + $0.setAttributedText(defaultText: "이름", font: UIFont.hanSansMediumFont(ofSize: 14), color: UIColor.grey06, kernValue: -0.6) + } + + private let nameTextView = GrayTextView(type: .register) + + private let nameTextField = GrayTextField(type: .email, placeHolder: "이름") + //email + private let emailView = UIView().then{ + $0.backgroundColor = .none + } + + private let emailViewheadLabel = UILabel().then{ + $0.setAttributedText(defaultText: "이메일", font: UIFont.hanSansMediumFont(ofSize: 14), color: UIColor.grey06, kernValue: -0.6) + } + + private let emailTextView = GrayTextView(type: .register) + + private let emailTextField = GrayTextField(type: .email, placeHolder: "이메일") + + private let emailWarningLabel = UILabel().then { + $0.setAttributedText(defaultText: "올바른 이메일을 입력해주세요.", font: UIFont.hanSansRegularFont(ofSize: 12), color: UIColor.red, kernValue: -0.6) + } + //password + private let passwordView = UIView().then { + $0.backgroundColor = .none + } + + private let passwordViewheadLabel = UILabel().then { + $0.setAttributedText(defaultText: "비밀번호", font: UIFont.hanSansMediumFont(ofSize: 14), color: UIColor.grey06, kernValue: -0.6) + } + + private let passwordTextView = GrayTextView(type: .register) + private let passwordTextField = GrayTextField(type: .password, placeHolder: "비밀번호") + + private let seePasswordButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "ic_password_notsee"), for: .normal) + } + + private let passwordWarningLabel = UILabel().then { + $0.setAttributedText(defaultText: "8자 이상의 비밀번호를 입력해주세요.", font: UIFont.hanSansRegularFont(ofSize: 12), color: UIColor.red, kernValue: -0.6) + } + //passwordConfirm + private let confirmView = UIView().then { + $0.backgroundColor = .none + } + + private let confirmViewheadLabel = UILabel().then { + $0.setAttributedText(defaultText: "비밀번호 확인", font: UIFont.hanSansMediumFont(ofSize: 14), color: UIColor.grey06, kernValue: -0.6) + } + + private let confirmTextView = GrayTextView(type: .register) + private let confirmTextField = GrayTextField(type: .password, placeHolder: "비밀번호 확인") + private let confirmWarningLabel = UILabel().then { + $0.setAttributedText(defaultText: "비밀번호가 일치하지 않아요", font: UIFont.hanSansRegularFont(ofSize: 12), color: UIColor.red, kernValue: -0.6) + } + + private let seeConfirmButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "ic_password_notsee"), for: .normal) + } + + private let bottomLineView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private let nextButton = UIButton().then { + $0.backgroundColor = UIColor.grey02 + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.setTitle("다음으로", for: .normal) + } + + // MARK: - Properties + + weak var coordinator: Coordinator? + + private var isConfirmNotSee = true + private var isPasswordNotSee = true + private var emailBool: Bool = false + private var pwdBool: Bool = false + private var pwdCheckBool: Bool = false + private var buttonOn: Bool = false + private var isKeboardShow: Bool = false + var delegate: EmailRegisterVCDelegate? + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setRegisterLayout() + configUI() + } + + // MARK: - Layout + + private func setRegisterLayout(){ + view.addSubviews([headerView, + emailView, + passwordView, + confirmView, + nextButton, + bottomLineView]) + headerView.addSubviews([backButton,headLabel,closeButton]) + + + emailView.addSubviews([emailViewheadLabel, + emailTextView, + emailWarningLabel]) + emailTextView.addSubview(emailTextField) + + passwordView.addSubviews([passwordViewheadLabel, + passwordTextView, + passwordWarningLabel]) + + passwordTextView.addSubviews([passwordTextField, seePasswordButton]) + + confirmView.addSubviews([confirmViewheadLabel, confirmTextView, confirmWarningLabel]) + confirmTextView.addSubviews([confirmTextField, seeConfirmButton]) + + emailTextField.delegate = self + passwordTextField.delegate = self + confirmTextField.delegate = self + + emailWarningLabel.isHidden = true + passwordWarningLabel.isHidden = true + confirmWarningLabel.isHidden = true + + view.dismissKeyboardWhenTappedAround() + + headerView.snp.makeConstraints{ + $0.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + $0.height.equalTo(58) + } + backButton.snp.makeConstraints{ + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(2) + $0.width.height.equalTo(48) + } + headLabel.snp.makeConstraints{ + $0.centerY.equalTo(backButton) + $0.centerX.equalToSuperview() + } + closeButton.snp.makeConstraints{ + $0.centerY.equalToSuperview() + $0.trailing.equalToSuperview().offset(-4) + $0.width.height.equalTo(48) + } + emailView.snp.makeConstraints{ + $0.top.equalTo(headLabel.snp.bottom).offset(40) + $0.leading.equalToSuperview().offset(20) + $0.trailing.equalTo(view.snp.trailing).offset(-20) + $0.height.equalTo(103) + } + emailViewheadLabel.snp.makeConstraints{ + $0.top.equalToSuperview().offset(1) + $0.leading.equalToSuperview().offset(0) + $0.width.equalTo(50) + } + emailTextView.snp.makeConstraints{ + $0.top.equalTo(emailViewheadLabel.snp.bottom).offset(12) + $0.leading.trailing.equalToSuperview().offset(0) + $0.height.equalTo(50) + } + emailTextField.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(12) + $0.top.bottom.trailing.equalToSuperview().offset(0) + } + emailWarningLabel.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(0) + $0.bottom.equalToSuperview().offset(1) + $0.width.equalTo(150) + } + passwordView.snp.makeConstraints{ + $0.top.equalTo(emailView.snp.bottom).offset(18) + $0.leading.equalToSuperview().offset(20) + $0.trailing.equalTo(view.snp.trailing).offset(-20) + $0.height.equalTo(103) + } + passwordViewheadLabel.snp.makeConstraints{ + $0.top.equalToSuperview().offset(1) + $0.leading.equalToSuperview().offset(0) + $0.width.equalTo(50) + } + passwordTextView.snp.makeConstraints{ + $0.top.equalTo(passwordViewheadLabel.snp.bottom).offset(12) + $0.leading.trailing.equalToSuperview().offset(0) + $0.height.equalTo(50) + } + passwordTextField.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(12) + $0.top.bottom.equalToSuperview().offset(0) + $0.trailing.equalTo(seePasswordButton.snp.leading).offset(0) + } + passwordWarningLabel.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(0) + $0.bottom.equalToSuperview().offset(1) + $0.width.equalTo(250) + } + seePasswordButton.snp.makeConstraints{ + $0.top.bottom.equalToSuperview().offset(0) + $0.trailing.equalToSuperview().offset(0) + $0.width.equalTo(48) + } + + confirmView.snp.makeConstraints{ + $0.top.equalTo(passwordView.snp.bottom).offset(18) + $0.leading.equalToSuperview().offset(20) + $0.trailing.equalTo(view.snp.trailing).offset(-20) + $0.height.equalTo(103) + } + confirmViewheadLabel.snp.makeConstraints{ + $0.top.equalToSuperview().offset(1) + $0.leading.equalToSuperview().offset(0) + $0.width.equalTo(200) + } + confirmTextView.snp.makeConstraints{ + $0.top.equalTo(confirmViewheadLabel.snp.bottom).offset(12) + $0.leading.trailing.equalToSuperview().offset(0) + $0.height.equalTo(50) + } + confirmTextField.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(12) + $0.top.bottom.equalToSuperview().offset(0) + $0.trailing.equalTo(seeConfirmButton.snp.leading).offset(0) + } + confirmWarningLabel.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(0) + $0.bottom.equalToSuperview().offset(1) + $0.width.equalTo(150) + } + seeConfirmButton.snp.makeConstraints{ + $0.top.bottom.equalToSuperview().offset(0) + $0.trailing.equalToSuperview().offset(0) + $0.width.equalTo(48) + } + + bottomLineView.snp.makeConstraints{ + $0.leading.trailing.equalToSuperview().offset(0) + $0.height.equalTo(1) + $0.bottom.equalToSuperview().offset(-111) + } + nextButton.snp.makeConstraints{ + $0.top.equalTo(bottomLineView).offset(15) + $0.leading.equalToSuperview().offset(20) + $0.trailing.equalToSuperview().offset(-20) + $0.height.equalTo(54) + } + + } + + private func configUI() { + backButton.addTarget(self, action: #selector(backButtonClicked(_:)), for: .touchUpInside) + closeButton.addTarget(self, action: #selector(closeButtonClicked(_:)), for: .touchUpInside) + passwordTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) + seePasswordButton.addTarget(self, action: #selector(seePasswordButtonClicked(_:)), for: .touchUpInside) + seeConfirmButton.addTarget(self, action: #selector(seeConfirmButtonClicked(_:)), for: .touchUpInside) + nextButton.addTarget(self, action: #selector(registerButtonClicked(_:)), for: .touchUpInside) + + } + + private func isValidEmail(testStr:String) -> Bool { + let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,50}" + let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx) + return emailTest.evaluate(with: testStr) + } + + private func isValidPassword(testStr:String, regEx: [String]) -> [Bool]{ + var passwordStatus: [Bool] = [] + for reg in regEx{ + let passwordRegEx = reg + let passwordTest = NSPredicate(format:"SELF MATCHES %@", passwordRegEx) + passwordStatus.append(passwordTest.evaluate(with: testStr)) + } + return passwordStatus + } + + private func isButtonOn() { + if emailBool && pwdBool && pwdCheckBool { + buttonOn = true + nextButton.backgroundColor = UIColor.pink01 + } + else { + buttonOn = false + nextButton.backgroundColor = UIColor.grey02 + } + } + + func overlappingEmail(){ + emailWarningLabel.isHidden = false + emailTextView.layer.borderWidth = 1 + emailTextView.layer.borderColor = UIColor.red.cgColor + emailWarningLabel.text = "이미 등록된 이메일이에요." + emailBool = false + } + + // MARK: - Actions + + @objc func seeConfirmButtonClicked(_ sender: UIButton) { + if isConfirmNotSee == true{ + isConfirmNotSee = false + seeConfirmButton.setBackgroundImage(UIImage(named: "ic_password_see"), for: .normal) + confirmTextField.isSecureTextEntry = isConfirmNotSee + } + else{ + isConfirmNotSee = true + seeConfirmButton.setBackgroundImage(UIImage(named: "ic_password_notsee"), for: .normal) + confirmTextField.isSecureTextEntry = isConfirmNotSee + } + } + @objc func seePasswordButtonClicked(_ sender: UIButton) { + if isPasswordNotSee == true{ + isPasswordNotSee = false + seePasswordButton.setBackgroundImage(UIImage(named: "ic_password_see"), for: .normal) + passwordTextField.isSecureTextEntry = isPasswordNotSee + } + else{ + isPasswordNotSee = true + seePasswordButton.setBackgroundImage(UIImage(named: "ic_password_notsee"), for: .normal) + passwordTextField.isSecureTextEntry = isPasswordNotSee + } + } + + @objc func backButtonClicked(_ sender: UIButton) { + delegate?.backButtonDidTap() + } + + @objc func closeButtonClicked(_ sender: UIButton){ + delegate?.closeButtonDidTap() + } + + @objc func registerButtonClicked(_ sender: UIButton) { + if buttonOn { + PostRegisterService.shared.register(email: emailTextField.text ?? "", password: passwordTextField.text ?? "", passwordConfirm: confirmTextField.text ?? ""){ (response) in + switch(response) + { + case .success(let success): + if let success = success as? RegisterDataModel { + if success.status == 404 { + self.overlappingEmail() + } + else if success.status == 200 { + guard let accessToken = success.data?.accesstoken, + let refreshToken = success.data?.refreshtoken else { return } + + if let email = success.data?.newUser.email{ + self.delegate?.nextButtonDidTap(accessToken: accessToken, refreshToken: refreshToken, email: email) + } + } + } + case .requestErr(let message) : + print("requestERR", message) + if message as? String == "이미 사용중인 이메일입니다."{ + self.emailTextView.layer.borderWidth = 1 + self.emailTextView.layer.borderColor = UIColor.red.cgColor + self.emailWarningLabel.text = "이미 사용중인 이메일입니다." + self.emailWarningLabel.isHidden = false + } + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } + } + + + +} + +extension EmailRegisterVC: UITextFieldDelegate{ + func textFieldDidBeginEditing(_ textField: UITextField) { + if textField == emailTextField{ + emailTextView.layer.borderWidth = 0 + emailWarningLabel.isHidden = true + } + } + func textFieldDidEndEditing(_ textField: UITextField) { + switch textField{ + case emailTextField: + if !isValidEmail(testStr: emailTextField.text ?? ""){ + emailTextView.layer.borderWidth = 1 + emailTextView.layer.borderColor = UIColor.red.cgColor + emailWarningLabel.text = "올바른 이메일을 입력해주세요." + emailWarningLabel.isHidden = false + emailBool = false + isButtonOn() + } + else{ + emailTextView.layer.borderWidth = 0 + emailWarningLabel.isHidden = true + emailBool = true + isButtonOn() + } + case passwordTextField: + let regList: [String] = ["^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[!@#$%^&*()_+=-]).{8,16}", "^(?=.*[A-Za-z])(?=.*[0-9]).{8,16}", "^(?=.*[A-Za-z])(?=.*[!@#$%^&*()_+=-]).{8,16}", "^(?=.*[0-9])(?=.*[!@#$%^&*()_+=-]).{8,16}"] + var regiBoolList: [Bool] = [] + var regiCount = 0 + regiBoolList = isValidPassword(testStr: passwordTextField.text ?? "", regEx: regList) + for valid in regiBoolList{ + if valid { + regiCount += 1 + } + } + if regiCount < 1{ + passwordTextView.layer.borderWidth = 1 + passwordTextView.layer.borderColor = UIColor.red.cgColor + passwordWarningLabel.text = "영문, 숫자, 특수문자 중 2가지 이상을 사용해주세요." + passwordWarningLabel.isHidden = false + pwdBool = false + isButtonOn() + } + else{ + passwordTextView.layer.borderWidth = 0 + passwordWarningLabel.isHidden = true + pwdBool = true + isButtonOn() + } + case confirmTextField: + if confirmTextField.text != passwordTextField.text { + confirmTextView.layer.borderWidth = 1 + confirmTextView.layer.borderColor = UIColor.red.cgColor + confirmWarningLabel.isHidden = false + confirmWarningLabel.text = "비밀번호가 일치하지 않아요." + pwdCheckBool = false + isButtonOn() + } + else{ + confirmTextView.layer.borderWidth = 0 + confirmWarningLabel.isHidden = true + pwdCheckBool = true + isButtonOn() + } + default: + print("default") + } + + } + @objc func textFieldDidChange(_ sender: Any?) { + if passwordTextField.text?.count ?? 0 > 1{ + if passwordTextField.text?.count ?? 0 < 8 { + passwordTextView.layer.borderWidth = 1 + passwordTextView.layer.borderColor = UIColor.red.cgColor + passwordWarningLabel.isHidden = false + passwordWarningLabel.text = "8자 이상의 비밀번호를 입력해주세요." + } + else { + passwordTextView.layer.borderWidth = 0 + passwordWarningLabel.isHidden = true + } + } + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + switch textField{ + case passwordTextField, confirmTextField: + if let char = string.cString(using: String.Encoding.utf8) { + let isBackSpace = strcmp(char, "\\b") + if isBackSpace == -92 { + return true + } + } + guard textField.text!.count < 16 else { return false } + default: + return true + + } + return true + } +} + + diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/RegisterScene/ProfileRegisterVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/RegisterScene/ProfileRegisterVC.swift new file mode 100644 index 0000000..6fed140 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/RegisterScene/ProfileRegisterVC.swift @@ -0,0 +1,463 @@ +// +// ProfileRegisterVC.swift +// SeeMeet +// +// Created by 이유진 on 2022/03/08. +// + +import UIKit +import SnapKit +import Then + +protocol ProfileRegisterVCDelegate { + func registerBackButtonDidTap() + func closeButtonDidTap() +} + +class ProfileRegisterVC: UIViewController { + + // MARK: - UI Components + + private let navigationBarView = UIView().then{ + $0.backgroundColor = UIColor.white + } + + private let backButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_back"), for: .normal) + } + + private let navigationTitleLabel = UILabel().then { + $0.font = UIFont.hanSansBoldFont(ofSize: 18) + $0.textColor = UIColor.grey06 + $0.setAttributedText(defaultText: "SeeMeet 시작하기", kernValue: -0.6) + } + + private let closeButton = UIButton().then { + $0.setImage(UIImage(named: "btn_close_bold"), for: .normal) + } + + private let directionInLabel = UILabel().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.textColor = UIColor.grey05 + $0.setAttributedText(defaultText: "SeeMeet에서 사용할 이름과 닉네임을 입력해주세요", kernValue: -0.6) + $0.textAlignment = .center + $0.numberOfLines = 3 + } + + //name + private let nameView = UIView().then { + $0.backgroundColor = .none + } + + private let nameViewheadLabel = UILabel().then { + $0.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.textColor = UIColor.grey06 + $0.setAttributedText(defaultText: "이름", kernValue: -0.6) + } + + private let nameTextView = GrayTextView(type: .register) + private let nameTextField = GrayTextField(type: .email, placeHolder: "이름") + + private let idView = UIView().then { + $0.backgroundColor = .none + } + + private let idViewheadLabel = UILabel().then { + $0.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.textColor = UIColor.grey06 + $0.setAttributedText(defaultText: "닉네임", kernValue: -0.6) + } + + private let idTextView = GrayTextView(type: .register) + private let idTextField = GrayTextField(type: .email, placeHolder: "닉네임") + private let idWarningLabel = UILabel().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 12) + $0.textColor = UIColor.red + $0.setAttributedText(defaultText: "올바른 아이디를 입력해주세요.", kernValue: -0.6) + } + + private let startButton = UIButton().then { + $0.backgroundColor = UIColor.grey02 + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + $0.setTitle("시작하기", for: .normal) + } + + // MARK: - Properties + private var isKeyboardShow: Bool = false + + weak var coordinator: Coordinator? + var delegate: ProfileRegisterVCDelegate? + + var accessTokenToSet: String? + var refreshTokenToSet: String? + var nameToSet: String? + var email: String? + var isAppleLogin: Bool = false + + enum IDValidCase { + case isShortLength + case isOnlyNumber + case isNotAcceptableCharacters + case isAlreadyUsed + case isValid + } + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setAutoLayouts() + NotificationCenter.default.do { + $0.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) + $0.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) + } + configUI() + + if let nameToSet { // 소셜로그인 인 경우, 이름 필드 숨기기 + nameView.isHidden = true + nameTextView.isHidden = true + nameTextField.isHidden = true + nameTextField.text = nameToSet + directionInLabel.setAttributedText(defaultText: "친구들이 회원님에게 약속을 보내기 위해서는 닉네임이 필요해요.", kernValue: -0.6) + } + } + + // MARK: - setLayouts + + private func setAutoLayouts() { + self.navigationController?.isNavigationBarHidden = true + view.addSubviews( [navigationBarView, + directionInLabel, + nameView, + idView, + idWarningLabel, + startButton + ]) + + navigationBarView.addSubviews([backButton,navigationTitleLabel,closeButton]) + + nameView.addSubviews([nameViewheadLabel, nameTextView]) + nameTextView.addSubview(nameTextField) + + idView.addSubviews([idViewheadLabel, idTextView]) + + idTextView.addSubview(idTextField) + + nameTextField.delegate = self + idTextField.delegate = self + + idWarningLabel.isHidden = true + + view.dismissKeyboardWhenTappedAround() + + navigationBarView.snp.makeConstraints{ + $0.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + $0.height.equalTo(58 * heightRatio) + } + backButton.snp.makeConstraints{ + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(2 * widthRatio) + $0.width.height.equalTo(48 * widthRatio) + } + navigationTitleLabel.snp.makeConstraints{ + $0.centerY.equalTo(backButton) + $0.centerX.equalToSuperview() + } + closeButton.snp.makeConstraints{ + $0.centerY.equalToSuperview() + $0.trailing.equalToSuperview().offset(-4 * widthRatio) + $0.width.height.equalTo(48 * widthRatio) + } + directionInLabel.snp.makeConstraints{ + $0.top.equalTo(navigationBarView.snp.bottom).offset(16 * heightRatio) + $0.centerX.equalToSuperview() + $0.width.equalTo(158 * widthRatio) + + } + nameView.snp.makeConstraints{ + $0.top.equalTo(directionInLabel.snp.bottom).offset(32 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalTo(view.snp.trailing).offset(-20 * widthRatio) + $0.height.equalTo(80 * heightRatio) + } + nameViewheadLabel.snp.makeConstraints{ + $0.top.equalToSuperview().offset(1 * heightRatio) + $0.leading.equalToSuperview() + + } + nameTextView.snp.makeConstraints{ + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(50 * heightRatio) + $0.bottom.equalToSuperview() + } + nameTextField.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(12 * widthRatio) + $0.top.bottom.trailing.equalToSuperview() + } + idView.snp.makeConstraints{ + $0.top.equalTo(nameView.snp.bottom).offset(16 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalTo(view.snp.trailing).offset(-20 * widthRatio) + $0.height.equalTo(80 * heightRatio) + } + idViewheadLabel.snp.makeConstraints{ + $0.top.equalToSuperview().offset(1 * heightRatio) + $0.leading.equalToSuperview() + $0.width.equalTo(50 * widthRatio) + } + idTextView.snp.makeConstraints{ + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(50 * heightRatio) + $0.bottom.equalToSuperview() + } + idTextField.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(12 * widthRatio) + $0.top.bottom.trailing.equalToSuperview() + } + idWarningLabel.snp.makeConstraints{ + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.top.equalTo(idView.snp.bottom).offset(10 * heightRatio) + } + startButton.snp.makeConstraints{ + $0.top.equalTo(idWarningLabel.snp.bottom).offset(15 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.height.equalTo(54 * heightRatio) + } + } + + // MARK: - Custom Methods + + private func configUI() { + backButton.addTarget(self, action: #selector(backButtonClicked(_:)), for: .touchUpInside) + closeButton.addTarget(self, action: #selector(closeButtonClicked(_:)), for: .touchUpInside) + startButton.addTarget(self, action: #selector(startButtonClicked(_:)), for: .touchUpInside) + startButton.isEnabled = false + nameTextField.addTarget(self, action: #selector(nameTextFieldDidChange(_:)), for: .editingChanged) + idTextField.addTarget(self, action: #selector(idTextFieldDidChange(_:)), for: .editingChanged) + } + + private func checkIdValid(id: String) -> IDValidCase { + let pattern = "^[a-zA-Z0-9_\\.]*$" + + guard let _ = id.range(of: pattern, options: .regularExpression) else { + return .isNotAcceptableCharacters + } + + if id.count < 7 { + return .isShortLength + } else if id.allSatisfy({$0.isNumber}) { + return .isOnlyNumber + } else { + return .isValid + } + } + + private func presentIdWarning() { + idTextView.layer.do { + $0.borderColor = UIColor.pink01.cgColor + $0.borderWidth = 1 + } + idWarningLabel.isHidden = false + } + + private func hideIdWarning() { + idTextView.layer.do { + $0.borderColor = nil + $0.borderWidth = 0 + } + idWarningLabel.isHidden = true + } + + // MARK: - Actions + + @objc private func keyboardWillShow(_ sender: Notification) { + if isKeyboardShow == false { + view.frame.origin.y -= 150 * heightRatio + isKeyboardShow = true + } + } + @objc private func keyboardWillHide(_ sender: Notification) { + if isKeyboardShow == true { + view.frame.origin.y += 150 * heightRatio + isKeyboardShow = false + } + } + + @objc private func backButtonClicked(_ sender: UIButton) { + delegate?.registerBackButtonDidTap() + } + + @objc private func closeButtonClicked(_ sender: UIButton){ + self.delegate?.closeButtonDidTap() + } + + @objc private func startButtonClicked(_ sender: UIButton) { + guard let accessTokenToSet = accessTokenToSet, + let refreshTokenToSet = refreshTokenToSet, + let name = nameTextField.text, + let id = idTextField.text else { return } + + var nameToRequest: String? = nil + if let nameToSet { + nameToRequest = nameToSet + } + + PutNameAndIdService.shared.putNameAndId(name: nameToRequest, userId: id, accessToken: accessTokenToSet) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let success): + if let data = success as? PutNameAndIdResponseDataModel { + switch data.status { + case 404: + self.view.makeToastAnimation(message: "네트워크 오류! 씨밋 개발자 연락처로 연락해주세요.") + default: + TokenUtils.shared.create(account: "accessToken", value: accessTokenToSet) + TokenUtils.shared.create(account: "refreshToken", value: refreshTokenToSet) + + UserDefaults.standard.do { + $0.set(name, forKey: Constants.UserDefaultsKey.userName) + $0.set(id, forKey: Constants.UserDefaultsKey.userNickname) + } + UserDefaults.standard.set(self.email, forKey: Constants.UserDefaultsKey.userEmail) + UserDefaults.standard.set(true, forKey: Constants.UserDefaultsKey.isLogin) + UserDefaults.standard.set(self.isAppleLogin, forKey: Constants.UserDefaultsKey.isAppleLogin) + + self.delegate?.closeButtonDidTap() + } + } + case .requestErr(let error): + self.idWarningLabel.text = "이미 사용 중이에요" + self.presentIdWarning() + case .pathErr: + print("pathErr") + case .networkFail: + print("networkFail") + case .serverErr: + print("serverErr") + } + } + } + + @objc func nameTextFieldDidChange(_ sender: Any?) { + guard let nameText = nameTextField.text else {return} + if nameText.count > 5 { + nameTextField.deleteBackward() + } + } + + @objc func idTextFieldDidChange(_ sender: Any?) { + + guard let text = idTextField.text else { return } + switch checkIdValid(id: text) { + case .isShortLength: + idWarningLabel.text = "7자 이상 써주세요" + presentIdWarning() + startButton.do { + $0.isEnabled = false + $0.backgroundColor = UIColor.grey02 + } + case .isOnlyNumber: + idWarningLabel.text = "숫자로만은 만들 수 없어요" + presentIdWarning() + startButton.do { + $0.isEnabled = false + $0.backgroundColor = UIColor.grey02 + } + case .isNotAcceptableCharacters: + idWarningLabel.text = "아이디는 알파벳, 숫자, 밑줄, 마침표만 사용 가능해요" + presentIdWarning() + startButton.do { + $0.isEnabled = false + $0.backgroundColor = UIColor.grey02 + } + case .isAlreadyUsed: + idWarningLabel.text = "이미 사용 중이에요" + presentIdWarning() + startButton.do { + $0.isEnabled = false + $0.backgroundColor = UIColor.grey02 + } + case .isValid: + hideIdWarning() + startButton.do { + $0.isEnabled = true + $0.backgroundColor = UIColor.pink01 + } + } + + } +} + + + +// MARK: - Extensions + +extension ProfileRegisterVC: UITextFieldDelegate { + func textFieldDidBeginEditing(_ textField: UITextField) { + if textField == idTextField { + idTextView.layer.borderWidth = 0 + idWarningLabel.isHidden = true + } + } + func textFieldDidEndEditing(_ textField: UITextField) { + if textField == idTextField { + guard let text = textField.text else { return } + switch checkIdValid(id: text) { + case .isShortLength: + idWarningLabel.text = "7자 이상 써주세요" + presentIdWarning() + startButton.do { + $0.isEnabled = false + $0.backgroundColor = UIColor.grey02 + } + case .isOnlyNumber: + idWarningLabel.text = "숫자로만은 만들 수 없어요" + presentIdWarning() + startButton.do { + $0.isEnabled = false + $0.backgroundColor = UIColor.grey02 + } + case .isNotAcceptableCharacters: + idWarningLabel.text = "아이디는 알파벳, 숫자, 밑줄, 마침표만 사용 가능해요" + presentIdWarning() + startButton.do { + $0.isEnabled = false + $0.backgroundColor = UIColor.grey02 + } + case .isAlreadyUsed: + idWarningLabel.text = "이미 사용 중이에요" + presentIdWarning() + startButton.do { + $0.isEnabled = false + $0.backgroundColor = UIColor.grey02 + } + case .isValid: + hideIdWarning() + startButton.do { + $0.isEnabled = true + $0.backgroundColor = UIColor.pink01 + } + } + } + } + + // 한 글자 입력시 호출 + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + if textField == nameTextField{ + let utf8Char = string.cString(using: .utf8) + let isBackSpace = strcmp(utf8Char, "\\b") + if string.hasCharacters() || isBackSpace == -92{ + return true + } + return false + } + + return true + } + +} + + + diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/Components/SelectableWeekDayCVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/Components/SelectableWeekDayCVC.swift new file mode 100644 index 0000000..1ae6b1f --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/Components/SelectableWeekDayCVC.swift @@ -0,0 +1,146 @@ +import UIKit + +/// 셀의 상태를 관리하기 위한 열거타입 +enum SelectableDayCVCState { + case invalid + case unselected + case selected +} + +class SelectableWeekDayCVC: UICollectionViewCell { + + // MARK: - UI Components + + private let cellView = UIView().then { + $0.tintColor = UIColor.white + $0.layer.cornerRadius = 10 + $0.backgroundColor = UIColor.white + } + + private let weekDayLabel = UILabel().then { + $0.font = UIFont.hanSansMediumFont(ofSize: 13) + $0.textColor = UIColor.grey04 + } + + private let dayLabel = UILabel().then { + $0.font = UIFont.hanSansBoldFont(ofSize: 13) + $0.textColor = UIColor.grey06 + } + + private let pinkDotView = UIView().then { + $0.backgroundColor = UIColor.pink01 + } + + private let touchAreaView: UIView = UIView().then { + $0.backgroundColor = UIColor.clear + } + + // MARK: - Properties + + static let identifier: String = "SelectableWeekDayCVC" + + + /// 표시할 날짜 + var dateToShow: Date? { + didSet { + if let dateToShow = dateToShow { + weekDayLabel.text = Date.getKoreanWeekDay(from: dateToShow) + dayLabel.text = "\(dateToShow.day)" + } + } + } + /// 해당 날짜에 약속이 있는지 여부 + var isScheduled = Bool() { + didSet { + pinkDotView.isHidden = !isScheduled + } + } + /// 셀의 상태를 관리할 프로퍼티 + var status: SelectableDayCVCState = .unselected { + didSet { + guard let date = dateToShow else { return } + + switch status { + case .invalid: + isUserInteractionEnabled = false + cellView.layer.borderWidth = 0 + cellView.backgroundColor = .grey02 + weekDayLabel.textColor = .grey03 + dayLabel.textColor = .grey03 + + case .unselected: + isUserInteractionEnabled = true + if Calendar.current.isDateInToday(date) { + cellView.layer.borderWidth = 0 + cellView.backgroundColor = UIColor.pink01 + weekDayLabel.textColor = .white + dayLabel.textColor = .white + } else { + cellView.layer.borderWidth = 0 + cellView.backgroundColor = UIColor.white + weekDayLabel.textColor = .grey04 + dayLabel.textColor = .grey06 + } + case .selected: + isUserInteractionEnabled = true + if Calendar.current.isDateInToday(date) { // 이 셀이 만약 '오늘'을 나타낸다면 + cellView.layer.borderColor = UIColor.black.cgColor + cellView.layer.borderWidth = 1 + cellView.backgroundColor = UIColor.pink01 + weekDayLabel.textColor = .white + dayLabel.textColor = .white + } else { + cellView.backgroundColor = UIColor.black + weekDayLabel.textColor = .white + dayLabel.textColor = .white + } + } + } + } + // MARK: - initializer + + override init(frame: CGRect) { + super.init(frame: frame) + setAutoLayouts() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setAutoLayouts() + } + + private func setAutoLayouts() { + addSubviews([cellView,pinkDotView]) + cellView.addSubviews([weekDayLabel,dayLabel]) + + +// self.snp.makeConstraints({ +// $0.width.equalTo(42 * widthRatio) +// $0.height.equalTo(79 * heightRatio) +// }) + + cellView.snp.makeConstraints{ + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(70 * heightRatio) + } + + weekDayLabel.snp.makeConstraints{ + $0.top.equalToSuperview().offset(13 * heightRatio) + $0.centerX.equalToSuperview() + $0.centerY.equalToSuperview().offset(-11 * heightRatio) + } + + dayLabel.snp.makeConstraints{ + $0.top.equalTo(weekDayLabel.snp.bottom).offset(8 * heightRatio) + $0.centerX.equalToSuperview() + } + pinkDotView.snp.makeConstraints{ + $0.top.equalTo(cellView.snp.bottom).offset(4 * heightRatio) + $0.centerX.equalTo(cellView.snp.centerX) + $0.width.height.equalTo(5 * widthRatio) + } + + pinkDotView.layer.cornerRadius = 5/2 + pinkDotView.isHidden = true + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/Components/SelectedDateSheet.swift b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/Components/SelectedDateSheet.swift new file mode 100644 index 0000000..2a6b2ac --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/Components/SelectedDateSheet.swift @@ -0,0 +1,236 @@ +import UIKit +import RxSwift +import RxCocoa +import RxRelay + +class SelectedDateSheet: UIView { + + // MARK: - UI Components + + private let grabber: UIView = UIView().then { + $0.backgroundColor = UIColor.grey02 + $0.layer.cornerRadius = 2 + } + + let titleLabel: UILabel = UILabel().then { + $0.text = "선택한 날짜" + $0.font = UIFont.hanSansMediumFont(ofSize: 16) + $0.textColor = UIColor.grey06 + } + private let touchAreaView: UIView = UIView().then { + $0.backgroundColor = .clear + } + + let selectedCountLabel: UILabel = UILabel().then { + $0.text = "0/4" + $0.textColor = UIColor.pink01 + $0.font = UIFont.dinProMediumFont(ofSize: 14) + } + + let tableView: UITableView = UITableView(frame: CGRect.zero, style: .plain).then { + $0.register(SelectedDateSheetTVC.self, forCellReuseIdentifier: SelectedDateSheetTVC.identifier) + $0.rowHeight = 73 * heightRatio + $0.isScrollEnabled = false + $0.isUserInteractionEnabled = true + } + + // MARK: - properties + + static let identifier: String = "SelectedDateSheet" + + // Snap 효과를 위한 케이스 + enum SheetViewState { + case expanded // 펼침 + case folded // 접음 + } + + private let disposeBag = DisposeBag() + private var tableViewCellDisposeBag: DisposeBag? = DisposeBag() + + private lazy var panGesture = UIPanGestureRecognizer().then { + $0.delaysTouchesBegan = false + $0.delaysTouchesEnded = false + addGestureRecognizer($0) + } + // 퍌친 상태 Top + private lazy var sheetPanMinTopConstant: CGFloat = UIScreen.getDeviceHeight() - 482 * heightRatio + // 접힌 상태 Top + private lazy var sheetPanMaxTopConstant: CGFloat = UIScreen.getDeviceHeight() - 172 * heightRatio + // 드래그 하기 전에 Bottom Sheet의 top Constraint value를 저장하기 위한 프로퍼티 + private lazy var sheetPanStartingTopConstant: CGFloat = frame.origin.y + + let rx_requestIsEnabled = PublishRelay() + + // MARK: - Life Cycle + + override init(frame: CGRect) { + super.init(frame: frame) + setAutoLayouts() + setupViews() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setAutoLayouts() + } + + // MARK: - func + + private func setAutoLayouts() { + isUserInteractionEnabled = true + backgroundColor = .white + layer.cornerRadius = 20 + getShadowView(color: UIColor.black.cgColor, masksToBounds: false, shadowOffset: CGSize(width: 0, height: -3), shadowRadius: 3, shadowOpacity: 0.1) + + addSubviews([grabber, titleLabel, selectedCountLabel, touchAreaView, tableView]) + + grabber.snp.makeConstraints { + $0.height.equalTo(4 * heightRatio) + $0.centerX.equalToSuperview() + $0.width.equalTo(29 * widthRatio) + $0.top.equalToSuperview().offset(6 * heightRatio) + } + + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(30 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + } + + touchAreaView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(60 * heightRatio) + } + + selectedCountLabel.snp.makeConstraints { + $0.leading.equalTo(titleLabel.snp.trailing).offset(7 * widthRatio) + $0.top.equalToSuperview().offset(31 * heightRatio) + } + + tableView.backgroundColor = .clear + tableView.separatorColor = .clear + tableView.separatorStyle = .none + tableView.dataSource = self + tableView.snp.makeConstraints { + $0.leading.equalToSuperview().offset(19 * widthRatio) + $0.trailing.equalToSuperview().offset(-6 * widthRatio) + $0.top.equalTo(titleLabel.snp.bottom).offset(1 * heightRatio) + $0.height.equalTo(292 * heightRatio) + } + } + + private func setupViews() { + panGesture.rx.event + .asDriver() + .drive(onNext: { [weak self] event in + guard let self = self else { return } + let transition = event.translation(in: self) + + switch event.state { + case .began: + self.sheetPanStartingTopConstant = self.frame.origin.y + case .changed: + if self.sheetPanStartingTopConstant + transition.y > self.sheetPanMinTopConstant { + self.frame = CGRect(x: 0, y: self.sheetPanStartingTopConstant + transition.y, width: self.frame.width, height: self.frame.height) + } + case .ended: + let nearestValue = self.nearest(to: self.frame.origin.y, inValues: [self.sheetPanMinTopConstant, self.sheetPanMaxTopConstant]) + + if nearestValue == self.sheetPanMinTopConstant { // 시트를 펼쳐야 한다 + self.showSheet(atState: .expanded) + } else { // 시트를 접어야 한다 + self.showSheet(atState: .folded) + } + default: + break + } + }) + .disposed(by: disposeBag) + } + + /// 여러 후보 값들 중 어느 것이 가장 number값에 가까운지 판별하는 메서드. + /// - Parameters: + /// - number: 기준 값 + /// - values: 후보값들 + /// - Returns: 후보값들 중 가장 기준 값에 가까운 값 + private func nearest(to number: CGFloat, inValues values: [CGFloat]) -> CGFloat { + guard let nearestVal = values.min(by: { abs(number - $0) < abs(number - $1) }) else { return number } + return nearestVal + } + + func showSheet(atState: SheetViewState = .folded) { + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseIn, animations: { + self.layoutIfNeeded() + if atState == .folded { + self.frame = CGRect(x: 0, y: self.sheetPanMaxTopConstant, width: self.frame.width, height: self.frame.height) + } else { + self.frame = CGRect(x: 0, y: self.sheetPanMinTopConstant, width: self.frame.width, height: self.frame.height) + } + }, completion: { _ in // 바뀐 시트 상태로 오토레이아웃을 업데이트 시킨다. 해주지 않으면 티켓 뷰 추가시 강제로 내려가는 버그 발생. + let currentTop = self.frame.minY + self.snp.remakeConstraints { + $0.top.equalTo(currentTop) + $0.trailing.leading.equalToSuperview() + $0.height.equalTo(482 * heightRatio) + } + }) + } + + func addSelectedTimeData(date: String, start: String, end: String) { // 선택지의 데이터 추가 처리 + let currentDataCount = PostRequestPlansService.sharedParameterData.date.count + if currentDataCount < 4 { + PostRequestPlansService.sharedParameterData.date.append(date) + PostRequestPlansService.sharedParameterData.start.append(start) + PostRequestPlansService.sharedParameterData.end.append(end) + + selectedCountLabel.text = "\(PostRequestPlansService.sharedParameterData.date.count)/4" + tableView.reloadData() + rx_requestIsEnabled.accept(true) + } + } +} + +// MARK: - extensions + +extension SelectedDateSheet: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + PostRequestPlansService.sharedParameterData.date.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell: SelectedDateSheetTVC = tableView.dequeueReusableCell(withIdentifier: SelectedDateSheetTVC.identifier, for: indexPath) as? SelectedDateSheetTVC else { return UITableViewCell() } + + let cellData = PostRequestPlansService.sharedParameterData + + cell.tag = indexPath.row + cell.dateLabel.text = cellData.date[indexPath.row].replacingOccurrences(of: "-", with: ".") + + let dateFormatter: DateFormatter = DateFormatter().then { + $0.dateFormat = "HH:mm" + $0.locale = Locale(identifier: "ko_KR") + $0.timeZone = TimeZone(identifier: "ko_KR") + } + + guard let startDate = dateFormatter.date(from: cellData.start[indexPath.row]), + let endDate = dateFormatter.date(from: cellData.end[indexPath.row]) else { return UITableViewCell() } + dateFormatter.dateFormat = "a hh:mm" + + cell.timeLabel.text = "\(dateFormatter.string(from: startDate)) ~ \(dateFormatter.string(from: endDate))" + + cell.removeButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + PostRequestPlansService.sharedParameterData.removeTimeData(at: cell.tag) + self?.selectedCountLabel.text = "\(PostRequestPlansService.sharedParameterData.date.count)/4" + if PostRequestPlansService.sharedParameterData.date.count == 0 { + self?.rx_requestIsEnabled.accept(false) + } + self?.tableViewCellDisposeBag = nil // 날려주지 않으면 셀 재사용으로 인해 중복으로 호출되게 된다. + self?.tableViewCellDisposeBag = DisposeBag() + tableView.reloadData() + }) + .disposed(by: tableViewCellDisposeBag!) + + return cell + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/PickedDate.swift b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/PickedDate.swift new file mode 100644 index 0000000..edb0621 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/PickedDate.swift @@ -0,0 +1,59 @@ +// +// PickedDate.swift +// SeeMeet +// +// Created by 이유진 on 2022/01/19. +// + +import Foundation + +struct PickedDate { + var startTime: Date = Date() + var endTime: Date = Date() + + func getDateString() -> String{ + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy.MM.dd" // 2020.08.13 16:30 + let str = dateFormatter.string(from: startTime) + return str + } + + func getDateStringForRequest() -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" // 2020-08-13 16:30 + let str = dateFormatter.string(from: startTime) + return str + } + + func getStartTimeStringForRequest() -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "HH:mm" + let str = dateFormatter.string(from: startTime) + return str + } + + func getEndTimeStringForRequest() -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "HH:mm" + let str = dateFormatter.string(from: endTime) + return str + } + + func getStartToEndString() -> String{ + let dateFormatter = DateFormatter() + dateFormatter.timeZone = NSTimeZone(name: "ko_KR") as TimeZone? + dateFormatter.locale = Locale(identifier: "ko_KR") + dateFormatter.dateFormat = "a hh:mm" // 2020-08-13 16:30 + let start = dateFormatter.string(from: startTime) + let end = dateFormatter.string(from: endTime) + + return start + "~" + end + } +} + +extension PickedDate: Equatable { + static func == (lhs: PickedDate, rhs: PickedDate) -> Bool { + return lhs.startTime == rhs.startTime + && lhs.endTime == rhs.endTime + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/RequestPlansContentsVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/RequestPlansContentsVC.swift new file mode 100644 index 0000000..ed9f4ac --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/RequestPlansContentsVC.swift @@ -0,0 +1,549 @@ +// +// RequestPlansContentsVC.swift +// SeeMeet +// +// Created by 이유진 on 2022/01/10. +// + +import UIKit +import SnapKit +import Then +import RxSwift +import RxCocoa +import RxRelay +import Alamofire + +protocol RequestPlansContentsVCDelegate { + func exitButtonDidTap() + func nextButtonDidTap() +} + +class RequestPlansContentsVC: UIViewController,UIGestureRecognizerDelegate { + + // MARK: - UI Components + + private let titleView = UIView() + + private let titleLabel = UILabel().then { + $0.text = "약속 신청" + $0.font = UIFont.hanSansBoldFont(ofSize: 18) + } + + private let exitButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_close_bold"), for: .normal) + } + + private let friendSelectionLabel = UILabel().then { + $0.text = "약속 신청할 친구를 선택하세요" + $0.font = UIFont.hanSansRegularFont(ofSize: 18) + $0.textColor = UIColor.grey06 + let attributedString = NSMutableAttributedString(string: "약속 신청할 친구를 선택하세요") + attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.pink01, range: ($0.text! as NSString).range(of:"친구")) + attributedString.addAttribute(.font, value: UIFont.hanSansBoldFont(ofSize: 18), range: ($0.text! as NSString).range(of: "친구")) + $0.attributedText = attributedString + } + + private let searchFieldView = UIView().then { + $0.backgroundColor = .grey01 + $0.layer.cornerRadius = 10 + } + + private let searchFieldPlaceholderLabel = UILabel().then { + $0.attributedText = NSAttributedString(string: "받는 사람:", attributes: [.foregroundColor: UIColor.grey04]) + $0.font = .hanSansRegularFont(ofSize: 14) + } + + private let searchResultTableView: UITableView = { + let searchTableView = UITableView() + searchTableView.register(SearchTVC.self, forCellReuseIdentifier: SearchTVC.identifier) + return searchTableView + }() + + private lazy var selectedCollectionView = UICollectionView(frame: .zero, collectionViewLayout: .init()).then { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + $0.collectionViewLayout = layout + $0.backgroundColor = .clear + $0.register(SearchFieldTokenCVC.self, forCellWithReuseIdentifier: SearchFieldTokenCVC.identifier) + $0.register(SearchInputFieldCVC.self, forCellWithReuseIdentifier: SearchInputFieldCVC.identifier) + $0.showsHorizontalScrollIndicator = false + layout.sectionInset = UIEdgeInsets(top: 11 * heightRatio, left: 0, bottom: 11 * heightRatio, right: 0) + } + + private let tableBackgroundView = UIView().then { + $0.backgroundColor = UIColor.white + } + + private let searchImageView = UIImageView().then { + $0.image = UIImage(named: "ic_search") + } + + private let contentsWritingLabel = UILabel().then { + $0.text = "약속의 내용을 작성하세요" + $0.font = UIFont.hanSansRegularFont(ofSize: 18) + $0.textColor = UIColor.grey06 + let attributedString = NSMutableAttributedString(string: "약속의 내용을 작성하세요") + attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.pink01, range: ($0.text! as NSString).range(of:"내용")) + attributedString.addAttribute(.font, value: UIFont.hanSansBoldFont(ofSize: 18), range: ($0.text! as NSString).range(of: "내용")) + $0.attributedText = attributedString + } + + private let plansContentsView = UIView().then { + $0.backgroundColor = UIColor.grey01 + $0.layer.cornerRadius = 10 + } + + private lazy var plansTitleTextField = UITextField().then { + $0.font = UIFont.hanSansBoldFont(ofSize: 14) + $0.textColor = UIColor.grey06 + $0.attributedPlaceholder = NSAttributedString(string: "제목", attributes: [.foregroundColor: UIColor.grey04, .font: UIFont.hanSansRegularFont(ofSize: 14)]) + $0.returnKeyType = .done + $0.delegate = self + } + + private let seperateLineView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private lazy var plansContentsTextView = UITextView().then { + $0.font = UIFont.hanSansRegularFont(ofSize: 14) + $0.backgroundColor = UIColor.grey01 + $0.textColor = UIColor.grey06 + $0.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + $0.textContainer.lineFragmentPadding = 0 + $0.scrollIndicatorInsets = $0.textContainerInset + $0.returnKeyType = .done + $0.delegate = self + } + + private let navigationLineView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private let nextButton = UIButton().then { + $0.backgroundColor = UIColor.grey02 + $0.isEnabled = false + $0.setTitle("다음", for: .normal) + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 16) + $0.layer.cornerRadius = 10 + } + + // MARK: - Properties + + weak var coordinator: Coordinator? + var delegate: RequestPlansContentsVCDelegate? + var friendDataToSet: FriendsData? + + private let disposeBag = DisposeBag() + + private var friendDataList: [FriendsData] = [] + private var filteredFriendList: [FriendsData] = [] + private var selectedFriendList: [FriendsData] = [] + + enum SearchTokenType { + case selectedFriendToken(data: FriendsData) // 선택하여 추가한 친구 이름 토큰 + case inputFieldToken // 항상 마지막에 위치하는, 입력창이 있는 토큰 + } + + private lazy var selectedFriendsListRelay: BehaviorRelay<[SearchTokenType]> = { [weak self] in + if let friendDataToSet = self?.friendDataToSet { + return BehaviorRelay<[SearchTokenType]>(value: [.selectedFriendToken(data: friendDataToSet), .inputFieldToken]) // 항상 마지막은 입력 필드 셀 + } else { + return BehaviorRelay<[SearchTokenType]>(value: [.inputFieldToken]) // 항상 마지막은 입력 필드 셀 + } + }() + + private lazy var filteredFriendsRelay = BehaviorRelay<[FriendsData]>(value: friendDataList) + + private lazy var isPlansTitleValid = plansTitleTextField.rx.text.asDriver().map { !($0?.isEmpty ?? true) } + private lazy var isPlansContentsTextValid = plansContentsTextView.rx.text.asDriver().map { [weak self] in + $0 != self?.textViewPlaceHolder && $0 == "" ? false : true } + private lazy var isNextButtonValid = Driver.combineLatest(isPlansTitleValid, + isPlansContentsTextValid, selectedFriendsListRelay.asDriver()).map { $0.0 && $0.1 && $0.2.count > 1 } + + + private lazy var plansTextBeginEditing = Driver.merge(plansTitleTextField.rx.controlEvent(.editingDidBegin).asDriver(), + plansContentsTextView.rx.didBeginEditing.asDriver()) + private lazy var plansTextEndEditing = Driver.merge(plansTitleTextField.rx.controlEvent(.editingDidEnd).asDriver(), + plansContentsTextView.rx.didEndEditing.asDriver()) + + + private let textViewPlaceHolder = "약속 상세 내용" + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + if let friendDataToSet = friendDataToSet { + selectedFriendList.append(friendDataToSet) + } + view.backgroundColor = .white + setAutoLayouts() + setupViews() + setupSearchTableView() + dismissKeyboard() + getFriendsList() // 네트워킹 + setContentsTextViewPlaceholder() + // setDelegate() + setupSelectedCollectionView() + } + + // MARK: - Layouts + + private func setAutoLayouts() { + navigationController?.navigationBar.isHidden = true // 숨겨라 + + view.addSubviews([titleView, nextButton, + friendSelectionLabel, searchFieldView, contentsWritingLabel, plansContentsView,navigationLineView,tableBackgroundView,searchResultTableView]) + titleView.addSubviews([titleLabel,exitButton]) + searchFieldView.addSubviews([searchFieldPlaceholderLabel,searchImageView, selectedCollectionView]) + plansContentsView.addSubviews([plansTitleTextField,seperateLineView,plansContentsTextView]) + + titleView.snp.makeConstraints { + $0.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + $0.height.equalTo(58 * heightRatio) + } + + titleLabel.snp.makeConstraints { + $0.centerX.centerY.equalToSuperview() + } + + exitButton.snp.makeConstraints { + $0.trailing.equalToSuperview().offset(-4 * widthRatio) + $0.centerY.equalToSuperview() + $0.width.height.equalTo(48 * heightRatio) + } + + friendSelectionLabel.snp.makeConstraints { + $0.top.equalTo(titleView.snp.bottom).offset(24 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.height.equalTo(32 * heightRatio) + } + + searchFieldView.snp.makeConstraints { + $0.top.equalTo(friendSelectionLabel.snp.bottom).offset(7 * heightRatio) + $0.leading.equalTo(friendSelectionLabel) + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.height.equalTo(50 * heightRatio) + } + + tableBackgroundView.snp.makeConstraints { + $0.top.equalTo(searchFieldView.snp.bottom) + $0.leading.bottom.trailing.equalToSuperview() + } + + searchResultTableView.snp.makeConstraints { + $0.top.equalTo(searchFieldView.snp.bottom) + $0.leading.equalTo(searchFieldView.snp.leading).offset(23 * widthRatio) + $0.trailing.equalTo(searchFieldView.snp.trailing).offset(-22 * widthRatio) + $0.height.equalTo(270 * heightRatio) + } + + searchImageView.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(8 * widthRatio) + $0.width.height.equalTo(34 * widthRatio) + } + + selectedCollectionView.snp.makeConstraints { + $0.centerY.height.equalToSuperview() + $0.leading.equalTo(searchImageView.snp.trailing).offset(2 * widthRatio) + $0.trailing.equalToSuperview() + } + + contentsWritingLabel.snp.makeConstraints { + $0.top.equalTo(searchFieldView.snp.bottom).offset(38 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.height.equalTo(32 * heightRatio) + } + + plansContentsView.snp.makeConstraints { + $0.top.equalTo(contentsWritingLabel.snp.bottom).offset(12 * heightRatio) + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.height.equalTo(255 * heightRatio) + } + + plansTitleTextField.snp.makeConstraints { + $0.top.equalToSuperview().offset(15 * heightRatio) + $0.leading.equalToSuperview().offset(19 * widthRatio) + $0.trailing.equalToSuperview().offset(-15 * widthRatio) + $0.height.equalTo(32 * widthRatio) + } + + seperateLineView.snp.makeConstraints { + $0.top.equalTo(plansTitleTextField.snp.bottom).offset(9 * heightRatio) + $0.leading.equalToSuperview().offset(15 * widthRatio) + $0.trailing.equalToSuperview().offset(-15 * widthRatio) + $0.height.equalTo(1 * heightRatio) + } + + plansContentsTextView.snp.makeConstraints { + $0.top.equalTo(seperateLineView.snp.bottom).offset(3 * heightRatio) + $0.leading.equalToSuperview().offset(9 * widthRatio) + $0.bottom.equalToSuperview().offset(-8 * heightRatio) + $0.trailing.equalToSuperview().offset(-5 * widthRatio) + } + + navigationLineView.snp.makeConstraints { + $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(nextButton.snp.top).offset(-16 * widthRatio) + $0.height.equalTo(1 * heightRatio) + } + + nextButton.snp.makeConstraints { + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.bottom.equalToSuperview().offset(-42 * heightRatio) + $0.height.equalTo(54 * heightRatio) + } + + tableBackgroundView.isHidden = true + searchResultTableView.isHidden = true + searchResultTableView.separatorStyle = .none + } + + func setContentsTextViewPlaceholder() { + plansContentsTextView.text = textViewPlaceHolder + plansContentsTextView.textColor = UIColor.grey04 + } + + //입력하다가 다른 곳 터치시 키패드 내려가게 하기 + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + self.view.endEditing(true) + } + + private func dismissKeyboard() { + let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer() + view.addGestureRecognizer(tapGesture) + + tapGesture.rx.event.bind(onNext: { [weak self] _ in + guard let self = self else { return } + [self.plansTitleTextField, self.plansContentsTextView].forEach { + $0.endEditing(true) + } + }) + .disposed(by: disposeBag) + tapGesture.cancelsTouchesInView = false + } + + // MARK: - Actions + + private func setupViews() { + + exitButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + self?.delegate?.exitButtonDidTap() + }) + .disposed(by: disposeBag) + + nextButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + guard let self = self else { return } + + var parameterData = PostRequestPlansService.sharedParameterData + parameterData.title = self.plansTitleTextField.text ?? "" + parameterData.contents = self.plansContentsTextView.text ?? "" + parameterData.guests = self.selectedFriendList.map { ["id": $0.id, "username": $0.username] } + + self.delegate?.nextButtonDidTap() + }) + .disposed(by: disposeBag) + + selectedCollectionView.rx.itemSelected // 마지막 아이템(텍스트 입력 셀) 터치했을 때 처리 + .filter { [weak self] in $0 == IndexPath(item: self?.selectedFriendList.count ?? 0, section: 0) } + .asObservable() + .subscribe(onNext: { [weak self] _ in + self?.tableBackgroundView.isHidden = false + self?.searchResultTableView.isHidden = false + self?.filteredFriendList = self?.friendDataList ?? [] + self?.searchResultTableView.reloadData() + }) + .disposed(by: disposeBag) + + isNextButtonValid + .asDriver() + .drive(onNext: { [weak self] isEnabled in + self?.nextButton.backgroundColor = isEnabled ? .pink01 : .grey02 + self?.nextButton.isEnabled = isEnabled + }) + .disposed(by: disposeBag) + + plansTextBeginEditing + .drive(onNext: { [weak self] in + self?.plansContentsView.layer.borderColor = UIColor.pink01.cgColor // 테두리 하이라이팅 시키고 플레이스홀더 지움 + self?.plansContentsView.layer.borderWidth = 1.0 + + if self?.plansContentsTextView.text == self?.textViewPlaceHolder { + self?.plansContentsTextView.text = nil + self?.plansContentsTextView.textColor = UIColor.black + } + }) + .disposed(by: disposeBag) + + plansTextEndEditing + .drive(onNext: { [weak self] in + self?.plansContentsView.layer.borderColor = UIColor.grey04.cgColor + self?.plansContentsView.layer.borderWidth = 0 + if self?.plansContentsTextView.text == self?.textViewPlaceHolder || self?.plansContentsTextView.text == "" { + self?.setContentsTextViewPlaceholder() + } + }) + .disposed(by: disposeBag) + + plansContentsTextView.rx.didChange + .asDriver() + .drive(onNext: { [weak self] element in + guard let self = self else { return } + let attrString = NSMutableAttributedString(string: self.plansContentsTextView.text, attributes: [.font: UIFont.hanSansRegularFont(ofSize: 14),.kern: -0.6]) + let style = NSMutableParagraphStyle() + style.lineSpacing = 10 + attrString.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: NSMakeRange(0, attrString.length)) + self.plansContentsTextView.attributedText = attrString + }) + .disposed(by: disposeBag) + } + + private func setupSearchTableView() { + filteredFriendsRelay // 검색된 데이터 뿌리기 + .bind(to: searchResultTableView.rx.items) { (tableView, row, element) in + guard let cell = tableView.dequeueReusableCell(withIdentifier: SearchTVC.identifier) as? SearchTVC else { return UITableViewCell() } + cell.setData(data: element) + return cell + } + .disposed(by: disposeBag) + + searchResultTableView.rx.modelSelected(FriendsData.self) + .asDriver() + .drive(onNext: { [weak self] data in + self?.selectedFriendList.append(data) + var tokens = self?.selectedFriendList.map { SearchTokenType.selectedFriendToken(data: $0) } ?? [] + tokens.append(.inputFieldToken) + self?.selectedFriendsListRelay.accept(tokens) + }) + .disposed(by: disposeBag) + } + + private func setupSelectedCollectionView() { + selectedFriendsListRelay + .bind(to: selectedCollectionView.rx.items) { (collectionView, item, element) in + switch element { + case .selectedFriendToken(let friendsData): + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SearchFieldTokenCVC.identifier, for: IndexPath(row: item, section: 0)) as? SearchFieldTokenCVC else { return UICollectionViewCell() } + cell.setFriendsData(friendsData: friendsData) + + cell.removeButton.rx.tap // 토큰 삭제버튼 눌렀을 때 처리 + .asDriver() + .drive(onNext: { [weak self] in + guard let self = self else { return } + self.selectedFriendList = self.selectedFriendList.filter { $0.username != cell.friendsData.username } + var tokens = self.selectedFriendList.map { SearchTokenType.selectedFriendToken(data: $0) } + tokens.append(.inputFieldToken) + self.selectedFriendsListRelay.accept(tokens) + }) + .disposed(by: self.disposeBag) + + return cell + + case .inputFieldToken: // 항상 맨 마지막에 위치한다. + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SearchInputFieldCVC.identifier, for: IndexPath(row: item, section: 0)) as? SearchInputFieldCVC else { return UICollectionViewCell() } + cell.inputField.rx.text // 검색 처리 + .distinctUntilChanged() + .bind(onNext: { [weak self] text in + self?.filteredFriendsRelay.accept( + self?.friendDataList.filter { $0.username.lowercased().prefix(text?.count ?? 0) == text?.lowercased().prefix(text?.count ?? 0) ?? "" + && !(self?.selectedFriendList.contains($0) ?? false) } ?? [] + )// 검색 필드 처리 + }) + .disposed(by: self.disposeBag) + + cell.inputField.rx.controlEvent(.editingDidBegin) + .asDriver() + .drive(onNext: { [weak self] _ in + self?.tableBackgroundView.isHidden = false + self?.searchResultTableView.isHidden = false + self?.searchFieldView.layer.borderColor = UIColor.pink01.cgColor + self?.searchFieldView.layer.borderWidth = 1.0 + }) + .disposed(by: self.disposeBag) + + cell.inputField.rx.controlEvent(.editingDidEnd) + .asDriver() + .drive(onNext: { [weak self] _ in + self?.tableBackgroundView.isHidden = true + self?.searchResultTableView.isHidden = true + self?.searchFieldView.layer.borderColor = UIColor.grey01.cgColor + self?.searchFieldView.layer.borderWidth = 1.0 + }) + .disposed(by: self.disposeBag) + + return cell + } + } + .disposed(by: disposeBag) + + selectedCollectionView.rx.setDelegate(self).disposed(by: disposeBag) // cell 크기 조정을 위해 delegate 사용 + } + + // MARK: - Network + + private func getFriendsList() { + GetFriendsListService.shared.getFriendsList() { [weak self] response in + switch response { + case .success(let data) : + if let response = data as? FriendsDataModel { + self?.friendDataList = response.data ?? [] + if let friendDataToSet = self?.friendDataToSet { + self?.filteredFriendsRelay.accept(self?.friendDataList.filter { $0 != friendDataToSet } ?? []) + } else { + self?.filteredFriendsRelay.accept(self?.friendDataList ?? []) + } + } + case .requestErr(_): + print("requestERR") + case .pathErr : + print("pathERR") + case .serverErr: + print("serverERR") + case .networkFail: + print("networkFail") + } + } + } +} + +// MARK: - Extension + +extension RequestPlansContentsVC: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + switch indexPath.item { + case self.selectedFriendsListRelay.value.count-1: // 컬렉션 뷰 셀 마지막 -> 입력 필드 셀 + if self.selectedFriendsListRelay.value.count > 1 { + return CGSize(width: collectionView.bounds.width * 0.3, height: 26 * heightRatio) + } else { // 아직 친구를 선택하지 않았을 때는 컬렉션 뷰 전체 너비를 차지하자 + return CGSize(width: collectionView.bounds.width, height: 26 * heightRatio) + } + default: + return CGSize.init(width: 260 / 3 * widthRatio, height: 26 * heightRatio) + } + } +} + +extension RequestPlansContentsVC: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() // TextField 비활성화 + return true + } +} + +extension RequestPlansContentsVC: UITextViewDelegate { + func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + if text == "\n" { + textView.resignFirstResponder() + return false + } + return true + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/RequestPlansDateVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/RequestPlansDateVC.swift new file mode 100644 index 0000000..ed5e2a4 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/RequestPlansDateVC.swift @@ -0,0 +1,797 @@ +// +// RequestPlansDate.swift +// SeeMeet_iOS +// +// Created by 김인환 on 2022/06/23. +// + +import UIKit +import RxSwift +import RxCocoa +import RxRelay +import Then +import SnapKit + +protocol RequestPlansDateVCDelegate { + func backButtonDidTap() + func exitButtonDidTap() +} + + +class RequestPlansDateVC: UIViewController { + + // MARK: - UI Components + + private let navigationAreaView = UIView() + + private let backButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_back"), for: .normal) + } + + private let exitButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_close_bold"), for: .normal) + } + + private let titleLabel = UILabel().then { + $0.text = "약속 신청" + $0.font = UIFont.hanSansBoldFont(ofSize: 18) + } + + private let addDateView = UIView().then { + $0.backgroundColor = UIColor.white + } + + private let currentSelectedDateView = UIView().then { + $0.backgroundColor = UIColor.grey01 + $0.layer.cornerRadius = 10 + $0.layer.borderColor = UIColor.pink01.cgColor + $0.layer.borderWidth = 1 + } + + private var selectedDateLabel = UILabel().then { + $0.font = UIFont.dinProBoldFont(ofSize:18) + $0.textColor = UIColor.grey06 + } + + private let selectedTimeLabel = UILabel().then { + $0.text = "오전 11:00 ~ 오전 12:00" + $0.font = UIFont.dinProRegularFont(ofSize: 14) + $0.textColor = UIColor.grey06 + } + + private let addDateButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_embody_on"), for: .normal) + } + + private let selectDateLabel = UILabel().then { + $0.text = "약속 신청할 날짜를 선택하세요" + $0.font = UIFont.hanSansRegularFont(ofSize: 18) + $0.textColor = UIColor.grey06 + let attributedString = NSMutableAttributedString(string: "약속 신청할 날짜를 선택하세요") + attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.pink01, range: ($0.text! as NSString).range(of:"날짜")) + attributedString.addAttribute(.font, value: UIFont.hanSansBoldFont(ofSize: 18), range: ($0.text! as NSString).range(of: "날짜")) + $0.attributedText = attributedString + } + + private let separateLineView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private let scrollView = UIScrollView().then { + $0.isPagingEnabled = false + $0.bounces = true + $0.showsHorizontalScrollIndicator = true + $0.isUserInteractionEnabled = true + $0.isScrollEnabled = true + } + + private let scheduleAreaBackgroundView = UIView().then { + $0.backgroundColor = UIColor.grey01 + } + + private let prevWeekButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "leftchevron"), for: .normal) + $0.tag = 1 + } + + private let yearAndWeekLabelButton = UIButton().then { + $0.setTitle("\(Date.getCurrentYear())년 \(Date.getCurrentMonth())월", for: .normal) + $0.setTitleColor(.pink01, for: .normal) + $0.titleLabel?.font = UIFont.dinProMediumFont(ofSize: 18) + } + + private let nextWeekButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "rightchevron"), for: .normal) + $0.tag = 2 + } + + private lazy var weekDayCollectionView = UICollectionView(frame: .zero, collectionViewLayout: .init()).then { + $0.register(SelectableWeekDayCVC.self, forCellWithReuseIdentifier: SelectableWeekDayCVC.identifier) + $0.isPagingEnabled = false // paging 시 어그러짐을 잡기 위해 이렇게 설정한다. + $0.decelerationRate = .fast + $0.showsHorizontalScrollIndicator = false + $0.backgroundColor = .clear + + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.minimumInteritemSpacing = 7 * widthRatio + layout.itemSize = CGSize(width: 42 * widthRatio, height: 80 * heightRatio) + layout.sectionInset = UIEdgeInsets(top: 10 * heightRatio, left: 0.0, bottom: 0.0, right: 0.0) + $0.collectionViewLayout = layout + } + + private let weekdaySeparateLineView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + + private let selectedDayLabel = UILabel().then { + $0.text = "1월 6일 금요일" + $0.text = "\(Date.getCurrentMonth())월 \(Date.getCurrentDate())일 \(Date.getCurrentKoreanWeekDay())요일" + $0.font = UIFont.hanSansBoldFont(ofSize: 16) + $0.textColor = UIColor.grey06 + } + +// private let scheduleStackView = UIStackView().then { +// $0.axis = .vertical +// $0.distribution = .fill +// $0.spacing = 10 * heightRatio +// $0.alignment = .fill +// } + + private lazy var scheduleTableView = UITableView(frame: CGRect.zero, style: .plain).then { + $0.register(ScheduleTVC.self, forCellReuseIdentifier: ScheduleTVC.identifier) + $0.bounces = false + $0.backgroundColor = .clear + } + + private let emptyScheduleLabel = UILabel().then { + $0.text = "일정이 없어요" + $0.font = UIFont.hanSansRegularFont(ofSize: 16) + $0.textColor = UIColor.grey04 + } + + private let allDayView = UIView().then { + $0.backgroundColor = UIColor.white + } + + private let allDayLabel = UILabel().then { + $0.text = "하루 종일" + $0.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.textColor = UIColor.grey04 + } + + private let separateLineView2 = UIView().then{ + $0.backgroundColor = UIColor.grey02 + } + + private let allDaySwitch = UISwitch().then{ + $0.onTintColor = UIColor.pink01 + } + + private let startTimeLabel = UILabel().then { + $0.text = "시작 시간" + $0.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.textColor = UIColor.grey04 + } + + private let startTimePicker = UIDatePicker().then { + $0.datePickerMode = .time + $0.locale = Locale(identifier: "ko-KR") + $0.timeZone = .current + $0.preferredDatePickerStyle = .inline + $0.minuteInterval = 5 + $0.tag = 1 + } + + private let timeSeparateView = UIView().then { + $0.backgroundColor = UIColor.grey01 + } + + private let endTimeLabel = UILabel().then{ + $0.text = "종료 시간" + $0.font = UIFont.hanSansMediumFont(ofSize: 14) + $0.textColor = UIColor.grey04 + } + + private let endTimePicker = UIDatePicker().then{ + $0.datePickerMode = .time + $0.locale = Locale(identifier: "ko-KR") + $0.timeZone = .current + $0.preferredDatePickerStyle = .inline + $0.minuteInterval = 5 + $0.tag = 2 + } + + private let bottomSheetView = SelectedDateSheet() + + private let bottomSeparateLineView = UIView().then { + $0.backgroundColor = UIColor.grey02 + } + private let bottomAreaBackgroundView = UIView().then { + $0.backgroundColor = UIColor.white + } + + private let requestButton = UIButton().then { + $0.backgroundColor = UIColor.grey02 + $0.setTitle("약속 신청", for: .normal) + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 16) + $0.layer.cornerRadius = 10 + $0.isEnabled = false + } + + // MARK: - Properties + + weak var coordinator: Coordinator? + var delegate: RequestPlansDateVCDelegate? + + private let disposeBag = DisposeBag() + + private let startSundayToDisplay: Date = { + if Date.getCurrentKoreanWeekDay() == "일" { // 오늘이 일요일이면 바로 리턴 + return Date() + } else { // 일요일이 아니라면 전주의 일요일을 반환 + let previousWeekDayByInt = Calendar.current.component(.weekday, from: Date().previousDate(value: 7)) + return Date().previousDate(value: 7).nextDate(value: 8 - previousWeekDayByInt) + } + }() + private var scheduleDataList: [ScheduleData]? + + /// 주간 달력에 표시할 값을 관리, 초기값은 이번주 포함 3주를 표시 + private lazy var weekDayRelay = BehaviorRelay(value: Array(repeating: startSundayToDisplay, count: 21).enumerated().map { $0.1.nextDate(value: $0.0)} ) + + private let selectedDaySchedule = PublishRelay<[ScheduleData]>() + /// 선택된 셀 인덱스 관리 + private var selectedCellIndex: Int? + + private var pickedDate = PickedDate() + + // MARK: - Life Cycle + + override func viewDidLoad() { + view.backgroundColor = .white + self.navigationController?.isNavigationBarHidden = false + setAutoLayouts() + setupViews() + setupWeekDayCollectionView() + setupScheduleTableView() + + for index in 0...2 { // 이번 달 부터 3달치 스케쥴을 미리 로드한다. + let monthDateToRequest = Calendar.current.date(byAdding: DateComponents(month: index), to: Date()) + guard let year = monthDateToRequest?.year, let month = monthDateToRequest?.month else { return } + requestCalendarData(yearString: "\(year)", monthString: "\(month)") + } + + // 상단 선택된 시간 라벨의 초기값은 오늘 날짜이고 시간은 아래 기준으로 결정 + let min = Calendar.current.component(.minute, from: Date()) + + //시간이 30분 이하일 때 다음 정각으로 설정 + if Int(min) < 30 { + guard let minuteRevisedDate = Calendar.current.date(bySetting: .minute, value: 0, of: pickedDate.startTime) else { return } + pickedDate.startTime = minuteRevisedDate//올림해줘버림.. + guard let hourRevisedDate = Calendar.current.date(byAdding: .hour, value: 1, to: pickedDate.endTime) else { return } + pickedDate.endTime = hourRevisedDate + guard let minuteRevisedDate = Calendar.current.date(bySetting: .minute, value: 0, of: pickedDate.endTime) else { return } + pickedDate.endTime = minuteRevisedDate + + } else { //30분 이상일 때 다음 삼십분으로 설정 + guard let minuteRevisedDate = Calendar.current.date(bySetting: .minute, value: 30, of: pickedDate.startTime) else { return } + pickedDate.startTime = minuteRevisedDate + guard let hourRevisedDate = Calendar.current.date(byAdding: .hour, value: 1, to: pickedDate.endTime) else { return } + pickedDate.endTime = hourRevisedDate + guard let minuteRevisedDate = Calendar.current.date(bySetting: .minute, value: 30, of: pickedDate.endTime) else { return } + pickedDate.endTime = minuteRevisedDate + } + + updateSelectedDateLabel() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + // 시간 데이터 모두 지우기 + PostRequestPlansService.sharedParameterData.date.removeAll() + PostRequestPlansService.sharedParameterData.start.removeAll() + PostRequestPlansService.sharedParameterData.end.removeAll() + } + + // MARK: - Layout + + private func setAutoLayouts() { + view.addSubviews([navigationAreaView, currentSelectedDateView + ,addDateButton, selectDateLabel, scrollView, + bottomSheetView, + bottomAreaBackgroundView]) + + navigationAreaView.addSubviews([backButton, exitButton, titleLabel]) + + navigationAreaView.snp.makeConstraints { + $0.top.equalTo(view.safeAreaLayoutGuide) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(58 * widthRatio) + } + + backButton.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalTo(2 * widthRatio) + } + + exitButton.snp.makeConstraints { + $0.trailing.equalToSuperview().offset(-4 * widthRatio) + $0.centerY.equalToSuperview() + } + + titleLabel.snp.makeConstraints { + $0.center.equalToSuperview() + } + + currentSelectedDateView.snp.makeConstraints { + $0.width.equalTo(274 * widthRatio) + $0.height.equalTo(53 * heightRatio) + $0.leading.equalToSuperview().offset(20 * heightRatio) + $0.top.equalTo(backButton.snp.bottom).offset(30 * heightRatio) + } + + currentSelectedDateView.addSubviews([selectedDateLabel, selectedTimeLabel]) + + selectedDateLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(16 * widthRatio) + $0.top.equalToSuperview().offset(14 * heightRatio) + } + + selectedTimeLabel.snp.makeConstraints { + $0.leading.equalTo(selectedDateLabel.snp.trailing).offset(16 * widthRatio) + $0.top.equalToSuperview().offset(16 * heightRatio) + } + + addDateButton.snp.makeConstraints { + $0.width.height.equalTo(53 * widthRatio) + $0.leading.equalTo(currentSelectedDateView.snp.trailing).offset(8 * widthRatio) + $0.top.equalTo(currentSelectedDateView.snp.top) + } + + selectDateLabel.snp.makeConstraints { + $0.leading.equalTo(20 * heightRatio) + $0.top.equalTo(currentSelectedDateView.snp.bottom).offset(36 * heightRatio) + } + + scrollView.snp.makeConstraints { + $0.width.centerX.equalToSuperview() + $0.top.equalTo(selectDateLabel.snp.bottom).offset(13 * heightRatio) + $0.bottom.equalToSuperview().offset(-112 * heightRatio) + } + + scrollView.addSubviews([separateLineView, scheduleAreaBackgroundView]) + + separateLineView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(1 * heightRatio) + } + + scheduleAreaBackgroundView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.leading.equalTo(view.snp.leading) + $0.trailing.equalTo(view.snp.trailing) + $0.height.equalTo(330 * heightRatio) + } + + scheduleAreaBackgroundView.addSubviews([prevWeekButton, yearAndWeekLabelButton, nextWeekButton, weekDayCollectionView, + weekdaySeparateLineView, selectedDayLabel, scheduleTableView]) + + prevWeekButton.snp.makeConstraints { + $0.width.height.equalTo(42 * heightRatio) + $0.leading.equalTo(1 * widthRatio) + $0.top.equalTo(6 * heightRatio) + } + + yearAndWeekLabelButton.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalToSuperview().offset(15 * heightRatio) + } + + nextWeekButton.snp.makeConstraints { + $0.width.height.equalTo(42 * heightRatio) + $0.trailing.equalTo(-2 * widthRatio) + $0.top.equalTo(6 * heightRatio) + } + + weekDayCollectionView.snp.makeConstraints { + $0.leading.equalToSuperview().offset(9 * widthRatio) + $0.trailing.equalToSuperview().offset(-9 * widthRatio) + $0.height.equalTo(90 * heightRatio) + $0.top.equalTo(yearAndWeekLabelButton.snp.bottom).offset(5 * heightRatio) + } + + weekdaySeparateLineView.snp.makeConstraints { + $0.top.equalTo(weekDayCollectionView.snp.bottom).offset(8 * heightRatio) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(1 * heightRatio) + } + + selectedDayLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(20 * widthRatio) + $0.top.equalTo(weekdaySeparateLineView.snp.bottom).offset(15 * heightRatio) + } + + scheduleTableView.snp.makeConstraints { + $0.top.equalTo(selectedDayLabel.snp.bottom).offset(11 * heightRatio) + $0.width.equalTo(335 * widthRatio) + $0.centerX.equalToSuperview() + $0.height.equalTo(30 * heightRatio) + } + + scrollView.addSubviews([allDayLabel, allDaySwitch, separateLineView2, + startTimeLabel, startTimePicker, timeSeparateView, endTimeLabel, endTimePicker]) + + allDayLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(21 * widthRatio) + $0.top.equalTo(scheduleAreaBackgroundView.snp.bottom).offset(24 * heightRatio) + } + + allDaySwitch.snp.makeConstraints { + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.top.equalTo(scheduleAreaBackgroundView.snp.bottom).offset(18 * heightRatio) + } + + separateLineView2.snp.makeConstraints { + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(1 * heightRatio) + $0.top.equalTo(allDaySwitch.snp.bottom).offset(17 * heightRatio) + } + + startTimeLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(21 * widthRatio) + $0.top.equalTo(separateLineView2.snp.bottom).offset(26 * heightRatio) + } + + startTimePicker.snp.makeConstraints { + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.top.equalTo(separateLineView2).offset(15 * heightRatio) + } + + timeSeparateView.snp.makeConstraints { + $0.width.equalTo(335 * widthRatio) + $0.centerX.equalToSuperview() + $0.top.equalTo(startTimeLabel.snp.bottom).offset(19 * heightRatio) + $0.height.equalTo(1) + } + + endTimeLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(21 * widthRatio) + $0.top.equalTo(timeSeparateView.snp.bottom).offset(24 * heightRatio) + } + + endTimePicker.snp.makeConstraints { + $0.trailing.equalToSuperview().offset(-20 * widthRatio) + $0.top.equalTo(timeSeparateView.snp.bottom).offset(15 * heightRatio) + $0.bottom.equalToSuperview().offset(-77 * heightRatio) + } + + bottomAreaBackgroundView.snp.makeConstraints { + $0.leading.trailing.bottom.equalToSuperview() + $0.height.equalTo(112 * heightRatio) + } + + bottomAreaBackgroundView.addSubviews([bottomSeparateLineView, requestButton]) + + bottomSeparateLineView.snp.makeConstraints { + $0.height.equalTo(1 * heightRatio) + $0.leading.trailing.top.equalToSuperview() + } + + requestButton.snp.makeConstraints { + $0.width.equalTo(335 * widthRatio) + $0.centerX.equalToSuperview() + $0.top.equalToSuperview().offset(16 * heightRatio) + $0.height.equalTo(54 * heightRatio) + } + + bottomSheetView.snp.makeConstraints { + $0.bottom.equalTo(bottomAreaBackgroundView.snp.bottom).offset(310 * heightRatio) + $0.trailing.leading.equalToSuperview() + $0.height.equalTo(482 * heightRatio) + } + } + + private func setupViews() { + backButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + self?.delegate?.backButtonDidTap() + }) + .disposed(by: disposeBag) + + exitButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + self?.dismiss(animated: false) + }) + .disposed(by: disposeBag) + + yearAndWeekLabelButton.rx.tap // 라벨을 탭하면 가장 처음 페이지로 돌아간다 + .asDriver() + .drive(onNext: { [weak self] in + self?.weekDayCollectionView.setContentOffset(.zero, animated: true) + }) + .disposed(by: disposeBag) + + prevWeekButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + guard let vself = self else { return } + let pageWidth = vself.weekDayCollectionView.frame.width + 7 * widthRatio + let index: CGFloat = round(vself.weekDayCollectionView.contentOffset.x / pageWidth) - 1 + guard index >= 0 else { return } + self?.weekDayCollectionView.setContentOffset(CGPoint(x: index * pageWidth, y: vself.weekDayCollectionView.contentOffset.y), animated: true) + }) + .disposed(by: disposeBag) + + nextWeekButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + guard let vself = self else { return } + let pageWidth = vself.weekDayCollectionView.frame.width + 7 * widthRatio + let index: CGFloat = floor(vself.weekDayCollectionView.contentOffset.x / pageWidth) + 1 + self?.weekDayCollectionView.setContentOffset(CGPoint(x: index * pageWidth, y: vself.weekDayCollectionView.contentOffset.y), animated: true) + }) + .disposed(by: disposeBag) + + addDateButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + // 데이터 변경은 바텀 시트쪽에서 처리하도록 몰아넣도록 하자 + guard let vself = self else { return } + + if vself.startTimePicker.date > vself.endTimePicker.date { // 만약 시작 시간이 종료시간 보다 빠르다면 + vself.view.makeToastAnimation(message: "종료시간을 확인해주세요") + } else { + vself.bottomSheetView.addSelectedTimeData(date: vself.pickedDate.getDateStringForRequest(), + start: vself.pickedDate.getStartTimeStringForRequest(), + end: vself.pickedDate.getEndTimeStringForRequest()) + } + }) + .disposed(by: disposeBag) + + allDaySwitch.rx.isOn + .asDriver() + .drive(onNext: { [weak self] isOn in + guard let vself = self else { return } + + let dateFormatter = DateFormatter().then { + $0.dateFormat = "a hh:mm" + $0.timeZone = NSTimeZone(name: "ko_KR") as TimeZone? + $0.locale = Locale(identifier: "ko_KR") + } + + guard let startDate = dateFormatter.date(from: isOn ? "오전 12:00" : "오전 09:00"), + let endDate = dateFormatter.date(from: isOn ? "오후 11:55" : "오후 02:00") else { return } + + vself.startTimePicker.setDate(startDate,animated: true) + vself.endTimePicker.setDate(endDate,animated: true) + + vself.startTimePicker.isUserInteractionEnabled = !isOn + vself.endTimePicker.isUserInteractionEnabled = !isOn + + let startDateComponents = DateComponents(year: vself.pickedDate.startTime.year, month: vself.pickedDate.startTime.month, day: vself.pickedDate.startTime.day, hour: isOn ? 0 : 9, minute: 0) + let endDateComponents = DateComponents(year: vself.pickedDate.startTime.year, month: vself.pickedDate.startTime.month, day: vself.pickedDate.startTime.day, hour: isOn ? 23 : 14, minute: isOn ? 55 : 0) + guard let revisedStartDate = Calendar.current.date(from: startDateComponents), + let revisedEndDate = Calendar.current.date(from: endDateComponents) else { return } + + vself.pickedDate.startTime = revisedStartDate + vself.pickedDate.endTime = revisedEndDate + + vself.updateSelectedDateLabel() + }) + .disposed(by: disposeBag) + + startTimePicker.rx.date + .asDriver() + .drive(onNext: { [weak self] date in + guard let vself = self else { return } + let startDateComponents = DateComponents(year: vself.pickedDate.startTime.year, month: vself.pickedDate.startTime.month, day: vself.pickedDate.startTime.day, hour: date.hour, minute: date.minute) + if let revisedDate = Calendar.current.date(from: startDateComponents) { + vself.pickedDate.startTime = revisedDate + } + vself.updateSelectedDateLabel() + }) + .disposed(by: disposeBag) + + endTimePicker.rx.date + .asDriver() + .drive(onNext: { [weak self] date in + guard let vself = self else { return } + let endDateComponents = DateComponents(year: vself.pickedDate.startTime.year, month: vself.pickedDate.startTime.month, day: vself.pickedDate.startTime.day, hour: date.hour, minute: date.minute) + if let revisedDate = Calendar.current.date(from: endDateComponents) { + vself.pickedDate.endTime = revisedDate + } + vself.updateSelectedDateLabel() + }) + .disposed(by: disposeBag) + + scrollView.rx.willBeginDragging + .asDriver() + .drive(onNext: { [weak self] in + self?.bottomSheetView.showSheet(atState: .folded) + }) + .disposed(by: disposeBag) + + bottomSheetView.rx_requestIsEnabled + .asObservable() + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] isEnabled in + self?.requestButton.isEnabled = isEnabled + self?.requestButton.backgroundColor = isEnabled ? UIColor.pink01 : UIColor.grey02 + }) + .disposed(by: disposeBag) + + requestButton.rx.tap + .asDriver() + .drive(onNext: { [weak self] in + PostRequestPlansService.shared.requestPlans { response in + switch response { + case .success(_): + self?.view.makeToastAnimation(message: "약속 요청 보내기 완료!") + self?.delegate?.exitButtonDidTap() + case .networkFail: + self?.view.makeToastAnimation(message: "네트워크 에러입니다.") + case .pathErr: + self?.view.makeToastAnimation(message: "내부 오류") + case .requestErr(_): + self?.view.makeToastAnimation(message: "요청 오류, 다시 시도하십시오") + case .serverErr: + self?.view.makeToastAnimation(message: "서버 에러") + } + } + }) + .disposed(by: disposeBag) + } + + private func setupWeekDayCollectionView() { + /// 셀 표시 구현 + weekDayRelay + .bind(to: weekDayCollectionView.rx.items) { [weak self] (collectionView, item, element) in + guard let vself = self, + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SelectableWeekDayCVC.identifier, for: IndexPath(item: item, section: 0)) as? SelectableWeekDayCVC else { return UICollectionViewCell() } + + cell.dateToShow = element + if element.year < Date().year || + (element.year == Date().year && element.month < Date().month) || + (element.year == Date().year && element.month == Date().month && element.day < Date().day) { + cell.status = .invalid + } else if vself.selectedCellIndex == item { + cell.status = .selected + } else { + cell.status = .unselected + } + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + formatter.locale = Locale(identifier: "ko_KR") + formatter.timeZone = NSTimeZone(name: "ko_KR") as TimeZone? + let dateString: String = formatter.string(from: element) + + cell.isScheduled = !(vself.scheduleDataList?.filter { $0.date == dateString }.isEmpty ?? false) // 찾아서 있으면 표시 + + return cell + } + .disposed(by: disposeBag) + + /// 무한스크롤링 + weekDayCollectionView.rx.contentOffset.changed + .asDriver() + .drive(onNext: { [weak self] contentOffset in + guard let vself = self else { return } + if contentOffset.x > vself.weekDayCollectionView.contentSize.width - vself.weekDayCollectionView.frame.width { // 마지막 페이지에 도달 시 추가한다 + + guard let lastDisplayedDate = vself.weekDayRelay.value.last else { return } + let datesForAppend: [Date] = Array(repeating: lastDisplayedDate.nextDate(value: 1), count: 14).enumerated().map { $0.1.nextDate(value: $0.0) } // 2주씩 추가 + + vself.weekDayRelay.accept(vself.weekDayRelay.value + datesForAppend) + if let appendLastDate = datesForAppend.last, appendLastDate.month > lastDisplayedDate.month { // 만약 추가될 마지막 날짜가 원래 표시되던 마지막 날의 달 보다 크면 스케쥴 데이터 요청시킨다. + vself.requestCalendarData(yearString: "\(datesForAppend.last?.year ?? 2022)", monthString: "\(datesForAppend.last?.month ?? 1)") + } + } + }) + .disposed(by: disposeBag) + + /// paging시 컨텐트 오프셋을 조정, 출처 : https://eunjin3786.tistory.com/203 + weekDayCollectionView.rx.willEndDragging + .asDriver() + .drive(onNext: { [weak self] draggingValue in + guard let vself = self else { return } + // 한 페이지 사이즈 + let pageWidth = vself.weekDayCollectionView.frame.width + 7 * widthRatio + + // 도착할 오프셋 + var offsetWillMove = draggingValue.targetContentOffset.pointee + // 페이지 인덱스 (몇번째 페이지?) + let index = (offsetWillMove.x + vself.weekDayCollectionView.contentInset.left) / pageWidth + var roundedIndex = round(index) + + roundedIndex = vself.weekDayCollectionView.contentOffset.x > draggingValue.targetContentOffset.pointee.x ? floor(index) : ceil(index) + + + offsetWillMove = CGPoint(x: roundedIndex * pageWidth, y: draggingValue.targetContentOffset.pointee.y) + draggingValue.targetContentOffset.pointee = offsetWillMove + }) + .disposed(by: disposeBag) + + // 셀 선택시 동작을 정의 + weekDayCollectionView.rx.itemSelected + .asDriver() + .drive(onNext: { [weak self] indexPath in + guard let vself = self else { return } + guard let cell = vself.weekDayCollectionView.cellForItem(at: indexPath) as? SelectableWeekDayCVC else { return } + guard let dateToShow = cell.dateToShow else { return } + + // 어차피 invalid는 선택할 수 없으므로 아래와 같이 설정 + if let selectedCellIndex = vself.selectedCellIndex { + let previousSelectedCell = vself.weekDayCollectionView.cellForItem(at: IndexPath(item: selectedCellIndex, section: 0)) as? SelectableWeekDayCVC + previousSelectedCell?.status = .unselected + } + vself.selectedCellIndex = indexPath.row + cell.status = .selected + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "YYYY-MM-dd" // 2020-08-13 16:30 + vself.selectedDateLabel.text = dateFormatter.string(from: dateToShow) + + let selectedDay = DateComponents(year: dateToShow.year, month: dateToShow.month, day: dateToShow.day, hour: vself.pickedDate.startTime.hour, minute: vself.pickedDate.startTime.minute) + vself.pickedDate.startTime = Calendar.current.date(from: selectedDay) ?? dateToShow + vself.selectedDayLabel.text = "\(dateToShow.month)월 \(dateToShow.day)일 \(Date.getKoreanWeekDay(from: dateToShow))요일" + vself.yearAndWeekLabelButton.setTitle("\(dateToShow.year)년 \(dateToShow.month)월", for: .normal) + + // 해당 날짜에 해당하는 스케쥴이 있다면 스케쥴 표시를 위한 이벤트 발행 + guard let scheduleDataList = vself.scheduleDataList else { return } + let selectedDaySchedule = scheduleDataList.filter({ $0.date == dateFormatter.string(from: dateToShow) }) + vself.selectedDaySchedule.accept(selectedDaySchedule) + }) + .disposed(by: disposeBag) + } + + private func setupScheduleTableView() { + selectedDaySchedule + .bind(to: scheduleTableView.rx.items) { (tableView, row, element) in + guard let cell = tableView.dequeueReusableCell(withIdentifier: ScheduleTVC.identifier, for: IndexPath(row: row, section: 0)) as? ScheduleTVC else { return UITableViewCell() } + + cell.setData(time: "\(element.start)-\(element.end)", plansTitle: element.invitationTitle) + return cell + } + .disposed(by: disposeBag) + } + + private func updateSelectedDateLabel() { + selectedDateLabel.text = pickedDate.getDateString() + selectedTimeLabel.text = pickedDate.getStartToEndString() + } + + // MARK: - Networks + + /// 1달 단위의 스케쥴이 있는지 조회하는 메서드 + /// - Parameters: + /// - yearString: 연도 + /// - monthString: 월 + private func requestCalendarData(yearString: String, monthString: String) { + GetScheduleService.shared.getScheduleData(year: yearString, month: monthString) { [weak self] responseData in + switch responseData { + case .success(let response): + guard let response = response as? InvitationPlanData else { return } + if (self?.scheduleDataList) != nil { + self?.scheduleDataList?.append(contentsOf: response.data ?? []) + } else { + self?.scheduleDataList = response.data ?? [] + } + DispatchQueue.main.async { + self?.weekDayCollectionView.reloadData() + } + case .requestErr(let msg): + print(msg) + case .pathErr: + print("path error") + case .serverErr: + print("server error") + case .networkFail: + print("network Fail") + } + } + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/WeekDay.swift b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/WeekDay.swift new file mode 100644 index 0000000..b83a2ef --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/RequestScene/WeekDay.swift @@ -0,0 +1,15 @@ +// +// WeekDay.swift +// SeeMeet +// +// Created by 이유진 on 2022/01/19. +// + +import Foundation + + + +enum WeekDay: Int{ + case Sun=6, Mon=5, Tue=4, + Wed=3,Thu=2, Fri=1, Sat=0 +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/SMPopUpVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/SMPopUpVC.swift new file mode 100644 index 0000000..69713e0 --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/SMPopUpVC.swift @@ -0,0 +1,229 @@ +import UIKit +import SnapKit + +enum SMPopUpType { + case needLogin // 메인뷰 + case deletePlans // 캘린더 상세 뷰 + case dismissRequest // 약속 신청 뷰 + case cancelPlans // 약속 내역 보낸 요청 + case refusePlans // 약속 내역 받은 요청 + case accountWithdrawal// 계정 탈퇴 + case logout// 로그아웃 + case profile +} + +class SMPopUpVC: UIViewController { + + // MARK: - properties + static let identifier: String = "SMPopUpVC" + + private var messageLabels: [UILabel] = [] + + var greyButtonText: String? { + didSet { + greyButton.setTitle(oldValue, for: .normal) + } + } + + var pinkButtonText: String? { + didSet { + pinkButton.setTitle(oldValue, for: .normal) + } + } + + var type: SMPopUpType = .needLogin { + didSet { + setContents() + } + } + + private let popUpView: UIView = UIView().then { + $0.backgroundColor = UIColor.white + $0.layer.masksToBounds = true + $0.layer.cornerRadius = 10 + } + + private let greyButton: UIButton = UIButton().then { + $0.setTitleColor(UIColor.grey05, for: .normal) + $0.backgroundColor = UIColor.grey02 + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 16) + } + + private let pinkButton: UIButton = UIButton().then { + $0.setTitleColor(UIColor.white, for: .normal) + $0.backgroundColor = UIColor.pink01 + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 16) + } + + var pinkButtonCompletion: (() -> Void)? + + var greyButtonCompletion: (() -> Void)? + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setLayouts() + setMessageLayout() + configUI() + } + + convenience init() { + self.init() + setContents() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setContents() + } + + init(withType: SMPopUpType) { + super.init(nibName: nil, bundle: nil) + type = withType + setContents() + } + + private func setContents() { + var messageText: [String] = [] + + switch type { + case .needLogin: + messageText = ["로그인이 필요한 서비스에요", "로그인 하러 갈까요?"] + greyButton.setTitle("취소", for: .normal) + pinkButton.setTitle("로그인", for: .normal) + case .deletePlans: + messageText = ["정말 이 약속을 삭제할까요?"] + greyButton.setTitle("취소", for: .normal) + pinkButton.setTitle("삭제", for: .normal) + case .dismissRequest: + messageText = ["작성중인 내용이 있어요", "여기서 나갈까요?"] + greyButton.setTitle("취소", for: .normal) + pinkButton.setTitle("나가기", for: .normal) + case .cancelPlans: + messageText = ["약속을 취소하시겠어요?"] + greyButton.setTitle("아니오", for: .normal) + pinkButton.setTitle("예", for: .normal) + case .refusePlans: + messageText = ["약속을 거절하시겠어요?"] + greyButton.setTitle("취소", for: .normal) + pinkButton.setTitle("거절", for: .normal) + case .accountWithdrawal: + messageText = ["회원탈퇴 시 \n 저장된 모든 정보가 삭제되며 \n 복구가 불가능합니다.\n(애플 로그인의 경우\n탈퇴 진행을 위해 재로그인이\n 필요합니다.)"] + greyButton.setTitle("네", for: .normal) + pinkButton.setTitle("다음에 할게요", for: .normal) + case .logout: + messageText = ["정말 로그아웃 하시겠어요?"] + greyButton.setTitle("네", for: .normal) + pinkButton.setTitle("다음에 할게요", for: .normal) + case .profile: + messageText = ["아직 회원가입을 완료하지 않았어요 \n 씨밋에서 사용할 이름과 아아디를 \n 입력해주세요"] + greyButton.setTitle("네", for: .normal) + pinkButton.setTitle("확인", for: .normal) + } + + messageText.forEach { + let label: UILabel = UILabel() + label.font = UIFont.hanSansMediumFont(ofSize: 16) + label.text = $0 + label.textColor = UIColor.black + label.textAlignment = .center + label.numberOfLines = 0 + messageLabels.append(label) + + } + } + + private func setLayouts() { + view.backgroundColor = UIColor.black.withAlphaComponent(0.7) + + view.addSubview(popUpView) + popUpView.snp.makeConstraints { + $0.leading.equalToSuperview().offset(53 * widthRatio) + $0.top.equalToSuperview().offset(317 * heightRatio) + $0.width.equalTo(270 * widthRatio) + $0.height.equalTo(167 * heightRatio) + } + + popUpView.addSubviews([greyButton, pinkButton]) + + greyButton.snp.makeConstraints { + $0.leading.bottom.equalToSuperview() + $0.width.equalTo(popUpView.snp.width).dividedBy(2) + $0.height.equalTo(50 * heightRatio) + } + + pinkButton.snp.makeConstraints { + $0.bottom.trailing.equalToSuperview() + $0.width.equalTo(greyButton.snp.width) + $0.height.equalTo(greyButton.snp.height) + } + + } + + private func setMessageLayout() { + popUpView.addSubviews(messageLabels) + + switch type { + case .deletePlans, .cancelPlans, .refusePlans, .accountWithdrawal, .logout: + if let messageLabel = messageLabels.first { + messageLabel.snp.makeConstraints { + $0.centerY.equalToSuperview().offset(-25*heightRatio) + $0.centerX.equalToSuperview() + } + } + case .needLogin, .dismissRequest: + if let messageLabelFirst = messageLabels.first, + let messageLabelLast = messageLabels.last { + messageLabelFirst.snp.makeConstraints { + $0.top.equalToSuperview().offset(33 * heightRatio) + $0.leading.equalToSuperview().offset(12 * widthRatio) + $0.trailing.equalToSuperview().offset(-12 * widthRatio) + } + + messageLabelLast.snp.makeConstraints { + $0.top.equalTo(messageLabelFirst.snp.bottom).offset(10 * heightRatio) + $0.leading.equalToSuperview().offset(12 * widthRatio) + $0.trailing.equalToSuperview().offset(-12 * widthRatio) + } + } + case .profile: + greyButton.isHidden = true + pinkButton.snp.remakeConstraints{ + + $0.leading.bottom.trailing.equalToSuperview() + $0.height.equalTo(greyButton.snp.height) + + } + if let messageLabel = messageLabels.first { + messageLabel.snp.makeConstraints { + $0.centerY.equalToSuperview().offset(-25*heightRatio) + $0.centerX.equalToSuperview() + } + } + } + + } + + private func configUI() { + greyButton.addTarget(self, action: #selector(touchUpGreyButton(_:)), for: .touchUpInside) + pinkButton.addTarget(self, action: #selector(touchUpPinkButton(_:)), for: .touchUpInside) + } + + // MARK: - objc + + @objc func touchUpGreyButton(_ sender: UIButton) { + switch type { + case .accountWithdrawal, .logout : + guard let completion = greyButtonCompletion else {return} + completion() + default: + dismiss(animated: false, completion: nil) + } + } + + @objc func touchUpPinkButton(_ sender: UIButton) { + guard let completion = pinkButtonCompletion else { return } + completion() + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/SMRequestPopUpVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/SMRequestPopUpVC.swift new file mode 100644 index 0000000..5f7b9af --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/SMRequestPopUpVC.swift @@ -0,0 +1,309 @@ +// +// SMRequestPopUpVC.swift +// SeeMeet +// +// Created by 박익범 on 2022/01/15. +// + +import UIKit +import Then +import SnapKit + +enum SMRequestPopUpType{ +case recieveConfirm // 받은약속 확정 후 보내는 뷰 +case sendConfirm //보낸약속 확정하기 +case sendNotSelectConfirm //해당 날짜를 선택하지 않은 친구가 있을때 +case sendNotRequestConfirm // 답변을 보내지 않은 친구가 있을때 +} + +class SMRequestPopUpVC: UIViewController { + + // MARK: - properties + static let identifier: String = "SMPopUpVC" + + private var messageLabels: [UILabel] = [] + private var dateLabels: [UILabel] = [] + var yearText: [String] = [] + var dateText: [String] = [] + + var greyButtonText: String? { + didSet { + greyButton.setTitle(oldValue, for: .normal) + } + } + + var pinkButtonText: String? { + didSet { + pinkButton.setTitle(oldValue, for: .normal) + } + } + + var type: SMRequestPopUpType = .recieveConfirm { + didSet { + setContents() + } + } + + private let popUpView: UIView = UIView().then { + $0.backgroundColor = UIColor.white + $0.layer.masksToBounds = true + $0.layer.cornerRadius = 10 + } + private let textView = UIView ().then{ + $0.backgroundColor = UIColor.grey01 + $0.clipsToBounds = true + $0.layer.cornerRadius = 10 + } + private let yearStackView = UIStackView().then{ + $0.axis = .vertical + $0.backgroundColor = .none + $0.alignment = .fill + $0.distribution = .fillEqually + $0.spacing = 0 + $0.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + $0.isLayoutMarginsRelativeArrangement = true + } + private let dateStackView = UIStackView().then{ + $0.axis = .vertical + $0.backgroundColor = .none + $0.alignment = .fill + $0.distribution = .fillEqually + $0.spacing = 0 + $0.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + $0.isLayoutMarginsRelativeArrangement = true + } + private let greyButton: UIButton = UIButton().then { + $0.setTitleColor(UIColor.grey05, for: .normal) + $0.backgroundColor = UIColor.grey02 + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 16) + } + + private let pinkButton: UIButton = UIButton().then { + $0.setTitleColor(UIColor.white, for: .normal) + $0.backgroundColor = UIColor.pink01 + $0.titleLabel?.font = UIFont.hanSansMediumFont(ofSize: 16) + } + + var pinkButtonCompletion: (() -> Void)? + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setLayouts() + setMessageLayout() + configUI() + } + + convenience init() { + self.init() + setContents() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setContents() + } + + init(withType: SMRequestPopUpType) { + super.init(nibName: nil, bundle: nil) + type = withType + setContents() + } + + private func configUI() { + greyButton.addTarget(self, action: #selector(touchUpGreyButton(_:)), for: .touchUpInside) + pinkButton.addTarget(self, action: #selector(touchUpPinkButton(_:)), for: .touchUpInside) + } + + private func setContents() { + var messageText: [String] = [] + + switch type { + case .recieveConfirm: + messageText = ["이렇게 보낼까요?"] + greyButton.setTitle("취소", for: .normal) + pinkButton.setTitle("보내기", for: .normal) + case .sendConfirm: + messageText = ["약속을 확정하시겠어요?"] + greyButton.setTitle("취소", for: .normal) + pinkButton.setTitle("확정", for: .normal) + case .sendNotSelectConfirm: + messageText = ["만날 수 없는 친구가 있어요", "확정할까요?"] + greyButton.setTitle("취소", for: .normal) + pinkButton.setTitle("확정", for: .normal) + case .sendNotRequestConfirm: + messageText = ["답변하지 않은 친구가 있어요", "확정할까요?"] + greyButton.setTitle("아니오", for: .normal) + pinkButton.setTitle("예", for: .normal) + } + + messageText.forEach { + let label: UILabel = UILabel() + label.font = UIFont.hanSansMediumFont(ofSize: 16) + label.text = $0 + label.textColor = UIColor.black + label.textAlignment = .center + messageLabels.append(label) + } + } + + private func setLayouts() { + view.backgroundColor = UIColor.black.withAlphaComponent(0.7) + + view.addSubview(popUpView) + popUpView.snp.makeConstraints { + $0.leading.equalToSuperview().offset(30 * widthRatio) + $0.top.equalToSuperview().offset(229 * heightRatio) + $0.width.equalTo(315 * widthRatio) + $0.height.equalTo(333 * heightRatio) + } + + popUpView.addSubview(textView) + switch type{ + case .recieveConfirm: + textView.snp.makeConstraints{ + $0.top.equalToSuperview().offset(18 * heightRatio) + $0.leading.equalToSuperview().offset(16 * widthRatio) + $0.trailing.equalToSuperview().offset(-16 * heightRatio) + $0.height.equalTo(189 * heightRatio) + } + textView.addSubviews([yearStackView, dateStackView]) + yearStackView.snp.makeConstraints{ + $0.top.equalToSuperview().offset(18 * heightRatio) + $0.leading.equalToSuperview().offset(21 * widthRatio) + $0.height.equalTo(152 * heightRatio) +// $0.width.equalTo(88 * widthRatio) + } + dateStackView.snp.makeConstraints{ + $0.top.equalToSuperview().offset(18 * heightRatio) + $0.leading.equalTo(yearStackView.snp.trailing).offset(18 * widthRatio) + $0.trailing.equalToSuperview().offset(21 * widthRatio) + $0.height.equalTo(152 * heightRatio) + } + setDateLayout() + default: + textView.snp.makeConstraints{ + $0.top.equalToSuperview().offset(18 * heightRatio) + $0.leading.equalToSuperview().offset(16 * widthRatio) + $0.trailing.equalToSuperview().offset(-16 * widthRatio) + $0.height.equalTo(150 * heightRatio) + } + } + popUpView.addSubviews([greyButton, pinkButton]) + + greyButton.snp.makeConstraints { + $0.leading.bottom.equalToSuperview() + $0.width.equalTo(popUpView.snp.width).dividedBy(2) + $0.height.equalTo(50 * heightRatio) + } + + pinkButton.snp.makeConstraints { + $0.bottom.trailing.equalToSuperview() + $0.width.equalTo(greyButton.snp.width) + $0.height.equalTo(greyButton.snp.height) + } + } + private func setDateLayout(){ + yearText.forEach { + let label: UILabel = UILabel() + label.font = UIFont.dinProBoldFont(ofSize: 18) + label.text = $0 + label.textColor = UIColor.black + label.textAlignment = .left + yearStackView.addArrangedSubview(label) + } + dateText.forEach { + let label: UILabel = UILabel() + label.font = UIFont.dinProRegularFont(ofSize: 14) + label.text = $0 + label.textColor = UIColor.black + label.textAlignment = .left + dateStackView.addArrangedSubview(label) + } + } + func setTextMessageLayout(){ + yearText.forEach { + let label: UILabel = UILabel() + label.font = UIFont.dinProBoldFont(ofSize: 18) + label.text = $0 + label.textColor = UIColor.black + label.textAlignment = .left + dateLabels.append(label) + } + dateText.forEach { + let label: UILabel = UILabel() + label.font = UIFont.dinProRegularFont(ofSize: 14) + label.text = $0 + label.textColor = UIColor.black + label.textAlignment = .left + dateLabels.append(label) + } + + textView.addSubviews(dateLabels) + if type != .recieveConfirm { + if let dateLabelFirst = dateLabels.first, + let dateLabelSectond = dateLabels.last { + dateLabelFirst.snp.makeConstraints { + $0.top.equalToSuperview().offset(46) + $0.centerX.equalToSuperview() + $0.height.equalTo(23) + } + dateLabelSectond.snp.makeConstraints{ + $0.top.equalTo(dateLabelFirst.snp.bottom).offset(10) + $0.centerX.equalToSuperview() + $0.height.equalTo(18) + } + } + } + } + + + private func setMessageLayout() { + popUpView.addSubviews(messageLabels) + + switch type { + case .recieveConfirm: + if let messageLabel = messageLabels.first { + messageLabel.snp.makeConstraints { + $0.top.equalTo(textView.snp.bottom).offset(26 * heightRatio) + $0.centerX.equalToSuperview() + } + } + case .sendConfirm: + if let messageLabel = messageLabels.first { + messageLabel.snp.makeConstraints { + $0.top.equalTo(textView.snp.bottom).offset(46 * heightRatio) + $0.centerX.equalToSuperview() + } + } + case .sendNotSelectConfirm, .sendNotRequestConfirm: + if let messageLabelFirst = messageLabels.first, + let messageLabelLast = messageLabels.last { + messageLabelFirst.snp.makeConstraints { + $0.top.equalTo(textView.snp.bottom).offset(33 * heightRatio) + $0.leading.equalToSuperview().offset(12 * widthRatio) + $0.trailing.equalToSuperview().offset(-12 * widthRatio) + } + messageLabelLast.snp.makeConstraints { + $0.top.equalTo(messageLabelFirst.snp.bottom).offset(10 * heightRatio) + $0.leading.equalToSuperview().offset(12 * widthRatio) + $0.trailing.equalToSuperview().offset(-12 * widthRatio) + } + } + } + setTextMessageLayout() + } + + // MARK: - objc + + @objc func touchUpGreyButton(_ sender: UIButton) { + dismiss(animated: false, completion: nil) + } + + @objc func touchUpPinkButton(_ sender: UIButton) { + guard let completion = pinkButtonCompletion else { return } + completion() + } + +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/Tabbar/TabbarVC.swift b/SeeMeet/SeeMeet/Source/Views/VCs/Tabbar/TabbarVC.swift new file mode 100644 index 0000000..764ffbe --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/Tabbar/TabbarVC.swift @@ -0,0 +1,96 @@ +// +// TabbarVC.swift +// SeeMeet +// +// Created by 박익범 on 2022/01/08. +// + + +import SnapKit +import Then +import UIKit +import RxSwift +import RxCocoa + +protocol TabbarVCDelegate { + func presentAlert() + func needLogin() + func dismissAlert() + func gotoRequestScene(friendData: FriendsData?) +} + +final class TabbarVC: UITabBarController { + + // MARK: - UI Components + + private let plansRequestButton = UIButton().then { + $0.setBackgroundImage(UIImage(named: "btn_send_message"), for: .normal) + } + + // MARK: - Properties + + static let identifier: String = "TabbarVC" + public var tabs: [UIViewController] = [] + + private let disposeBag = DisposeBag() + + var tabBarDelegate: TabbarVCDelegate? + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + configTabbar() + setAutoLayouts() + setupButtons() + + // Tabbar는 사라지지 않으니 이곳에서 노티를 받자. +// NotificationCenter.default.rx.notification(Notification.Name.RefreshTokenExpired) +// .asDriver(onErrorJustReturn: Notification(name: Notification.Name.init(rawValue: "error"))) +// .drive(onNext: { [weak self] noti in +// +// }) +// .disposed(by: disposeBag) + } + + // MARK: - setLayouts + + private func setAutoLayouts() { + tabBar.addSubview(plansRequestButton) + + plansRequestButton.snp.makeConstraints{ + $0.top.equalTo(tabBar.snp.top).offset(-9 * heightRatio) + $0.centerX.equalTo(tabBar.snp.centerX) + $0.width.height.equalTo(63 * heightRatio) + } + } + + // MARK: - Custom Methods + + private func configTabbar(){ + + tabBar.do { + $0.tintColor = .black + $0.unselectedItemTintColor = .black + $0.backgroundColor = UIColor.white + $0.itemPositioning = .centered//하단줄의 tabBar.itemSpacing을 지정해주기 위해서 .centered를 해줘야함 + $0.itemSpacing = 140 * widthRatio //탭바 아이템간 간격 설정 - 곱해서 하자 .. + } + + } + + private func setupButtons() { + plansRequestButton.rx.tap + .asDriver() + .throttle(.milliseconds(100)) + .drive(onNext: { [weak self] _ in + guard let self = self else { return } + if UserDefaults.standard.bool(forKey: Constants.UserDefaultsKey.isLogin) == false { + self.tabBarDelegate?.presentAlert() + } else { + self.tabBarDelegate?.gotoRequestScene(friendData: nil) + } + }) + .disposed(by: disposeBag) + } +} diff --git a/SeeMeet/SeeMeet/Source/Views/VCs/ToastMessageView.swift b/SeeMeet/SeeMeet/Source/Views/VCs/ToastMessageView.swift new file mode 100644 index 0000000..b136f3f --- /dev/null +++ b/SeeMeet/SeeMeet/Source/Views/VCs/ToastMessageView.swift @@ -0,0 +1,34 @@ +import UIKit + +class ToastMessageView: UIView{ + + private let messageText = UILabel().then{ + $0.text = "" + $0.font = UIFont.hanSansRegularFont(ofSize: 13) + $0.textColor = UIColor.white + } + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + init(message: String) { + super.init(frame: .zero) + backgroundColor = UIColor.black.withAlphaComponent(0.8) + clipsToBounds = true + layer.cornerRadius = 10 + messageText.text = message + addSubview(messageText) + messageText.snp.makeConstraints{ + $0.top.equalToSuperview().offset(5) + $0.leading.equalToSuperview().offset(10) + $0.trailing.equalToSuperview().offset(-10) + $0.bottom.equalToSuperview().offset(-5) + } + } + +} diff --git a/SeeMeet/ci_scripts/ci_post_clone.sh b/SeeMeet/ci_scripts/ci_post_clone.sh new file mode 100644 index 0000000..ed33ad9 --- /dev/null +++ b/SeeMeet/ci_scripts/ci_post_clone.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# ci_post_clone.sh +# SeeMeet +# +# Created by 김인환 on 2022/08/14. +# +# Install CocoaPods using Homebrew. +brew install cocoapods + +# Install dependencies you manage with CocoaPods. +pod install