├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── README.md
└── Routinus
├── .swiftlint.yml
├── Podfile
├── Routinus.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Routinus.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Routinus
├── Application
│ ├── AppDelegate.swift
│ ├── Coordinator
│ │ ├── AppCoordinator.swift
│ │ ├── AuthCoordinator.swift
│ │ ├── AuthImagesCoordinator.swift
│ │ ├── ChallengeCoordinator.swift
│ │ ├── DetailCoordinator.swift
│ │ ├── FormCoordinator.swift
│ │ ├── HomeCoordinator.swift
│ │ ├── ManageCoordinator.swift
│ │ ├── MyPageCoordinator.swift
│ │ ├── RoutinusCoordinator.swift
│ │ ├── SearchCoordinator.swift
│ │ └── TabBarCoordinator.swift
│ └── SceneDelegate.swift
├── Data
│ ├── Factory
│ │ └── UsernameFactory.swift
│ └── Repository
│ │ ├── AchievementRepository.swift
│ │ ├── AuthRepository.swift
│ │ ├── ChallengeRepository.swift
│ │ ├── ImageRepository.swift
│ │ ├── ParticipationRepository.swift
│ │ ├── RoutinusRepository.swift
│ │ ├── TodayRoutineRepository.swift
│ │ └── UserRepository.swift
├── Domain
│ ├── Entity
│ │ ├── Achievement.swift
│ │ ├── ArchievementInfo.swift
│ │ ├── Auth.swift
│ │ ├── Calendar.swift
│ │ ├── Challenge.swift
│ │ ├── Participation.swift
│ │ ├── PopularKeyword.swift
│ │ ├── TodayRoutine.swift
│ │ └── User.swift
│ └── Usecase
│ │ ├── AchievementFetchUsecase.swift
│ │ ├── AchievementUpdateUsecase.swift
│ │ ├── AuthCreateUsecase.swift
│ │ ├── AuthFetchUsecase.swift
│ │ ├── ChallengeCreateUsecase.swift
│ │ ├── ChallengeFetchUsecase.swift
│ │ ├── ChallengeUpdateUsecase.swift
│ │ ├── ImageFetchUsecase.swift
│ │ ├── ImageSaveUsecase.swift
│ │ ├── ImageUpdateUsecase.swift
│ │ ├── ParticipationCreateUsecase.swift
│ │ ├── ParticipationFetchUsecase.swift
│ │ ├── ParticipationUpdateUsecase.swift
│ │ ├── TodayRoutineFetchUsecase.swift
│ │ ├── UserCreateUsecase.swift
│ │ ├── UserFetchUsecase.swift
│ │ └── UserUpdateUsecase.swift
├── Extensions
│ ├── Date+Extensions.swift
│ ├── String+Extensions.swift
│ ├── UICollectionView+Extensions.swift
│ ├── UIImage+Extensions.swift
│ ├── UIScrollView+Extensions.swift
│ └── UIView+Extensions.swift
├── Network
│ ├── DTO
│ │ ├── AchievementDTO.swift
│ │ ├── AuthDTO.swift
│ │ ├── ChallengeDTO.swift
│ │ ├── Fields.swift
│ │ ├── ParticipationDTO.swift
│ │ ├── TodayRoutineDTO.swift
│ │ └── UserDTO.swift
│ ├── FirebaseService.swift
│ └── Query
│ │ ├── AchievementQuery.swift
│ │ ├── AuthQuery.swift
│ │ ├── ChallengeQuery.swift
│ │ ├── ParticipationQuery.swift
│ │ └── UserQuery.swift
├── Presentation
│ ├── Auth
│ │ ├── AuthViewController.swift
│ │ ├── AuthViewModel.swift
│ │ └── Subviews
│ │ │ ├── AuthButton.swift
│ │ │ └── AuthPreviewView.swift
│ ├── AuthImages
│ │ ├── AuthImagesViewController.swift
│ │ ├── AuthImagesViewModel.swift
│ │ └── Cells
│ │ │ └── AuthImagesCollectionViewCell.swift
│ ├── Challenge
│ │ ├── Cells
│ │ │ ├── ChallengeCategoryCollectionViewCell.swift
│ │ │ ├── ChallengeCategoryCollectionViewHeader.swift
│ │ │ ├── ChallengeRecommendCollectionViewCell.swift
│ │ │ └── ChallengeRecommendCollectionViewHeader.swift
│ │ ├── ChallengeViewController.swift
│ │ ├── ChallengeViewModel.swift
│ │ ├── Delegates
│ │ │ ├── ChallengeCategoryHeaderDelegate.swift
│ │ │ └── ChallengeRecommendHeaderDelegate.swift
│ │ ├── Layouts
│ │ │ └── ChallengeCollectionViewLayouts.swift
│ │ └── Subviews
│ │ │ └── ChallengeCategoryIconView.swift
│ ├── Common
│ │ ├── Cells
│ │ │ └── ChallengeCollectionViewCell.swift
│ │ ├── ImagePanViewController.swift
│ │ └── Subviews
│ │ │ ├── AuthMethodView.swift
│ │ │ └── ChallengePromotionView.swift
│ ├── Detail
│ │ ├── DetailViewController.swift
│ │ ├── DetailViewModel.swift
│ │ └── Subviews
│ │ │ ├── DetailAuthDisplayListView.swift
│ │ │ ├── DetailAuthDisplayView.swift
│ │ │ ├── DetailInformationView.swift
│ │ │ └── DetailParticipantButton.swift
│ ├── Form
│ │ ├── Delegates
│ │ │ ├── FormImagePickerDelegate.swift
│ │ │ └── FormSubviewDelegate.swift
│ │ ├── FormViewController.swift
│ │ ├── FormViewModel.swift
│ │ └── Subviews
│ │ │ ├── FormAuthImageRegisterView.swift
│ │ │ ├── FormAuthMethodView.swift
│ │ │ ├── FormCategoryView.swift
│ │ │ ├── FormImageRegisterView.swift
│ │ │ ├── FormIntroductionView.swift
│ │ │ ├── FormTitleView.swift
│ │ │ └── FormWeekView.swift
│ ├── Home
│ │ ├── Cells
│ │ │ ├── HomeCalendarDetailTableViewCell.swift
│ │ │ ├── HomeDateCollelctionViewCell.swift
│ │ │ └── HomeRoutineTableViewCell.swift
│ │ ├── HomeCalendarDetailViewController.swift
│ │ ├── HomeCalendarExplanationViewController.swift
│ │ ├── HomeViewController.swift
│ │ ├── HomeViewModel.swift
│ │ └── Subviews
│ │ │ ├── HomeCalendarHeaderView.swift
│ │ │ ├── HomeCalendarView.swift
│ │ │ ├── HomeContinuityView.swift
│ │ │ └── HomeTodayRoutineView.swift
│ ├── Launch
│ │ ├── Base.lproj
│ │ │ └── LaunchScreen.storyboard
│ │ └── Subviews
│ │ │ └── LaunchView.swift
│ ├── Manage
│ │ ├── Cells
│ │ │ ├── ManageAddCollectionViewCell.swift
│ │ │ └── ManageCollectionViewHeader.swift
│ │ ├── Layouts
│ │ │ └── ManageCollectionViewLayouts.swift
│ │ ├── ManageViewController.swift
│ │ └── ManageViewModel.swift
│ ├── MyPage
│ │ ├── Delegates
│ │ │ └── MyPageUserNameUpdatableDelegate.swift
│ │ ├── MyPageViewController.swift
│ │ ├── MyPageViewModel.swift
│ │ └── Subviews
│ │ │ └── MyPageProfileView.swift
│ └── Search
│ │ ├── Cells
│ │ ├── SearchCollectionViewHeader.swift
│ │ └── SearchPopularKeywordCollectionViewCell.swift
│ │ ├── Delegates
│ │ └── SearchPopularKeywordDelegate.swift
│ │ ├── Layouts
│ │ └── SearchCollectionViewLayouts.swift
│ │ ├── SearchViewController.swift
│ │ ├── SearchViewModel.swift
│ │ └── Subviews
│ │ ├── SearchBarContainerView.swift
│ │ └── SearchPopularKeywordButton.swift
├── Resources
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── icon-40.png
│ │ │ ├── icon-40@2x.png
│ │ │ ├── icon-40@3x.png
│ │ │ ├── icon-60@2x.png
│ │ │ ├── icon-60@3x.png
│ │ │ ├── icon-72.png
│ │ │ ├── icon-72@2x.png
│ │ │ ├── icon-76.png
│ │ │ ├── icon-76@2x.png
│ │ │ ├── icon-83.5@2x.png
│ │ │ ├── icon-small-50.png
│ │ │ ├── icon-small-50@2x.png
│ │ │ ├── icon-small.png
│ │ │ ├── icon-small@2x.png
│ │ │ ├── icon-small@3x.png
│ │ │ ├── icon.png
│ │ │ ├── icon@2x.png
│ │ │ ├── ios-marketing.png
│ │ │ ├── notification-icon@2x.png
│ │ │ ├── notification-icon@3x.png
│ │ │ ├── notification-icon~ipad.png
│ │ │ └── notification-icon~ipad@2x.png
│ │ ├── Contents.json
│ │ ├── Icons
│ │ │ ├── Contents.json
│ │ │ ├── challenge.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── challenge@x1.png
│ │ │ │ ├── challenge@x2.png
│ │ │ │ └── challenge@x3.png
│ │ │ ├── check.calendar.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── calendar.svg
│ │ │ └── dumbbell.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── dumbbell.svg
│ │ ├── Logo.imageset
│ │ │ ├── Contents.json
│ │ │ ├── logo@x1.png
│ │ │ ├── logo@x2.png
│ │ │ └── logo@x3.png
│ │ ├── achievement
│ │ │ ├── 1-19.imageset
│ │ │ │ ├── 0-20.png
│ │ │ │ └── Contents.json
│ │ │ ├── 100.imageset
│ │ │ │ ├── 100.png
│ │ │ │ └── Contents.json
│ │ │ ├── 20-39.imageset
│ │ │ │ ├── 20-40.png
│ │ │ │ └── Contents.json
│ │ │ ├── 40-65.imageset
│ │ │ │ ├── 40-70.png
│ │ │ │ └── Contents.json
│ │ │ ├── 66-99.imageset
│ │ │ │ ├── 70-99.png
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── colors
│ │ │ ├── Black.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── Calendar
│ │ │ │ ├── Contents.json
│ │ │ │ ├── DayColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── SaturdayColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── SundayColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Category
│ │ │ │ ├── Contents.json
│ │ │ │ ├── ETCColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── EmotionManageColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── ExerciseColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── FinanceColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── HobbyColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── LifeStyleColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── ReadColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── StudyColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ ├── EndDateColor.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── LightGray.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── MainColor.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── MainColor0.5.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── MainColorReverse.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── SystemBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── SystemForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── WeekColor.colorset
│ │ │ │ └── Contents.json
│ │ │ └── White.colorset
│ │ │ │ └── Contents.json
│ │ └── seed
│ │ │ ├── Contents.json
│ │ │ ├── seed1.imageset
│ │ │ ├── Contents.json
│ │ │ └── seed1.svg
│ │ │ ├── seed2.imageset
│ │ │ ├── Contents.json
│ │ │ └── seed2.svg
│ │ │ ├── seed3.imageset
│ │ │ ├── Contents.json
│ │ │ └── seed3.svg
│ │ │ ├── seed4.imageset
│ │ │ ├── Contents.json
│ │ │ └── seed4.svg
│ │ │ ├── seed5.imageset
│ │ │ ├── Contents.json
│ │ │ └── seed5.svg
│ │ │ ├── seed6.imageset
│ │ │ ├── Contents.json
│ │ │ └── seed6.svg
│ │ │ └── seed7.imageset
│ │ │ ├── Contents.json
│ │ │ └── seed7.svg
│ ├── Info.plist
│ ├── en.lproj
│ │ └── Localizable.strings
│ └── ko.lproj
│ │ └── Localizable.strings
└── Storage
│ └── ImageManager.swift
└── RoutinusTests
├── MockDatas
├── AchievementFetchableUsecaseMock.swift
├── AchievementUpdatableUsecaseMock.swift
├── AuthFetchableUsecaseMock.swift
├── ChallengeFetchableUsecaseMock.swift
├── ChallengeUpdatableUsecaseMock.swift
├── ImageFetchableUsecaseMock.swift
├── ParticipationCreatableUsecaseMock.swift
├── ParticipationFetchableUsecaseMock.swift
├── ParticipationFetchableUsecaseNilMock.swift
├── TodayRoutineFetchableUsecaseMock.swift
├── UserCreatableUsecaseMock.swift
├── UserFetchableUsecaseMock.swift
└── UserUpdatableUsecaseMock.swift
└── ViewModelTests
├── ChallengeViewModelTests.swift
├── DetailViewModelTests.swift
├── HomeViewModelTests.swift
├── ManageViewModelTests.swift
├── MyPageViewModelTests.swift
└── SearchViewModelTests.swift
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj merge=union
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: "\U0001F41Bbug"
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### 상황
11 |
12 | 1. 홈화면으로 이동한다.
13 | 2. 새로고침 버튼을 누른다.
14 | 3. 앱종료되는 버그 발생.
15 |
16 | ### 스크린샷
17 |
18 | ![]()
19 |
20 | ### 스마트폰 정보
21 |
22 | - 디바이스 : iPhone12
23 | - OS : 15.0.2
24 | - 네트워크 상태 : wifi
25 |
26 | ### 기타 사항
27 |
28 | 의견
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: "✨feature"
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### 내용
11 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## 관련 issue
2 |
3 | #1
4 |
5 | ## 작업 내용
6 |
7 | - [x] task 1
8 | - [x] task 2
9 | - [ ] task 3
10 |
11 | ## 기타 사항
12 |
13 | - contents
14 |
--------------------------------------------------------------------------------
/Routinus/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | line_length: 140
2 |
3 | disabled_rules:
4 | - type_body_length
5 | - function_parameter_count
6 | - file_length
7 | - identifier_name
8 | - function_body_length
9 | - inclusive_language
10 |
11 | excluded:
12 | - Pods/
13 |
--------------------------------------------------------------------------------
/Routinus/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | platform :ios, '15.0'
3 |
4 | target 'Routinus' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for Routinus
9 | pod 'SwiftLint', '0.45.0'
10 | end
11 |
--------------------------------------------------------------------------------
/Routinus/Routinus.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Routinus/Routinus.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Routinus/Routinus.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Routinus/Routinus.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Application/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/01.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | final class AppDelegate: UIResponder, UIApplicationDelegate {
12 | func application(
13 | _ application: UIApplication,
14 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
15 | ) -> Bool {
16 | preloadImagePickerController()
17 | return true
18 | }
19 |
20 | func application(_ application: UIApplication,
21 | configurationForConnecting connectingSceneSession: UISceneSession,
22 | options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | return UISceneConfiguration(name: "Default Configuration",
24 | sessionRole: connectingSceneSession.role)
25 | }
26 |
27 | func application(
28 | _ application: UIApplication,
29 | supportedInterfaceOrientationsFor window: UIWindow?
30 | ) -> UIInterfaceOrientationMask {
31 | return .portrait
32 | }
33 |
34 | private func preloadImagePickerController() {
35 | _ = UIImagePickerController()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Application/Coordinator/AppCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppCoordinator.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/02.
6 | //
7 |
8 | import UIKit
9 |
10 | final class AppCoordinator: RoutinusCoordinator {
11 | var childCoordinator: [RoutinusCoordinator] = []
12 | var navigationController: UINavigationController
13 |
14 | init(navigationController: UINavigationController) {
15 | self.navigationController = navigationController
16 | configureNavigationController()
17 | }
18 |
19 | func start() {
20 | configureTabBar()
21 | }
22 |
23 | private func configureNavigationController() {
24 | navigationController.navigationBar.isHidden = true
25 | }
26 |
27 | private func configureTabBar() {
28 | let tabBarCoordinator = TabBarCoordinator(navigationController: navigationController)
29 | tabBarCoordinator.start()
30 | childCoordinator.append(tabBarCoordinator)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Application/Coordinator/AuthCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthCoordinator.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/07.
6 | //
7 |
8 | import Combine
9 | import UIKit
10 |
11 | final class AuthCoordinator: RoutinusCoordinator {
12 | let challengeID: String
13 | var childCoordinator: [RoutinusCoordinator] = []
14 | var navigationController: UINavigationController
15 | var cancellables = Set()
16 |
17 | init(navigationController: UINavigationController, challengeID: String) {
18 | self.navigationController = navigationController
19 | self.challengeID = challengeID
20 | }
21 |
22 | func start() {
23 | let repository = RoutinusRepository()
24 | let challengeFetchUsecase = ChallengeFetchUsecase(repository: repository)
25 | let imageFetchUsecase = ImageFetchUsecase(repository: repository)
26 | let imageSaveUsecase = ImageSaveUsecase(repository: repository)
27 | let authCreateUsecase = AuthCreateUsecase(repository: repository)
28 | let participationUpdateUsecase = ParticipationUpdateUsecase(repository: repository)
29 | let achievementUpdateUsecase = AchievementUpdateUsecase(repository: repository)
30 | let userUpdateUsecase = UserUpdateUsecase(repository: repository)
31 | let userFetchUsecase = UserFetchUsecase(repository: repository)
32 | let authViewModel = AuthViewModel(challengeID: challengeID,
33 | challengeFetchUsecase: challengeFetchUsecase,
34 | imageFetchUsecase: imageFetchUsecase,
35 | imageSaveUsecase: imageSaveUsecase,
36 | authCreateUsecase: authCreateUsecase,
37 | participationUpdateUsecase: participationUpdateUsecase,
38 | achievementUpdateUsecase: achievementUpdateUsecase,
39 | userUpdateUsecase: userUpdateUsecase,
40 | userFetchUsecase: userFetchUsecase)
41 | let authViewController = AuthViewController(viewModel: authViewModel)
42 |
43 | authViewModel.authMethodImageTap
44 | .receive(on: RunLoop.main)
45 | .sink { [weak self] imageData in
46 | guard let self = self else { return }
47 | let imageViewController = ImagePanViewController()
48 | imageViewController.updateImage(data: imageData)
49 | imageViewController.modalPresentationStyle = .overCurrentContext
50 | imageViewController.modalTransitionStyle = .crossDissolve
51 | self.navigationController.present(imageViewController, animated: true)
52 | }
53 | .store(in: &cancellables)
54 |
55 | authViewModel.authMethodImageLoad
56 | .receive(on: RunLoop.main)
57 | .sink { imageData in
58 | let imageViewController = authViewController.presentedViewController as? ImagePanViewController
59 | imageViewController?.updateImage(data: imageData)
60 | }
61 | .store(in: &cancellables)
62 |
63 | authViewController.hidesBottomBarWhenPushed = true
64 | navigationController.pushViewController(authViewController, animated: true)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Application/Coordinator/AuthImagesCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthImagesCoordinator.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/23.
6 | //
7 |
8 | import Combine
9 | import UIKit
10 |
11 | final class AuthImagesCoordinator: RoutinusCoordinator {
12 | let challengeID: String
13 | let authDisplayState: AuthDisplayState
14 | var childCoordinator: [RoutinusCoordinator] = []
15 | var navigationController: UINavigationController
16 | var cancellables = Set()
17 |
18 | init(navigationController: UINavigationController,
19 | challengeID: String,
20 | authDisplayState: AuthDisplayState) {
21 | self.navigationController = navigationController
22 | self.challengeID = challengeID
23 | self.authDisplayState = authDisplayState
24 | }
25 |
26 | func start() {
27 | let repository = RoutinusRepository()
28 | let authFetchUsecase = AuthFetchUsecase(repository: repository)
29 | let imageFetchUsecase = ImageFetchUsecase(repository: repository)
30 | let authImagesViewModel = AuthImagesViewModel(
31 | challengeID: challengeID,
32 | authDisplayState: authDisplayState,
33 | authFetchUsecase: authFetchUsecase,
34 | imageFetchUsecase: imageFetchUsecase
35 | )
36 | let authImagesViewController = AuthImagesViewController(viewModel: authImagesViewModel)
37 |
38 | authImagesViewModel.authImageTap
39 | .receive(on: RunLoop.main)
40 | .sink { [weak self] imageData in
41 | guard let self = self else { return }
42 | let imageViewController = ImagePanViewController()
43 | imageViewController.updateImage(data: imageData)
44 | imageViewController.modalPresentationStyle = .overCurrentContext
45 | imageViewController.modalTransitionStyle = .crossDissolve
46 | self.navigationController.present(imageViewController, animated: true)
47 | }
48 | .store(in: &cancellables)
49 |
50 | authImagesViewModel.authImageLoad
51 | .receive(on: RunLoop.main)
52 | .sink { imageData in
53 | let imageViewController = authImagesViewController.presentedViewController as? ImagePanViewController
54 | imageViewController?.updateImage(data: imageData)
55 | }
56 | .store(in: &cancellables)
57 |
58 | navigationController.pushViewController(authImagesViewController, animated: true)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Application/Coordinator/ChallengeCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeCoordinator.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/02.
6 | //
7 |
8 | import Combine
9 | import UIKit
10 |
11 | final class ChallengeCoordinator: RoutinusCoordinator {
12 | var childCoordinator: [RoutinusCoordinator] = []
13 | var navigationController: UINavigationController
14 | var cancellables = Set()
15 |
16 | init(navigationController: UINavigationController) {
17 | self.navigationController = navigationController
18 | }
19 |
20 | func start() {
21 | let repository = RoutinusRepository()
22 | let challengeFetchUsecase = ChallengeFetchUsecase(repository: repository)
23 | let challengeViewModel = ChallengeViewModel(challengeFetchUsecase: challengeFetchUsecase)
24 | let challengeViewController = ChallengeViewController(with: challengeViewModel)
25 |
26 | challengeViewModel.searchButtonTap
27 | .sink { [weak self] _ in
28 | guard let self = self else { return }
29 | let searchCoordinator = SearchCoordinator(navigationController: self.navigationController)
30 | searchCoordinator.start()
31 | self.childCoordinator.append(searchCoordinator)
32 | }
33 | .store(in: &cancellables)
34 |
35 | challengeViewModel.seeAllButtonTap
36 | .sink { [weak self] _ in
37 | guard let self = self else { return }
38 | let searchCoordinator = SearchCoordinator(navigationController: self.navigationController)
39 | searchCoordinator.start()
40 | self.childCoordinator.append(searchCoordinator)
41 | }
42 | .store(in: &cancellables)
43 |
44 | challengeViewModel.recommendChallengeTap
45 | .sink { [weak self] challengeID in
46 | guard let self = self else { return }
47 | let detailCoordinator = DetailCoordinator(
48 | navigationController: self.navigationController,
49 | challengeID: challengeID
50 | )
51 | detailCoordinator.start()
52 | self.childCoordinator.append(detailCoordinator)
53 | }
54 | .store(in: &cancellables)
55 |
56 | challengeViewModel.categoryButtonTap
57 | .sink { [weak self] category in
58 | guard let self = self else { return }
59 | let searchCoordinator = SearchCoordinator(
60 | navigationController: self.navigationController,
61 | category: category
62 | )
63 | searchCoordinator.start()
64 | self.childCoordinator.append(searchCoordinator)
65 | }
66 | .store(in: &cancellables)
67 |
68 | self.navigationController.pushViewController(challengeViewController, animated: false)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Application/Coordinator/FormCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormCoordinator.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/10.
6 | //
7 |
8 | import Combine
9 | import UIKit
10 |
11 | final class FormCoordinator: RoutinusCoordinator {
12 | var challengeID: String?
13 | var childCoordinator: [RoutinusCoordinator] = []
14 | var navigationController: UINavigationController
15 | var cancellables = Set()
16 |
17 | init(navigationController: UINavigationController) {
18 | self.navigationController = navigationController
19 | }
20 |
21 | init(navigationController: UINavigationController, challengeID: String? = nil) {
22 | self.navigationController = navigationController
23 | self.challengeID = challengeID
24 | }
25 |
26 | func start() {
27 | let repository = RoutinusRepository()
28 | let challengeCreateUsecase = ChallengeCreateUsecase(repository: repository)
29 | let challengeUpdateUsecase = ChallengeUpdateUsecase(repository: repository)
30 | let challengeFetchUsecase = ChallengeFetchUsecase(repository: repository)
31 | let imageFetchUsecase = ImageFetchUsecase(repository: repository)
32 | let imageSaveUsecase = ImageSaveUsecase(repository: repository)
33 | let imageUpdateUsecase = ImageUpdateUsecase(repository: repository)
34 | let formViewModel = FormViewModel(challengeID: challengeID,
35 | challengeCreateUsecase: challengeCreateUsecase,
36 | challengeUpdateUsecase: challengeUpdateUsecase,
37 | challengeFetchUsecase: challengeFetchUsecase,
38 | imageFetchUsecase: imageFetchUsecase,
39 | imageSaveUsecase: imageSaveUsecase,
40 | imageUpdateUsecase: imageUpdateUsecase)
41 | let formViewController = FormViewController(with: formViewModel)
42 |
43 | formViewController.hidesBottomBarWhenPushed = true
44 | navigationController.pushViewController(formViewController, animated: true)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Application/Coordinator/ManageCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ManageCoordinator.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/02.
6 | //
7 |
8 | import Combine
9 | import UIKit
10 |
11 | final class ManageCoordinator: RoutinusCoordinator {
12 | var childCoordinator: [RoutinusCoordinator] = []
13 | var navigationController: UINavigationController
14 | var cancellables = Set()
15 |
16 | init(navigationController: UINavigationController) {
17 | self.navigationController = navigationController
18 | }
19 |
20 | func start() {
21 | let repository = RoutinusRepository()
22 | let imageFetchUsecase = ImageFetchUsecase(repository: repository)
23 | let challengeFetchUsecase = ChallengeFetchUsecase(repository: repository)
24 | let manageViewModel = ManageViewModel(imageFetchUsecase: imageFetchUsecase,
25 | challengeFetchUsecase: challengeFetchUsecase)
26 | let manageViewController = ManageViewController(with: manageViewModel)
27 |
28 | manageViewModel.challengeTap
29 | .sink { [weak self] challengeID in
30 | guard let self = self else { return }
31 | let detailCoordinator = DetailCoordinator(
32 | navigationController: self.navigationController,
33 | challengeID: challengeID
34 | )
35 | detailCoordinator.start()
36 | self.childCoordinator.append(detailCoordinator)
37 | }
38 | .store(in: &cancellables)
39 |
40 | manageViewModel.challengeAddButtonTap
41 | .sink { [weak self] _ in
42 | guard let self = self else { return }
43 | self.navigationController.navigationBar.prefersLargeTitles = false
44 | let formCoordinator = FormCoordinator(navigationController: self.navigationController)
45 | formCoordinator.start()
46 | self.childCoordinator.append(formCoordinator)
47 | }
48 | .store(in: &cancellables)
49 |
50 | navigationController.pushViewController(manageViewController, animated: false)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Application/Coordinator/MyPageCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyPageCoordinator.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/02.
6 | //
7 |
8 | import Combine
9 | import UIKit
10 |
11 | final class MyPageCoordinator: RoutinusCoordinator {
12 | var childCoordinator: [RoutinusCoordinator] = []
13 | var navigationController: UINavigationController
14 | var cancellables = Set()
15 |
16 | init(navigationController: UINavigationController) {
17 | self.navigationController = navigationController
18 | }
19 |
20 | func start() {
21 | let repository = RoutinusRepository()
22 | let userFetchUsecase = UserFetchUsecase(repository: repository)
23 | let userUpdateUsecase = UserUpdateUsecase(repository: repository)
24 | let myPageViewModel = MyPageViewModel(userFetchUsecase: userFetchUsecase,
25 | userUpdateUsecase: userUpdateUsecase)
26 | let myPageViewController = MyPageViewController(with: myPageViewModel)
27 |
28 | navigationController.pushViewController(myPageViewController, animated: false)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Application/Coordinator/RoutinusCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoutinusCoordinator.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/02.
6 | //
7 |
8 | import UIKit
9 |
10 | protocol RoutinusCoordinator: AnyObject {
11 | var childCoordinator: [RoutinusCoordinator] { get set }
12 | var navigationController: UINavigationController { get }
13 |
14 | func start()
15 | }
16 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Application/Coordinator/SearchCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchCoordinator.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/09.
6 | //
7 |
8 | import Combine
9 | import UIKit
10 |
11 | final class SearchCoordinator: RoutinusCoordinator {
12 | var category: Challenge.Category?
13 | var childCoordinator: [RoutinusCoordinator] = []
14 | var navigationController: UINavigationController
15 | var cancellables = Set()
16 |
17 | init(navigationController: UINavigationController, category: Challenge.Category? = nil) {
18 | self.navigationController = navigationController
19 | self.category = category
20 | }
21 |
22 | func start() {
23 | let repository = RoutinusRepository()
24 | let imageFetchUsecase = ImageFetchUsecase(repository: repository)
25 | let challengeFetchUsecase = ChallengeFetchUsecase(repository: repository)
26 | let searchViewModel = SearchViewModel(category: category,
27 | imageFetchUsecase: imageFetchUsecase,
28 | challengeFetchUsecase: challengeFetchUsecase)
29 | let searchViewController = SearchViewController(with: searchViewModel)
30 |
31 | searchViewModel.challengeTap
32 | .sink { [weak self] challengeID in
33 | guard let self = self else { return }
34 | let detailCoordinator = DetailCoordinator(
35 | navigationController: self.navigationController,
36 | challengeID: challengeID
37 | )
38 | detailCoordinator.start()
39 | self.childCoordinator.append(detailCoordinator)
40 | }
41 | .store(in: &cancellables)
42 |
43 | navigationController.pushViewController(searchViewController, animated: true)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Application/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/01.
6 | //
7 |
8 | import UIKit
9 |
10 | final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 | var window: UIWindow?
12 | var appCoordinator: AppCoordinator?
13 |
14 | func scene(_ scene: UIScene,
15 | willConnectTo session: UISceneSession,
16 | options connectionOptions: UIScene.ConnectionOptions) {
17 | guard let windowScene = (scene as? UIWindowScene) else { return }
18 | window = UIWindow(windowScene: windowScene)
19 | let naviationController = UINavigationController()
20 | window?.rootViewController = naviationController
21 | window?.makeKeyAndVisible()
22 |
23 | let appCoordinator = AppCoordinator(navigationController: naviationController)
24 | appCoordinator.start()
25 | self.appCoordinator = appCoordinator
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Data/Factory/UsernameFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UsernameFactory.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/08.
6 | //
7 |
8 | import Foundation
9 |
10 | enum UsernameFactory {
11 | static func randomName() -> String {
12 | var result = ""
13 | let adjective = ["귀여운", "멋진", "정직한", "재미있는", "느긋한", "친절한", "다정한"]
14 | let noun = ["코끼리", "감자", "맥북", "강아지", "고양이", "토끼", "송아지", "사자", "호랑이", "병아리"]
15 |
16 | result += adjective.randomElement() ?? ""
17 | result += noun.randomElement() ?? ""
18 |
19 | return result
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Data/Repository/AchievementRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AchievementRepository.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/17.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol AchievementRepository {
11 | func fetchAchievements(by id: String,
12 | in yearMonth: String,
13 | completion: (([Achievement]) -> Void)?)
14 | func updateAchievementCount(userID: String,
15 | yearMonth: String,
16 | day: String,
17 | completion: (() -> Void)?)
18 | func updateTotalCount(userID: String,
19 | yearMonth: String,
20 | day: String,
21 | completion: (() -> Void)?)
22 | }
23 |
24 | extension RoutinusRepository: AchievementRepository {
25 | func fetchAchievements(by id: String,
26 | in yearMonth: String,
27 | completion: (([Achievement]) -> Void)?) {
28 | FirebaseService.achievements(of: id, in: yearMonth) { list in
29 | completion?(list.map { Achievement(achievementDTO: $0) })
30 | }
31 | }
32 |
33 | func updateAchievementCount(userID: String,
34 | yearMonth: String,
35 | day: String,
36 | completion: (() -> Void)?) {
37 | FirebaseService.updateAchievementCount(userID: userID,
38 | yearMonth: yearMonth,
39 | day: day) {
40 | completion?()
41 | }
42 | }
43 |
44 | func updateTotalCount(userID: String,
45 | yearMonth: String,
46 | day: String,
47 | completion: (() -> Void)?) {
48 | FirebaseService.updateTotalCount(userID: userID, yearMonth: yearMonth, day: day) {
49 | completion?()
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Data/Repository/ParticipationRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParticipationRepository.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/17.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ParticipationRepository {
11 | func fetchParticipation(userID: String,
12 | challengeID: String,
13 | completion: @escaping (Participation?) -> Void)
14 | func save(challengeID: String, joinDate: String, completion: (() -> Void)?)
15 | func updateAuthCount(challengeID: String, completion: (() -> Void)?)
16 | }
17 |
18 | extension RoutinusRepository: ParticipationRepository {
19 | func fetchParticipation(userID: String,
20 | challengeID: String,
21 | completion: @escaping (Participation?) -> Void) {
22 | FirebaseService.participation(userID: userID, challengeID: challengeID) { dto in
23 | guard let dto = dto, dto.document != nil else {
24 | completion(nil)
25 | return
26 | }
27 | completion(Participation(participationDTO: dto))
28 | }
29 | }
30 |
31 | func save(challengeID: String, joinDate: String, completion: (() -> Void)?) {
32 | guard let userID = RoutinusRepository.userID() else { return }
33 | let dto = ParticipationDTO(authCount: 0,
34 | challengeID: challengeID,
35 | joinDate: joinDate,
36 | userID: userID)
37 | FirebaseService.insertParticipation(dto: dto) {
38 | completion?()
39 | }
40 | }
41 |
42 | func updateAuthCount(challengeID: String, completion: (() -> Void)?) {
43 | guard let userID = RoutinusRepository.userID() else { return }
44 | FirebaseService.updateParticipationAuthCount(challengeID: challengeID, userID: userID) {
45 | completion?()
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Data/Repository/RoutinusRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoutinusRepository.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/08.
6 | //
7 |
8 | import Foundation
9 |
10 | final class RoutinusRepository {
11 | static let userIDKey = "id"
12 | static let themeStyleKey = "themeStyle"
13 |
14 | static func userID() -> String? {
15 | return UserDefaults.standard.string(forKey: RoutinusRepository.userIDKey)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Data/Repository/TodayRoutineRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TodayRoutineRepository.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/17.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol TodayRoutineRepository {
11 | func fetchTodayRoutine(by id: String, completion: (([TodayRoutine]) -> Void)?)
12 | }
13 |
14 | extension RoutinusRepository: TodayRoutineRepository {
15 | func fetchTodayRoutine(by id: String, completion: (([TodayRoutine]) -> Void)?) {
16 | FirebaseService.routines(of: id) { list in
17 | completion?(list.map { TodayRoutine(todayRoutineDTO: $0) })
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Data/Repository/UserRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserRepository.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/17.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol UserRepository {
11 | func isEmptyUserID() -> Bool
12 | func save(id: String, name: String, completion: (() -> Void)?)
13 | func fetchUser(by id: String, completion: ((User) -> Void)?)
14 | func updateContinuityDay(by id: String, completion: ((UserDTO) -> Void)?)
15 | func updateContinuityDayByAuth(by id: String, completion: (() -> Void)?)
16 | func fetchThemeStyle() -> Int
17 | func updateUsername(of id: String, name: String, completion: (() -> Void)?)
18 | func updateThemeStyle(_ style: Int)
19 | }
20 |
21 | extension RoutinusRepository: UserRepository {
22 | func isEmptyUserID() -> Bool {
23 | return UserDefaults.standard.string(forKey: RoutinusRepository.userIDKey) == nil
24 | }
25 |
26 | func save(id: String, name: String, completion: (() -> Void)?) {
27 | UserDefaults.standard.set(id, forKey: RoutinusRepository.userIDKey)
28 | FirebaseService.insertUser(id: id, name: name) {
29 | completion?()
30 | }
31 | }
32 |
33 | func fetchUser(by id: String, completion: ((User) -> Void)?) {
34 | FirebaseService.user(of: id) { dto in
35 | completion?(User(userDTO: dto))
36 | }
37 | }
38 |
39 | func updateContinuityDay(by id: String, completion: ((UserDTO) -> Void)?) {
40 | FirebaseService.updateContinuityDay(of: id) { dto in
41 | completion?(dto)
42 | }
43 | }
44 |
45 | func updateContinuityDayByAuth(by id: String, completion: (() -> Void)?) {
46 | FirebaseService.updateContinuityDayByAuth(of: id) {
47 | completion?()
48 | }
49 | }
50 |
51 | func fetchThemeStyle() -> Int {
52 | return UserDefaults.standard.integer(forKey: RoutinusRepository.themeStyleKey)
53 | }
54 |
55 | func updateUsername(of id: String, name: String, completion: (() -> Void)?) {
56 | FirebaseService.updateUsername(of: id, name: name) {
57 | completion?()
58 | }
59 | }
60 |
61 | func updateThemeStyle(_ style: Int) {
62 | UserDefaults.standard.set(style, forKey: RoutinusRepository.themeStyleKey)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Entity/Achievement.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Achievement.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/03.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Achievement {
11 | let yearMonth: String
12 | let day: String
13 | let achievementCount: Int
14 | let totalCount: Int
15 |
16 | init(yearMonth: String, day: String, achievementCount: Int, totalCount: Int) {
17 | self.yearMonth = yearMonth
18 | self.day = day
19 | self.achievementCount = achievementCount
20 | self.totalCount = totalCount
21 | }
22 |
23 | init(achievementDTO: AchievementDTO) {
24 | let document = achievementDTO.document?.fields
25 |
26 | self.yearMonth = document?.yearMonth.stringValue ?? ""
27 | self.day = document?.day.stringValue ?? ""
28 | self.achievementCount = Int(document?.achievementCount.integerValue ?? "") ?? 0
29 | self.totalCount = Int(document?.totalCount.integerValue ?? "") ?? 0
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Entity/ArchievementInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArchievementInfo.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/03.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ArchievementInfo {
11 | let day: Int
12 | let achievementCount: Int
13 | let totalCount: Int
14 | }
15 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Entity/Auth.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Auth.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/17.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Auth {
11 | var challengeID: String
12 | var userID: String
13 | var date: Date?
14 | var time: Date?
15 |
16 | init(challengeID: String, userID: String, date: Date?, time: Date?) {
17 | self.challengeID = challengeID
18 | self.userID = userID
19 | self.date = date
20 | self.time = time
21 | }
22 |
23 | init(authDTO: AuthDTO) {
24 | let document = authDTO.document?.fields
25 |
26 | self.challengeID = document?.challengeID.stringValue ?? ""
27 | self.userID = document?.userID.stringValue ?? ""
28 | self.date = Date(dateString: document?.date.stringValue ?? "")
29 | self.time = Date(timeString: document?.time.stringValue ?? "")
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Entity/Calendar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Calendar.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/16.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Day {
11 | let date: Date
12 | let number: String
13 | let isSelected: Bool
14 | var achievementRate: Double
15 | let isWithinDisplayedMonth: Bool
16 | }
17 |
18 | struct MonthMetadata {
19 | let numberOfDays: Int
20 | let firstDay: Date
21 | let firstDayWeekday: Int
22 | }
23 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Entity/Participation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Participation.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/18.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Participation {
11 | let authCount: Int
12 | let challengeID: String
13 | let joinDate: String
14 | let userID: String
15 |
16 | init(participationDTO: ParticipationDTO) {
17 | let document = participationDTO.document?.fields
18 | self.authCount = Int(document?.authCount.integerValue ?? "") ?? 0
19 | self.challengeID = document?.challengeID.stringValue ?? ""
20 | self.joinDate = document?.joinDate.stringValue ?? ""
21 | self.userID = document?.userID.stringValue ?? ""
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Entity/PopularKeyword.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PopularKeyword.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/11.
6 | //
7 |
8 | import Foundation
9 |
10 | enum PopularKeyword: String, CaseIterable {
11 | case exercise = "운동"
12 | case reading = "독서"
13 | case commit = "커밋"
14 | case study = "공부"
15 | case english = "영어"
16 | }
17 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Entity/TodayRoutine.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TodayRoutine.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/04.
6 | //
7 |
8 | import Foundation
9 |
10 | struct TodayRoutine: Hashable {
11 | let challengeID: String
12 | let category: Challenge.Category
13 | let title: String
14 | let authCount: Int
15 | let totalCount: Int
16 |
17 | init(challengeID: String,
18 | category: Challenge.Category,
19 | title: String,
20 | authCount: Int,
21 | totalCount: Int) {
22 | self.challengeID = challengeID
23 | self.category = category
24 | self.title = title
25 | self.authCount = authCount
26 | self.totalCount = totalCount
27 | }
28 |
29 | init(todayRoutineDTO: TodayRoutineDTO) {
30 | self.challengeID = todayRoutineDTO.challengeID
31 | self.category = Challenge.Category.category(by: todayRoutineDTO.categoryID)
32 | self.title = todayRoutineDTO.title
33 | self.authCount = todayRoutineDTO.authCount
34 | self.totalCount = Date.days(from: todayRoutineDTO.endDate, to: todayRoutineDTO.joinDate)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Entity/User.swift:
--------------------------------------------------------------------------------
1 | //
2 | // User.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/03.
6 | //
7 |
8 | import Foundation
9 |
10 | struct User {
11 | var id: String
12 | var name: String
13 | var continuityDay: Int
14 | var userImageCategoryID: String
15 | var grade: Int
16 | var lastAuthDay: String
17 |
18 | init() {
19 | self.id = ""
20 | self.name = ""
21 | self.continuityDay = 0
22 | self.userImageCategoryID = ""
23 | self.grade = 0
24 | self.lastAuthDay = ""
25 | }
26 |
27 | init(id: String,
28 | name: String,
29 | continuityDay: Int,
30 | userImageCategoryID: String,
31 | grade: Int,
32 | lastAuthDay: String) {
33 | self.id = id
34 | self.name = name
35 | self.continuityDay = continuityDay
36 | self.userImageCategoryID = userImageCategoryID
37 | self.grade = grade
38 | self.lastAuthDay = lastAuthDay
39 | }
40 |
41 | init(userDTO: UserDTO) {
42 | let document = userDTO.document?.fields
43 |
44 | self.id = document?.id.stringValue ?? ""
45 | self.name = document?.name.stringValue ?? ""
46 | self.continuityDay = Int(document?.continuityDay.integerValue ?? "") ?? 0
47 | self.userImageCategoryID = document?.userImageCategoryID.stringValue ?? ""
48 | self.grade = Int(document?.grade.integerValue ?? "") ?? 0
49 | self.lastAuthDay = document?.lastAuthDay.stringValue ?? ""
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/AchievementFetchUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AchievementFetchUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/17.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol AchievementFetchableUsecase {
11 | func fetchAchievements(yearMonth: String, completion: @escaping ([Achievement]) -> Void)
12 | }
13 |
14 | struct AchievementFetchUsecase: AchievementFetchableUsecase {
15 | var repository: AchievementRepository
16 |
17 | init(repository: AchievementRepository) {
18 | self.repository = repository
19 | }
20 |
21 | func fetchAchievements(yearMonth: String, completion: @escaping ([Achievement]) -> Void) {
22 | guard let id = RoutinusRepository.userID() else { return }
23 |
24 | repository.fetchAchievements(by: id, in: yearMonth) { achievements in
25 | completion(achievements)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/AchievementUpdateUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AchievementUpdateUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/18.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol AchievementUpdatableUsecase {
11 | func updateAchievementCount()
12 | func updateTotalCount()
13 | }
14 |
15 | struct AchievementUpdateUsecase: AchievementUpdatableUsecase {
16 | static let didUpdateAchievement = Notification.Name("didUpdateAchievement")
17 |
18 | var repository: AchievementRepository
19 |
20 | init(repository: AchievementRepository) {
21 | self.repository = repository
22 | }
23 |
24 | func updateAchievementCount() {
25 | guard let userID = RoutinusRepository.userID() else { return }
26 | let yearMonth = Date().toYearMonthString()
27 | let day = Date().toDayString()
28 | repository.updateAchievementCount(userID: userID, yearMonth: yearMonth, day: day) {
29 | NotificationCenter.default.post(name: AchievementUpdateUsecase.didUpdateAchievement,
30 | object: nil)
31 | }
32 | }
33 |
34 | func updateTotalCount() {
35 | guard let userID = RoutinusRepository.userID() else { return }
36 | let yearMonth = Date().toYearMonthString()
37 | let day = Date().toDayString()
38 | repository.updateTotalCount(userID: userID, yearMonth: yearMonth, day: day) {
39 | NotificationCenter.default.post(name: AchievementUpdateUsecase.didUpdateAchievement,
40 | object: nil)
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/AuthCreateUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthCreateUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/17.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol AuthCreatableUsecase {
11 | func createAuth(challengeID: String,
12 | userAuthImageURL: String,
13 | userAuthThumbnailImageURL: String)
14 | }
15 |
16 | struct AuthCreateUsecase: AuthCreatableUsecase {
17 | static let didCreateAuth = Notification.Name("didCreateAuth")
18 |
19 | var repository: AuthRepository
20 |
21 | init(repository: AuthRepository) {
22 | self.repository = repository
23 | }
24 |
25 | func createAuth(challengeID: String,
26 | userAuthImageURL: String,
27 | userAuthThumbnailImageURL: String) {
28 | guard let userID = RoutinusRepository.userID() else { return }
29 | let auth = Auth(challengeID: challengeID, userID: userID, date: Date(), time: Date())
30 |
31 | repository.insert(auth: auth,
32 | userAuthImageURL: userAuthImageURL,
33 | userAuthThumbnailImageURL: userAuthThumbnailImageURL) {
34 | NotificationCenter.default.post(name: AuthCreateUsecase.didCreateAuth,
35 | object: nil)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/AuthFetchUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthFetchUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/18.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol AuthFetchableUsecase {
11 | func fetchAuth(challengeID: String, completion: @escaping (Auth?) -> Void)
12 | func fetchAuths(challengeID: String, completion: @escaping ([Auth]) -> Void)
13 | func fetchMyAuths(challengeID: String, completion: @escaping ([Auth]) -> Void)
14 | func fetchAuthedChallenges(date: Date, completion: @escaping ([Challenge]) -> Void)
15 | }
16 |
17 | struct AuthFetchUsecase: AuthFetchableUsecase {
18 | var repository: AuthRepository
19 |
20 | init(repository: AuthRepository) {
21 | self.repository = repository
22 | }
23 |
24 | func fetchAuth(challengeID: String, completion: @escaping (Auth?) -> Void) {
25 | guard let userID = RoutinusRepository.userID() else { return }
26 | repository.fetchAuth(todayDate: Date().toDateString(),
27 | userID: userID,
28 | challengeID: challengeID) { auth in
29 | completion(auth)
30 | }
31 | }
32 |
33 | func fetchAuths(challengeID: String, completion: @escaping ([Auth]) -> Void) {
34 | repository.fetchAuths(challengeID: challengeID) { auths in
35 | completion(auths)
36 | }
37 | }
38 |
39 | func fetchMyAuths(challengeID: String, completion: @escaping ([Auth]) -> Void) {
40 | guard let userID = RoutinusRepository.userID() else { return }
41 | repository.fetchMyAuths(userID: userID, challengeID: challengeID) { auths in
42 | completion(auths)
43 | }
44 | }
45 |
46 | func fetchAuthedChallenges(date: Date, completion: @escaping ([Challenge]) -> Void) {
47 | guard let userID = RoutinusRepository.userID() else { return }
48 | repository.fetchMyAuthedChallenges(date: date.toDateString(), userID: userID) { challenges in
49 | completion(challenges)
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/ChallengeUpdateUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeUpdateUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/13.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ChallengeUpdatableUsecase {
11 | func endDate(startDate: Date, week: Int) -> Date?
12 | func update(challenge: Challenge)
13 | func updateParticipantCount(challengeID: String)
14 | }
15 |
16 | struct ChallengeUpdateUsecase: ChallengeUpdatableUsecase {
17 | static let didUpdateChallenge = Notification.Name("didUpdateChallenge")
18 |
19 | var repository: ChallengeRepository
20 |
21 | init(repository: ChallengeRepository) {
22 | self.repository = repository
23 | }
24 |
25 | func endDate(startDate: Date, week: Int) -> Date? {
26 | let calendar = Calendar.current
27 | let day = DateComponents(day: week * 7)
28 | guard let endDate = calendar.date(byAdding: day, to: startDate) else { return nil }
29 | return endDate
30 | }
31 |
32 | func update(challenge: Challenge) {
33 | repository.update(challenge: challenge) {
34 | NotificationCenter.default.post(name: ChallengeUpdateUsecase.didUpdateChallenge,
35 | object: nil)
36 | }
37 | }
38 |
39 | func updateParticipantCount(challengeID: String) {
40 | repository.updateParticipantCount(challengeID: challengeID) {
41 | NotificationCenter.default.post(name: ChallengeUpdateUsecase.didUpdateChallenge,
42 | object: nil)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/ImageFetchUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageFetchUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/17.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ImageFetchableUsecase {
11 | func fetchImageData(from directory: String,
12 | filename: String,
13 | completion: ((Data?) -> Void)?)
14 | }
15 |
16 | struct ImageFetchUsecase: ImageFetchableUsecase {
17 | var repository: ImageRepository
18 |
19 | init(repository: ImageRepository) {
20 | self.repository = repository
21 | }
22 |
23 | func fetchImageData(from directory: String,
24 | filename: String,
25 | completion: ((Data?) -> Void)? = nil) {
26 | repository.fetchImageData(from: directory, filename: filename) { data in
27 | completion?(data)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/ImageSaveUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageSaveUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/17.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ImageSavableUsecase {
11 | func saveImage(to directory: String, filename: String, data: Data?) -> String?
12 | }
13 |
14 | struct ImageSaveUsecase: ImageSavableUsecase {
15 | var repository: ImageRepository
16 |
17 | init(repository: ImageRepository) {
18 | self.repository = repository
19 | }
20 |
21 | func saveImage(to directory: String, filename: String, data: Data?) -> String? {
22 | return repository.saveImage(to: directory, filename: filename, data: data)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/ImageUpdateUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageUpdateUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/23.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ImageUpdatableUsecase {
11 | func updateImage(challenge: Challenge, isChangedImage: Bool, isChangedAuthImage: Bool)
12 | }
13 |
14 | struct ImageUpdateUsecase: ImageUpdatableUsecase {
15 | static let didUpdateChallengeImage = Notification.Name("didUpdateChallengeImage")
16 |
17 | var repository: ImageRepository
18 |
19 | init(repository: ImageRepository) {
20 | self.repository = repository
21 | }
22 |
23 | func updateImage(challenge: Challenge, isChangedImage: Bool, isChangedAuthImage: Bool) {
24 | if isChangedImage {
25 | repository.updateImage(challengeID: challenge.challengeID,
26 | imageURL: challenge.imageURL,
27 | thumbnailImageURL: challenge.thumbnailImageURL) {
28 | NotificationCenter.default.post(name: ImageUpdateUsecase.didUpdateChallengeImage,
29 | object: nil)
30 | }
31 | }
32 | if isChangedAuthImage {
33 | repository.updateImage(
34 | challengeID: challenge.challengeID,
35 | authExampleImageURL: challenge.authExampleImageURL,
36 | authExampleThumbnailImageURL: challenge.authExampleThumbnailImageURL
37 | ) {
38 | NotificationCenter.default.post(name: ImageUpdateUsecase.didUpdateChallengeImage,
39 | object: nil)
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/ParticipationCreateUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParticipationCreateUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/18.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ParticipationCreatableUsecase {
11 | func createParticipation(challengeID: String)
12 | }
13 |
14 | struct ParticipationCreateUsecase: ParticipationCreatableUsecase {
15 | static let didCreateParticipation = Notification.Name("didCreateParticipation")
16 |
17 | var repository: ParticipationRepository
18 |
19 | init(repository: ParticipationRepository) {
20 | self.repository = repository
21 | }
22 |
23 | func createParticipation(challengeID: String) {
24 | let date = Date().toDateString()
25 | repository.save(challengeID: challengeID, joinDate: date) {
26 | NotificationCenter.default.post(name: ParticipationCreateUsecase.didCreateParticipation,
27 | object: nil)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/ParticipationFetchUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParticipationFetchUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/18.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ParticipationFetchableUsecase {
11 | func fetchParticipation(challengeID: String, completion: @escaping (Participation?) -> Void)
12 | }
13 |
14 | struct ParticipationFetchUsecase: ParticipationFetchableUsecase {
15 | var repository: ParticipationRepository
16 |
17 | init(repository: ParticipationRepository) {
18 | self.repository = repository
19 | }
20 |
21 | func fetchParticipation(challengeID: String, completion: @escaping (Participation?) -> Void) {
22 | guard let userID = RoutinusRepository.userID() else {
23 | completion(nil)
24 | return
25 | }
26 | repository.fetchParticipation(userID: userID, challengeID: challengeID) { participation in
27 | completion(participation)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/ParticipationUpdateUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParticipationUpdateUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/18.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ParticipationUpdatableUsecase {
11 | func updateParticipationAuthCount(challengeID: String)
12 | }
13 |
14 | struct ParticipationUpdateUsecase: ParticipationUpdatableUsecase {
15 | static let didUpdateParticipation = Notification.Name("didUpdateParticipation")
16 |
17 | var repository: ParticipationRepository
18 |
19 | init(repository: ParticipationRepository) {
20 | self.repository = repository
21 | }
22 |
23 | func updateParticipationAuthCount(challengeID: String) {
24 | repository.updateAuthCount(challengeID: challengeID) {
25 | NotificationCenter.default.post(name: ParticipationUpdateUsecase.didUpdateParticipation,
26 | object: nil)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/TodayRoutineFetchUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TodayRoutineFetchUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/17.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol TodayRoutineFetchableUsecase {
11 | func fetchTodayRoutines(completion: @escaping ([TodayRoutine]) -> Void)
12 | }
13 |
14 | struct TodayRoutineFetchUsecase: TodayRoutineFetchableUsecase {
15 | var repository: TodayRoutineRepository
16 |
17 | init(repository: TodayRoutineRepository) {
18 | self.repository = repository
19 | }
20 |
21 | func fetchTodayRoutines(completion: @escaping ([TodayRoutine]) -> Void) {
22 | guard let id = RoutinusRepository.userID() else { return }
23 |
24 | repository.fetchTodayRoutine(by: id) { todayRoutines in
25 | completion(todayRoutines)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/UserCreateUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeCreateUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/08.
6 | //
7 |
8 | import CryptoKit
9 | import Foundation
10 |
11 | protocol UserCreatableUsecase {
12 | func createUser()
13 | }
14 |
15 | struct UserCreateUsecase: UserCreatableUsecase {
16 | static let didCreateUser = Notification.Name("didCreateUser")
17 |
18 | var repository: UserRepository
19 |
20 | init(repository: UserRepository) {
21 | self.repository = repository
22 | }
23 |
24 | private func createID() -> String {
25 | let uuid = UUID().uuidString
26 | guard let data = uuid.data(using: .utf8) else { return "" }
27 | let sha256 = SHA256.hash(data: data)
28 | return sha256.compactMap { String(format: "%02x", $0) }.joined()
29 | }
30 |
31 | func createUser() {
32 | guard repository.isEmptyUserID() else { return }
33 | let id = createID()
34 | let name = UsernameFactory.randomName()
35 |
36 | repository.save(id: id, name: name) {
37 | NotificationCenter.default.post(name: UserCreateUsecase.didCreateUser,
38 | object: nil)
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/UserFetchUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserFetchUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/17.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol UserFetchableUsecase {
11 | func fetchUser(id: String, completion: @escaping (User) -> Void)
12 | func fetchUserID() -> String?
13 | func fetchThemeStyle() -> Int
14 | }
15 |
16 | struct UserFetchUsecase: UserFetchableUsecase {
17 | var repository: UserRepository
18 |
19 | init(repository: UserRepository) {
20 | self.repository = repository
21 | }
22 |
23 | func fetchUser(id: String, completion: @escaping (User) -> Void) {
24 | repository.fetchUser(by: id) { user in
25 | completion(user)
26 | }
27 | }
28 |
29 | func fetchUserID() -> String? {
30 | return RoutinusRepository.userID()
31 | }
32 |
33 | func fetchThemeStyle() -> Int {
34 | return repository.fetchThemeStyle()
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Domain/Usecase/UserUpdateUsecase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserUpdateUsecase.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/21.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol UserUpdatableUsecase {
11 | func updateContinuityDay(completion: ((User) -> Void)?)
12 | func updateContinuityDayByAuth()
13 | func updateUsername(of id: String, name: String)
14 | func updateThemeStyle(_ style: Int)
15 | }
16 |
17 | struct UserUpdateUsecase: UserUpdatableUsecase {
18 | static let didUpdateUser = Notification.Name("didUpdateUser")
19 |
20 | var repository: UserRepository
21 |
22 | init(repository: UserRepository) {
23 | self.repository = repository
24 | }
25 |
26 | func updateContinuityDay(completion: ((User) -> Void)?) {
27 | guard let userID = RoutinusRepository.userID() else { return }
28 | repository.updateContinuityDay(by: userID) { userDTO in
29 | let user = User(userDTO: userDTO)
30 | completion?(user)
31 | }
32 | }
33 |
34 | func updateContinuityDayByAuth() {
35 | guard let userID = RoutinusRepository.userID() else { return }
36 | repository.updateContinuityDayByAuth(by: userID) {
37 | NotificationCenter.default.post(name: UserUpdateUsecase.didUpdateUser,
38 | object: nil)
39 | }
40 | }
41 |
42 | func updateUsername(of id: String, name: String) {
43 | repository.updateUsername(of: id, name: name) {
44 | NotificationCenter.default.post(name: UserUpdateUsecase.didUpdateUser,
45 | object: nil)
46 | }
47 | }
48 |
49 | func updateThemeStyle(_ style: Int) {
50 | repository.updateThemeStyle(style)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Extensions/Date+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date+Extensions.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/04.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Date {
11 | init(dateString: String) {
12 | let formatter = DateFormatter()
13 | formatter.dateFormat = "yyyyMMdd"
14 | guard let date = formatter.date(from: dateString) else {
15 | self.init(timeInterval: 0, since: Date())
16 | return
17 | }
18 | self.init(timeInterval: 0, since: date)
19 | }
20 |
21 | init(timeString: String) {
22 | let formatter = DateFormatter()
23 | formatter.dateFormat = "HHmm"
24 | guard let date = formatter.date(from: timeString) else {
25 | self.init(timeInterval: 0, since: Date())
26 | return
27 | }
28 | self.init(timeInterval: 0, since: date)
29 | }
30 |
31 | public var year: Int {
32 | return Calendar.current.component(.year, from: self)
33 | }
34 |
35 | public var month: Int {
36 | return Calendar.current.component(.month, from: self)
37 | }
38 |
39 | public var day: Int {
40 | return Calendar.current.component(.day, from: self)
41 | }
42 |
43 | static func days(from: String, to: String) -> Int {
44 | let formatter = DateFormatter()
45 | formatter.dateFormat = "yyyyMMdd"
46 |
47 | guard let fromDate = formatter.date(from: from),
48 | let toDate = formatter.date(from: to) else { return -1 }
49 |
50 | let dateComponents = Calendar.current.dateComponents([.day], from: fromDate, to: toDate)
51 | guard let days = dateComponents.day else { return -1 }
52 |
53 | return abs(days) + 1
54 | }
55 |
56 | static func currentYearMonth() -> String {
57 | let dateComponents = Calendar.current.dateComponents([.year, .month], from: Date())
58 |
59 | guard let year = dateComponents.year,
60 | let month = dateComponents.month else { return "" }
61 |
62 | return "\(year)\(month)"
63 | }
64 |
65 | func toDateString() -> String {
66 | let dateFormatter = DateFormatter()
67 | dateFormatter.dateFormat = "yyyyMMdd"
68 | return dateFormatter.string(from: self)
69 | }
70 |
71 | func toYearMonthString() -> String {
72 | let dateFormatter = DateFormatter()
73 | dateFormatter.dateFormat = "yyyyMM"
74 | return dateFormatter.string(from: self)
75 | }
76 |
77 | func toDayString() -> String {
78 | let dateFormatter = DateFormatter()
79 | dateFormatter.dateFormat = "dd"
80 | return dateFormatter.string(from: self)
81 | }
82 |
83 | func toTimeString() -> String {
84 | let dateFormatter = DateFormatter()
85 | dateFormatter.dateFormat = "HHmm"
86 | return dateFormatter.string(from: self)
87 | }
88 |
89 | func toTimeColonString() -> String {
90 | let dateFormatter = DateFormatter()
91 | dateFormatter.dateFormat = "HH:mm"
92 | return dateFormatter.string(from: self)
93 | }
94 |
95 | func toDateWithWeekdayString() -> String {
96 | let dateFormatter = DateFormatter()
97 | dateFormatter.dateFormat = "yyyy.MM.dd(E)"
98 | return dateFormatter.string(from: self)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Extensions/String+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Extensions.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/16.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 | var localized: String {
12 | return NSLocalizedString(self, value: self, comment: "")
13 | }
14 |
15 | func localized(with argument: CVarArg = []) -> String {
16 | return String(format: localized, argument)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Extensions/UICollectionView+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+Extensions.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/24.
6 | //
7 |
8 | import UIKit
9 |
10 | extension UICollectionView {
11 | func notifyEmptyData() {
12 | let informationView: UIView = {
13 | let view = UIView(frame: CGRect(x: center.x,
14 | y: center.y,
15 | width: bounds.width,
16 | height: bounds.height))
17 | return view
18 | }()
19 |
20 | let titleLabel: UILabel = {
21 | let label = UILabel()
22 | label.text = "auth empty".localized
23 | label.textColor = UIColor(named: "SystemForeground")
24 | label.font = UIFont.boldSystemFont(ofSize: 18)
25 | return label
26 | }()
27 |
28 | let messageLabel: UILabel = {
29 | let label = UILabel()
30 | label.text = "lets auth".localized
31 | label.textColor = UIColor(named: "DayColor")
32 | label.font = UIFont.systemFont(ofSize: 15, weight: .medium)
33 | label.numberOfLines = 0
34 | label.textAlignment = .center
35 | label.numberOfLines = 2
36 | return label
37 | }()
38 |
39 | informationView.addSubview(titleLabel)
40 | informationView.addSubview(messageLabel)
41 |
42 | titleLabel.anchor(centerX: informationView.centerXAnchor,
43 | centerY: informationView.centerYAnchor)
44 |
45 | messageLabel.anchor(centerX: informationView.centerXAnchor,
46 | top: titleLabel.bottomAnchor, paddingTop: 20)
47 |
48 | backgroundView = informationView
49 | }
50 |
51 | func restore() {
52 | backgroundView = nil
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Extensions/UIImage+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+Extension.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/10.
6 | //
7 |
8 | import UIKit
9 |
10 | extension UIImage {
11 | enum RoutinusImageType {
12 | case original
13 | case thumbnail
14 |
15 | var length: CGFloat {
16 | switch self {
17 | case .original:
18 | return CGFloat(200.0)
19 | case .thumbnail:
20 | return CGFloat(80.0)
21 | }
22 | }
23 | }
24 |
25 | func resizedImage(_ type: RoutinusImageType) -> UIImage {
26 | let isWidthLongerThanHeight = size.width > size.height
27 | guard (isWidthLongerThanHeight ? size.width : size.height) >= type.length else { return self }
28 |
29 | let longerSide = type.length
30 | let scale = longerSide / (isWidthLongerThanHeight ? size.width : size.height)
31 | let shorterSide = (isWidthLongerThanHeight ? size.height : size.width) * scale
32 | let size = CGSize(width: (isWidthLongerThanHeight ? longerSide : shorterSide),
33 | height: (isWidthLongerThanHeight ? shorterSide : longerSide))
34 | let render = UIGraphicsImageRenderer(size: size)
35 |
36 | return render.image { _ in
37 | draw(in: CGRect(origin: .zero, size: size))
38 | }
39 | }
40 |
41 | func insertText(name: String, date: String, time: String) -> UIImage {
42 | let scale = UIScreen.main.scale
43 | UIGraphicsBeginImageContextWithOptions(size, false, scale)
44 |
45 | let label = UILabel(frame: CGRect(origin: .zero, size: size))
46 | label.backgroundColor = UIColor(named: "SystemBackground")?.withAlphaComponent(0)
47 | label.textAlignment = .center
48 | label.numberOfLines = 2
49 | label.textColor = UIColor(named: "White")?.withAlphaComponent(0.5)
50 | label.font = UIFont.boldSystemFont(ofSize: 12)
51 | label.text = "\(name)\n\(date) \(time)"
52 |
53 | let imageView = UIImageView(image: self)
54 | UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0)
55 | guard let context = UIGraphicsGetCurrentContext() else { return UIImage() }
56 | imageView.layer.render(in: context)
57 | label.layer.render(in: context)
58 | guard let imageWithText = UIGraphicsGetImageFromCurrentImageContext() else { return UIImage() }
59 | UIGraphicsEndImageContext()
60 |
61 | return imageWithText
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Extensions/UIScrollView+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIScrollView+Extensions.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/27.
6 | //
7 |
8 | import UIKit
9 |
10 | extension UIScrollView {
11 | func removeAfterimage() {
12 | refreshControl?.beginRefreshing()
13 | refreshControl?.endRefreshing()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Network/DTO/AchievementDTO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AchievementDTO.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/29.
6 | //
7 |
8 | import Foundation
9 |
10 | struct AchievementDTO: Codable {
11 | let document: Fields?
12 |
13 | var documentID: String? {
14 | return document?.name?.components(separatedBy: "/").last
15 | }
16 |
17 | init() {
18 | self.document = nil
19 | }
20 |
21 | init(totalCount: Int, day: String, userID: String, achievementCount: Int, yearMonth: String) {
22 | let field = AchievementFields(
23 | totalCount: IntegerField(integerValue: "\(totalCount)"),
24 | day: StringField(stringValue: day),
25 | userID: StringField(stringValue: userID),
26 | achievementCount: IntegerField(integerValue: "\(achievementCount)"),
27 | yearMonth: StringField(stringValue: yearMonth)
28 | )
29 | self.document = Fields(name: nil, fields: field)
30 | }
31 | }
32 |
33 | struct AchievementFields: Codable {
34 | let totalCount: IntegerField
35 | let day: StringField
36 | let userID: StringField
37 | let achievementCount: IntegerField
38 | let yearMonth: StringField
39 |
40 | enum CodingKeys: String, CodingKey {
41 | case day
42 | case totalCount = "total_count"
43 | case userID = "user_id"
44 | case achievementCount = "achievement_count"
45 | case yearMonth = "year_month"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Network/DTO/AuthDTO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthDTO.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/29.
6 | //
7 |
8 | import Foundation
9 |
10 | struct AuthDTO: Codable {
11 | let document: Fields?
12 |
13 | init() {
14 | self.document = nil
15 | }
16 |
17 | init(challengeID: String, userID: String, date: String, time: String) {
18 | let field = AuthFields(challengeID: StringField(stringValue: challengeID),
19 | userID: StringField(stringValue: userID),
20 | date: StringField(stringValue: date),
21 | time: StringField(stringValue: time))
22 | self.document = Fields(name: nil, fields: field)
23 | }
24 | }
25 |
26 | struct AuthFields: Codable {
27 | let challengeID: StringField
28 | let userID: StringField
29 | let date: StringField
30 | let time: StringField
31 |
32 | enum CodingKeys: String, CodingKey {
33 | case date, time
34 | case challengeID = "challenge_id"
35 | case userID = "user_id"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Network/DTO/ChallengeDTO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeDTO.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/29.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ChallengeDTO: Codable {
11 | var document: Fields?
12 |
13 | var documentID: String? {
14 | return document?.name?.components(separatedBy: "/").last
15 | }
16 |
17 | init() {
18 | self.document = nil
19 | }
20 |
21 | init(id: String,
22 | title: String,
23 | authMethod: String,
24 | categoryID: String,
25 | week: Int,
26 | desc: String,
27 | startDate: String,
28 | endDate: String,
29 | participantCount: Int,
30 | ownerID: String) {
31 | let field = ChallengeFields(
32 | authMethod: StringField(stringValue: authMethod),
33 | categoryID: StringField(stringValue: categoryID),
34 | desc: StringField(stringValue: desc),
35 | endDate: StringField(stringValue: endDate),
36 | id: StringField(stringValue: id),
37 | ownerID: StringField(stringValue: ownerID),
38 | participantCount: IntegerField(integerValue: "\(participantCount)"),
39 | startDate: StringField(stringValue: startDate),
40 | title: StringField(stringValue: title),
41 | week: IntegerField(integerValue: "\(week)")
42 | )
43 | self.document = Fields(name: nil, fields: field)
44 | }
45 | }
46 |
47 | struct ChallengeFields: Codable {
48 | var authMethod: StringField
49 | var categoryID: StringField
50 | var desc: StringField
51 | var endDate: StringField
52 | var id: StringField
53 | var ownerID: StringField
54 | var participantCount: IntegerField
55 | var startDate: StringField
56 | var title: StringField
57 | var week: IntegerField
58 |
59 | enum CodingKeys: String, CodingKey {
60 | case desc, id, title, week
61 | case authMethod = "auth_method"
62 | case categoryID = "category_id"
63 | case endDate = "end_date"
64 | case ownerID = "owner_id"
65 | case participantCount = "participant_count"
66 | case startDate = "start_date"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Network/DTO/Fields.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Fields.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/29.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Fields: Codable {
11 | var name: String?
12 | var fields: T
13 | var createTime: String?
14 | }
15 |
16 | struct StringField: Codable {
17 | var stringValue: String
18 | }
19 |
20 | struct IntegerField: Codable {
21 | var integerValue: String
22 | }
23 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Network/DTO/ParticipationDTO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParticipationDTO.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/29.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ParticipationDTO: Codable {
11 | let document: Fields?
12 |
13 | var documentID: String? {
14 | return document?.name?.components(separatedBy: "/").last
15 | }
16 |
17 | init() {
18 | self.document = nil
19 | }
20 |
21 | init(authCount: Int, challengeID: String, joinDate: String, userID: String) {
22 | let field = ParticipationFields(authCount: IntegerField(integerValue: "\(authCount)"),
23 | challengeID: StringField(stringValue: challengeID),
24 | joinDate: StringField(stringValue: joinDate),
25 | userID: StringField(stringValue: userID))
26 | self.document = Fields(name: nil, fields: field)
27 | }
28 | }
29 |
30 | struct ParticipationFields: Codable {
31 | let authCount: IntegerField
32 | let challengeID: StringField
33 | let joinDate: StringField
34 | let userID: StringField
35 |
36 | enum CodingKeys: String, CodingKey {
37 | case authCount = "auth_count"
38 | case challengeID = "challenge_id"
39 | case joinDate = "join_date"
40 | case userID = "user_id"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Network/DTO/TodayRoutineDTO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TodayRoutineDTO.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/29.
6 | //
7 |
8 | import Foundation
9 |
10 | struct TodayRoutineDTO {
11 | var id: String
12 | var challengeID: String
13 | var categoryID: String
14 | var title: String
15 | var authCount: Int
16 | var joinDate: String
17 | var endDate: String
18 |
19 | init(participation: ParticipationDTO, challenge: ChallengeDTO) {
20 | self.id = participation.document?.fields.userID.stringValue ?? ""
21 | self.challengeID = participation.document?.fields.challengeID.stringValue ?? ""
22 | self.categoryID = challenge.document?.fields.categoryID.stringValue ?? ""
23 | self.title = challenge.document?.fields.title.stringValue ?? ""
24 | self.authCount = Int(participation.document?.fields.authCount.integerValue ?? "") ?? 0
25 | self.joinDate = participation.document?.fields.joinDate.stringValue ?? ""
26 | self.endDate = challenge.document?.fields.endDate.stringValue ?? ""
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Network/DTO/UserDTO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDTO.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/29.
6 | //
7 |
8 | import Foundation
9 |
10 | struct UserDTO: Codable {
11 | let document: Fields?
12 |
13 | var documentID: String? {
14 | return document?.name?.components(separatedBy: "/").last
15 | }
16 |
17 | init() {
18 | self.document = nil
19 | }
20 |
21 | init(id: String,
22 | name: String,
23 | grade: Int,
24 | continuityDay: Int,
25 | userImageCategoryID: String,
26 | lastAuthDay: String) {
27 | let field = UserFields(id: StringField(stringValue: id),
28 | name: StringField(stringValue: name),
29 | grade: IntegerField(integerValue: "\(grade)"),
30 | continuityDay: IntegerField(integerValue: "\(continuityDay)"),
31 | userImageCategoryID: StringField(stringValue: userImageCategoryID),
32 | lastAuthDay: StringField(stringValue: lastAuthDay))
33 | self.document = Fields(name: nil, fields: field)
34 | }
35 | }
36 |
37 | struct UserFields: Codable {
38 | let id: StringField
39 | let name: StringField
40 | let grade: IntegerField
41 | let continuityDay: IntegerField
42 | let userImageCategoryID: StringField
43 | let lastAuthDay: StringField
44 |
45 | enum CodingKeys: String, CodingKey {
46 | case id, name, grade
47 | case continuityDay = "continuity_day"
48 | case userImageCategoryID = "user_image_category_id"
49 | case lastAuthDay = "last_auth_day"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Network/Query/ParticipationQuery.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParticipationQuery.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/29.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ParticipationQuery {
11 | static func select(userID: String) -> Data? {
12 | return """
13 | {
14 | "structuredQuery": {
15 | "from": { "collectionId": "challenge_participation" },
16 | "where": {
17 | "fieldFilter": {
18 | "field": { "fieldPath": "user_id" },
19 | "op": "EQUAL",
20 | "value": { "stringValue": "\(userID)" }
21 | },
22 | },
23 | "limit": 50
24 | }
25 | }
26 | """.data(using: .utf8)
27 | }
28 |
29 | static func select(userID: String, challengeID: String) -> Data? {
30 | return """
31 | {
32 | "structuredQuery": {
33 | "from": { "collectionId": "challenge_participation" },
34 | "where": {
35 | "compositeFilter": {
36 | "filters": [
37 | {
38 | "fieldFilter": {
39 | "field": { "fieldPath": "user_id" },
40 | "op": "EQUAL",
41 | "value": { "stringValue": "\(userID)" }
42 | }
43 | },
44 | {
45 | "fieldFilter": {
46 | "field": { "fieldPath": "challenge_id" },
47 | "op": "EQUAL",
48 | "value": { "stringValue": "\(challengeID)" }
49 | },
50 | }
51 | ],
52 | "op": "AND"
53 | }
54 | },
55 | "limit": 50
56 | }
57 | }
58 | """.data(using: .utf8)
59 | }
60 |
61 | static func insert(document: ParticipationFields) -> Data? {
62 | return """
63 | {
64 | "fields": {
65 | "auth_count": { "integerValue": "0" },
66 | "challenge_id": { "stringValue": "\(document.challengeID.stringValue)" },
67 | "join_date": { "stringValue": "\(document.joinDate.stringValue)" },
68 | "user_id": { "stringValue": "\(document.userID.stringValue)" }
69 | }
70 | }
71 | """.data(using: .utf8)
72 | }
73 |
74 | static func update(document: ParticipationFields) -> Data? {
75 | return """
76 | {
77 | "fields": {
78 | "auth_count": { "integerValue": "\(document.authCount.integerValue)" }
79 | }
80 | }
81 | """.data(using: .utf8)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Network/Query/UserQuery.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserQuery.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/29.
6 | //
7 |
8 | import Foundation
9 |
10 | enum UserQuery {
11 | static func select(id: String) -> Data? {
12 | return """
13 | {
14 | "structuredQuery": {
15 | "from": { "collectionId": "user" },
16 | "where": {
17 | "fieldFilter": {
18 | "field": { "fieldPath": "id" },
19 | "op": "EQUAL",
20 | "value": { "stringValue": "\(id)" }
21 | },
22 | },
23 | "limit": 1
24 | }
25 | }
26 | """.data(using: .utf8)
27 | }
28 |
29 | static func insert(id: String, name: String) -> Data? {
30 | return """
31 | {
32 | "fields": {
33 | "id": { "stringValue": "\(id)" },
34 | "name": { "stringValue": "\(name)" },
35 | "grade": { "integerValue": "0" },
36 | "continuity_day": { "integerValue": "0" },
37 | "user_image_category_id": { "stringValue": "0" },
38 | "last_auth_day": { "stringValue": "0" }
39 | }
40 | }
41 | """.data(using: .utf8)
42 | }
43 |
44 | static func update(document: UserFields) -> Data? {
45 | return """
46 | {
47 | "fields": {
48 | "continuity_day": { "integerValue": "\(document.continuityDay.integerValue)" },
49 | "last_auth_day": { "stringValue": "\(document.lastAuthDay.stringValue)" }
50 | }
51 | }
52 | """.data(using: .utf8)
53 | }
54 |
55 | static func updateUsername(document: UserFields) -> Data? {
56 | return """
57 | {
58 | "fields": {
59 | "name": { "stringValue": "\(document.name.stringValue)" },
60 | }
61 | }
62 | """.data(using: .utf8)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Auth/Subviews/AuthButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthButton.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/16.
6 | //
7 |
8 | import UIKit
9 |
10 | final class AuthButton: UIButton {
11 | weak var delegate: AuthButtonDelegate?
12 |
13 | init() {
14 | super.init(frame: .zero)
15 | self.setTitle("certify".localized, for: .normal)
16 | self.titleLabel?.font = UIFont.boldSystemFont(ofSize: 26)
17 | self.setTitleColor(UIColor(named: "Black"), for: .normal)
18 | self.backgroundColor = UIColor(named: "MainColor")
19 | self.layer.cornerRadius = 15
20 | self.addTarget(self, action: #selector(didTappedAuthButton), for: .touchUpInside)
21 | }
22 |
23 | override init(frame: CGRect) {
24 | super.init(frame: frame)
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | super.init(coder: coder)
29 | }
30 |
31 | func updateEnabled(isEnabled: Bool) {
32 | self.isEnabled = isEnabled
33 | let color = isEnabled ? "MainColor" : "MainColor0.5"
34 | self.backgroundColor = UIColor(named: color)
35 | }
36 |
37 | @objc func didTappedAuthButton() {
38 | delegate?.didTappedAuthButton()
39 | }
40 | }
41 |
42 | protocol AuthButtonDelegate: AnyObject {
43 | func didTappedAuthButton()
44 | }
45 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Auth/Subviews/AuthPreviewView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthPreviewView.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/16.
6 | //
7 |
8 | import UIKit
9 |
10 | final class AuthPreviewView: UIView {
11 | weak var delegate: PreviewViewDelegate?
12 |
13 | private lazy var previewView: UIView = {
14 | let view = UIView()
15 | view.backgroundColor = UIColor(named: "LightGray")
16 | view.layer.cornerRadius = 15
17 | view.layer.borderColor = UIColor(named: "MainColor")?.cgColor
18 | view.layer.borderWidth = 2
19 | view.clipsToBounds = true
20 | return view
21 | }()
22 | private lazy var authButton: UIButton = {
23 | let button = UIButton()
24 | button.setImage(UIImage(systemName: "camera.fill"), for: .normal)
25 | button.tintColor = UIColor(named: "SystemForeground")
26 | button.imageView?.contentMode = .scaleAspectFit
27 | button.contentVerticalAlignment = .fill
28 | button.contentHorizontalAlignment = .fill
29 | button.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
30 | button.layer.borderWidth = 1
31 | button.layer.cornerRadius = 15
32 | button.isUserInteractionEnabled = false
33 | return button
34 | }()
35 |
36 | override init(frame: CGRect) {
37 | super.init(frame: frame)
38 | configure()
39 | }
40 |
41 | required init?(coder: NSCoder) {
42 | super.init(coder: coder)
43 | configure()
44 | }
45 |
46 | convenience init() {
47 | self.init(frame: CGRect.zero)
48 | }
49 |
50 | func updateImage(_ image: UIImage) {
51 | let backgroundImage = UIImageView(frame: previewView.bounds)
52 | backgroundImage.image = image
53 | backgroundImage.contentMode = .scaleAspectFill
54 | backgroundImage.clipsToBounds = true
55 | previewView.addSubview(backgroundImage)
56 | }
57 | }
58 |
59 | extension AuthPreviewView {
60 | private func configure() {
61 | configureSubviews()
62 | configureGesture()
63 | }
64 |
65 | private func configureSubviews() {
66 | addSubview(previewView)
67 | previewView.anchor(horizontal: self, paddingHorizontal: 20, vertical: self)
68 |
69 | previewView.addSubview(authButton)
70 | authButton.anchor(centerX: self.centerXAnchor,
71 | centerY: self.centerYAnchor,
72 | width: 80,
73 | height: 80)
74 | }
75 |
76 | private func configureGesture() {
77 | let recognizer = UITapGestureRecognizer(target: self,
78 | action: #selector(didTappedPreviewView))
79 | previewView.isUserInteractionEnabled = true
80 | previewView.addGestureRecognizer(recognizer)
81 | }
82 |
83 | @objc private func didTappedPreviewView(_ sender: UITapGestureRecognizer) {
84 | guard sender.state == .ended else { return }
85 | delegate?.didTappedPreviewView()
86 | }
87 | }
88 |
89 | protocol PreviewViewDelegate: AnyObject {
90 | func didTappedPreviewView()
91 | }
92 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/AuthImages/AuthImagesViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthImagesViewModel.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/24.
6 | //
7 |
8 | import Combine
9 | import Foundation
10 |
11 | protocol AuthImagesViewModelInput {
12 | func fetchAuthData(authDisplayState: AuthDisplayState)
13 | func imageData(from directory: String, filename: String, completion: ((Data?) -> Void)?)
14 | }
15 |
16 | protocol AuthImagesViewModelOutput {
17 | var auths: CurrentValueSubject<[Auth], Never> { get }
18 | var authDisplayState: CurrentValueSubject { get }
19 |
20 | var authImageTap: PassthroughSubject { get }
21 | var authImageLoad: PassthroughSubject { get }
22 | }
23 |
24 | protocol AuthImagesViewModelIO: AuthImagesViewModelInput, AuthImagesViewModelOutput { }
25 |
26 | final class AuthImagesViewModel: AuthImagesViewModelIO {
27 | var auths = CurrentValueSubject<[Auth], Never>([])
28 | var authDisplayState = CurrentValueSubject(.all)
29 |
30 | var authImageTap = PassthroughSubject()
31 | var authImageLoad = PassthroughSubject()
32 |
33 | private var challengeID: String?
34 |
35 | let authFetchUsecase: AuthFetchableUsecase
36 | let imageFetchUsecase: ImageFetchableUsecase
37 |
38 | init(challengeID: String,
39 | authDisplayState: AuthDisplayState,
40 | authFetchUsecase: AuthFetchableUsecase,
41 | imageFetchUsecase: ImageFetchableUsecase) {
42 | self.challengeID = challengeID
43 | self.authFetchUsecase = authFetchUsecase
44 | self.imageFetchUsecase = imageFetchUsecase
45 | fetchAuthData(authDisplayState: authDisplayState)
46 | }
47 | }
48 |
49 | extension AuthImagesViewModel {
50 | private func fetchAuths() {
51 | guard let challengeID = challengeID else { return }
52 | authFetchUsecase.fetchAuths(challengeID: challengeID) { auths in
53 | self.auths.value = auths
54 | }
55 | }
56 |
57 | private func fetchMyAuths() {
58 | guard let challengeID = challengeID else { return }
59 | authFetchUsecase.fetchMyAuths(challengeID: challengeID) { auths in
60 | self.auths.value = auths
61 | }
62 | }
63 | }
64 |
65 | extension AuthImagesViewModel: AuthImagesViewModelInput {
66 | func fetchAuthData(authDisplayState: AuthDisplayState) {
67 | switch authDisplayState {
68 | case .all:
69 | self.authDisplayState.value = .all
70 | self.fetchAuths()
71 | case .my:
72 | self.authDisplayState.value = .my
73 | self.fetchMyAuths()
74 | }
75 | }
76 |
77 | func imageData(from directory: String, filename: String, completion: ((Data?) -> Void)? = nil) {
78 | imageFetchUsecase.fetchImageData(from: directory, filename: filename) { data in
79 | completion?(data)
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/AuthImages/Cells/AuthImagesCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthImagesCollectionViewCell.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/24.
6 | //
7 |
8 | import UIKit
9 |
10 | final class AuthImagesCollectionViewCell: UICollectionViewCell {
11 | static let identifier = "AuthImagesCollectionViewCell"
12 |
13 | private lazy var imageView: UIImageView = {
14 | let imageView = UIImageView()
15 | imageView.layer.cornerRadius = 10
16 | imageView.contentMode = .scaleAspectFill
17 | imageView.clipsToBounds = true
18 | return imageView
19 | }()
20 |
21 | override init(frame: CGRect) {
22 | super.init(frame: frame)
23 | configure()
24 | }
25 |
26 | required init?(coder: NSCoder) {
27 | super.init(coder: coder)
28 | configure()
29 | }
30 |
31 | func update(image: UIImage) {
32 | imageView.image = image
33 | }
34 |
35 | func imageData() -> Data {
36 | guard let imageData = imageView.image?.jpegData(compressionQuality: 1) else { return Data() }
37 | return imageData
38 | }
39 | }
40 |
41 | extension AuthImagesCollectionViewCell {
42 | private func configure() {
43 | configureCell()
44 | }
45 |
46 | private func configureCell() {
47 | addSubview(imageView)
48 | imageView.anchor(leading: leadingAnchor,
49 | trailing: trailingAnchor,
50 | top: topAnchor,
51 | bottom: bottomAnchor)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Challenge/Cells/ChallengeCategoryCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeCategoryCollectionViewCell.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/04.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ChallengeCategoryCollectionViewCell: UICollectionViewCell {
11 | static let identifier = "ChallengeCategoryCollectionViewCell"
12 |
13 | weak var delegate: ChallengeCategoryCellDelegate?
14 |
15 | private lazy var yStackView: UIStackView = {
16 | let stack = UIStackView()
17 | stack.axis = .vertical
18 | stack.spacing = 25
19 | return stack
20 | }()
21 |
22 | private lazy var xStackView1: UIStackView = {
23 | let stack = UIStackView()
24 | stack.axis = .horizontal
25 | stack.distribution = .equalSpacing
26 | return stack
27 | }()
28 |
29 | private lazy var xStackView2: UIStackView = {
30 | let stack = UIStackView()
31 | stack.axis = .horizontal
32 | stack.distribution = .equalSpacing
33 | return stack
34 | }()
35 |
36 | func configureViews() {
37 | addSubview(yStackView)
38 | yStackView.anchor(horizontal: yStackView.superview, top: yStackView.superview?.topAnchor)
39 |
40 | yStackView.addArrangedSubview(xStackView1)
41 | yStackView.addArrangedSubview(xStackView2)
42 |
43 | for (index, category) in Challenge.Category.allCases.enumerated() {
44 | let button = ChallengeCategoryIconView()
45 | if UIImage(named: category.symbol) != nil {
46 | button.updateImage(UIImage(named: category.symbol))
47 | } else {
48 | button.updateImage(UIImage(systemName: category.symbol))
49 | }
50 | button.updateTitle(category.title)
51 | button.updateTintColor(UIColor(named: category.color))
52 |
53 | let gesture = ChallengeCategoryIconViewTapGesture(
54 | target: self,
55 | action: #selector(didTappedCategoryButton)
56 | )
57 | gesture.numberOfTapsRequired = 1
58 | gesture.configureCategory(category: category)
59 | isUserInteractionEnabled = true
60 | button.addGestureRecognizer(gesture)
61 |
62 | if index < 4 {
63 | xStackView1.addArrangedSubview(button)
64 | } else {
65 | xStackView2.addArrangedSubview(button)
66 | }
67 | }
68 | }
69 | }
70 |
71 | extension ChallengeCategoryCollectionViewCell {
72 | @objc private func didTappedCategoryButton(_ gesture: ChallengeCategoryIconViewTapGesture) {
73 | guard let category = gesture.category else { return }
74 | delegate?.didTappedCategoryButton(category: category)
75 | }
76 | }
77 |
78 | protocol ChallengeCategoryCellDelegate: AnyObject {
79 | func didTappedCategoryButton(category: Challenge.Category)
80 | }
81 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Challenge/Cells/ChallengeCategoryCollectionViewHeader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeCollectionViewHeader.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/04.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ChallengeCategoryCollectionViewHeader: UICollectionReusableView {
11 | static let identifier = "ChallengeCategoryCollectionViewHeader"
12 |
13 | weak var delegate: ChallengeCategoryHeaderDelegate?
14 |
15 | private let stackView = UIStackView()
16 |
17 | private lazy var label: UILabel = {
18 | let label = UILabel()
19 | label.textColor = UIColor(named: "SystemForeground")
20 | label.font = UIFont.boldSystemFont(ofSize: 18)
21 |
22 | return label
23 | }()
24 |
25 | private lazy var seeAllButton: UIButton = {
26 | let button = UIButton(type: .system)
27 | button.setTitle("See all", for: .normal)
28 | button.setTitleColor(UIColor(named: "SystemForeground"), for: .normal)
29 | button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
30 | button.addTarget(self, action: #selector(didTappedSeeAllButton), for: .touchUpInside)
31 | return button
32 | }()
33 |
34 | var title: String = "" {
35 | didSet {
36 | label.text = title
37 | }
38 | }
39 |
40 | override init(frame: CGRect) {
41 | super.init(frame: frame)
42 | configure()
43 | }
44 |
45 | required init?(coder: NSCoder) {
46 | super.init(coder: coder)
47 | configure()
48 | }
49 |
50 | override func layoutSubviews() {
51 | super.layoutSubviews()
52 | stackView.frame = bounds
53 | }
54 |
55 | func addSeeAllButton() {
56 | stackView.addArrangedSubview(seeAllButton)
57 | }
58 | }
59 |
60 | extension ChallengeCategoryCollectionViewHeader {
61 | private func configure() {
62 | configureView()
63 | }
64 |
65 | private func configureView() {
66 | addSubview(stackView)
67 | stackView.addArrangedSubview(label)
68 | }
69 |
70 | @objc private func didTappedSeeAllButton() {
71 | delegate?.didTappedSeeAllButton()
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Challenge/Cells/ChallengeRecommendCollectionViewHeader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeRecommendCollectionViewHeader.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/24.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ChallengeRecommendCollectionViewHeader: UICollectionReusableView {
11 | static let identifier = "ChallengeRecommendCollectionViewHeader"
12 |
13 | weak var delegate: ChallengeRecommendHeaderDelegate?
14 |
15 | private let stackView = UIStackView()
16 |
17 | private lazy var titleLabel: UILabel = {
18 | let label = UILabel()
19 | label.text = "challenges".localized
20 | label.font = UIFont.systemFont(ofSize: UIScreen.main.bounds.width <= 350 ? 30 : 34,
21 | weight: .bold)
22 | return label
23 | }()
24 |
25 | private lazy var searchButton: UIButton = {
26 | let button = UIButton()
27 | button.setImage(UIImage(systemName: "magnifyingglass"), for: .normal)
28 | button.tintColor = UIColor(named: "SystemForeground")
29 | button.addTarget(self, action: #selector(didTappedSearchButton), for: .touchUpInside)
30 | return button
31 | }()
32 |
33 | var title: String = "" {
34 | didSet {
35 | titleLabel.text = title
36 | }
37 | }
38 |
39 | override init(frame: CGRect) {
40 | super.init(frame: frame)
41 | configure()
42 | }
43 |
44 | required init?(coder: NSCoder) {
45 | super.init(coder: coder)
46 | configure()
47 | }
48 |
49 | override func layoutSubviews() {
50 | super.layoutSubviews()
51 | stackView.frame = bounds
52 | }
53 |
54 | func addSearchButton() {
55 | stackView.addArrangedSubview(searchButton)
56 | }
57 | }
58 |
59 | extension ChallengeRecommendCollectionViewHeader {
60 | private func configure() {
61 | configureView()
62 | }
63 |
64 | private func configureView() {
65 | addSubview(stackView)
66 | stackView.addArrangedSubview(titleLabel)
67 | }
68 |
69 | @objc private func didTappedSearchButton() {
70 | delegate?.didTappedSearchButton()
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Challenge/Delegates/ChallengeCategoryHeaderDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeCategoryHeaderDelegate.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/24.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ChallengeCategoryHeaderDelegate: AnyObject {
11 | func didTappedSeeAllButton()
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Challenge/Delegates/ChallengeRecommendHeaderDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeRecommendHeaderDelegate.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/24.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ChallengeRecommendHeaderDelegate: AnyObject {
11 | func didTappedSearchButton()
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Challenge/Layouts/ChallengeCollectionViewLayouts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeCollectionViewLayouts.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/04.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ChallengeCollectionViewLayouts {
11 | private var recommendLayout: NSCollectionLayoutSection {
12 | let smallWidth = UIScreen.main.bounds.width <= 350
13 | let offset = smallWidth ? 15.0 : 25.0
14 |
15 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
16 | heightDimension: .fractionalHeight(1))
17 | let item = NSCollectionLayoutItem(layoutSize: itemSize)
18 |
19 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.8),
20 | heightDimension: .estimated(170))
21 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
22 |
23 | let section = NSCollectionLayoutSection(group: group)
24 | section.boundarySupplementaryItems = [.init(
25 | layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .estimated(80)),
26 | elementKind: UICollectionView.elementKindSectionHeader,
27 | alignment: .topLeading
28 | )]
29 |
30 | section.orthogonalScrollingBehavior = .groupPaging
31 | section.contentInsets = .init(top: 10, leading: offset, bottom: 20, trailing: offset)
32 | section.interGroupSpacing = 20
33 | return section
34 | }
35 |
36 | private var mainLayout: NSCollectionLayoutSection {
37 | let smallWidth = UIScreen.main.bounds.width <= 350
38 | let offset = smallWidth ? 15.0 : 25.0
39 |
40 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
41 | heightDimension: .estimated(200))
42 | let item = NSCollectionLayoutItem(layoutSize: itemSize)
43 |
44 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
45 | heightDimension: .estimated(200))
46 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
47 |
48 | let section = NSCollectionLayoutSection(group: group)
49 | section.boundarySupplementaryItems = [.init(
50 | layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(70)),
51 | elementKind: UICollectionView.elementKindSectionHeader,
52 | alignment: .topLeading
53 | )]
54 |
55 | section.orthogonalScrollingBehavior = .none
56 | section.contentInsets = .init(top: 0, leading: offset, bottom: 0, trailing: offset)
57 | return section
58 | }
59 |
60 | func section(at sectionNumber: Int) -> NSCollectionLayoutSection {
61 | if sectionNumber == 0 {
62 | return recommendLayout
63 | } else {
64 | return mainLayout
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Challenge/Subviews/ChallengeCategoryIconView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeCategoryIconView.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/08.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ChallengeCategoryIconView: UIView {
11 | private var imageView = UIImageView()
12 | private var title = UILabel()
13 |
14 | override init(frame: CGRect) {
15 | super.init(frame: frame)
16 | imageView = UIImageView()
17 | title = UILabel()
18 | configure()
19 | }
20 |
21 | required init?(coder aDecoder: NSCoder) {
22 | super.init(coder: aDecoder)
23 | imageView = UIImageView()
24 | title = UILabel()
25 | configure()
26 | }
27 |
28 | func updateImage(_ image: UIImage?) {
29 | imageView.image = image
30 | imageView.contentMode = .scaleAspectFit
31 | }
32 |
33 | func updateTitle(_ text: String) {
34 | title.text = text
35 | title.tintColor = UIColor(named: "SystemForeground")
36 | }
37 |
38 | func updateTintColor(_ color: UIColor?) {
39 | imageView.tintColor = color
40 | }
41 | }
42 |
43 | extension ChallengeCategoryIconView {
44 | private func configure() {
45 | configureViews()
46 | }
47 |
48 | private func configureViews() {
49 | anchor(width: 60, height: 60)
50 |
51 | addSubview(imageView)
52 | imageView.anchor(centerX: imageView.superview?.centerXAnchor,
53 | top: imageView.superview?.topAnchor,
54 | width: 35,
55 | height: 35)
56 |
57 | addSubview(title)
58 | title.anchor(centerX: title.superview?.centerXAnchor,
59 | bottom: title.superview?.bottomAnchor)
60 | }
61 | }
62 |
63 | final class ChallengeCategoryIconViewTapGesture: UITapGestureRecognizer {
64 | private(set) var category: Challenge.Category?
65 |
66 | func configureCategory(category: Challenge.Category) {
67 | self.category = category
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Common/Cells/ChallengeCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeCollectionViewCell.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/09.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ChallengeCollectionViewCell: UICollectionViewCell {
11 | static let identifier = "ChallengeCollectionViewCell"
12 |
13 | private lazy var imageView: UIImageView = {
14 | let imageView = UIImageView()
15 | imageView.contentMode = .scaleAspectFill
16 | imageView.clipsToBounds = true
17 | imageView.layer.cornerRadius = 5
18 | return imageView
19 | }()
20 |
21 | private lazy var titleLabel: UILabel = {
22 | let label = UILabel()
23 | label.textColor = UIColor(named: "SystemForeground")
24 | label.font = UIFont.boldSystemFont(ofSize: 15)
25 | label.lineBreakMode = .byTruncatingTail
26 | return label
27 | }()
28 |
29 | override init(frame: CGRect) {
30 | super.init(frame: frame)
31 | configure()
32 | }
33 |
34 | required init?(coder: NSCoder) {
35 | super.init(coder: coder)
36 | configure()
37 | }
38 |
39 | override func prepareForReuse() {
40 | super.prepareForReuse()
41 | titleLabel.text = ""
42 | imageView.image = nil
43 | }
44 |
45 | func updateTitle(_ title: String) {
46 | titleLabel.text = title
47 | }
48 |
49 | func updateImage(_ image: UIImage) {
50 | imageView.image = image
51 | }
52 | }
53 |
54 | extension ChallengeCollectionViewCell {
55 | private func configure() {
56 | configureViews()
57 | }
58 |
59 | private func configureViews() {
60 | addSubview(imageView)
61 | imageView.anchor(horizontal: imageView.superview,
62 | top: imageView.superview?.topAnchor,
63 | height: 110)
64 |
65 | addSubview(titleLabel)
66 | titleLabel.anchor(horizontal: titleLabel.superview,
67 | top: imageView.bottomAnchor,
68 | paddingTop: 10,
69 | bottom: titleLabel.superview?.bottomAnchor)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Common/ImagePanViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePanViewController.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/23.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ImagePanViewController: UIViewController {
11 | private lazy var dimmedBackgroundView: UIView = {
12 | let view = UIView()
13 | view.backgroundColor = UIColor(named: "Black")?.withAlphaComponent(0.8)
14 | return view
15 | }()
16 |
17 | private lazy var imageView: UIImageView = {
18 | let imageView = UIImageView()
19 | imageView.contentMode = .scaleAspectFit
20 | return imageView
21 | }()
22 |
23 | private var imageData: Data? {
24 | didSet {
25 | fetchImage()
26 | }
27 | }
28 |
29 | override func viewDidLoad() {
30 | super.viewDidLoad()
31 | configureViews()
32 | configureGesture()
33 | }
34 |
35 | func updateImage(data: Data) {
36 | imageData = data
37 | }
38 | }
39 |
40 | extension ImagePanViewController {
41 | private func configureViews() {
42 | view.addSubview(dimmedBackgroundView)
43 | dimmedBackgroundView.anchor(centerX: view.centerXAnchor,
44 | centerY: view.centerYAnchor,
45 | width: view.frame.width * 2,
46 | height: view.frame.height * 2)
47 | view.addSubview(imageView)
48 | imageView.anchor(centerX: view.centerXAnchor,
49 | centerY: view.centerYAnchor,
50 | width: UIScreen.main.bounds.width,
51 | height: UIScreen.main.bounds.height)
52 | }
53 |
54 | private func configureGesture() {
55 | let pan = UIPanGestureRecognizer(target: self, action: #selector(doPan(_:)))
56 | pan.delegate = self
57 | view.addGestureRecognizer(pan)
58 | }
59 |
60 | @objc func doPan(_ pan: UIPanGestureRecognizer) {
61 | let translation = pan.translation(in: imageView)
62 | let centerX = imageView.center.x
63 | let centerY = imageView.center.y
64 |
65 | if let imageView = pan.view {
66 | imageView.center = CGPoint(x: imageView.center.x + translation.x,
67 | y: imageView.center.y + translation.y)
68 | let xDistance = imageView.center.x - centerX
69 | let yDistacne = imageView.center.y - centerY
70 | let distanceFromCenter = sqrt(xDistance * xDistance + yDistacne * yDistacne)
71 | dimmedBackgroundView.alpha = 0.9 - (distanceFromCenter / 250)
72 | }
73 | pan.setTranslation(.zero, in: imageView)
74 |
75 | if pan.state == .ended {
76 | dismiss(animated: false)
77 | }
78 | }
79 |
80 | private func fetchImage() {
81 | guard let image = imageData else { return }
82 | DispatchQueue.main.async { [weak self] in
83 | guard let self = self else { return }
84 | self.imageView.image = UIImage(data: image)
85 | }
86 | }
87 | }
88 |
89 | extension ImagePanViewController: UIGestureRecognizerDelegate {
90 | func gestureRecognizer(
91 | _ gestureRecognizer: UIGestureRecognizer,
92 | shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
93 | ) -> Bool {
94 | return true
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Common/Subviews/ChallengePromotionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AddChallengeView.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/20.
6 | //
7 |
8 | import UIKit
9 |
10 | protocol ChallengePromotionViewDelegate: AnyObject {
11 | func didTappedPromotionButton()
12 | }
13 |
14 | final class ChallengePromotionView: UIView {
15 | weak var delegate: ChallengePromotionViewDelegate?
16 |
17 | private lazy var stackView: UIStackView = {
18 | let stackView = UIStackView()
19 | stackView.axis = .vertical
20 | stackView.spacing = 20
21 | return stackView
22 | }()
23 |
24 | private lazy var titleLabel: UILabel = {
25 | let label = UILabel()
26 | label.text = "make your challenge".localized
27 | label.numberOfLines = 2
28 | label.textAlignment = .center
29 | label.font = .boldSystemFont(ofSize: 18)
30 | return label
31 | }()
32 |
33 | private lazy var promotionButton: UIButton = {
34 | let button = UIButton()
35 | button.setTitle("make challenge".localized, for: .normal)
36 | button.setTitleColor(UIColor(named: "SystemBackground"), for: .normal)
37 | button.backgroundColor = UIColor(named: "MainColor")
38 | button.layer.cornerRadius = 5
39 | button.addTarget(self, action: #selector(didTappedPromotionButton), for: .touchUpInside)
40 | return button
41 | }()
42 |
43 | override init(frame: CGRect) {
44 | super.init(frame: frame)
45 | configure()
46 | }
47 |
48 | required init?(coder: NSCoder) {
49 | super.init(coder: coder)
50 | configure()
51 | }
52 |
53 | convenience init() {
54 | self.init(frame: CGRect.zero)
55 | }
56 |
57 | func configureChallengePromotionView(titleLabel: String, buttonLabel: String) {
58 | self.titleLabel.text = titleLabel
59 | promotionButton.setTitle(buttonLabel, for: .normal)
60 | }
61 | }
62 |
63 | extension ChallengePromotionView {
64 | private func configure() {
65 | configureView()
66 | }
67 |
68 | private func configureView() {
69 | backgroundColor = UIColor(named: "MainColor")?.withAlphaComponent(0.5)
70 | layer.cornerRadius = 10
71 | anchor(height: 160)
72 |
73 | addSubview(stackView)
74 | stackView.addArrangedSubview(titleLabel)
75 | stackView.addArrangedSubview(promotionButton)
76 | stackView.anchor(centerX: centerXAnchor, centerY: centerYAnchor)
77 | }
78 |
79 | @objc func didTappedPromotionButton() {
80 | delegate?.didTappedPromotionButton()
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Detail/Subviews/DetailParticipantButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DetailParticipantButton.swift
3 | // Routinus
4 | //
5 | // Created by 백지현 on 2021/11/16.
6 | //
7 |
8 | import UIKit
9 |
10 | final class DetailParticipantButton: UIButton {
11 | weak var delegate: ParticipantButtonDelegate?
12 |
13 | init() {
14 | super.init(frame: .zero)
15 | self.setTitle(ParticipationAuthState.notParticipating.rawValue, for: .normal)
16 | self.isEnabled = true
17 | self.titleLabel?.font = UIFont.boldSystemFont(ofSize: 26)
18 | self.setTitleColor(UIColor(named: "Black"), for: .normal)
19 | self.backgroundColor = UIColor(named: "MainColor")
20 | self.layer.cornerRadius = 15
21 | self.addTarget(self, action: #selector(didTappedAuthButton), for: .touchUpInside)
22 | }
23 |
24 | override init(frame: CGRect) {
25 | super.init(frame: frame)
26 | }
27 |
28 | required init?(coder: NSCoder) {
29 | super.init(coder: coder)
30 | }
31 |
32 | @objc func didTappedAuthButton() {
33 | delegate?.didTappedParticipantButton()
34 | }
35 |
36 | func update(to state: ParticipationAuthState) {
37 | isEnabled = state == .authenticated ? false : true
38 | let backgroundColor = isEnabled ? "MainColor" : "MainColor0.5"
39 | let titleColor = isEnabled ? "SystemForeground" : "DayColor"
40 | setTitle(state.rawValue.localized, for: .normal)
41 | setTitleColor(UIColor(named: titleColor), for: .normal)
42 | self.backgroundColor = UIColor(named: backgroundColor)
43 | }
44 | }
45 |
46 | protocol ParticipantButtonDelegate: AnyObject {
47 | func didTappedParticipantButton()
48 | }
49 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Form/Delegates/FormImagePickerDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormImagePickerDelegate.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/10.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol FormImagePickerDelegate: AnyObject {
11 | func didTappedImageView(_ tag: Int)
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Form/Delegates/FormSubviewDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormSubviewDelegate.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/10.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol FormSubviewDelegate: AnyObject {
11 | func didChange(category: Challenge.Category)
12 | func didChange(imageURL: String)
13 | func didChange(authExampleImageURL: String)
14 | func didTappedCategoryButton()
15 | }
16 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Form/Subviews/FormAuthImageRegisterView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormAuthImageRegisterView.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/09.
6 | //
7 |
8 | import UIKit
9 |
10 | final class FormAuthImageRegisterView: UIView {
11 | weak var delegate: FormImagePickerDelegate?
12 |
13 | private lazy var titleLabel: UILabel = {
14 | let label = UILabel()
15 | label.text = "upload auth exmaple photo".localized
16 | label.font = .boldSystemFont(ofSize: 20)
17 | label.numberOfLines = 2
18 | return label
19 | }()
20 | private lazy var descriptionLabel: UILabel = {
21 | let label = UILabel()
22 | label.text = "add auth example photo".localized
23 | label.font = .systemFont(ofSize: 16)
24 | label.textColor = UIColor(named: "DayColor")
25 | label.numberOfLines = 3
26 | return label
27 | }()
28 | private lazy var imageView: UIImageView = {
29 | let imageView = UIImageView()
30 | imageView.image = UIImage(systemName: "camera")
31 | imageView.tintColor = UIColor(named: "SystemForeground")
32 | imageView.contentMode = .scaleAspectFit
33 | imageView.clipsToBounds = true
34 | imageView.layer.cornerRadius = 10
35 | imageView.layer.borderWidth = 1
36 | imageView.layer.borderColor = UIColor(named: "DayColor")?.cgColor
37 | return imageView
38 | }()
39 |
40 | override init(frame: CGRect) {
41 | super.init(frame: frame)
42 | configure()
43 | }
44 |
45 | required init?(coder: NSCoder) {
46 | super.init(coder: coder)
47 | configure()
48 | }
49 |
50 | convenience init() {
51 | self.init(frame: CGRect.zero)
52 | }
53 |
54 | func updateImage(_ image: UIImage) {
55 | imageView.image = image
56 | imageView.contentMode = .scaleAspectFill
57 | }
58 | }
59 |
60 | extension FormAuthImageRegisterView {
61 | private func configure() {
62 | configureSubviews()
63 | configureGesture()
64 | }
65 |
66 | private func configureSubviews() {
67 | addSubview(titleLabel)
68 | titleLabel.anchor(top: titleLabel.superview?.topAnchor,
69 | width: UIScreen.main.bounds.width - 40)
70 |
71 | addSubview(descriptionLabel)
72 | descriptionLabel.anchor(top: titleLabel.bottomAnchor, paddingTop: 10,
73 | width: UIScreen.main.bounds.width - 40)
74 |
75 | addSubview(imageView)
76 | imageView.anchor(centerX: imageView.superview?.centerXAnchor,
77 | top: descriptionLabel.bottomAnchor, paddingTop: 30,
78 | width: 150, height: 150)
79 |
80 | anchor(bottom: imageView.bottomAnchor)
81 | }
82 |
83 | private func configureGesture() {
84 | let recognizer = UITapGestureRecognizer(target: self,
85 | action: #selector(didTappedImageView(_:)))
86 | imageView.isUserInteractionEnabled = true
87 | imageView.addGestureRecognizer(recognizer)
88 | }
89 |
90 | @objc private func didTappedImageView(_ sender: UITapGestureRecognizer) {
91 | guard sender.state == .ended else { return }
92 | delegate?.didTappedImageView(FormViewController.InputTag.authImage.rawValue)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Form/Subviews/FormAuthMethodView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormAuthMethodView.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/09.
6 | //
7 |
8 | import UIKit
9 |
10 | final class FormAuthMethodView: UIView {
11 | typealias Tag = FormViewController.InputTag
12 |
13 | weak var delegate: UITextViewDelegate? {
14 | didSet {
15 | textView.delegate = delegate
16 | }
17 | }
18 |
19 | private lazy var titleLabel: UILabel = {
20 | let label = UILabel()
21 | label.text = "write auth method".localized
22 | label.font = .boldSystemFont(ofSize: 20)
23 | label.numberOfLines = 2
24 | return label
25 | }()
26 | private lazy var descriptionLabel: UILabel = {
27 | let label = UILabel()
28 | label.text = "auth method 150".localized
29 | label.font = .systemFont(ofSize: 16)
30 | label.textColor = UIColor(named: "DayColor")
31 | label.numberOfLines = 3
32 | return label
33 | }()
34 | private lazy var textView: UITextView = {
35 | let textView = UITextView()
36 | textView.layer.borderColor = UIColor(named: "SystemForeground")?.cgColor
37 | textView.layer.borderWidth = 1
38 | textView.layer.cornerRadius = 10
39 | textView.font = .systemFont(ofSize: 16)
40 | textView.tag = Tag.authMethod.rawValue
41 | return textView
42 | }()
43 |
44 | override init(frame: CGRect) {
45 | super.init(frame: frame)
46 | configure()
47 | }
48 |
49 | required init?(coder: NSCoder) {
50 | super.init(coder: coder)
51 | configure()
52 | }
53 |
54 | convenience init() {
55 | self.init(frame: CGRect.zero)
56 | }
57 |
58 | func hideKeyboard() {
59 | textView.endEditing(true)
60 | }
61 |
62 | func update(authMethod: String) {
63 | textView.text = authMethod
64 | }
65 | }
66 |
67 | extension FormAuthMethodView {
68 | private func configure() {
69 | configureSubviews()
70 | }
71 |
72 | private func configureSubviews() {
73 | addSubview(titleLabel)
74 | titleLabel.anchor(top: titleLabel.superview?.topAnchor,
75 | width: UIScreen.main.bounds.width - 40)
76 |
77 | addSubview(descriptionLabel)
78 | descriptionLabel.anchor(top: titleLabel.bottomAnchor, paddingTop: 10,
79 | width: UIScreen.main.bounds.width - 40)
80 |
81 | addSubview(textView)
82 | textView.anchor(top: descriptionLabel.bottomAnchor, paddingTop: 20,
83 | width: UIScreen.main.bounds.width - 40,
84 | height: 150)
85 |
86 | anchor(bottom: textView.bottomAnchor)
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Form/Subviews/FormImageRegisterView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormImageRegisterView.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/09.
6 | //
7 |
8 | import UIKit
9 |
10 | final class FormImageRegisterView: UIView {
11 | weak var delegate: FormImagePickerDelegate?
12 |
13 | private lazy var titleLabel: UILabel = {
14 | let label = UILabel()
15 | label.text = "upload challenge main image".localized
16 | label.font = .boldSystemFont(ofSize: 20)
17 | label.numberOfLines = 2
18 | return label
19 | }()
20 | private lazy var imageView: UIImageView = {
21 | let imageView = UIImageView()
22 | imageView.image = UIImage(systemName: "camera")
23 | imageView.tintColor = UIColor(named: "SystemForeground")
24 | imageView.contentMode = .scaleAspectFit
25 | imageView.clipsToBounds = true
26 | imageView.layer.cornerRadius = 10
27 | imageView.layer.borderWidth = 1
28 | imageView.layer.borderColor = UIColor(named: "DayColor")?.cgColor
29 | return imageView
30 | }()
31 |
32 | override init(frame: CGRect) {
33 | super.init(frame: frame)
34 | configure()
35 | }
36 |
37 | required init?(coder: NSCoder) {
38 | super.init(coder: coder)
39 | configure()
40 | }
41 |
42 | convenience init() {
43 | self.init(frame: CGRect.zero)
44 | }
45 |
46 | func updateImage(_ image: UIImage) {
47 | imageView.image = image
48 | imageView.contentMode = .scaleAspectFill
49 | }
50 | }
51 |
52 | extension FormImageRegisterView {
53 | private func configure() {
54 | configureSubviews()
55 | configureGesture()
56 | }
57 |
58 | private func configureSubviews() {
59 | addSubview(titleLabel)
60 | titleLabel.anchor(top: titleLabel.superview?.topAnchor,
61 | width: UIScreen.main.bounds.width - 40)
62 |
63 | addSubview(imageView)
64 | imageView.anchor(centerX: imageView.superview?.centerXAnchor,
65 | top: titleLabel.bottomAnchor, paddingTop: 30,
66 | width: 150, height: 150)
67 |
68 | anchor(bottom: imageView.bottomAnchor)
69 | }
70 |
71 | private func configureGesture() {
72 | let recognizer = UITapGestureRecognizer(
73 | target: self,
74 | action: #selector(didTappedImageView(_:))
75 | )
76 | imageView.isUserInteractionEnabled = true
77 | imageView.addGestureRecognizer(recognizer)
78 | }
79 |
80 | @objc private func didTappedImageView(_ sender: UITapGestureRecognizer) {
81 | guard sender.state == .ended else { return }
82 | delegate?.didTappedImageView(FormViewController.InputTag.image.rawValue)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Form/Subviews/FormIntroductionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormIntroductionView.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/09.
6 | //
7 |
8 | import UIKit
9 |
10 | final class FormIntroductionView: UIView {
11 | typealias Tag = FormViewController.InputTag
12 |
13 | weak var delegate: UITextViewDelegate? {
14 | didSet {
15 | textView.delegate = delegate
16 | }
17 | }
18 |
19 | private lazy var titleLabel: UILabel = {
20 | let label = UILabel()
21 | label.text = "introduce challenge".localized
22 | label.font = .boldSystemFont(ofSize: 20)
23 | return label
24 | }()
25 | private lazy var descriptionLabel: UILabel = {
26 | let label = UILabel()
27 | label.text = "introduce challenge 150".localized
28 | label.font = .systemFont(ofSize: 16)
29 | label.textColor = UIColor(named: "DayColor")
30 | label.numberOfLines = 2
31 | return label
32 | }()
33 | private lazy var textView: UITextView = {
34 | let textView = UITextView()
35 | textView.layer.borderColor = UIColor(named: "SystemForeground")?.cgColor
36 | textView.layer.borderWidth = 1
37 | textView.layer.cornerRadius = 10
38 | textView.font = .systemFont(ofSize: 16)
39 | textView.tag = Tag.introduction.rawValue
40 | return textView
41 | }()
42 |
43 | override init(frame: CGRect) {
44 | super.init(frame: frame)
45 | configure()
46 | }
47 |
48 | required init?(coder: NSCoder) {
49 | super.init(coder: coder)
50 | configure()
51 | }
52 |
53 | convenience init() {
54 | self.init(frame: CGRect.zero)
55 | }
56 |
57 | func hideKeyboard() {
58 | textView.endEditing(true)
59 | }
60 |
61 | func update(introduction: String) {
62 | textView.text = introduction
63 | }
64 | }
65 |
66 | extension FormIntroductionView {
67 | private func configure() {
68 | configureSubviews()
69 | }
70 |
71 | private func configureSubviews() {
72 | addSubview(titleLabel)
73 | titleLabel.anchor(top: titleLabel.superview?.topAnchor,
74 | width: UIScreen.main.bounds.width - 40)
75 |
76 | addSubview(descriptionLabel)
77 | descriptionLabel.anchor(top: titleLabel.bottomAnchor, paddingTop: 10,
78 | width: UIScreen.main.bounds.width - 40)
79 |
80 | addSubview(textView)
81 | textView.anchor(top: descriptionLabel.bottomAnchor, paddingTop: 20,
82 | width: UIScreen.main.bounds.width - 40,
83 | height: 150)
84 |
85 | anchor(bottom: textView.bottomAnchor)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Form/Subviews/FormTitleView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormTitleView.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/09.
6 | //
7 |
8 | import UIKit
9 |
10 | final class FormTitleView: UIView {
11 | typealias Tag = FormViewController.InputTag
12 |
13 | weak var delegate: UITextFieldDelegate? {
14 | didSet {
15 | textField.delegate = delegate
16 | }
17 | }
18 |
19 | private lazy var titleLabel: UILabel = {
20 | let label = UILabel()
21 | label.text = "write challenge name".localized
22 | label.font = .boldSystemFont(ofSize: 20)
23 | label.numberOfLines = 2
24 | return label
25 | }()
26 | private lazy var descriptionLabel: UILabel = {
27 | let label = UILabel()
28 | label.text = "don't use bad words".localized
29 | label.font = .systemFont(ofSize: 16)
30 | label.numberOfLines = 2
31 | label.textColor = UIColor(named: "DayColor")
32 | return label
33 | }()
34 | private lazy var textField: UITextField = {
35 | let textField = UITextField()
36 | textField.placeholder = "ex) wake up at 6am".localized
37 | textField.borderStyle = .roundedRect
38 | textField.tag = Tag.title.rawValue
39 | return textField
40 | }()
41 |
42 | override init(frame: CGRect) {
43 | super.init(frame: frame)
44 | configure()
45 | }
46 |
47 | required init?(coder: NSCoder) {
48 | super.init(coder: coder)
49 | configure()
50 | }
51 |
52 | convenience init() {
53 | self.init(frame: CGRect.zero)
54 | }
55 |
56 | func hideKeyboard() {
57 | textField.endEditing(true)
58 | }
59 |
60 | func update(title: String) {
61 | textField.text = title
62 | }
63 | }
64 |
65 | extension FormTitleView {
66 | private func configure() {
67 | configureSubviews()
68 | }
69 |
70 | private func configureSubviews() {
71 | addSubview(titleLabel)
72 | titleLabel.anchor(top: titleLabel.superview?.topAnchor,
73 | width: UIScreen.main.bounds.width - 40)
74 |
75 | addSubview(descriptionLabel)
76 | descriptionLabel.anchor(top: titleLabel.bottomAnchor, paddingTop: 10,
77 | width: UIScreen.main.bounds.width - 40)
78 |
79 | addSubview(textField)
80 | textField.anchor(top: descriptionLabel.bottomAnchor, paddingTop: 20,
81 | width: UIScreen.main.bounds.width - 40)
82 |
83 | anchor(bottom: textField.bottomAnchor)
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Home/Cells/HomeCalendarDetailTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeCalendarDetailTableViewCell.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/12/01.
6 | //
7 |
8 | import UIKit
9 |
10 | final class HomeCalendarDetailTableViewCell: UITableViewCell {
11 | static let identifier: String = "HomeCalendarDetailTableViewCell"
12 |
13 | private lazy var categoryImageView: UIImageView = {
14 | let imageView = UIImageView()
15 | imageView.contentMode = .scaleAspectFit
16 | imageView.tintColor = UIColor(named: "SystemForeground")
17 | return imageView
18 | }()
19 |
20 | private lazy var challengeNameLabel: UILabel = {
21 | let label = UILabel()
22 | label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
23 | return label
24 | }()
25 |
26 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
27 | super.init(style: style, reuseIdentifier: reuseIdentifier)
28 | self.configureViews()
29 | }
30 |
31 | required init?(coder: NSCoder) {
32 | super.init(coder: coder)
33 | self.configureViews()
34 | }
35 |
36 | func updateCell(challenge: Challenge) {
37 | if UIImage(systemName: challenge.category.symbol) == nil {
38 | categoryImageView.image = UIImage(named: challenge.category.symbol)
39 | } else {
40 | categoryImageView.image = UIImage(systemName: challenge.category.symbol)
41 | }
42 | categoryImageView.tintColor = UIColor(named: challenge.category.color)
43 | challengeNameLabel.text = challenge.title
44 | }
45 |
46 | override func awakeFromNib() {
47 | super.awakeFromNib()
48 | }
49 | }
50 |
51 | extension HomeCalendarDetailTableViewCell {
52 | private func configureViews() {
53 | let smallWidth = UIScreen.main.bounds.width <= 350
54 | let offset = smallWidth ? 15.0 : 20.0
55 |
56 | backgroundColor = UIColor(named: "SystemBackground")
57 |
58 | contentView.addSubview(categoryImageView)
59 | categoryImageView.anchor(leading: leadingAnchor,
60 | paddingLeading: offset,
61 | centerY: centerYAnchor,
62 | width: 30,
63 | height: 30)
64 |
65 | contentView.addSubview(challengeNameLabel)
66 | challengeNameLabel.anchor(leading: categoryImageView.trailingAnchor,
67 | paddingLeading: 20,
68 | trailing: contentView.trailingAnchor,
69 | centerY: centerYAnchor)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Launch/Subviews/LaunchView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LaunchView.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/22.
6 | //
7 |
8 | import UIKit
9 |
10 | final class LaunchView: UIView {
11 | private lazy var launchImageView: UIImageView = {
12 | let imageView = UIImageView(image: UIImage(named: "Logo"))
13 | imageView.contentMode = .scaleAspectFit
14 | return imageView
15 | }()
16 |
17 | override init(frame: CGRect) {
18 | super.init(frame: frame)
19 | self.configureViews()
20 | self.animation()
21 | }
22 |
23 | required init?(coder: NSCoder) {
24 | super.init(coder: coder)
25 | self.configureViews()
26 | self.animation()
27 | }
28 |
29 | convenience init() {
30 | self.init(frame: CGRect.zero)
31 | }
32 | }
33 |
34 | extension LaunchView {
35 | private func configureViews() {
36 | backgroundColor = UIColor(named: "SystemBackground")
37 | addSubview(launchImageView)
38 |
39 | launchImageView.anchor(leading: leadingAnchor,
40 | paddingLeading: 30,
41 | trailing: trailingAnchor,
42 | paddingTrailing: 30,
43 | centerX: centerXAnchor,
44 | centerY: centerYAnchor)
45 | }
46 |
47 | private func animation() {
48 | UIView.animate(withDuration: 0.5) {
49 | self.launchImageView.alpha = 0
50 | } completion: { _ in
51 | self.removeFromSuperview()
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Manage/Cells/ManageAddCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ManageAddCollectionViewCell.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/21.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ManageAddCollectionViewCell: UICollectionViewCell {
11 | static let identifier = "ManageAddCollectionViewCell"
12 |
13 | private lazy var titleLabel: UILabel = {
14 | let label = UILabel()
15 | label.text = "my challenges".localized
16 | label.font = UIFont.systemFont(ofSize: UIScreen.main.bounds.width <= 350 ? 30 : 34,
17 | weight: .bold)
18 | return label
19 | }()
20 |
21 | private lazy var addChallengeView = ChallengePromotionView()
22 |
23 | weak var delegate: ChallengePromotionViewDelegate? {
24 | didSet {
25 | addChallengeView.delegate = delegate
26 | }
27 | }
28 |
29 | override init(frame: CGRect) {
30 | super.init(frame: frame)
31 | configureView()
32 | }
33 |
34 | required init?(coder: NSCoder) {
35 | super.init(coder: coder)
36 | }
37 | }
38 |
39 | extension ManageAddCollectionViewCell {
40 | private func configureView() {
41 | let smallWidth = UIScreen.main.bounds.width <= 350
42 | let offset = smallWidth ? 28.0 : 32.0
43 |
44 | anchor(height: 250 + offset)
45 |
46 | addSubview(titleLabel)
47 | titleLabel.anchor(horizontal: self, top: topAnchor, paddingTop: offset, height: 80)
48 |
49 | addSubview(addChallengeView)
50 | addChallengeView.anchor(horizontal: self, top: titleLabel.bottomAnchor, paddingTop: 10)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Manage/Cells/ManageCollectionViewHeader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ManageCollectionViewHeader.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/20.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ManageCollectionViewHeader: UICollectionReusableView {
11 | enum Section: Int {
12 | case participating = 1, created, ended
13 |
14 | var title: String {
15 | switch self {
16 | case .participating:
17 | return "participatingChallenges".localized
18 | case .created:
19 | return "createdChallenges".localized
20 | case .ended:
21 | return "endedChallenges".localized
22 | }
23 | }
24 | }
25 |
26 | static let identifier = "ManageCollectionViewHeader"
27 |
28 | private lazy var label: UILabel = {
29 | let label = UILabel()
30 | label.textColor = UIColor(named: "SystemForeground")
31 | label.font = UIFont.boldSystemFont(ofSize: 18)
32 | return label
33 | }()
34 |
35 | private lazy var toggleImageView: UIImageView = {
36 | let imageView = UIImageView()
37 | imageView.image = UIImage(systemName: "chevron.down")
38 | imageView.tintColor = UIColor(named: "SystemForeground")
39 | return imageView
40 | }()
41 |
42 | private(set) var section: Section?
43 | public var isExpanded: Bool = true {
44 | didSet {
45 | let angle = isExpanded ? 0 : -Double.pi / 2
46 | DispatchQueue.main.async { [weak self] in
47 | guard let self = self else { return }
48 | UIView.animate(withDuration: 0.3) {
49 | self.toggleImageView.transform = CGAffineTransform(rotationAngle: angle)
50 | }
51 | }
52 | }
53 | }
54 |
55 | func updateViews(section: Section) {
56 | self.section = section
57 | label.text = section.title
58 |
59 | addSubview(label)
60 | label.anchor(leading: leadingAnchor, centerY: centerYAnchor)
61 |
62 | addSubview(toggleImageView)
63 | toggleImageView.anchor(trailing: trailingAnchor, centerY: centerYAnchor)
64 | }
65 |
66 | func didTouchedHeader() {
67 | isExpanded.toggle()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Manage/Layouts/ManageCollectionViewLayouts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ManageCollectionViewLayouts.swift
3 | // Routinus
4 | //
5 | // Created by 김민서 on 2021/11/12.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ManageCollectionViewLayouts {
11 | private var addLayout: NSCollectionLayoutSection {
12 | let smallWidth = UIScreen.main.bounds.width <= 350
13 | let offset = smallWidth ? 15.0 : 20.0
14 |
15 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
16 | heightDimension: .fractionalHeight(1))
17 | let item = NSCollectionLayoutItem(layoutSize: itemSize)
18 |
19 | let groupSize = NSCollectionLayoutSize(
20 | widthDimension: .fractionalWidth(1),
21 | heightDimension: .estimated(250 + (smallWidth ? 28 : 32))
22 | )
23 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
24 | subitem: item,
25 | count: 1)
26 |
27 | let section = NSCollectionLayoutSection(group: group)
28 | section.orthogonalScrollingBehavior = .none
29 | section.contentInsets = .init(top: 0, leading: offset, bottom: 10, trailing: offset)
30 | return section
31 | }
32 |
33 | private var challengeLayout: NSCollectionLayoutSection {
34 | let smallWidth = UIScreen.main.bounds.width <= 350
35 | let offset = smallWidth ? 15.0 : 25.0
36 |
37 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5),
38 | heightDimension: .estimated(140))
39 | let item = NSCollectionLayoutItem(layoutSize: itemSize)
40 |
41 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
42 | heightDimension: .estimated(140))
43 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
44 | subitem: item,
45 | count: 2)
46 | group.interItemSpacing = .fixed(20)
47 |
48 | let section = NSCollectionLayoutSection(group: group)
49 | section.boundarySupplementaryItems = [.init(
50 | layoutSize: .init(widthDimension: .fractionalWidth(1),
51 | heightDimension: .absolute(70)),
52 | elementKind: UICollectionView.elementKindSectionHeader,
53 | alignment: .topLeading
54 | )]
55 | section.orthogonalScrollingBehavior = .none
56 | section.contentInsets = .init(top: 10, leading: offset, bottom: 10, trailing: offset)
57 | section.interGroupSpacing = 30
58 | return section
59 | }
60 |
61 | func section(at sectionNumber: Int) -> NSCollectionLayoutSection {
62 | if sectionNumber == 0 {
63 | return addLayout
64 | } else {
65 | return challengeLayout
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/MyPage/Delegates/MyPageUserNameUpdatableDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyPageUserNameUpdatableDelegate.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/22.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol MyPageUserNameUpdatableDelegate: AnyObject {
11 | func didTappedNameStackView()
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/MyPage/MyPageViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyPageViewModel.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/22.
6 | //
7 |
8 | import Combine
9 | import Foundation
10 |
11 | protocol MyPageViewModelInput {
12 | func fetchUser()
13 | func fetchThemeStyle()
14 | func updateUsername(_ name: String)
15 | func updateThemeStyle(_ style: Int)
16 | }
17 |
18 | protocol MyPageViewModelOutput {
19 | var user: CurrentValueSubject { get }
20 | var themeStyle: CurrentValueSubject { get }
21 | }
22 |
23 | protocol MyPageViewModelIO: MyPageViewModelInput, MyPageViewModelOutput { }
24 |
25 | final class MyPageViewModel: MyPageViewModelIO {
26 | var user = CurrentValueSubject(User())
27 | var themeStyle = CurrentValueSubject(0)
28 |
29 | var cancellables = Set()
30 |
31 | var userFetchUsecase: UserFetchableUsecase
32 | var userUpdateUsecase: UserUpdatableUsecase
33 |
34 | let userCreatePublisher = NotificationCenter.default.publisher(
35 | for: UserCreateUsecase.didCreateUser,
36 | object: nil
37 | )
38 | let userUpdatePublisher = NotificationCenter.default.publisher(
39 | for: UserUpdateUsecase.didUpdateUser,
40 | object: nil
41 | )
42 |
43 | init(userFetchUsecase: UserFetchableUsecase, userUpdateUsecase: UserUpdatableUsecase) {
44 | self.userFetchUsecase = userFetchUsecase
45 | self.userUpdateUsecase = userUpdateUsecase
46 | configure()
47 | fetchUser()
48 | fetchThemeStyle()
49 | }
50 | }
51 |
52 | extension MyPageViewModel {
53 | private func configure() {
54 | configurePublishers()
55 | }
56 |
57 | private func configurePublishers() {
58 | userCreatePublisher
59 | .receive(on: RunLoop.main)
60 | .sink { [weak self] _ in
61 | guard let self = self else { return }
62 | self.fetchUser()
63 | }
64 | .store(in: &cancellables)
65 |
66 | userUpdatePublisher
67 | .receive(on: RunLoop.main)
68 | .sink { [weak self] _ in
69 | guard let self = self else { return }
70 | self.fetchUser()
71 | }
72 | .store(in: &cancellables)
73 | }
74 | }
75 |
76 | extension MyPageViewModel: MyPageViewModelInput {
77 | func fetchUser() {
78 | guard let id = userFetchUsecase.fetchUserID() else { return }
79 | userFetchUsecase.fetchUser(id: id) { [weak self] user in
80 | guard let self = self else { return }
81 | self.user.value = user
82 | }
83 | }
84 |
85 | func fetchThemeStyle() {
86 | themeStyle.value = userFetchUsecase.fetchThemeStyle()
87 | }
88 |
89 | func updateUsername(_ name: String) {
90 | userUpdateUsecase.updateUsername(of: user.value.id, name: name)
91 | }
92 |
93 | func updateThemeStyle(_ style: Int) {
94 | userUpdateUsecase.updateThemeStyle(style)
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/MyPage/Subviews/MyPageProfileView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyPageProfileView.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/22.
6 | //
7 |
8 | import UIKit
9 |
10 | final class MyPageProfileView: UIView {
11 | weak var delegate: MyPageUserNameUpdatableDelegate?
12 |
13 | private lazy var imageView: UIImageView = {
14 | let imageView = UIImageView()
15 | imageView.contentMode = .scaleAspectFit
16 | return imageView
17 | }()
18 | private lazy var recognizer = UITapGestureRecognizer(target: self,
19 | action: #selector(didTapNameStackView))
20 | private lazy var nameStackView: UIStackView = {
21 | let stackView = UIStackView()
22 | stackView.axis = .horizontal
23 | stackView.spacing = 10
24 | stackView.addGestureRecognizer(recognizer)
25 | return stackView
26 | }()
27 | private lazy var nameLabel: UILabel = {
28 | let label = UILabel()
29 | label.font = UIFont.systemFont(ofSize: 20)
30 | label.text = "username"
31 | return label
32 | }()
33 | private lazy var updateIconImageView: UIImageView = {
34 | let imageView = UIImageView()
35 | imageView.image = UIImage(systemName: "pencil")
36 | imageView.tintColor = UIColor(named: "SystemForeground")
37 | return imageView
38 | }()
39 |
40 | var name: String? {
41 | return nameLabel.text
42 | }
43 |
44 | override init(frame: CGRect) {
45 | super.init(frame: frame)
46 | configure()
47 | }
48 |
49 | required init?(coder: NSCoder) {
50 | super.init(coder: coder)
51 | configure()
52 | }
53 |
54 | func updateName(_ name: String) {
55 | nameLabel.text = name
56 | }
57 |
58 | func updateImage(with user: User) {
59 | imageView.image = UIImage(named: ContinuityState.image(for: user.continuityDay))
60 | }
61 | }
62 |
63 | extension MyPageProfileView {
64 | private func configure() {
65 | configureViews()
66 | }
67 |
68 | private func configureViews() {
69 | addSubview(imageView)
70 | imageView.anchor(centerX: centerXAnchor,
71 | top: topAnchor,
72 | paddingTop: 10,
73 | width: 120,
74 | height: 120)
75 |
76 | addSubview(nameStackView)
77 | nameStackView.anchor(centerX: centerXAnchor,
78 | top: imageView.bottomAnchor,
79 | paddingTop: 20)
80 |
81 | nameStackView.addArrangedSubview(nameLabel)
82 | nameStackView.addArrangedSubview(updateIconImageView)
83 | }
84 |
85 | @objc private func didTapNameStackView() {
86 | delegate?.didTappedNameStackView()
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Search/Cells/SearchCollectionViewHeader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchCollectionViewHeader.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/09.
6 | //
7 |
8 | import UIKit
9 |
10 | final class SearchCollectionViewHeader: UICollectionReusableView {
11 | static let identifier = "SearchCollectionViewHeader"
12 |
13 | private lazy var label: UILabel = {
14 | let label = UILabel()
15 | label.textColor = UIColor(named: "SystemForeground")
16 | label.font = UIFont.boldSystemFont(ofSize: 18)
17 | return label
18 | }()
19 |
20 | override init(frame: CGRect) {
21 | super.init(frame: frame)
22 | configure()
23 | }
24 |
25 | required init?(coder: NSCoder) {
26 | super.init(coder: coder)
27 | configure()
28 | }
29 |
30 | func updateTitle(_ title: String) {
31 | label.text = title
32 | }
33 | }
34 |
35 | extension SearchCollectionViewHeader {
36 | private func configure() {
37 | configureViews()
38 | }
39 |
40 | private func configureViews() {
41 | addSubview(label)
42 | label.anchor(leading: label.superview?.leadingAnchor,
43 | centerY: label.superview?.centerYAnchor)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Search/Cells/SearchPopularKeywordCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchPopularKeywordCollectionViewCell.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/09.
6 | //
7 |
8 | import UIKit
9 |
10 | final class SearchPopularKeywordCollectionViewCell: UICollectionViewCell {
11 | static let identifier = "SearchPopularKeywordCollectionViewCell"
12 |
13 | weak var delegate: SearchPopularKeywordDelegate?
14 |
15 | private lazy var popularKeywordButton: SearchPopularKeywordButton = {
16 | let button = SearchPopularKeywordButton()
17 | button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
18 | button.backgroundColor = UIColor(named: "SundayColor")
19 | button.layer.borderWidth = 1
20 | button.layer.borderColor = UIColor(named: "SundayColor")?.cgColor
21 | button.layer.cornerRadius = 15
22 | button.addTarget(self, action: #selector(didTappedPopularKeyword), for: .touchUpInside)
23 | return button
24 | }()
25 |
26 | override init(frame: CGRect) {
27 | super.init(frame: frame)
28 | configure()
29 | }
30 |
31 | required init?(coder: NSCoder) {
32 | super.init(coder: coder)
33 | configure()
34 | }
35 |
36 | func updateKeyword(_ keyword: String) {
37 | popularKeywordButton.keyword = keyword
38 | popularKeywordButton.setTitle(keyword, for: .normal)
39 | }
40 | }
41 |
42 | extension SearchPopularKeywordCollectionViewCell {
43 | private func configure() {
44 | configureViews()
45 | }
46 |
47 | private func configureViews() {
48 | addSubview(popularKeywordButton)
49 | popularKeywordButton.anchor(horizontal: popularKeywordButton.superview,
50 | vertical: popularKeywordButton.superview)
51 | }
52 |
53 | @objc private func didTappedPopularKeyword(_ sender: SearchPopularKeywordButton) {
54 | delegate?.didTappedKeywordButton(keyword: sender.keyword)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Search/Delegates/SearchPopularKeywordDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchPopularKeywordDelegate.swift
3 | // Routinus
4 | //
5 | // Created by 유석환 on 2021/11/30.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol SearchPopularKeywordDelegate: AnyObject {
11 | func didTappedKeywordButton(keyword: String?)
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Search/Layouts/SearchCollectionViewLayouts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchCollectionViewLayouts.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/09.
6 | //
7 |
8 | import UIKit
9 |
10 | final class SearchCollectionViewLayouts {
11 | private var pupularSearchKeywordLayout: NSCollectionLayoutSection {
12 | let smallWidth = UIScreen.main.bounds.width <= 350
13 | let offset = smallWidth ? 15.0 : 20.0
14 |
15 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
16 | heightDimension: .fractionalHeight(1))
17 | let item = NSCollectionLayoutItem(layoutSize: itemSize)
18 |
19 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.17),
20 | heightDimension: .estimated(30))
21 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
22 | subitems: [item])
23 |
24 | let section = NSCollectionLayoutSection(group: group)
25 | section.boundarySupplementaryItems = [.init(
26 | layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50)),
27 | elementKind: UICollectionView.elementKindSectionHeader,
28 | alignment: .topLeading
29 | )]
30 | section.orthogonalScrollingBehavior = .groupPaging
31 | section.contentInsets = .init(top: 5, leading: offset, bottom: 20, trailing: offset)
32 | section.interGroupSpacing = 20
33 | return section
34 | }
35 |
36 | private var challengeLayout: NSCollectionLayoutSection {
37 | let smallWidth = UIScreen.main.bounds.width <= 350
38 | let offset = smallWidth ? 15.0 : 20.0
39 |
40 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5),
41 | heightDimension: .estimated(140))
42 | let item = NSCollectionLayoutItem(layoutSize: itemSize)
43 |
44 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
45 | heightDimension: .estimated(140))
46 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
47 | subitem: item,
48 | count: 2)
49 | group.interItemSpacing = .fixed(15)
50 |
51 | let section = NSCollectionLayoutSection(group: group)
52 | section.boundarySupplementaryItems = [.init(
53 | layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50)),
54 | elementKind: UICollectionView.elementKindSectionHeader, alignment: .topLeading
55 | )]
56 | section.orthogonalScrollingBehavior = .none
57 | section.contentInsets = .init(top: 10, leading: offset, bottom: 10, trailing: offset)
58 | section.interGroupSpacing = 30
59 | return section
60 | }
61 |
62 | func section(at sectionNumber: Int) -> NSCollectionLayoutSection {
63 | if sectionNumber == 0 {
64 | return pupularSearchKeywordLayout
65 | } else {
66 | return challengeLayout
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Search/Subviews/SearchBarContainerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchBarContainerView.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/11.
6 | //
7 |
8 | import UIKit
9 |
10 | final class SearchBarContainerView: UIView {
11 | private lazy var searchBar: UISearchBar = {
12 | let searchBar = UISearchBar()
13 | return searchBar
14 | }()
15 |
16 | weak var delegate: UISearchBarDelegate? {
17 | didSet {
18 | searchBar.delegate = delegate
19 | }
20 | }
21 |
22 | init() {
23 | super.init(frame: CGRect.zero)
24 | configure()
25 | }
26 |
27 | required init?(coder aDecoder: NSCoder) {
28 | super.init(coder: aDecoder)
29 | configure()
30 | }
31 |
32 | override func layoutSubviews() {
33 | super.layoutSubviews()
34 | searchBar.frame = bounds
35 | }
36 |
37 | func hideKeyboard() {
38 | searchBar.endEditing(true)
39 | }
40 |
41 | func updateSearchBar(keyword: String) {
42 | searchBar.text = keyword
43 | }
44 | }
45 |
46 | extension SearchBarContainerView {
47 | private func configure() {
48 | self.addSubview(searchBar)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Presentation/Search/Subviews/SearchPopularKeywordButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchPopularKeywordButton.swift
3 | // Routinus
4 | //
5 | // Created by 박상우 on 2021/11/10.
6 | //
7 |
8 | import UIKit
9 |
10 | final class SearchPopularKeywordButton: UIButton {
11 | var keyword: String?
12 |
13 | override init(frame: CGRect) {
14 | super.init(frame: frame)
15 | }
16 |
17 | required init?(coder: NSCoder) {
18 | super.init(coder: coder)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-40.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-72.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-76.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-small-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-small-50.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-small.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/icon@2x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/ios-marketing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/ios-marketing.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Icons/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Icons/challenge.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "challenge@x1.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "challenge@x2.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "challenge@x3.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Icons/challenge.imageset/challenge@x1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/Icons/challenge.imageset/challenge@x1.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Icons/challenge.imageset/challenge@x2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/Icons/challenge.imageset/challenge@x2.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Icons/challenge.imageset/challenge@x3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/Icons/challenge.imageset/challenge@x3.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Icons/check.calendar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "calendar.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "auto-scaling" : "auto",
14 | "preserves-vector-representation" : true,
15 | "template-rendering-intent" : "template"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Icons/check.calendar.imageset/calendar.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Icons/dumbbell.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "dumbbell.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Icons/dumbbell.imageset/dumbbell.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
58 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "logo@x1.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "logo@x2.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "logo@x3.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Logo.imageset/logo@x1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/Logo.imageset/logo@x1.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Logo.imageset/logo@x2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/Logo.imageset/logo@x2.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/Logo.imageset/logo@x3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/Logo.imageset/logo@x3.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/achievement/1-19.imageset/0-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/achievement/1-19.imageset/0-20.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/achievement/1-19.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "0-20.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/achievement/100.imageset/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/achievement/100.imageset/100.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/achievement/100.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "100.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/achievement/20-39.imageset/20-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/achievement/20-39.imageset/20-40.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/achievement/20-39.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "20-40.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/achievement/40-65.imageset/40-70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/achievement/40-65.imageset/40-70.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/achievement/40-65.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "40-70.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/achievement/66-99.imageset/70-99.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/iOS03-Routinus/75f52a87ac74fd8e3f40a9814e8f363c2237fece/Routinus/Routinus/Resources/Assets.xcassets/achievement/66-99.imageset/70-99.png
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/achievement/66-99.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "70-99.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/achievement/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Black.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.100",
9 | "green" : "0.100",
10 | "red" : "0.100"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.100",
27 | "green" : "0.100",
28 | "red" : "0.100"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Calendar/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Calendar/DayColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.400",
9 | "green" : "0.400",
10 | "red" : "0.400"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.700",
27 | "green" : "0.700",
28 | "red" : "0.700"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Calendar/SaturdayColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "0.620",
10 | "red" : "0.420"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "1.000",
27 | "green" : "0.620",
28 | "red" : "0.420"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Calendar/SundayColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.467",
9 | "green" : "0.467",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.467",
27 | "green" : "0.467",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Category/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Category/ETCColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.627",
9 | "green" : "0.686",
10 | "red" : "0.902"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.627",
27 | "green" : "0.686",
28 | "red" : "0.902"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Category/EmotionManageColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.706",
9 | "green" : "0.627",
10 | "red" : "0.902"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.706",
27 | "green" : "0.627",
28 | "red" : "0.902"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Category/ExerciseColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.902",
9 | "green" : "0.843",
10 | "red" : "0.745"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.902",
27 | "green" : "0.843",
28 | "red" : "0.745"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Category/FinanceColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.627",
9 | "green" : "0.824",
10 | "red" : "0.902"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.627",
27 | "green" : "0.824",
28 | "red" : "0.902"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Category/HobbyColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.741",
9 | "green" : "0.635",
10 | "red" : "0.796"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.741",
27 | "green" : "0.635",
28 | "red" : "0.796"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Category/LifeStyleColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.902",
9 | "green" : "0.702",
10 | "red" : "0.627"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.902",
27 | "green" : "0.702",
28 | "red" : "0.627"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Category/ReadColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.855",
9 | "green" : "0.745",
10 | "red" : "0.800"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.855",
27 | "green" : "0.745",
28 | "red" : "0.800"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Category/StudyColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "0.871",
10 | "red" : "0.890"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "1.000",
27 | "green" : "0.871",
28 | "red" : "0.890"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/EndDateColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.467",
9 | "green" : "0.467",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.467",
27 | "green" : "0.467",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/LightGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.969",
9 | "green" : "0.949",
10 | "red" : "0.949"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.180",
27 | "green" : "0.173",
28 | "red" : "0.173"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/MainColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.627",
9 | "green" : "0.906",
10 | "red" : "0.706"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.384",
27 | "green" : "0.843",
28 | "red" : "0.514"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/MainColor0.5.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.816",
9 | "green" : "0.953",
10 | "red" : "0.855"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xD0",
27 | "green" : "0xF3",
28 | "red" : "0xDA"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/MainColorReverse.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.384",
9 | "green" : "0.843",
10 | "red" : "0.514"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.627",
27 | "green" : "0.906",
28 | "red" : "0.706"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/SystemBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.000",
27 | "green" : "0.000",
28 | "red" : "0.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/SystemForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.120",
9 | "green" : "0.120",
10 | "red" : "0.120"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.950",
27 | "green" : "0.950",
28 | "red" : "0.950"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/WeekColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "209",
9 | "green" : "209",
10 | "red" : "252"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "152",
27 | "green" : "152",
28 | "red" : "255"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/colors/White.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "1.000",
27 | "green" : "1.000",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/seed/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/seed/seed1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "seed1.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/seed/seed1.imageset/seed1.svg:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/seed/seed2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "seed2.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/seed/seed3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "seed3.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/seed/seed4.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "seed4.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/seed/seed5.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "seed5.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/seed/seed6.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "seed6.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Assets.xcassets/seed/seed7.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "seed7.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Routinus/Routinus/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIApplicationSceneManifest
6 |
7 | UIApplicationSupportsMultipleScenes
8 |
9 | UISceneConfigurations
10 |
11 | UIWindowSceneSessionRoleApplication
12 |
13 |
14 | UISceneConfigurationName
15 | Default Configuration
16 | UISceneDelegateClassName
17 | $(PRODUCT_MODULE_NAME).SceneDelegate
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/AchievementFetchableUsecaseMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AchievementFetchableUsecaseMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/11/30.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class AchievementFetchableUsecaseMock: AchievementFetchableUsecase {
12 | func fetchAchievements(yearMonth: String, completion: @escaping ([Achievement]) -> Void) {
13 | let achievements: [Achievement] = [Achievement(yearMonth: "202111",
14 | day: "29",
15 | achievementCount: 2,
16 | totalCount: 4),
17 | Achievement(yearMonth: "202111",
18 | day: "30",
19 | achievementCount: 4,
20 | totalCount: 5)]
21 | completion(achievements)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/AchievementUpdatableUsecaseMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AchievementUpdatableUsecaseMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/11/30.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class AchievementUpdatableUsecaseMock: AchievementUpdatableUsecase {
12 | func updateAchievementCount() {
13 | print("update AchievementCount")
14 | }
15 |
16 | func updateTotalCount() {
17 | print("update TotalCount")
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/AuthFetchableUsecaseMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthFetchableUsecaseMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/11/30.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class AuthFetchableUsecaseMock: AuthFetchableUsecase {
12 | func fetchAuth(challengeID: String, completion: @escaping (Auth?) -> Void) {
13 | completion(nil)
14 | }
15 |
16 | func fetchAuths(challengeID: String, completion: @escaping ([Auth]) -> Void) {
17 | let auths: [Auth] = [Auth(challengeID: "testChallenge2",
18 | userID: "testUserID",
19 | date: Date(dateString: "20211129"),
20 | time: Date(timeString: "1230")),
21 | Auth(challengeID: "testChallenge3",
22 | userID: "testUserID",
23 | date: Date(dateString: "20211130"),
24 | time: Date(timeString: "1330"))]
25 | completion(auths)
26 | }
27 |
28 | func fetchMyAuths(challengeID: String, completion: @escaping ([Auth]) -> Void) {
29 | let auths: [Auth] = [Auth(challengeID: "testChallenge4",
30 | userID: "testUserID",
31 | date: Date(dateString: "20211129"),
32 | time: Date(timeString: "1430")),
33 | Auth(challengeID: "testChallenge5",
34 | userID: "testUserID",
35 | date: Date(dateString: "20211130"),
36 | time: Date(timeString: "1530"))]
37 | completion(auths)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/ChallengeUpdatableUsecaseMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChallengeUpdatableUsecaseMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/11/30.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class ChallengeUpdatableUsecaseMock: ChallengeUpdatableUsecase {
12 | func endDate(startDate: Date, week: Int) -> Date? {
13 | let calendar = Calendar.current
14 | let day = DateComponents(day: week * 7)
15 | guard let endDate = calendar.date(byAdding: day, to: startDate) else { return nil }
16 | return endDate
17 | }
18 |
19 | func update(challenge: Challenge) {
20 | print("update Challenge")
21 | }
22 |
23 | func updateParticipantCount(challengeID: String) {
24 | print("update ParticipantCount")
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/ImageFetchableUsecaseMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageFetchableUsecaseMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/11/30.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class ImageFetchableUsecaseMock: ImageFetchableUsecase {
12 | func fetchImageData(from directory: String, filename: String, completion: ((Data?) -> Void)?) {
13 | completion?(Data())
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/ParticipationCreatableUsecaseMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParticipationCreatableUsecaseMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/11/30.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class ParticipationCreatableUsecaseMock: ParticipationCreatableUsecase {
12 | func createParticipation(challengeID: String) {
13 | print("create Participation")
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/ParticipationFetchableUsecaseMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParticipantionFetchableUsecaseMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/11/30.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class ParticipationFetchableUsecaseMock: ParticipationFetchableUsecase {
12 | func fetchParticipation(challengeID: String, completion: @escaping (Participation?) -> Void) {
13 | let participationDTO = ParticipationDTO(authCount: 2,
14 | challengeID: "testChallengeID",
15 | joinDate: "20211124",
16 | userID: "testUserID")
17 | let participation = Participation(participationDTO: participationDTO)
18 | completion(participation)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/ParticipationFetchableUsecaseNilMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParticipationFetchableUsecaseNilMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/12/01.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class ParticipationFetchableUsecaseNilMock: ParticipationFetchableUsecase {
12 | func fetchParticipation(challengeID: String, completion: @escaping (Participation?) -> Void) {
13 | completion(nil)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/TodayRoutineFetchableUsecaseMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TodayRoutineFetchableUsecaseMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/11/30.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class TodayRoutineFetchableUsecaseMock: TodayRoutineFetchableUsecase {
12 | func fetchTodayRoutines(completion: @escaping ([TodayRoutine]) -> Void) {
13 | let todayRoutines: [TodayRoutine] = [TodayRoutine(challengeID: "testChallenge1",
14 | category: .exercise,
15 | title: "테스트 챌린지1",
16 | authCount: 0,
17 | totalCount: 7),
18 | TodayRoutine(challengeID: "testChallenge2",
19 | category: .etc,
20 | title: "테스트 챌린지2",
21 | authCount: 1,
22 | totalCount: 14)]
23 | completion(todayRoutines)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/UserCreatableUsecaseMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserCreatableUsecaseMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/11/30.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class UserCreatableUsecaseMock: UserCreatableUsecase {
12 | func createUser() {
13 | print("User Create")
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/UserFetchableUsecaseMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserFetchableUsecaseMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/11/30.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class UserFetchableUsecaseMock: UserFetchableUsecase {
12 | func fetchUser(id: String, completion: @escaping (User) -> Void) {
13 | let user = User(id: "MockUserID",
14 | name: "차분한 고양이",
15 | continuityDay: 0,
16 | userImageCategoryID: "0",
17 | grade: 0,
18 | lastAuthDay: "20201130")
19 | completion(user)
20 | }
21 |
22 | func fetchUserID() -> String? {
23 | return "MockUserID"
24 | }
25 |
26 | func fetchThemeStyle() -> Int {
27 | return 1
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/MockDatas/UserUpdatableUsecaseMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserUpdatableUsecaseMock.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/11/30.
6 | //
7 |
8 | import XCTest
9 | @testable import Routinus
10 |
11 | class UserUpdatableUsecaseMock: UserUpdatableUsecase {
12 | func updateContinuityDay(completion: ((User) -> Void)?) {
13 | let user = User(id: "MockUserID",
14 | name: "차분한 고양이",
15 | continuityDay: 0,
16 | userImageCategoryID: "0",
17 | grade: 0,
18 | lastAuthDay: "20201130")
19 | completion?(user)
20 | }
21 |
22 | func updateContinuityDayByAuth() {
23 | print("update ContinuityDay!!")
24 | }
25 |
26 | func updateUsername(of id: String, name: String) {
27 | print("update id: \(id), userName: \(name)")
28 | }
29 |
30 | func updateThemeStyle(_ style: Int) {
31 | print("update ThemeStyle \(style)")
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Routinus/RoutinusTests/ViewModelTests/MyPageViewModelTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyPageViewModelTests.swift
3 | // RoutinusTests
4 | //
5 | // Created by 박상우 on 2021/12/01.
6 | //
7 |
8 | import Combine
9 | import XCTest
10 | @testable import Routinus
11 |
12 | class MyPageViewModelTests: XCTestCase {
13 | var myPageViewModel: MyPageViewModel!
14 | var cancellables: Set!
15 |
16 | override func setUp() {
17 | myPageViewModel = MyPageViewModel(userFetchUsecase: UserFetchableUsecaseMock(),
18 | userUpdateUsecase: UserUpdatableUsecaseMock())
19 | cancellables = []
20 | }
21 |
22 | override func tearDown() {
23 | cancellables.removeAll()
24 | }
25 |
26 | func testFetchUser() {
27 | myPageViewModel.fetchUser()
28 |
29 | let id = myPageViewModel.user.value.id
30 | let name = myPageViewModel.user.value.name
31 | let continuityDay = myPageViewModel.user.value.continuityDay
32 | let userImageCategoryID = myPageViewModel.user.value.userImageCategoryID
33 | let grade = myPageViewModel.user.value.grade
34 | let lastAuthDay = myPageViewModel.user.value.lastAuthDay
35 |
36 | XCTAssertEqual(id, "MockUserID")
37 | XCTAssertEqual(name, "차분한 고양이")
38 | XCTAssertEqual(continuityDay, 0)
39 | XCTAssertEqual(userImageCategoryID, "0")
40 | XCTAssertEqual(grade, 0)
41 | XCTAssertEqual(lastAuthDay, "20201130")
42 | }
43 |
44 | func testFetchThemeStyle() {
45 | myPageViewModel.fetchThemeStyle()
46 | let themeStyle = myPageViewModel.themeStyle.value
47 | XCTAssertEqual(themeStyle, 1)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------