├── .github
├── ISSUE_TEMPLATE
│ └── issue-raising-template.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── swiftlint.yaml
├── .gitignore
├── README.md
└── Tidify
├── .gitignore
├── Plugins
└── Tidify
│ ├── Package.swift
│ ├── Plugin.swift
│ ├── ProjectDescriptionHelpers
│ └── LocalHelper.swift
│ └── Sources
│ └── tuist-my-cli
│ └── main.swift
├── Project.swift
├── Targets
├── Tidify
│ ├── Config
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Resources
│ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── 100x100.png
│ │ │ │ ├── 1024x1024 1.png
│ │ │ │ ├── 1024x1024.png
│ │ │ │ ├── 120x120.png
│ │ │ │ ├── 144x144.png
│ │ │ │ ├── 152x152.png
│ │ │ │ ├── 167x167.png
│ │ │ │ ├── 180x180.png
│ │ │ │ ├── 20x20.png
│ │ │ │ ├── 29x29 1.png
│ │ │ │ ├── 29x29.png
│ │ │ │ ├── 29x29@2x.png
│ │ │ │ ├── 29x29@3x.png
│ │ │ │ ├── 40x40 1.png
│ │ │ │ ├── 40x40.png
│ │ │ │ ├── 50x50.png
│ │ │ │ ├── 57x57 1.png
│ │ │ │ ├── 57x57.png
│ │ │ │ ├── 58x58.png
│ │ │ │ ├── 60x60.png
│ │ │ │ ├── 72x72.png
│ │ │ │ ├── 76x76.png
│ │ │ │ ├── 80x80.png
│ │ │ │ ├── Contents.json
│ │ │ │ ├── tidify_logo-1.png
│ │ │ │ ├── tidify_logo@2x.png
│ │ │ │ └── tidify_logo@3x.png
│ │ │ ├── BookmarkCreation
│ │ │ │ ├── Contents.json
│ │ │ │ └── folderSelectIcon.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── check.png
│ │ │ │ │ ├── check@2x.png
│ │ │ │ │ └── check@3x.png
│ │ │ ├── Contents.json
│ │ │ ├── Folder
│ │ │ │ ├── Contents.json
│ │ │ │ ├── colorSelectIcon.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── check.png
│ │ │ │ │ ├── check@2x.png
│ │ │ │ │ └── check@3x.png
│ │ │ │ └── plusIcon.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── _Add.png
│ │ │ │ │ ├── _Add@2x.png
│ │ │ │ │ └── _Add@3x.png
│ │ │ ├── Home
│ │ │ │ ├── Contents.json
│ │ │ │ ├── home_exclamationMark.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── home_exclamationMark.png
│ │ │ │ │ ├── home_exclamationMark@2x.png
│ │ │ │ │ └── home_exclamationMark@3x.png
│ │ │ │ ├── home_search_bookmark.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── home_search_bookmark.png
│ │ │ │ │ ├── home_search_bookmark@2x.png
│ │ │ │ │ └── home_search_bookmark@3x.png
│ │ │ │ ├── ogIcon.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── bookmarkDefaultOg.png
│ │ │ │ │ ├── bookmarkDefaultOg@2x.png
│ │ │ │ │ └── bookmarkDefaultOg@3x.png
│ │ │ │ ├── starOffIcon.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── starOff.png
│ │ │ │ │ ├── starOff@2x.png
│ │ │ │ │ └── starOff@3x.png
│ │ │ │ └── starOnIcon.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── starOn.png
│ │ │ │ │ ├── starOn@2x.png
│ │ │ │ │ └── starOn@3x.png
│ │ │ ├── LaunchScreen_Logo.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── LaunchScreen_Logo.png
│ │ │ │ ├── LaunchScreen_Logo@2x.png
│ │ │ │ └── LaunchScreen_Logo@3x.png
│ │ │ ├── NavigationBar
│ │ │ │ ├── Contents.json
│ │ │ │ ├── backButtonIcon.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── arrows.png
│ │ │ │ │ ├── arrows@2x.png
│ │ │ │ │ └── arrows@3x.png
│ │ │ │ └── settingButtonIcon.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── setting.png
│ │ │ │ │ ├── setting@2x.png
│ │ │ │ │ └── setting@3x.png
│ │ │ ├── Onboarding
│ │ │ │ ├── Contents.json
│ │ │ │ ├── onboardingImage_0.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── onboardingImage_0.png
│ │ │ │ │ ├── onboardingImage_0@2x.png
│ │ │ │ │ └── onboardingImage_0@3x.png
│ │ │ │ ├── onboardingImage_1.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── onboardingImage_1.png
│ │ │ │ │ ├── onboardingImage_1@2x.png
│ │ │ │ │ └── onboardingImage_1@3x.png
│ │ │ │ └── onboardingImage_2.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── onboardingImage_2.png
│ │ │ │ │ ├── onboardingImage_2@2x.png
│ │ │ │ │ └── onboardingImage_2@3x.png
│ │ │ ├── Search
│ │ │ │ ├── Contents.json
│ │ │ │ ├── icon_arrowRight.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── icon_arrowRight_gray.png
│ │ │ │ │ ├── icon_arrowRight_gray@2x.png
│ │ │ │ │ └── icon_arrowRight_gray@3x.png
│ │ │ │ ├── icon_search.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── icon_search.png
│ │ │ │ │ ├── icon_search@2x.png
│ │ │ │ │ └── icon_search@3x.png
│ │ │ │ └── icon_searchField_erase.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── icon_searchField_ erase.png
│ │ │ │ │ ├── icon_searchField_ erase@2x.png
│ │ │ │ │ └── icon_searchField_ erase@3x.png
│ │ │ ├── SignIn
│ │ │ │ ├── Contents.json
│ │ │ │ ├── icon_google.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── google logo.png
│ │ │ │ │ ├── google logo@2x.png
│ │ │ │ │ └── google logo@3x.png
│ │ │ │ ├── icon_symbolColor.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── icon_symbolColor.png
│ │ │ │ │ ├── icon_symbolColor@2x.png
│ │ │ │ │ └── icon_symbolColor@3x.png
│ │ │ │ ├── login_apple_symbol.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── apple-16.png
│ │ │ │ │ ├── apple-32.png
│ │ │ │ │ └── apple-48.png
│ │ │ │ └── login_kakao_symbol.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── login_kakao_symbol.png
│ │ │ │ │ ├── login_kakao_symbol@2x.png
│ │ │ │ │ └── login_kakao_symbol@3x.png
│ │ │ └── TabBar
│ │ │ │ ├── Contents.json
│ │ │ │ ├── folderCreationIcon.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Tap Bar - Button.png
│ │ │ │ ├── Tap Bar - Button@2x.png
│ │ │ │ └── Tap Bar - Button@3x.png
│ │ │ │ ├── folderDeselectedIcon.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Tap Bar - Button.png
│ │ │ │ ├── Tap Bar - Button@2x.png
│ │ │ │ └── Tap Bar - Button@3x.png
│ │ │ │ ├── folderSelectedIcon.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Tap Bar - Button.png
│ │ │ │ ├── Tap Bar - Button@2x.png
│ │ │ │ └── Tap Bar - Button@3x.png
│ │ │ │ ├── homeDeselectedIcon.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Tap Bar - Button.png
│ │ │ │ ├── Tap Bar - Button@2x.png
│ │ │ │ └── Tap Bar - Button@3x.png
│ │ │ │ └── homeSelectedIcon.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Tap Bar - Button.png
│ │ │ │ ├── Tap Bar - Button@2x.png
│ │ │ │ └── Tap Bar - Button@3x.png
│ │ ├── LaunchScreen.storyboard
│ │ └── splashLottie.json
│ ├── Sources
│ │ └── AppDelegate.swift
│ └── Tests
│ │ └── AppTests.swift
├── TidifyCore
│ ├── Sources
│ │ ├── AppProperties.swift
│ │ ├── DIContainer.swift
│ │ ├── Extension
│ │ │ ├── Array+.swift
│ │ │ ├── Date+.swift
│ │ │ ├── ObservableType+.swift
│ │ │ └── Optional+.swift
│ │ ├── KeyChain.swift
│ │ ├── UserDefaultsStorage.swift
│ │ └── UserProperties.swift
│ └── Tests
│ │ └── TidifyCoreTests.swift
├── TidifyData
│ ├── Sources
│ │ ├── DataAssembly.swift
│ │ ├── Extension
│ │ │ └── Reactive+.swift
│ │ ├── Repositories
│ │ │ ├── DefaultBookmarkRepository.swift
│ │ │ ├── DefaultFolderDetailRepository.swift
│ │ │ ├── DefaultFolderRepository.swift
│ │ │ ├── DefaultSearchRepository.swift
│ │ │ └── DefaultUserRepository.swift
│ │ ├── Response
│ │ │ ├── APIResponse.swift
│ │ │ ├── BookmarkDTO+Mapping.swift
│ │ │ ├── BookmarkListDTO+Mapping.swift
│ │ │ ├── FolderCreationDTO+Mapping.swift
│ │ │ ├── FolderDTO+Mapping.swift
│ │ │ ├── FolderListDTO+Mapping.swift
│ │ │ ├── Responsable.swift
│ │ │ └── UserTokenDTO+Mapping.swift
│ │ └── Services
│ │ │ ├── Endpoint
│ │ │ ├── BookmarkEndpoint.swift
│ │ │ ├── EndpointType.swift
│ │ │ ├── FolderEndpoint.swift
│ │ │ └── UserEndpoint.swift
│ │ │ ├── Network
│ │ │ ├── HTTPMethod.swift
│ │ │ ├── NetworkError.swift
│ │ │ ├── NetworkMonitor.swift
│ │ │ └── NetworkProvider.swift
│ │ │ └── NetworkPlugin.swift
│ └── Tests
│ │ └── TidifyDataTests.swift
├── TidifyDomain
│ ├── Sources
│ │ ├── DomainAssembly.swift
│ │ ├── Entities
│ │ │ ├── Bookmark.swift
│ │ │ ├── Folder.swift
│ │ │ ├── Onboarding.swift
│ │ │ └── UserToken.swift
│ │ ├── Interfaces
│ │ │ ├── BookmarkRepository.swift
│ │ │ ├── FolderDetailRepository.swift
│ │ │ ├── FolderRepository.swift
│ │ │ ├── SearchRepository.swift
│ │ │ └── UserRepository.swift
│ │ ├── Requests
│ │ │ ├── BookmarkListRequest.swift
│ │ │ ├── BookmarkRequestDTO.swift
│ │ │ └── FolderRequestDTO.swift
│ │ └── UseCases
│ │ │ ├── Bookmark
│ │ │ ├── BookmarkListUseCase.swift
│ │ │ ├── CreateBookmarkUseCase.swift
│ │ │ ├── DeleteBookmarkUseCase.swift
│ │ │ ├── FavoriteBookmarkUseCase.swift
│ │ │ ├── FetchBookmarkUseCase.swift
│ │ │ └── UpdateBookmarkUseCase.swift
│ │ │ ├── Folder
│ │ │ ├── DeleteFolderUseCase.swift
│ │ │ ├── FetchFolderUseCase.swift
│ │ │ ├── FolderCreationUseCase.swift
│ │ │ ├── FolderDetailUseCase.swift
│ │ │ └── FolderListUseCase.swift
│ │ │ ├── Search
│ │ │ └── SearchUseCase.swift
│ │ │ └── User
│ │ │ └── UserUseCase.swift
│ └── Tests
│ │ ├── Bookmark
│ │ └── BookmarkUseCaseTests.swift
│ │ ├── Mocks
│ │ ├── MockBookmarkRepository.swift
│ │ ├── MockFolderRepository.swift
│ │ ├── MockSearchRepository.swift
│ │ └── Mocks.swift
│ │ └── Search
│ │ └── SearchUseCaseTests.swift
└── TidifyPresentation
│ ├── Sources
│ ├── BookmarkCreation
│ │ ├── BookmarkCreationCoordinator.swift
│ │ ├── View
│ │ │ └── BookmarkCreationViewController.swift
│ │ └── ViewModel
│ │ │ └── BookmarkCreationViewModel.swift
│ ├── Common
│ │ └── Base
│ │ │ ├── BaseViewController.swift
│ │ │ ├── Coordinator.swift
│ │ │ ├── Extension
│ │ │ ├── CGSize+.swift
│ │ │ ├── Publisher+.swift
│ │ │ ├── UICollectionView+.swift
│ │ │ ├── UIColor+.swift
│ │ │ ├── UIFont+.swift
│ │ │ ├── UIImage+.swift
│ │ │ ├── UIImageview+.swift
│ │ │ ├── UITableView+.swift
│ │ │ ├── UITextField+.swift
│ │ │ ├── UIView+.swift
│ │ │ └── UIViewController+.swift
│ │ │ ├── MainCoordinator.swift
│ │ │ ├── Protocol
│ │ │ ├── Alertable.swift
│ │ │ ├── LoadingIndicatable.swift
│ │ │ └── VIewModelType.swift
│ │ │ ├── SplashViewController.swift
│ │ │ └── View
│ │ │ ├── EmptyGuideCell.swift
│ │ │ ├── TidifyNavigationBar.swift
│ │ │ ├── TidifyRightButtonTextField.swift
│ │ │ ├── TidifyTabBar.swift
│ │ │ └── TidifyTextFieldView.swift
│ ├── Folder
│ │ ├── Cell
│ │ │ ├── FolderCreationCollectionViewCell.swift
│ │ │ └── FolderTableViewCell.swift
│ │ ├── FolderCoordinator.swift
│ │ ├── FolderCreationCoordinator.swift
│ │ ├── FolderDetailCoordinator.swift
│ │ ├── View
│ │ │ ├── FolderCreationViewController.swift
│ │ │ ├── FolderDetailViewController.swift
│ │ │ └── FolderViewController.swift
│ │ └── ViewModel
│ │ │ ├── FolderCreationViewModel.swift
│ │ │ ├── FolderDetailViewModel.swift
│ │ │ └── FolderViewModel.swift
│ ├── Home
│ │ ├── HomeCoordinator.swift
│ │ ├── VIewModel
│ │ │ └── HomeViewModel.swift
│ │ └── View
│ │ │ ├── BookmarkCell.swift
│ │ │ ├── EmptyBookmarkSearchResultCell.swift
│ │ │ ├── HomeViewController.swift
│ │ │ └── SearchHistoryCell.swift
│ ├── Onboarding
│ │ ├── OnboardingCoordinator.swift
│ │ └── View
│ │ │ ├── Cell
│ │ │ └── OnboardingCollectionViewCell.swift
│ │ │ └── OnboardingViewController.swift
│ ├── PresentationAssembly.swift
│ ├── Search
│ │ ├── Cell
│ │ │ └── SearchTableViewCell.swift
│ │ ├── SearchCoordinator.swift
│ │ ├── View
│ │ │ ├── SearchTableViewHeaderView.swift
│ │ │ └── SearchViewController.swift
│ │ └── ViewModel
│ │ │ └── SearchViewModel.swift
│ ├── Setting
│ │ ├── SettingCoordinator.swift
│ │ ├── VIew
│ │ │ ├── Cells
│ │ │ │ └── SettingCell.swift
│ │ │ ├── Headers
│ │ │ │ └── SettingSectionHeaderView.swift
│ │ │ └── SettingViewController.swift
│ │ └── ViewModel
│ │ │ └── SettingViewModel.swift
│ ├── SignIn
│ │ ├── LoginCoordinator.swift
│ │ ├── View
│ │ │ └── LoginViewController.swift
│ │ └── ViewModel
│ │ │ └── LoginViewModel.swift
│ ├── TabBar
│ │ ├── TabBarCoordinator.swift
│ │ └── View
│ │ │ └── TabBarController.swift
│ └── WebView
│ │ └── View
│ │ └── WebViewController.swift
│ └── Tests
│ ├── Home
│ ├── HomeReactorTests.swift
│ └── MockHomeCoordinator.swift
│ └── Search
│ ├── MockSearchCoordinator.swift
│ └── SearchReactorTests.swift
├── Tidify.entitlements
├── TidifyShareExtension
├── Resources
│ └── MainInterface.storyboard
├── Sources
│ └── ShareViewController.swift
└── TidifyShareExtension.entitlements
└── Tuist
├── Config.swift
├── Dependencies.swift
└── ProjectDescriptionHelpers
└── Project+Templates.swift
/.github/ISSUE_TEMPLATE/issue-raising-template.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Issue Raising Template
3 | about: 이슈 등록 템플릿
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### 이슈 유형
11 | - [ ] 기능구현
12 | - [ ] 버그
13 | - [ ] 리팩토링
14 |
15 |
16 |
17 | ### 이슈 내용
18 | **AS-IS**: 개선되어야 할 현재의 문제상황 기재
19 | **TO-BE**: 개선 방향 및 개선 이후의 기대효과 기재
20 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### 📌 Issue Number
2 |
3 | ### 📘 작업 유형
4 |
5 | - [ ] 신규 기능 추가
6 | - [ ] 버그 수정
7 | - [ ] 리팩토링
8 |
9 |
10 |
11 | ### 📙 작업 내역
12 |
13 | > 구현 내용 및 작업 내역을 기재합니다.
14 |
15 | - [ ] 작업 내역 1
16 | - [ ] 작업 내역 2
17 |
18 |
19 |
20 | ### 📋 체크리스트
21 |
22 | - [ ] PR 제목에 Prefix(Feat, Refactor, Fix...etc) 와 작업 내용을 요약하여 기재했는가?
23 | - [ ] 코딩컨벤션을 준수하는가?
24 | - [ ] PR과 관련없는 변경사항이 없는가?
25 | - [ ] 내 코드에 대한 자기 검토가 되었는가?
26 |
27 |
28 |
29 | ### 📝 PR 특이 사항
30 |
31 | > PR을 볼 때 주의깊게 봐야하거나 말하고 싶은 점으로 없을 경우 제거 이후 PR을 생성합니다.
32 |
33 | - 특이 사항 1
34 | - 특이 사항 2
35 |
36 |
37 |
--------------------------------------------------------------------------------
/.github/workflows/swiftlint.yaml:
--------------------------------------------------------------------------------
1 | name: SwiftLint
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - '.github/workflows/swiftlint.yaml'
7 | - '.swiftlint.yaml'
8 | - '**/*.swift'
9 |
10 | jobs:
11 | SwiftLint:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: GitHub Action for SwiftLint
16 | uses: norio-nomura/action-swiftlint@3.2.1
17 | - name: GitHub Action for SwiftLint with --strict
18 | uses: norio-nomura/action-swiftlint@3.2.1
19 | with:
20 | args: --strict
21 | - name: GitHub Action for SwiftLint (Only files changed in the PR)
22 | uses: norio-nomura/action-swiftlint@3.2.1
23 | env:
24 | DIFF_BASE: ${{ github.base_ref }}
25 | - name: GitHub Action for SwiftLint (Different working directory)
26 | uses: norio-nomura/action-swiftlint@3.2.1
27 | env:
28 | WORKING_DIRECTORY: Source
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | R.generated.swift
6 |
7 | *.lock
8 |
9 | ## User settings
10 | xcuserdata/
11 | .DS_Store
12 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
13 | *.xcscmblueprint
14 | *.xccheckout
15 |
16 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
17 | build/
18 | DerivedData/
19 | *.moved-aside
20 | *.pbxuser
21 | !default.pbxuser
22 | *.mode1v3
23 | !default.mode1v3
24 | *.mode2v3
25 | !default.mode2v3
26 | *.perspectivev3
27 | !default.perspectivev3
28 |
29 | ## Obj-C/Swift specific
30 | *.hmap
31 |
32 | ## App packaging
33 | *.ipa
34 | *.dSYM.zip
35 | *.dSYM
36 |
37 | ## Playgrounds
38 | timeline.xctimeline
39 | playground.xcworkspace
40 |
41 | # Swift Package Manager
42 | #
43 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
44 | # Packages/
45 | # Package.pins
46 | # Package.resolved
47 | # *.xcodeproj
48 | #
49 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
50 | # hence it is not needed unless you have added a package configuration file to your project
51 | # .swiftpm
52 |
53 | .build/
54 |
55 | # CocoaPods
56 | #
57 | # We recommend against adding the Pods directory to your .gitignore. However
58 | # you should judge for yourself, the pros and cons are mentioned at:
59 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
60 | #
61 | # Pods/
62 | #
63 |
64 | ### Projects ###
65 | *.xcodeproj
66 | *.xcworkspace
67 |
68 | ### Tuist derived files ###
69 | graph.dot
70 | Derived/
71 |
72 | ### Tuist managed dependencies ###
73 | Tuist/Dependencies
74 |
75 |
76 | # Carthage
77 | #
78 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
79 | # Carthage/Checkouts
80 |
81 | Carthage/Build/
82 |
83 | # Accio dependency management
84 | Dependencies/
85 | .accio/
86 |
87 | # fastlane
88 | #
89 | # It is recommended to not store the screenshots in the git repo.
90 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
91 | # For more information about the recommended setup visit:
92 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
93 |
94 | fastlane/report.xml
95 | fastlane/Preview.html
96 | fastlane/screenshots/**/*.png
97 | fastlane/test_output
98 |
99 | # Code Injection
100 | #
101 | # After new code Injection tools there's a generated folder /iOSInjectionProject
102 | # https://github.com/johnno1962/injectionforxcode
103 |
104 | iOSInjectionProject/
105 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tidify-iOS
2 |
3 |
4 |
5 | ## 소개
6 |
7 | Nexters 19th **테스토스테론** 팀 **tidify-iOS** 저장소입니다.
8 |
9 | 쉽고 간편한 북마크 서비스 Tidify - 티디파이
10 |
11 |
12 | ## 개발 기간
13 | - 리팩토링: 2023.01.12 ~ 2023.05.23
14 |
15 |
16 |
17 | ## Skill Stack
18 |
19 | ### Architecture
20 | - CleanArchitecture + ReactorKit + Coordinator
21 | - Tuist(Modulization)
22 |
23 | ### Networking
24 | - RxMoya
25 |
26 | ### Reactive
27 | - RxSwift
28 | - RxCocoa
29 |
30 | ### UI Layout
31 | - SnapKit
32 | - Then
33 |
34 |
35 |
36 |
37 | ## 코딩 컨벤션
38 | - [Tidify Coding Convetion](https://github.com/Nexters/tidify-iOS/wiki/Tidify-Coding-Convention)
39 |
40 |
--------------------------------------------------------------------------------
/Tidify/.gitignore:
--------------------------------------------------------------------------------
1 | ### macOS ###
2 | # General
3 | .DS_Store
4 | .AppleDouble
5 | .LSOverride
6 |
7 | # Icon must end with two
8 | Icon
9 |
10 | # Thumbnails
11 | ._*
12 |
13 | # Files that might appear in the root of a volume
14 | .DocumentRevisions-V100
15 | .fseventsd
16 | .Spotlight-V100
17 | .TemporaryItems
18 | .Trashes
19 | .VolumeIcon.icns
20 | .com.apple.timemachine.donotpresent
21 |
22 | # Directories potentially created on remote AFP share
23 | .AppleDB
24 | .AppleDesktop
25 | Network Trash Folder
26 | Temporary Items
27 | .apdisk
28 |
29 | ### Xcode ###
30 | # Xcode
31 | #
32 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
33 |
34 | ## User settings
35 | xcuserdata/
36 |
37 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
38 | *.xcscmblueprint
39 | *.xccheckout
40 |
41 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
42 | build/
43 | DerivedData/
44 | *.moved-aside
45 | *.pbxuser
46 | !default.pbxuser
47 | *.mode1v3
48 | !default.mode1v3
49 | *.mode2v3
50 | !default.mode2v3
51 | *.perspectivev3
52 | !default.perspectivev3
53 |
54 | ### Xcode Patch ###
55 | *.xcodeproj/*
56 | !*.xcodeproj/project.pbxproj
57 | !*.xcodeproj/xcshareddata/
58 | !*.xcworkspace/contents.xcworkspacedata
59 | /*.gcno
60 |
61 | ### Projects ###
62 | *.xcodeproj
63 | *.xcworkspace
64 |
65 | ### Tuist derived files ###
66 | graph.dot
67 | Derived/
68 |
69 | ### Tuist managed dependencies ###
70 | Tuist/Dependencies
71 |
--------------------------------------------------------------------------------
/Tidify/Plugins/Tidify/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.4
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "MyPlugin",
7 | products: [
8 | .executable(name: "tuist-my-cli", targets: ["tuist-my-cli"]),
9 | ],
10 | targets: [
11 | .executableTarget(
12 | name: "tuist-my-cli"
13 | ),
14 | ]
15 | )
16 |
--------------------------------------------------------------------------------
/Tidify/Plugins/Tidify/Plugin.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 |
3 | let plugin = Plugin(name: "MyPlugin")
--------------------------------------------------------------------------------
/Tidify/Plugins/Tidify/ProjectDescriptionHelpers/LocalHelper.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct LocalHelper {
4 | let name: String
5 |
6 | public init(name: String) {
7 | self.name = name
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Tidify/Plugins/Tidify/Sources/tuist-my-cli/main.swift:
--------------------------------------------------------------------------------
1 | print("Hello, from your Tuist Task")
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Config/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Debug.xcconfig
3 | // Tidify
4 | //
5 | // Created by 한상진 on 2022/10/20.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | // Configuration settings file format documentation can be found at:
10 | // https://help.apple.com/xcode/#/dev745c5c974
11 |
12 | KAKAO_NATIVE_APP_KEY = 2f2bda6aa215b8ede225cc7247fa120c
13 | BASE_URL = 118.67.134.190:8080
14 | USER_AGENT = Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1
15 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Config/Release.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Release.xcconfig
3 | // Tidify
4 | //
5 | // Created by 한상진 on 2022/10/20.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | // Configuration settings file format documentation can be found at:
10 | // https://help.apple.com/xcode/#/dev745c5c974
11 |
12 | KAKAO_NATIVE_APP_KEY = 2f2bda6aa215b8ede225cc7247fa120c
13 | BASE_URL = 118.67.134.190:8080
14 | USER_AGENT = Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1
15 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/100x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/100x100.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/1024x1024 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/1024x1024 1.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/1024x1024.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/120x120.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/144x144.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/152x152.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/167x167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/167x167.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/180x180.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/20x20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/20x20.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/29x29 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/29x29 1.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/29x29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/29x29.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/29x29@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/29x29@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/40x40 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/40x40 1.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/40x40.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/50x50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/50x50.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/57x57 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/57x57 1.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/57x57.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/58x58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/58x58.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/60x60.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/72x72.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/76x76.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/80x80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/80x80.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/tidify_logo-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/tidify_logo-1.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/tidify_logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/tidify_logo@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/tidify_logo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/AppIcon.appiconset/tidify_logo@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/BookmarkCreation/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/BookmarkCreation/folderSelectIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "check.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "check@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "check@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/BookmarkCreation/folderSelectIcon.imageset/check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/BookmarkCreation/folderSelectIcon.imageset/check.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/BookmarkCreation/folderSelectIcon.imageset/check@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/BookmarkCreation/folderSelectIcon.imageset/check@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/BookmarkCreation/folderSelectIcon.imageset/check@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/BookmarkCreation/folderSelectIcon.imageset/check@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/colorSelectIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "check.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "check@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "check@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/colorSelectIcon.imageset/check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/colorSelectIcon.imageset/check.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/colorSelectIcon.imageset/check@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/colorSelectIcon.imageset/check@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/colorSelectIcon.imageset/check@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/colorSelectIcon.imageset/check@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/plusIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "_Add.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "_Add@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "_Add@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/plusIcon.imageset/_Add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/plusIcon.imageset/_Add.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/plusIcon.imageset/_Add@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/plusIcon.imageset/_Add@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/plusIcon.imageset/_Add@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Folder/plusIcon.imageset/_Add@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_exclamationMark.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "home_exclamationMark.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "home_exclamationMark@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "home_exclamationMark@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_exclamationMark.imageset/home_exclamationMark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_exclamationMark.imageset/home_exclamationMark.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_exclamationMark.imageset/home_exclamationMark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_exclamationMark.imageset/home_exclamationMark@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_exclamationMark.imageset/home_exclamationMark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_exclamationMark.imageset/home_exclamationMark@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_search_bookmark.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "home_search_bookmark.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "home_search_bookmark@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "home_search_bookmark@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_search_bookmark.imageset/home_search_bookmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_search_bookmark.imageset/home_search_bookmark.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_search_bookmark.imageset/home_search_bookmark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_search_bookmark.imageset/home_search_bookmark@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_search_bookmark.imageset/home_search_bookmark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/home_search_bookmark.imageset/home_search_bookmark@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/ogIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "bookmarkDefaultOg.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "bookmarkDefaultOg@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "bookmarkDefaultOg@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/ogIcon.imageset/bookmarkDefaultOg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/ogIcon.imageset/bookmarkDefaultOg.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/ogIcon.imageset/bookmarkDefaultOg@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/ogIcon.imageset/bookmarkDefaultOg@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/ogIcon.imageset/bookmarkDefaultOg@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/ogIcon.imageset/bookmarkDefaultOg@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOffIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "starOff.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "starOff@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "starOff@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOffIcon.imageset/starOff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOffIcon.imageset/starOff.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOffIcon.imageset/starOff@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOffIcon.imageset/starOff@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOffIcon.imageset/starOff@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOffIcon.imageset/starOff@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOnIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "starOn.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "starOn@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "starOn@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOnIcon.imageset/starOn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOnIcon.imageset/starOn.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOnIcon.imageset/starOn@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOnIcon.imageset/starOn@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOnIcon.imageset/starOn@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Home/starOnIcon.imageset/starOn@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/LaunchScreen_Logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "LaunchScreen_Logo.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "LaunchScreen_Logo@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "LaunchScreen_Logo@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/LaunchScreen_Logo.imageset/LaunchScreen_Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/LaunchScreen_Logo.imageset/LaunchScreen_Logo.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/LaunchScreen_Logo.imageset/LaunchScreen_Logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/LaunchScreen_Logo.imageset/LaunchScreen_Logo@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/LaunchScreen_Logo.imageset/LaunchScreen_Logo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/LaunchScreen_Logo.imageset/LaunchScreen_Logo@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/backButtonIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "arrows.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "arrows@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "arrows@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "original"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/backButtonIcon.imageset/arrows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/backButtonIcon.imageset/arrows.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/backButtonIcon.imageset/arrows@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/backButtonIcon.imageset/arrows@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/backButtonIcon.imageset/arrows@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/backButtonIcon.imageset/arrows@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/settingButtonIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "setting.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "setting@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "setting@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/settingButtonIcon.imageset/setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/settingButtonIcon.imageset/setting.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/settingButtonIcon.imageset/setting@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/settingButtonIcon.imageset/setting@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/settingButtonIcon.imageset/setting@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/NavigationBar/settingButtonIcon.imageset/setting@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_0.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "onboardingImage_0.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "onboardingImage_0@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "onboardingImage_0@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_0.imageset/onboardingImage_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_0.imageset/onboardingImage_0.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_0.imageset/onboardingImage_0@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_0.imageset/onboardingImage_0@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_0.imageset/onboardingImage_0@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_0.imageset/onboardingImage_0@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "onboardingImage_1.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "onboardingImage_1@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "onboardingImage_1@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_1.imageset/onboardingImage_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_1.imageset/onboardingImage_1.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_1.imageset/onboardingImage_1@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_1.imageset/onboardingImage_1@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_1.imageset/onboardingImage_1@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_1.imageset/onboardingImage_1@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "onboardingImage_2.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "onboardingImage_2@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "onboardingImage_2@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_2.imageset/onboardingImage_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_2.imageset/onboardingImage_2.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_2.imageset/onboardingImage_2@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_2.imageset/onboardingImage_2@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_2.imageset/onboardingImage_2@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Onboarding/onboardingImage_2.imageset/onboardingImage_2@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_arrowRight.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_arrowRight_gray.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "icon_arrowRight_gray@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "icon_arrowRight_gray@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_arrowRight.imageset/icon_arrowRight_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_arrowRight.imageset/icon_arrowRight_gray.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_arrowRight.imageset/icon_arrowRight_gray@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_arrowRight.imageset/icon_arrowRight_gray@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_arrowRight.imageset/icon_arrowRight_gray@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_arrowRight.imageset/icon_arrowRight_gray@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_search.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_search.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "icon_search@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "icon_search@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_search.imageset/icon_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_search.imageset/icon_search.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_search.imageset/icon_search@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_search.imageset/icon_search@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_search.imageset/icon_search@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_search.imageset/icon_search@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_searchField_erase.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_searchField_ erase.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "icon_searchField_ erase@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "icon_searchField_ erase@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_searchField_erase.imageset/icon_searchField_ erase.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_searchField_erase.imageset/icon_searchField_ erase.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_searchField_erase.imageset/icon_searchField_ erase@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_searchField_erase.imageset/icon_searchField_ erase@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_searchField_erase.imageset/icon_searchField_ erase@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/Search/icon_searchField_erase.imageset/icon_searchField_ erase@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_google.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "google logo.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "google logo@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "google logo@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_google.imageset/google logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_google.imageset/google logo.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_google.imageset/google logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_google.imageset/google logo@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_google.imageset/google logo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_google.imageset/google logo@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_symbolColor.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_symbolColor.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "icon_symbolColor@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "icon_symbolColor@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_symbolColor.imageset/icon_symbolColor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_symbolColor.imageset/icon_symbolColor.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_symbolColor.imageset/icon_symbolColor@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_symbolColor.imageset/icon_symbolColor@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_symbolColor.imageset/icon_symbolColor@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/icon_symbolColor.imageset/icon_symbolColor@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_apple_symbol.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "apple-16.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "apple-32.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "apple-48.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_apple_symbol.imageset/apple-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_apple_symbol.imageset/apple-16.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_apple_symbol.imageset/apple-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_apple_symbol.imageset/apple-32.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_apple_symbol.imageset/apple-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_apple_symbol.imageset/apple-48.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_kakao_symbol.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "login_kakao_symbol.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "login_kakao_symbol@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "login_kakao_symbol@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_kakao_symbol.imageset/login_kakao_symbol.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_kakao_symbol.imageset/login_kakao_symbol.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_kakao_symbol.imageset/login_kakao_symbol@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_kakao_symbol.imageset/login_kakao_symbol@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_kakao_symbol.imageset/login_kakao_symbol@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/SignIn/login_kakao_symbol.imageset/login_kakao_symbol@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderCreationIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Tap Bar - Button.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Tap Bar - Button@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Tap Bar - Button@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderCreationIcon.imageset/Tap Bar - Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderCreationIcon.imageset/Tap Bar - Button.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderCreationIcon.imageset/Tap Bar - Button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderCreationIcon.imageset/Tap Bar - Button@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderCreationIcon.imageset/Tap Bar - Button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderCreationIcon.imageset/Tap Bar - Button@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderDeselectedIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Tap Bar - Button.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Tap Bar - Button@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Tap Bar - Button@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderDeselectedIcon.imageset/Tap Bar - Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderDeselectedIcon.imageset/Tap Bar - Button.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderDeselectedIcon.imageset/Tap Bar - Button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderDeselectedIcon.imageset/Tap Bar - Button@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderDeselectedIcon.imageset/Tap Bar - Button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderDeselectedIcon.imageset/Tap Bar - Button@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderSelectedIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Tap Bar - Button.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Tap Bar - Button@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Tap Bar - Button@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderSelectedIcon.imageset/Tap Bar - Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderSelectedIcon.imageset/Tap Bar - Button.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderSelectedIcon.imageset/Tap Bar - Button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderSelectedIcon.imageset/Tap Bar - Button@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderSelectedIcon.imageset/Tap Bar - Button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/folderSelectedIcon.imageset/Tap Bar - Button@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeDeselectedIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Tap Bar - Button.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Tap Bar - Button@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Tap Bar - Button@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeDeselectedIcon.imageset/Tap Bar - Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeDeselectedIcon.imageset/Tap Bar - Button.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeDeselectedIcon.imageset/Tap Bar - Button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeDeselectedIcon.imageset/Tap Bar - Button@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeDeselectedIcon.imageset/Tap Bar - Button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeDeselectedIcon.imageset/Tap Bar - Button@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeSelectedIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Tap Bar - Button.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Tap Bar - Button@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Tap Bar - Button@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeSelectedIcon.imageset/Tap Bar - Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeSelectedIcon.imageset/Tap Bar - Button.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeSelectedIcon.imageset/Tap Bar - Button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeSelectedIcon.imageset/Tap Bar - Button@2x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeSelectedIcon.imageset/Tap Bar - Button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nexters/tidify-iOS/3afeba106f2acb1a5317207a30de1d3f0140435b/Tidify/Targets/Tidify/Resources/Assets.xcassets/TabBar/homeSelectedIcon.imageset/Tap Bar - Button@3x.png
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Resources/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import TidifyCore
2 | import TidifyData
3 | import TidifyDomain
4 | import TidifyPresentation
5 | import UIKit
6 |
7 | import KakaoSDKAuth
8 | import KakaoSDKCommon
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 | private var mainCoordinator: MainCoordinator?
15 | private var navigationController: UINavigationController?
16 |
17 | func application(
18 | _ application: UIApplication,
19 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
20 | ) -> Bool {
21 | let window = UIWindow(frame: UIScreen.main.bounds)
22 | window.makeKeyAndVisible()
23 | self.window = window
24 |
25 | NetworkMonitor.shared.startMonitoring()
26 |
27 | let navigationController: UINavigationController = .init(nibName: nil, bundle: nil)
28 | self.navigationController = navigationController
29 | window.rootViewController = navigationController
30 |
31 | setupNavigationBar()
32 | setupLibrary()
33 | assemble()
34 |
35 | self.mainCoordinator = DefaultMainCoordinator(navigationController: navigationController)
36 | mainCoordinator?.startSplash()
37 |
38 | return true
39 | }
40 |
41 | func application(
42 | _ app: UIApplication,
43 | open url: URL,
44 | options: [UIApplication.OpenURLOptionsKey : Any] = [:]
45 | ) -> Bool {
46 | if AuthApi.isKakaoTalkLoginUrl(url) {
47 | return AuthController.handleOpenUrl(url: url, options: options)
48 | }
49 | return false
50 | }
51 | }
52 |
53 | private extension AppDelegate {
54 | func setupNavigationBar() {
55 | UIBarButtonItem.appearance().setTitleTextAttributes([.foregroundColor: UIColor.clear], for: .normal)
56 | let navigationBar: UINavigationBar = UINavigationBar.appearance()
57 | navigationBar.titleTextAttributes = [
58 | .foregroundColor: UIColor.t_gray(weight: 800),
59 | .font: UIFont.t_B(17)
60 | ]
61 | navigationBar.backIndicatorImage = UIImage(named: "backButtonIcon")
62 | navigationBar.backIndicatorTransitionMaskImage = UIImage(named: "backButtonIcon")
63 | }
64 |
65 | func setupLibrary() {
66 |
67 | // KakaoSDK
68 | guard let appKey: String = Bundle.main.object(forInfoDictionaryKey: "KAKAO_NATIVE_APP_KEY") as? String
69 | else { return }
70 | KakaoSDK.initSDK(appKey: appKey)
71 | }
72 |
73 | func assemble() {
74 | guard let navigationController = navigationController else { return }
75 | let container: DIContainer = .shared
76 | DataAssembly().assemble(container: container)
77 | DomainAssembly().assemble(container: container)
78 | PresentationAssembly(navigationController: navigationController).assemble(container: container)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Tidify/Targets/Tidify/Tests/AppTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 |
4 | final class TidifyTests: XCTestCase {
5 | func test_twoPlusTwo_isFour() {
6 | XCTAssertEqual(2+2, 4)
7 | }
8 | }
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyCore/Sources/AppProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Environment.swift
3 | // TidifyCore
4 | //
5 | // Created by Ian on 2022/08/20.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct AppProperties {
12 |
13 | // MARK: - Properties
14 | public static var baseURL: String {
15 | guard let infoDictionary = Bundle.main.infoDictionary,
16 | let baseURL = infoDictionary["BASE_URL"] as? String
17 | else { return .init() }
18 | return "http://\(baseURL)"
19 | }
20 |
21 | public static var accessToken: String {
22 | guard let accessTokenData = KeyChain.load(key: .accessToken) else { return .init() }
23 | return String(decoding: accessTokenData, as: UTF8.self)
24 | }
25 |
26 | public static var refreshToken: String {
27 | guard let refreshTokenData = KeyChain.load(key: .refreshToken) else { return .init() }
28 | return String(decoding: refreshTokenData, as: UTF8.self)
29 | }
30 |
31 | public static var userAgent: String {
32 | guard let infoDictionary = Bundle.main.infoDictionary,
33 | let userAgent = infoDictionary["USER_AGENT"] as? String
34 | else { return .init() }
35 | return userAgent
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyCore/Sources/DIContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DIContainer.swift
3 | // Tidify
4 | //
5 | // Created by Ian on 2022/08/16.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | public typealias DependencyFactoryClosure = (DIContainer) -> Any
10 |
11 | public protocol Assemblable {
12 | func assemble(container: DIContainer)
13 | }
14 |
15 | private protocol DIContainable {
16 | func register(type: Service.Type, factoryClosure: @escaping DependencyFactoryClosure)
17 | func resolve(type: Service.Type) -> Service?
18 | }
19 |
20 | public final class DIContainer: DIContainable {
21 |
22 | // MARK: - Properties
23 | public static let shared: DIContainer = .init()
24 | var services: [String: DependencyFactoryClosure] = [:]
25 |
26 | // MARK: - Initializer
27 | private init() {}
28 |
29 | // MARK: - Methods
30 | public func register(type: Service.Type, factoryClosure: @escaping DependencyFactoryClosure) {
31 | services["\(type)"] = factoryClosure
32 | }
33 |
34 | public func resolve(type: Service.Type) -> Service? {
35 | let service = services["\(type)"]?(self) as? Service
36 |
37 | if service.isNil {
38 | print("❌ \(#file) - \(#line): \(#function) - Fail: \(type) resolve Error")
39 | print(services)
40 | }
41 |
42 | return service
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyCore/Sources/Extension/Array+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+.swift
3 | // TidifyCore
4 | //
5 | // Created by 여정수 on 2023/05/21.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Array {
12 | subscript (safe index: Int) -> Element? {
13 | return indices ~= index ? self[index] : nil
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyCore/Sources/Extension/Date+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date+.swift
3 | // TidifyCore
4 | //
5 | // Created by Ian on 2022/09/24.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Date {
12 | func toString() -> String {
13 | let formatter: DateFormatter = .init()
14 | formatter.dateFormat = "yyyy-MM-dd"
15 |
16 | return formatter.string(from: self)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyCore/Sources/Extension/ObservableType+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObservableType+.swift
3 | // TidifyCore
4 | //
5 | // Created by Ian on 2022/08/06.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import RxSwift
10 |
11 | // MARK: - ObservableType Extension
12 | public extension ObservableType {
13 | func mapToVoid() -> Observable {
14 | map { _ in }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyCore/Sources/Extension/Optional+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Optional+.swift
3 | // TidifyCore
4 | //
5 | // Created by 여정수 on 2023/09/28.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Optional {
12 | var isNil: Bool {
13 | return self == nil
14 | }
15 |
16 | var isNotNil: Bool {
17 | return self != nil
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyCore/Sources/KeyChain.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyChain.swift
3 | // TidifyCore
4 | //
5 | // Created by 한상진 on 2022/10/17.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Security
11 |
12 | public enum AppData: String, CaseIterable {
13 | case accessToken = "accessToken"
14 | case refreshToken = "refreshToken"
15 | }
16 |
17 | public enum KeyChain {
18 | public static func save(key: AppData, data: Data) {
19 | let query: NSDictionary = .init(dictionary: [
20 | kSecClass: kSecClassGenericPassword,
21 | kSecAttrAccount: key.rawValue,
22 | kSecValueData: data
23 | ])
24 | SecItemDelete(query)
25 | SecItemAdd(query, nil)
26 | }
27 |
28 | public static func load(key: AppData) -> Data? {
29 | let query: NSDictionary = .init(dictionary: [
30 | kSecClass: kSecClassGenericPassword,
31 | kSecAttrAccount: key.rawValue,
32 | kSecReturnData: true,
33 | kSecMatchLimit: kSecMatchLimitOne
34 | ])
35 | var dataTypeReference: AnyObject?
36 | let status = withUnsafeMutablePointer(to: &dataTypeReference) {
37 | SecItemCopyMatching(query, UnsafeMutablePointer($0))
38 | }
39 |
40 | guard status == errSecSuccess,
41 | let data = dataTypeReference as? Data
42 | else { return nil }
43 |
44 | return data
45 | }
46 |
47 | public static func delete(key: AppData) {
48 | let query: NSDictionary = .init(dictionary: [
49 | kSecClass: kSecClassGenericPassword,
50 | kSecAttrAccount: key.rawValue
51 | ])
52 | SecItemDelete(query)
53 | }
54 |
55 | public static func deleteAll() {
56 | AppData.allCases.forEach { key in
57 | delete(key: key)
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyCore/Sources/UserDefaultsStorage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaultsStorage.swift
3 | // TidifyCore
4 | //
5 | // Created by Ian on 2022/11/01.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// UserDefaults Wrapper
12 | @propertyWrapper
13 | public struct UserDefaultsStorage {
14 |
15 | // MARK: - Properties
16 | public enum StorageKey: String, CaseIterable {
17 | case isFirstLaunch = "isFirstLaunch"
18 | case searchHistory = "searchHistory"
19 | }
20 |
21 | private let key: StorageKey
22 | private var defaultValue: T
23 |
24 | public var wrappedValue: T {
25 | get {
26 | UserDefaults.standard.value(forKey: key.rawValue) as? T ?? defaultValue
27 | }
28 | set {
29 | UserDefaults.standard.setValue(newValue, forKey: key.rawValue)
30 | }
31 | }
32 |
33 | // MARK: - Initializer
34 | public init(key: StorageKey, defaultValue: T) {
35 | self.key = key
36 | self.defaultValue = defaultValue
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyCore/Sources/UserProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserProperties.swift
3 | // TidifyCore
4 | //
5 | // Created by 여정수 on 2023/08/04.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public struct UserProperties {
10 | @UserDefaultsStorage (key: .isFirstLaunch, defaultValue: false)
11 | public static var isFirstLaunch: Bool
12 |
13 | @UserDefaultsStorage(key: .searchHistory, defaultValue: [])
14 | public static var searchHistory: [String]
15 | }
16 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyCore/Tests/TidifyCoreTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TidifyCoreTests.swift
3 | // TidifyDomainTests
4 | //
5 | // Created by Ian on 2022/08/03.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | final class TidifyCoreTests: XCTestCase {
12 | }
13 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/DataAssembly.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataAssembly.swift
3 | // Tidify
4 | //
5 | // Created by Ian on 2022/08/16.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import TidifyDomain
11 |
12 | public struct DataAssembly: Assemblable {
13 |
14 | // MARK: - Initializer
15 | public init() {}
16 |
17 | // MARK: - Methods
18 | public func assemble(container: DIContainer) {
19 | container.register(type: UserRepository.self) { _ in DefaultUserRepository() }
20 |
21 | container.register(type: FolderRepository.self) { _ in DefaultFolderRepository() }
22 |
23 | container.register(type: BookmarkRepository.self) { _ in DefaultBookmarkRepository() }
24 |
25 | container.register(type: SearchRepository.self) { _ in DefaultSearchRepository() }
26 |
27 | container.register(type: FolderDetailRepository.self) { _ in DefaultFolderDetailRepository() }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Extension/Reactive+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Reactive+.swift
3 | // TidifyData
4 | //
5 | // Created by 한상진 on 2023/04/26.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import TidifyCore
11 |
12 | import Moya
13 | import RxSwift
14 |
15 | public extension Reactive where Base: MoyaProviderType {
16 |
17 | // MARK: Methods
18 | func request(_ target: Base.Target) -> Single {
19 | let requestSingle = Single.create { [weak base] single in
20 | let cancellableToken = base?.request(target, callbackQueue: nil, progress: nil) { result in
21 | switch result {
22 | case let .success(response):
23 | updateTokenWhenExpired(response)
24 | single(.success(response))
25 | case let .failure(error):
26 | single(.failure(error))
27 | }
28 | }
29 |
30 | return Disposables.create {
31 | cancellableToken?.cancel()
32 | }
33 | }
34 |
35 | return requestSingle
36 | }
37 |
38 | private func updateTokenWhenExpired(_ response: Response) {
39 | guard let responseHeaderFields = response.response?.allHeaderFields,
40 | let updatedAccessTokenData = responseHeaderFields["X-AUTH-TOKEN"] as? Data,
41 | let updatedRefreshTokenData = responseHeaderFields["refreshToken"] as? Data else {
42 | return
43 | }
44 |
45 | KeyChain.save(key: .accessToken, data: updatedAccessTokenData)
46 | KeyChain.save(key: .refreshToken, data: updatedRefreshTokenData)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Repositories/DefaultBookmarkRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultHomeRepository.swift
3 | // TidifyData
4 | //
5 | // Created by Ian on 2022/08/20.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 |
11 | final class DefaultBookmarkRepository: BookmarkRepository {
12 |
13 | // MARK: Properties
14 | private let networkProvider: NetworkProviderType
15 |
16 | // MARK: Initializer
17 | init(networkProvider: NetworkProviderType = NetworkProvider()) {
18 | self.networkProvider = networkProvider
19 | }
20 |
21 | // MARK: Methods
22 | func fetchBookmarkList(request: BookmarkListRequest, category: BookmarkCategory) async throws -> FetchBookmarkResponse {
23 | let response = try await networkProvider.request(
24 | endpoint: BookmarkEndpoint.fetchBoomarkList(request: request, category: category),
25 | type: BookmarkListResponse.self
26 | )
27 |
28 | return FetchBookmarkResponse(
29 | bookmarks: response.toDomain(),
30 | currentPage: response.bookmarkListDTO.currentPage,
31 | isLastPage: response.bookmarkListDTO.isLastPage
32 | )
33 | }
34 |
35 | func createBookmark(request: BookmarkRequestDTO) async throws {
36 | try await networkProvider.request(endpoint: BookmarkEndpoint.createBookmark(request: request), type: BookmarkResponse.self)
37 | }
38 |
39 | func deleteBookmark(bookmarkID: Int) async throws {
40 | try await networkProvider.request(endpoint: BookmarkEndpoint.deleteBookmark(ID: bookmarkID), type: APIResponse.self)
41 | }
42 |
43 | func updateBookmark(bookmarkID: Int, request: BookmarkRequestDTO) async throws {
44 | try await networkProvider.request(endpoint: BookmarkEndpoint.updateBookmark(ID: bookmarkID, request: request), type: APIResponse.self)
45 | }
46 |
47 | func favoriteBookmark(bookmarkID: Int) async throws {
48 | try await networkProvider.request(endpoint: BookmarkEndpoint.favoriteBookmark(ID: bookmarkID), type: BookmarkResponse.self)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Repositories/DefaultFolderDetailRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultFolderDetailRepository.swift
3 | // TidifyData
4 | //
5 | // Created by 한상진 on 2023/04/26.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 |
11 | final class DefaultFolderDetailRepository: FolderDetailRepository {
12 |
13 | // MARK: - Properties
14 | private let networkProvider: NetworkProviderType
15 |
16 | // MARK: - Initializer
17 | init(networkProvider: NetworkProviderType = NetworkProvider()) {
18 | self.networkProvider = networkProvider
19 | }
20 |
21 | // MARK: - Methods
22 | func fetchBookmarkListInFolder(id: Int) async throws -> FetchBookmarkResponse {
23 | let response = try await networkProvider.request(endpoint: FolderEndpoint.fetchBookmarkListInFolder(id: id), type: BookmarkListResponse.self)
24 |
25 | return FetchBookmarkResponse(
26 | bookmarks: response.toDomain(),
27 | currentPage: response.bookmarkListDTO.currentPage,
28 | isLastPage: response.bookmarkListDTO.isLastPage
29 | )
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Repositories/DefaultFolderRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultFolderRepository.swift
3 | // TidifyData
4 | //
5 | // Created by 한상진 on 2022/08/16.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 |
11 | final class DefaultFolderRepository: FolderRepository {
12 |
13 | // MARK: - Properties
14 | private let networkProvider: NetworkProviderType
15 |
16 | // MARK: - Initializer
17 | init(networkProvider: NetworkProviderType = NetworkProvider()) {
18 | self.networkProvider = networkProvider
19 | }
20 |
21 | // MARK: - Methods
22 | func createFolder(request: FolderRequestDTO) async throws {
23 | try await networkProvider.request(endpoint: FolderEndpoint.createFolder(request: request), type: FolderCreationResponse.self)
24 | }
25 |
26 | func fetchFolderList(start: Int, count: Int, category: FolderCategory) async throws -> FetchFolderListResponse {
27 | let response = try await networkProvider.request(
28 | endpoint: FolderEndpoint.fetchFolderList(start: start, count: count, category: category),
29 | type: FolderListResponse.self
30 | )
31 |
32 | return FetchFolderListResponse(
33 | folders: response.folderListDTO.toDomain(),
34 | isLast: response.folderListDTO.isLast
35 | )
36 | }
37 |
38 | func updateFolder(id: Int, request: FolderRequestDTO) async throws {
39 | try await networkProvider.request(endpoint: FolderEndpoint.updateFolder(id: id, request: request), type: FolderCreationResponse.self)
40 | }
41 |
42 | func deleteFolder(id: Int) async throws {
43 | try await networkProvider.request(endpoint: FolderEndpoint.deleteFolder(id: id), type: APIResponse.self)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Repositories/DefaultSearchRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultSearchRepository.swift
3 | // TidifyData
4 | //
5 | // Created by Ian on 2022/09/27.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import TidifyCore
11 | import TidifyDomain
12 |
13 | final class DefaultSearchRepository: SearchRepository {
14 |
15 | // MARK: - Properties
16 | private let networkProvivder: NetworkProviderType
17 |
18 | // MARK: - Initializer
19 | public init(networkProvider: NetworkProviderType = NetworkProvider()) {
20 | self.networkProvivder = networkProvider
21 | }
22 |
23 | // MARK: Methods
24 | func fetchSearchHistory() -> [String] {
25 | return UserProperties.searchHistory
26 | }
27 |
28 | func eraseAllSearchHistory() {
29 | UserProperties.searchHistory = []
30 | }
31 |
32 | func fetchSearchResult(request: BookmarkListRequest) async throws -> FetchBookmarkResponse {
33 | let response = try await networkProvivder.request(
34 | endpoint: BookmarkEndpoint.fetchBoomarkList(request: request, category: .normal),
35 | type: BookmarkListResponse.self
36 | )
37 |
38 | return FetchBookmarkResponse(
39 | bookmarks: response.toDomain(),
40 | currentPage: response.bookmarkListDTO.currentPage,
41 | isLastPage: response.bookmarkListDTO.isLastPage
42 | )
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Response/APIResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIResponse.swift
3 | // TidifyData
4 | //
5 | // Created by Ian on 2022/09/04.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | struct APIResponse: Decodable, Responsable {
10 |
11 | // MARK: - Properties
12 | let code: String
13 | let message: String
14 |
15 | enum CodingKeys: String, CodingKey {
16 | case code, message
17 | }
18 |
19 | public init(from decoder: Decoder) throws {
20 | let container = try decoder.container(keyedBy: CodingKeys.self)
21 |
22 | code = try container.decode(String.self, forKey: .code)
23 | message = try container.decode(String.self, forKey: .message)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Response/BookmarkDTO+Mapping.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarkDTO+Mapping.swift
3 | // TidifyData
4 | //
5 | // Created by Ian on 2022/09/04.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 |
11 | struct BookmarkResponse: Decodable, Responsable {
12 |
13 | // MARK: Properties
14 | let code, message: String
15 | let bookmarkDTO: BookmarkDTO
16 |
17 | enum CodingKeys: String, CodingKey {
18 | case code, message
19 | case bookmarkDTO = "data"
20 | }
21 | }
22 |
23 | public struct BookmarkDTO: Decodable {
24 |
25 | // MARK: - Properties
26 | let id: Int
27 | let folderID: Int?
28 | let urlString: String?
29 | let name: String
30 | let star: Bool
31 | let ogImageURLString: String?
32 |
33 | enum CodingKeys: String, CodingKey {
34 | case id = "bookmarkId"
35 | case folderID = "folderId"
36 | case urlString = "url"
37 | case ogImageURLString = "ogImage"
38 | case name, star
39 | }
40 | }
41 |
42 | extension BookmarkDTO {
43 | public func toDomain() -> Bookmark {
44 | .init(
45 | id: id,
46 | folderID: folderID,
47 | urlString: urlString,
48 | name: name,
49 | star: star,
50 | ogImageURLString: ogImageURLString
51 | )
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Response/BookmarkListDTO+Mapping.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarkListDTO+Mapping.swift
3 | // TidifyData
4 | //
5 | // Created by Ian on 2022/09/04.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 |
11 | struct BookmarkListResponse: Decodable, Responsable {
12 |
13 | // MARK: Properties
14 | let code, message: String
15 | let bookmarkListDTO: BookmarkListDTO
16 |
17 | enum CodingKeys: String, CodingKey {
18 | case code, message
19 | case bookmarkListDTO = "data"
20 | }
21 | }
22 |
23 | extension BookmarkListResponse {
24 | public func toDomain() -> [Bookmark] {
25 | bookmarkListDTO.bookmarks.map { $0.toDomain() }
26 | }
27 | }
28 |
29 | public struct BookmarkListDTO: Decodable {
30 |
31 | // MARK: - Properties
32 | public let bookmarks: [BookmarkDTO]
33 | public let isLastPage: Bool
34 | public let currentPage: Int
35 |
36 | enum CodingKeys: String, CodingKey {
37 | case bookmarks = "content"
38 | case isLastPage = "isLast"
39 | case currentPage
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Response/FolderCreationDTO+Mapping.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderCreationDTO+Mapping.swift
3 | // TidifyData
4 | //
5 | // Created by 한상진 on 2023/04/19.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 |
11 | struct FolderCreationResponse: Decodable, Responsable {
12 |
13 | // MARK: Properties
14 | let message, code: String
15 | let folderCreationDTO: FolderCreationDTO
16 |
17 | enum CodingKeys: String, CodingKey {
18 | case folderCreationDTO = "data"
19 | case message, code
20 | }
21 | }
22 |
23 | struct FolderCreationDTO: Decodable {
24 |
25 | // MARK: Properties
26 | let folderID: Int
27 | let folderName, color: String
28 |
29 | enum CodingKeys: String, CodingKey {
30 | case folderID = "folderId"
31 | case folderName, color
32 | }
33 | }
34 |
35 | extension FolderCreationDTO {
36 | public func toDomain() -> Folder {
37 | .init(id: folderID, title: folderName, color: color, count: 0)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Response/FolderDTO+Mapping.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderDTO.swift
3 | // TidifyData
4 | //
5 | // Created by 한상진 on 2022/10/18.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 |
11 | public struct FolderDTO: Decodable {
12 |
13 | // MARK: - Properties
14 | let folderID: Int
15 | let folderName, color: String
16 | let count: Int
17 |
18 | enum CodingKeys: String, CodingKey {
19 | case folderID = "folderId"
20 | case folderName, color, count
21 | }
22 | }
23 |
24 | extension FolderDTO {
25 | public func toDomaion() -> Folder {
26 | .init(id: folderID, title: folderName, color: color, count: count)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Response/FolderListDTO+Mapping.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderListDTO+Mapping.swift
3 | // TidifyData
4 | //
5 | // Created by 한상진 on 2022/10/18.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 |
11 | struct FolderListResponse: Decodable, Responsable {
12 |
13 | // MARK: - Properties
14 | let message, code: String
15 | let folderListDTO: FolderListDTO
16 |
17 | enum CodingKeys: String, CodingKey {
18 | case folderListDTO = "data"
19 | case message, code
20 | }
21 | }
22 |
23 | public struct FolderListDTO: Decodable {
24 | let folders: [FolderDTO]
25 | let isLast: Bool
26 | let currentPage: Int
27 |
28 | enum CodingKeys: String, CodingKey {
29 | case folders = "content"
30 | case isLast, currentPage
31 | }
32 | }
33 |
34 | extension FolderListDTO {
35 | public func toDomain() -> [Folder] {
36 | return folders.map { $0.toDomaion() }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Response/Responsable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Responsable.swift
3 | // TidifyData
4 | //
5 | // Created by 여정수 on 2023/04/20.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol Responsable {
12 | var code: String { get }
13 | var message: String { get }
14 | }
15 |
16 | extension Responsable where Self: Decodable {
17 | var isSuccess: Bool {
18 | code == "200" && message == "success"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Response/UserTokenDTO+Mapping.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserTokenDTO+Mapping.swift
3 | // TidifyData
4 | //
5 | // Created by Ian on 2022/09/04.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | //import TidifyDomain
10 | //
11 | //struct UserResponse: Decodable, Responsable {
12 | //
13 | // // MARK: - Properties
14 | // let message, code: String
15 | // let userTokenDTO: UserTokenDTO
16 | //
17 | // enum CodingKeys: String, CodingKey {
18 | // case code, message
19 | // case userTokenDTO = "data"
20 | // }
21 | //}
22 | //
23 | //extension UserResponse {
24 | // func toDomain() -> UserToken {
25 | // return .init(
26 | // accessToken: userTokenDTO.accessToken,
27 | // refreshToken: userTokenDTO.refreshToken
28 | // )
29 | // }
30 | //}
31 | //
32 | //struct UserTokenDTO: Decodable {
33 | //
34 | // // MARK: - Properties
35 | // let accessToken, refreshToken, email, socialType: String
36 | //
37 | // enum CodingKeys: String, CodingKey {
38 | // case accessToken, refreshToken
39 | // case email = "key"
40 | // case socialType = "type"
41 | // }
42 | //}
43 | import TidifyDomain
44 |
45 | struct UserResponse: Decodable {
46 |
47 | // MARK: - Properties
48 | let accessToken, refreshToken, email, socialType: String
49 |
50 | enum CodingKeys: String, CodingKey {
51 | case accessToken, refreshToken
52 | case email = "key"
53 | case socialType = "type"
54 | }
55 | }
56 |
57 | extension UserResponse {
58 | func toDomain() -> UserToken {
59 | return .init(
60 | accessToken: accessToken,
61 | refreshToken: refreshToken
62 | )
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Services/Endpoint/BookmarkEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarkEndpoint.swift
3 | // TidifyData
4 | //
5 | // Created by 여정수 on 2023/07/19.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import TidifyDomain
11 |
12 | enum BookmarkEndpoint: EndpointType {
13 | case fetchBoomarkList(request: BookmarkListRequest, category: BookmarkCategory)
14 | case createBookmark(request: BookmarkRequestDTO)
15 | case deleteBookmark(ID: Int)
16 | case updateBookmark(ID: Int, request: BookmarkRequestDTO)
17 | case favoriteBookmark(ID: Int)
18 | }
19 |
20 | extension BookmarkEndpoint {
21 | var baseRouthPath: String {
22 | return "/app/bookmarks"
23 | }
24 |
25 | var fullPath: String {
26 | switch self {
27 | case .fetchBoomarkList(let request, let category):
28 | if request.keyword.isNotNil {
29 | return AppProperties.baseURL + baseRouthPath + "/search"
30 | }
31 | return AppProperties.baseURL + (category == .normal ? baseRouthPath : baseRouthPath + "/star")
32 | case .createBookmark:
33 | return AppProperties.baseURL + baseRouthPath
34 | case .deleteBookmark(let id), .updateBookmark(let id, _):
35 | return AppProperties.baseURL + baseRouthPath + "/\(id)"
36 | case .favoriteBookmark(let id):
37 | return AppProperties.baseURL + baseRouthPath + "/star/\(id)"
38 | }
39 | }
40 |
41 | var method: HTTPMethod {
42 | switch self {
43 | case .fetchBoomarkList:
44 | return .get
45 | case .createBookmark, .favoriteBookmark:
46 | return .post
47 | case .deleteBookmark:
48 | return .delete
49 | case .updateBookmark:
50 | return .patch
51 | }
52 | }
53 |
54 | var parameters: [String : String]? {
55 | switch self {
56 | case .fetchBoomarkList(let request, _):
57 | var params: [String: String] = [
58 | "size": "\(request.size)",
59 | "page": "\(request.page)"
60 | ]
61 |
62 | if let keyword = request.keyword {
63 | params["keyword"] = keyword
64 | }
65 | return params
66 |
67 | case .createBookmark(let request):
68 | return [
69 | "name": request.name,
70 | "url": request.url,
71 | "folderId": "\(request.folderID)"
72 | ]
73 |
74 | case .updateBookmark(_, let request):
75 | return [
76 | "folderId": "\(request.folderID)",
77 | "url": request.url,
78 | "name": request.name
79 | ]
80 |
81 | case .deleteBookmark, .favoriteBookmark:
82 | return nil
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Services/Endpoint/EndpointType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EndpointType.swift
3 | // TidifyData
4 | //
5 | // Created by 여정수 on 2023/07/19.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import TidifyCore
11 |
12 | protocol EndpointType {
13 | var baseRouthPath: String { get }
14 | var fullPath: String { get }
15 | var method: HTTPMethod { get }
16 | var parameters: [String: String]? { get }
17 | var headers: [String: String]? { get }
18 |
19 | func makeURLRequest() throws -> URLRequest
20 | }
21 |
22 | extension EndpointType {
23 | var headers: [String: String]? {
24 | return nil
25 | }
26 |
27 | func makeURLRequest() throws -> URLRequest {
28 | var request: URLRequest
29 |
30 | guard NetworkMonitor.shared.isConnected else {
31 | throw NetworkError.failConnection
32 | }
33 |
34 | if let parameters {
35 | switch method {
36 | case .get:
37 | guard var components: URLComponents = .init(string: fullPath) else {
38 | throw NetworkError.invalidURL
39 | }
40 |
41 | components.queryItems = parameters.map { .init(name: $0.key, value: $0.value) }
42 |
43 | guard let url = components.url else {
44 | throw NetworkError.invalidURL
45 | }
46 |
47 | request = .init(url: url)
48 | request.httpMethod = method.rawValue
49 |
50 | case .post, .put, .delete, .patch:
51 | guard let url = URL(string: fullPath) else {
52 | throw NetworkError.invalidURL
53 | }
54 |
55 | request = .init(url: url)
56 | request.httpMethod = method.rawValue
57 | request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
58 | }
59 | } else {
60 | guard let url = URL(string: fullPath) else {
61 | throw NetworkError.invalidURL
62 | }
63 |
64 | request = .init(url: url, timeoutInterval: 5.0)
65 | request.httpMethod = method.rawValue
66 | }
67 |
68 | request.addValue("application/json", forHTTPHeaderField: "Content-Type")
69 | if let headers {
70 | for header in headers {
71 | request.addValue(header.value, forHTTPHeaderField: header.key)
72 | }
73 | } else {
74 | request.addValue(AppProperties.accessToken, forHTTPHeaderField: "X-Auth-Token")
75 | request.addValue(AppProperties.refreshToken, forHTTPHeaderField: "refreshToken")
76 | }
77 | return request
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Services/Endpoint/FolderEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderEndpoint.swift
3 | // TidifyData
4 | //
5 | // Created by 한상진 on 2023/07/26.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import TidifyDomain
11 |
12 | enum FolderEndpoint: EndpointType {
13 | case createFolder(request: FolderRequestDTO)
14 | case fetchFolderList(start: Int, count: Int, category: FolderCategory)
15 | case fetchBookmarkListInFolder(id: Int)
16 | case updateFolder(id: Int, request: FolderRequestDTO)
17 | case deleteFolder(id: Int)
18 | }
19 |
20 | extension FolderEndpoint {
21 | var baseRouthPath: String {
22 | return "/app/folders"
23 | }
24 |
25 | var fullPath: String {
26 | switch self {
27 | case .createFolder:
28 | return AppProperties.baseURL + baseRouthPath
29 | case .fetchFolderList(_, _, let category):
30 | let path: String = AppProperties.baseURL + baseRouthPath
31 |
32 | switch category {
33 | case .normal: return path
34 | case .subscribe: return path + "/subscribed"
35 | case .share: return path + "/subscribing"
36 | }
37 | case .fetchBookmarkListInFolder(let id):
38 | return AppProperties.baseURL + baseRouthPath + "/\(id)/bookmarks"
39 | case .deleteFolder(let id), .updateFolder(let id, _):
40 | return AppProperties.baseURL + baseRouthPath + "/\(id)"
41 | }
42 | }
43 |
44 | var method: HTTPMethod {
45 | switch self {
46 | case .createFolder:
47 | return .post
48 | case .fetchFolderList, .fetchBookmarkListInFolder:
49 | return .get
50 | case .updateFolder:
51 | return .patch
52 | case .deleteFolder:
53 | return .delete
54 | }
55 | }
56 |
57 | var parameters: [String : String]? {
58 | switch self {
59 | case .fetchFolderList(let start, let count, _):
60 | return ["page": "\(start)", "size": "\(count)"]
61 |
62 | case .createFolder(let request):
63 | return [
64 | "folderName": request.title,
65 | "label": request.color,
66 | ]
67 |
68 | case .updateFolder(_, let request):
69 | return [
70 | "folderName": request.title,
71 | "label": request.color
72 | ]
73 |
74 | case .deleteFolder, .fetchBookmarkListInFolder:
75 | return nil
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Services/Endpoint/UserEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserEndpoint.swift
3 | // TidifyData
4 | //
5 | // Created by 한상진 on 2023/07/27.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import TidifyDomain
11 |
12 | enum SocialType {
13 | case kakao(token:String)
14 | case apple(token: String)
15 | }
16 |
17 | enum UserEndpoint: EndpointType {
18 | case signIn(socialType: SocialType)
19 | case signOut
20 | }
21 |
22 | extension UserEndpoint {
23 | var baseRouthPath: String {
24 | return "/oauth2"
25 | }
26 |
27 | var fullPath: String {
28 | switch self {
29 | case .signIn:
30 | return AppProperties.baseURL + baseRouthPath + "/login"
31 | case .signOut:
32 | return AppProperties.baseURL + baseRouthPath + "/withdrawal"
33 | }
34 | }
35 |
36 | var method: HTTPMethod {
37 | switch self {
38 | case .signIn:
39 | return .get
40 | case .signOut:
41 | return .delete
42 | }
43 | }
44 |
45 | var parameters: [String : String]? {
46 | switch self {
47 | case .signIn(let socialType):
48 | switch socialType {
49 | case .kakao:
50 | return ["type": "KAKAO"]
51 | case .apple:
52 | return ["type": "APPLE"]
53 | }
54 |
55 | case .signOut:
56 | return nil
57 | }
58 | }
59 |
60 | var headers: [String : String]? {
61 | switch self {
62 | case .signIn(let socialType):
63 | switch socialType {
64 | case .apple(let token), .kakao(let token):
65 | return ["Authorization": token]
66 | }
67 |
68 | case .signOut:
69 | return nil
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Services/Network/HTTPMethod.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPMethod.swift
3 | // TidifyData
4 | //
5 | // Created by 여정수 on 2023/07/20.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum HTTPMethod: String {
12 | case get = "GET"
13 | case post = "POST"
14 | case put = "PUT"
15 | case delete = "DELETE"
16 | case patch = "PATCH"
17 | }
18 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Services/Network/NetworkError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkError.swift
3 | // TidifyData
4 | //
5 | // Created by 여정수 on 2023/07/06.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum NetworkError: LocalizedError {
12 | case failConnection
13 | case invalidURL
14 | case invalidStatusCode(code: Int)
15 | case emptyData
16 | case decodingError
17 | case responseError
18 | case unknownError(message: String)
19 |
20 | var errorDescription: String? {
21 | switch self {
22 | case .failConnection:
23 | return "네트워크 연결이 불안정해요"
24 | case .invalidURL:
25 | return "유효하지 않은 URL이에요"
26 | case .invalidStatusCode, .responseError:
27 | return "나중에 다시 시도해주세요"
28 | case .emptyData:
29 | return "유효하지 않은 결과에요"
30 | case .decodingError:
31 | return "알 수 없는 에러가 발생했어요"
32 | case .unknownError(let message):
33 | return message
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Services/Network/NetworkMonitor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkMonitor.swift
3 | // TidifyData
4 | //
5 | // Created by 여정수 on 2023/07/19.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Network
11 |
12 | public final class NetworkMonitor {
13 |
14 | // MARK: Properties
15 | public static let shared: NetworkMonitor = .init()
16 | private let queue: DispatchQueue = .global()
17 | private let monitor: NWPathMonitor = .init()
18 | public private(set) var isConnected: Bool = false
19 |
20 | // MARK: Initializer
21 | private init() {}
22 | }
23 |
24 | // MARK: - Private
25 | public extension NetworkMonitor {
26 | func startMonitoring() {
27 | monitor.start(queue: queue)
28 |
29 | monitor.pathUpdateHandler = { path in
30 | self.isConnected = path.status == .satisfied
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Services/Network/NetworkProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkProvider.swift
3 | // TidifyData
4 | //
5 | // Created by 여정수 on 2023/07/06.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import TidifyDomain
11 |
12 | protocol NetworkRequestable {
13 | func data(for urlRequest: URLRequest) async throws -> (Data, URLResponse)
14 | }
15 |
16 | extension URLSession: NetworkRequestable {}
17 |
18 | protocol NetworkProviderType: AnyObject {
19 | @discardableResult
20 | func request(endpoint: EndpointType, type: T.Type) async throws -> T
21 | }
22 |
23 | final class NetworkProvider: NetworkProviderType {
24 |
25 | // MARK: Properties
26 | private let session: NetworkRequestable
27 |
28 | // MARK: Initializer
29 | init(session: URLSession = .shared) {
30 | self.session = session
31 | }
32 |
33 | // MARK: Methods
34 | @discardableResult
35 | func request(endpoint: EndpointType, type: T.Type) async throws -> T {
36 | guard NetworkMonitor.shared.isConnected else {
37 | throw NetworkError.failConnection
38 | }
39 |
40 | let request: URLRequest = try endpoint.makeURLRequest()
41 | let (data, response) = try await session.data(for: request)
42 | try filterNetworkingError(data: data, response: response)
43 |
44 | guard let decoded = try? JSONDecoder().decode(T.self, from: data) else {
45 | throw NetworkError.decodingError
46 | }
47 |
48 | // guard decoded.isSuccess else {
49 | // throw NetworkError.unknownError(message: decoded.message)
50 | // }
51 |
52 | return decoded
53 | }
54 | }
55 |
56 | // MARK: - Private
57 | private extension NetworkProvider {
58 | func filterNetworkingError(data: Data, response: URLResponse) throws {
59 | guard let httpResponse = response as? HTTPURLResponse else {
60 | throw NetworkError.responseError
61 | }
62 |
63 | guard 200...299 ~= httpResponse.statusCode else {
64 | throw NetworkError.invalidStatusCode(code: httpResponse.statusCode)
65 | }
66 |
67 | guard !data.isEmpty else {
68 | throw NetworkError.emptyData
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Sources/Services/NetworkPlugin.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkPlugin.swift
3 | // TidifyData
4 | //
5 | // Created by Ian on 2022/08/08.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 |
11 | import RxMoya
12 | import Moya
13 |
14 | public struct NetworkPlugin: PluginType {
15 | public func willSend(_ request: RequestType, target: TargetType) {
16 | #if DEBUG
17 | guard let request = request.request,
18 | let method = request.method else { return }
19 |
20 | let methodRawValue = method.rawValue
21 | let requestDescription = request.debugDescription
22 | let headers = String(describing: target.headers)
23 |
24 | let message = """
25 | [Moya-Logger] - @\(methodRawValue): \(requestDescription)
26 | [Moya-Logger] headers: \(headers)
27 | \n
28 | """
29 | print(message)
30 | #endif
31 | }
32 |
33 | public func didReceive(_ result: Result, target: TargetType) {
34 | #if DEBUG
35 | print("[Moya-Logger] - \(target.baseURL)\(target.path)")
36 |
37 | switch result {
38 | case .success(let response):
39 | guard let json = try? response.mapJSON() as? [String: Any] else { return }
40 | print("[Moya-Logger] Success: \(json)")
41 | case .failure(let error):
42 | print("[Moya-Logger] Fail: \(String(describing: error.errorDescription))")
43 | }
44 | #endif
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyData/Tests/TidifyDataTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 |
4 | final class TidifyDataTests: XCTestCase {
5 | func test_example() {
6 | XCTAssertEqual("TidifyUI", "TidifyUI")
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/DomainAssembly.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DomainAssembly.swift
3 | // Tidify
4 | //
5 | // Created by Ian on 2022/08/16.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 |
11 | public struct DomainAssembly: Assemblable {
12 |
13 | // MARK: - Initializer
14 | public init() {}
15 |
16 | // MARK: - Methods
17 | public func assemble(container: DIContainer) {
18 | container.register(type: UserUseCase.self) { container in
19 | return DefaultUserUseCase(userRepository: container.resolve(type: UserRepository.self)!)
20 | }
21 |
22 | container.register(type: FolderCreationUseCase.self) { container in
23 | return DefaultFolderCreationUseCase(repository: container.resolve(type: FolderRepository.self)!)
24 | }
25 |
26 | container.register(type: FolderListUseCase.self) { container in
27 | return DefaultFolderListUseCase(repository: container.resolve(type: FolderRepository.self)!)
28 | }
29 |
30 | container.register(type: BookmarkListUseCase.self) { container in
31 | return DefaultBookmarkListUseCase(repository: container.resolve(type: BookmarkRepository.self)!)
32 | }
33 |
34 | container.register(type: SearchListUseCase.self) { container in
35 | return DefaultSearchListUseCase(
36 | searchRepository: container.resolve(type: SearchRepository.self)!,
37 | bookmarkRepository: container.resolve(type: BookmarkRepository.self)!
38 | )
39 | }
40 |
41 | container.register(type: BookmarkCreationUseCase.self) { container in
42 | return DefaultBookmarkCreationUseCase(
43 | bookmarkRepository: container.resolve(type: BookmarkRepository.self)!,
44 | folderRepository: container.resolve(type: FolderRepository.self)!
45 | )
46 | }
47 |
48 | container.register(type: FolderDetailUseCase.self) { container in
49 | return DefaultFolderDetailUseCase(
50 | folderDetailRepository: container.resolve(type: FolderDetailRepository.self)!,
51 | bookmarkRepository: container.resolve(type: BookmarkRepository.self)!
52 | )
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Entities/Bookmark.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bookmark.swift
3 | // Tidify
4 | //
5 | // Created by Ian on 2022/08/20.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public enum BookmarkCategory {
12 | case normal, favorite
13 | }
14 |
15 | public struct Bookmark: Hashable {
16 |
17 | // MARK: - Properties
18 | public let id: Int
19 | public var folderID: Int?
20 | public var urlString: String?
21 | public var name: String
22 | public var star: Bool
23 | public let ogImageURLString: String?
24 |
25 | public var url: URL {
26 | return .init(string: urlString?.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")!
27 | }
28 |
29 | public init(
30 | id: Int,
31 | folderID: Int?,
32 | urlString: String?,
33 | name: String,
34 | star: Bool,
35 | ogImageURLString: String?
36 | ) {
37 | self.id = id
38 | self.folderID = folderID
39 | self.urlString = urlString
40 | self.name = name
41 | self.star = star
42 | self.ogImageURLString = ogImageURLString
43 | }
44 |
45 | public static func ==(lhs: Bookmark, rhs: Bookmark) -> Bool {
46 | lhs.id == rhs.id && lhs.folderID == rhs.folderID && lhs.urlString == rhs.urlString && lhs.name == rhs.name && lhs.star == rhs.star && lhs.ogImageURLString == rhs.ogImageURLString
47 | }
48 | }
49 |
50 | public extension Bookmark {
51 | func toRequestDTO() -> BookmarkRequestDTO {
52 | return .init(
53 | folderID: folderID ?? 0,
54 | url: urlString ?? "",
55 | name: name
56 | )
57 | }
58 |
59 | mutating func update(with requestDTO: BookmarkRequestDTO) {
60 | self.name = requestDTO.name
61 | self.urlString = requestDTO.url
62 | self.folderID = requestDTO.folderID
63 | }
64 | }
65 |
66 | public struct BookmarkList {
67 |
68 | // MARK: - Properties
69 | private let bookmarks: [Bookmark]
70 | private let count: Int
71 |
72 | public init(
73 | bookmarks: [Bookmark],
74 | count: Int
75 | ) {
76 | self.bookmarks = bookmarks
77 | self.count = count
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Entities/Folder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Folder.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2022/08/16.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | public enum FolderCategory {
10 | case normal, subscribe, share
11 | }
12 |
13 | public struct Folder: Equatable {
14 | public let id: Int
15 | public var title: String
16 | public var color: String
17 | public let count: Int
18 |
19 | public init(id: Int, title: String, color: String, count: Int) {
20 | self.id = id
21 | self.title = title
22 | self.color = color
23 | self.count = count
24 | }
25 | }
26 |
27 | public extension Folder {
28 | func toRequestDTO() -> FolderRequestDTO {
29 | return .init(title: title, color: color)
30 | }
31 |
32 | mutating func updateFolder(with requestDTO: FolderRequestDTO) {
33 | self.title = requestDTO.title
34 | self.color = requestDTO.color
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Entities/Onboarding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Onboarding.swift
3 | // TidifyDomain
4 | //
5 | // Created by Ian on 2022/08/06.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct Onboarding {
12 | public let imageName: String
13 | public let buttonTitle: String
14 |
15 | public init(imageName: String, buttonTitle: String) {
16 | self.imageName = imageName
17 | self.buttonTitle = buttonTitle
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Entities/UserToken.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserToken.swift
3 | // TidifyDomain
4 | //
5 | // Created by Ian on 2022/09/04.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | public struct UserToken {
10 | public let accessToken: String
11 | public let refreshToken: String
12 |
13 | public init(accessToken: String, refreshToken: String) {
14 | self.accessToken = accessToken
15 | self.refreshToken = refreshToken
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Interfaces/BookmarkRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarkRepository.swift
3 | // Tidify
4 | //
5 | // Created by Ian on 2022/08/20.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | public protocol BookmarkRepository: AnyObject {
10 |
11 | /// @GET: 대응되는 북마크 리스트를 반환합니다.
12 | func fetchBookmarkList(request: BookmarkListRequest, category: BookmarkCategory) async throws -> FetchBookmarkResponse
13 |
14 | /// @POST: 북마크를 생성합니다.
15 | func createBookmark(request: BookmarkRequestDTO) async throws
16 |
17 | /// @DELETE: 북마크를 삭제합니다.
18 | func deleteBookmark(bookmarkID: Int) async throws
19 |
20 | /// @PUT: 북마크 정보를 갱신합니다.
21 | func updateBookmark(bookmarkID: Int, request: BookmarkRequestDTO) async throws
22 |
23 | /// @POST: 북마크 즐겨찾기를 등록/해제 합니다..
24 | func favoriteBookmark(bookmarkID: Int) async throws
25 | }
26 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Interfaces/FolderDetailRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderDetailRepository.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2023/04/26.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public protocol FolderDetailRepository: AnyObject {
10 |
11 | /// 특정 폴더 ID에 포함된 북마크 리스트를 반환합니다.
12 | func fetchBookmarkListInFolder(id: Int) async throws -> FetchBookmarkResponse
13 | }
14 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Interfaces/FolderRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderRepository.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2022/08/16.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | public protocol FolderRepository: AnyObject {
10 |
11 | //MARK: - CRUD
12 | func createFolder(request: FolderRequestDTO) async throws
13 | func fetchFolderList(start: Int, count: Int, category: FolderCategory) async throws -> FetchFolderListResponse
14 | func updateFolder(id: Int, request: FolderRequestDTO) async throws
15 | func deleteFolder(id: Int) async throws
16 | }
17 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Interfaces/SearchRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchRepository.swift
3 | // TidifyDomain
4 | //
5 | // Created by Ian on 2022/09/27.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | public protocol SearchRepository: AnyObject {
10 |
11 | /// 로컬에 저장되어 있는 검색내역을 반환합니다.
12 | func fetchSearchHistory() -> [String]
13 |
14 | /// 로컬에 저장되어 있는 검색내역을 초기화합니다.
15 | func eraseAllSearchHistory()
16 |
17 | /// @GET: 검색 쿼리에 대응되는 결과를 반환합니다.
18 | func fetchSearchResult(request: BookmarkListRequest) async throws -> FetchBookmarkResponse
19 | }
20 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Interfaces/UserRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserRepository.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2023/07/27.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public protocol UserRepository: AnyObject {
10 | func appleLogin(token: String) async throws -> UserToken
11 | func kakaoLogin() async throws -> UserToken
12 | func signOut() async throws
13 | }
14 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Requests/BookmarkListRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarkListRequest.swift
3 | // TidifyDomain
4 | //
5 | // Created by 여정수 on 2023/04/20.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct BookmarkListRequest: Encodable {
12 |
13 | // MARK: Properties
14 | public let page: Int
15 | public let size: Int
16 | public let keyword: String?
17 |
18 | // MARK: Initializer
19 | public init(page: Int, size: Int = 12, keyword: String? = nil) {
20 | self.page = page
21 | self.size = size
22 | self.keyword = keyword
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Requests/BookmarkRequestDTO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarkRequestDTO.swift
3 | // TidifyDomain
4 | //
5 | // Created by Ian on 2022/09/04.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | public struct BookmarkRequestDTO: Encodable {
10 |
11 | // MARK: - Properties
12 | public let folderID: Int
13 | public let url: String
14 | public let name: String
15 |
16 | public init (folderID: Int, url: String, name: String) {
17 | self.folderID = folderID
18 | self.url = url
19 | self.name = name
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/Requests/FolderRequestDTO.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderRequestDTO.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2022/10/18.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | public struct FolderRequestDTO: Encodable {
10 | public let title: String
11 | public let color: String
12 |
13 | public init(title: String, color: String) {
14 | self.title = title
15 | self.color = color
16 | }
17 |
18 | enum CodingKeys: String, CodingKey {
19 | case title = "folderName"
20 | case color = "label"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Bookmark/BookmarkListUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarkListUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2023/12/14.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public typealias BookmarkListUseCase = FetchBookmarkUseCase & DeleteBookmarkUseCase & FavoriteBookmarkUseCase & CreateBookmarkUseCase
10 |
11 | public enum BookmarkListError: Error {
12 | case failFetchBookmarks
13 | case failDeleteBookmark
14 | case failFavoriteBookmark
15 | case failCreateBookmark
16 | }
17 |
18 | final class DefaultBookmarkListUseCase: BookmarkListUseCase {
19 |
20 | // MARK: Properties
21 | var bookmarkRepository: BookmarkRepository
22 |
23 | init(repository: BookmarkRepository) {
24 | self.bookmarkRepository = repository
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Bookmark/CreateBookmarkUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CreateBookmarkUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by Ian on 2022/10/22.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | public enum BookmarkCreationError: Error {
10 | case failCreateBookmark
11 | case failUpdateBookmark
12 | }
13 |
14 | public protocol CreateBookmarkUseCase {
15 | var bookmarkRepository: BookmarkRepository { get }
16 |
17 | func createBookmark(request: BookmarkRequestDTO) async throws
18 | }
19 |
20 | extension CreateBookmarkUseCase {
21 | func createBookmark(request: BookmarkRequestDTO) async throws {
22 | try await bookmarkRepository.createBookmark(request: request)
23 | }
24 | }
25 |
26 | public typealias BookmarkCreationUseCase = CreateBookmarkUseCase & UpdateBookmarkUseCase & FetchFolderUseCase
27 |
28 | final class DefaultBookmarkCreationUseCase: BookmarkCreationUseCase {
29 |
30 | // MARK: Properties
31 | let folderRepository: FolderRepository
32 | let bookmarkRepository: BookmarkRepository
33 |
34 | // MARK: Initializer
35 | init(
36 | bookmarkRepository: BookmarkRepository,
37 | folderRepository: FolderRepository
38 | ) {
39 | self.bookmarkRepository = bookmarkRepository
40 | self.folderRepository = folderRepository
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Bookmark/DeleteBookmarkUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeleteBookmarkUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2023/05/21.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public protocol DeleteBookmarkUseCase {
10 |
11 | var bookmarkRepository: BookmarkRepository { get }
12 |
13 | /// 북마크를 삭제합니다.
14 | func deleteBookmark(bookmarkID: Int) async throws
15 | }
16 |
17 | extension DeleteBookmarkUseCase {
18 | func deleteBookmark(bookmarkID: Int) async throws {
19 | try await bookmarkRepository.deleteBookmark(bookmarkID: bookmarkID)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Bookmark/FavoriteBookmarkUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FavoriteBookmarkUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2023/12/14.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public protocol FavoriteBookmarkUseCase {
10 |
11 | var bookmarkRepository: BookmarkRepository { get }
12 |
13 | /// 북마크를 수정합니다.
14 | func favoriteBookmark(id: Int) async throws
15 | }
16 |
17 | extension FavoriteBookmarkUseCase {
18 | func favoriteBookmark(id: Int) async throws {
19 | try await bookmarkRepository.favoriteBookmark(bookmarkID: id)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Bookmark/FetchBookmarkUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FetchBookmarkUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by 여정수 on 2023/09/18.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public typealias FetchBookmarkResponse = (bookmarks: [Bookmark], currentPage: Int, isLastPage: Bool)
10 |
11 | public protocol FetchBookmarkUseCase {
12 |
13 | var bookmarkRepository: BookmarkRepository { get }
14 |
15 | /// 북마크 리스트를 가져옵니다.
16 | /// - Returns: bookamrks: 북마크 리스트, currentPage: 현재 페이지, isLastPage: 마지막 페이지 여부
17 | func fetchBookmarkList(request: BookmarkListRequest, category: BookmarkCategory) async throws -> FetchBookmarkResponse
18 | }
19 |
20 | extension FetchBookmarkUseCase {
21 | func fetchBookmarkList(request: BookmarkListRequest, category: BookmarkCategory) async throws -> FetchBookmarkResponse {
22 | try await bookmarkRepository.fetchBookmarkList(request: request, category: category)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Bookmark/UpdateBookmarkUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UpdateBookmarkUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by 여정수 on 2023/05/04.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public protocol UpdateBookmarkUseCase {
10 |
11 | var bookmarkRepository: BookmarkRepository { get }
12 |
13 | /// 북마크를 수정합니다.
14 | func updateBookmark(id: Int, request: BookmarkRequestDTO) async throws
15 | }
16 |
17 | extension UpdateBookmarkUseCase {
18 | func updateBookmark(id: Int, request: BookmarkRequestDTO) async throws {
19 | try await bookmarkRepository.updateBookmark(bookmarkID: id, request: request)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Folder/DeleteFolderUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeleteFolderUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2023/11/29.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public protocol DeleteFolderUseCase {
10 | var folderRepository: FolderRepository { get }
11 |
12 | func deleteFolder(id: Int) async throws
13 | }
14 |
15 | extension DeleteFolderUseCase {
16 | func deleteFolder(id: Int) async throws {
17 | try await folderRepository.deleteFolder(id: id)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Folder/FetchFolderUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FetchFolderUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2023/11/29.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public typealias FetchFolderListResponse = (folders: [Folder], isLast: Bool)
10 |
11 | public protocol FetchFolderUseCase {
12 | var folderRepository: FolderRepository { get }
13 |
14 | func fetchFolderList(start: Int, count: Int, category: FolderCategory) async throws -> FetchFolderListResponse
15 | }
16 |
17 | extension FetchFolderUseCase {
18 | func fetchFolderList(start: Int, count: Int, category: FolderCategory) async throws -> FetchFolderListResponse {
19 | try await folderRepository.fetchFolderList(start: start, count: count, category: category)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Folder/FolderCreationUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderCreationUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2023/11/29.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public enum FolderCreationError: Error {
10 | case failCreateFolder
11 | case failUpdateFolder
12 | }
13 |
14 | public protocol FolderCreationUseCase {
15 | func createFolder(request: FolderRequestDTO) async throws
16 | func updateFolder(id: Int, request: FolderRequestDTO) async throws
17 | }
18 |
19 | final class DefaultFolderCreationUseCase: FolderCreationUseCase {
20 |
21 | // MARK: - Properties
22 | private let folderRepository: FolderRepository
23 |
24 | // MARK: - Initializer
25 | init(repository: FolderRepository) {
26 | self.folderRepository = repository
27 | }
28 |
29 | func createFolder(request: FolderRequestDTO) async throws {
30 | try await folderRepository.createFolder(request: request)
31 | }
32 |
33 | func updateFolder(id: Int, request: FolderRequestDTO) async throws {
34 | try await folderRepository.updateFolder(id: id, request: request)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Folder/FolderDetailUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderDetailUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2023/04/26.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public protocol FolderDetailUseCase: BookmarkListUseCase {
10 | var bookmarkRepository: BookmarkRepository { get }
11 |
12 | func fetchBookmarkListInFolder(id: Int) async throws -> FetchBookmarkResponse
13 | }
14 |
15 | final class DefaultFolderDetailUseCase: FolderDetailUseCase {
16 |
17 | // MARK: - Properties
18 | private let folderDetailRepository: FolderDetailRepository
19 | let bookmarkRepository: BookmarkRepository
20 |
21 | // MARK: - Initializer
22 | init(folderDetailRepository: FolderDetailRepository, bookmarkRepository: BookmarkRepository) {
23 | self.folderDetailRepository = folderDetailRepository
24 | self.bookmarkRepository = bookmarkRepository
25 | }
26 |
27 | // MARK: - Methods
28 | func fetchBookmarkListInFolder(id: Int) async throws -> FetchBookmarkResponse {
29 | try await folderDetailRepository.fetchBookmarkListInFolder(id: id)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Folder/FolderListUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderListUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by 한상진 on 2023/11/29.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | public typealias FolderListUseCase = FetchFolderUseCase & DeleteFolderUseCase
10 |
11 | public enum FolderListError: Error {
12 | case failFetchFolderList
13 | case failDeleteFolder
14 | }
15 |
16 | final class DefaultFolderListUseCase: FolderListUseCase {
17 |
18 | // MARK: Properties
19 | var folderRepository: FolderRepository
20 |
21 | init(repository: FolderRepository) {
22 | self.folderRepository = repository
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/Search/SearchUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by Ian on 2022/09/27.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 |
11 | public enum SearchError: Error {
12 | case failEraseAllSearchHistory
13 | case emptySearchQuery
14 | }
15 |
16 | public protocol SearchUseCase {
17 | var searchRepository: SearchRepository { get }
18 |
19 | /// 최근 검색내역을 반환합니다.
20 | func fetchSearchHistory() -> [String]
21 |
22 | /// 검색 쿼리에 대응되는 결과를 반환합니다.
23 | func fetchSearchResult(request: BookmarkListRequest) async throws -> FetchBookmarkResponse
24 |
25 | /// 검색내역을 초기화합니다.
26 | func eraseAllSearchHistory()
27 | }
28 |
29 | extension SearchUseCase {
30 | func fetchSearchHistory() -> [String] {
31 | searchRepository.fetchSearchHistory()
32 | }
33 |
34 | func fetchSearchResult(request: BookmarkListRequest) async throws -> FetchBookmarkResponse {
35 | try await searchRepository.fetchSearchResult(request: request)
36 | }
37 |
38 | func eraseAllSearchHistory() {
39 | searchRepository.eraseAllSearchHistory()
40 | }
41 | }
42 |
43 | public typealias SearchListUseCase = SearchUseCase & FetchBookmarkUseCase & FavoriteBookmarkUseCase
44 |
45 | final class DefaultSearchListUseCase: SearchListUseCase {
46 |
47 | // MARK: Properties
48 | let searchRepository: SearchRepository
49 | let bookmarkRepository: BookmarkRepository
50 |
51 | // MARK: Initializer
52 | public init(searchRepository: SearchRepository, bookmarkRepository: BookmarkRepository) {
53 | self.searchRepository = searchRepository
54 | self.bookmarkRepository = bookmarkRepository
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Sources/UseCases/User/UserUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserUseCase.swift
3 | // TidifyDomain
4 | //
5 | // Created by Ian on 2022/08/07.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | public enum UserError: Error {
10 | case failAppleLogin
11 | case failKakaoLogin
12 | case failSignOut
13 | }
14 |
15 | public protocol UserUseCase {
16 | func appleLogin(token: String) async throws -> UserToken
17 | func kakaoLogin() async throws -> UserToken
18 | func signOut() async throws
19 |
20 | }
21 |
22 | final class DefaultUserUseCase: UserUseCase {
23 |
24 | // MARK: Properties
25 | private let userRepository: UserRepository
26 |
27 | // MARK: Initializer
28 | public init(userRepository: UserRepository) {
29 | self.userRepository = userRepository
30 | }
31 |
32 | // MARK: Methods
33 | func appleLogin(token: String) async throws -> UserToken {
34 | try await userRepository.appleLogin(token: token)
35 | }
36 |
37 | func kakaoLogin() async throws -> UserToken {
38 | try await userRepository.kakaoLogin()
39 | }
40 |
41 | func signOut() async throws {
42 | try await userRepository.signOut()
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Tests/Mocks/MockBookmarkRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockBookmarkRepository.swift
3 | // Tidify
4 | //
5 | // Created by Ian on 2022/10/28.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | @testable import TidifyDomain
12 |
13 | final class MockBookmarkRepository: BookmarkRepository {
14 |
15 | private(set) var bookmarks: [Bookmark] = Bookmark.stubList()
16 |
17 | func fetchBookmarkList(requestDTO: BookmarkListRequest) -> Single {
18 | return .create { [weak self] observer in
19 | if let keyword = requestDTO.keyword {
20 | observer(.success((bookmarks: self?.bookmarks.filter { $0.name.contains(keyword)} ?? [] , currentPage: 1, isLastPage: true)))
21 | } else {
22 | observer(.success((bookmarks: self?.bookmarks ?? [], currentPage: 1, isLastPage: true)))
23 | }
24 |
25 | return Disposables.create()
26 | }
27 | }
28 |
29 | func createBookmark(requestDTO: BookmarkRequestDTO) -> Single {
30 | let bookmark: Bookmark = .init(
31 | id: 99,
32 | folderID: requestDTO.folderID,
33 | urlString: requestDTO.url,
34 | name: requestDTO.name
35 | )
36 | bookmarks.append(bookmark)
37 |
38 | return .create { [weak self] observer in
39 | if self?.bookmarks.contains(bookmark) ?? false {
40 | observer(.success(()))
41 | } else {
42 | observer(.failure(BookmarkError.failCreateBookmark))
43 | }
44 |
45 | return Disposables.create()
46 | }
47 | }
48 |
49 | func deleteBookmark(bookmarkID: Int) -> Single {
50 | return .create { [weak self] observer in
51 | if let index = self?.bookmarks.firstIndex(where: { $0.id == bookmarkID }) {
52 | self?.bookmarks.remove(at: index)
53 | observer(.success(()))
54 | } else {
55 | observer(.failure(BookmarkError.cannotFindMachedBookmark))
56 | }
57 |
58 | return Disposables.create()
59 | }
60 | }
61 |
62 | func updateBookmark(bookmarkID: Int, requestDTO: BookmarkRequestDTO) -> Single {
63 | return .create { [weak self] observer in
64 | if var updateTargetBookmark = self?.bookmarks.first(where: { $0.id == bookmarkID}) {
65 | updateTargetBookmark.update(with: requestDTO)
66 |
67 | if updateTargetBookmark.name == requestDTO.name,
68 | updateTargetBookmark.urlString == requestDTO.url {
69 | observer(.success(()))
70 | } else {
71 | observer(.failure(BookmarkError.failUpdateBookmark))
72 | }
73 | }
74 |
75 | return Disposables.create()
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Tests/Mocks/MockFolderRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockFolderRepository.swift
3 | // TidifyDomainTests
4 | //
5 | // Created by 여정수 on 2023/05/31.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | @testable import TidifyDomain
12 |
13 | final class MockFolderRepository: FolderRepository {
14 |
15 | private(set) var folders: [Folder] = Folder.stubList()
16 |
17 | func createFolder(requestDTO: FolderRequestDTO) -> Single {
18 | return .create { [weak self] observer in
19 | if requestDTO.title.isEmpty {
20 | observer(.failure(FolderError.emptyFolderTitle))
21 | }
22 |
23 | if requestDTO.color.isEmpty {
24 | observer(.failure(FolderError.emptyColorValue))
25 | }
26 |
27 | let folder = Folder(id: 2, title: requestDTO.title, color: requestDTO.color)
28 | self?.folders.append(folder)
29 | observer(.success(folder))
30 |
31 | return Disposables.create()
32 | }
33 | }
34 |
35 | func fetchFolders(start: Int, count: Int) -> Single {
36 | return .create { [weak self] observer in
37 | observer(.success((folders: self?.folders ?? [], isLast: true)))
38 |
39 | return Disposables.create()
40 | }
41 | }
42 |
43 | func updateFolder(id: Int, requestDTO: FolderRequestDTO) -> Single {
44 | return .create { [weak self] observer in
45 | if var folder = self?.folders.first(where: { $0.id == id }) {
46 | folder.title = requestDTO.title
47 | folder.color = requestDTO.color
48 | } else {
49 | observer(.failure(FolderError.emptyMatchedFolder))
50 | }
51 |
52 | observer(.success(()))
53 |
54 | return Disposables.create()
55 | }
56 | }
57 |
58 | func deleteFolder(id: Int) -> Single {
59 | return .create { [weak self] observer in
60 | if let index = self?.folders.firstIndex(where: { $0.id == id }) {
61 | self?.folders.remove(at: index)
62 | observer(.success(()))
63 | } else {
64 | observer(.failure(FolderError.failFetchDeleteFolder))
65 | }
66 |
67 | return Disposables.create()
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Tests/Mocks/MockSearchRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockSearchRepository.swift
3 | // TidifyDomainTests
4 | //
5 | // Created by Ian on 2022/10/27.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | @testable import TidifyDomain
11 |
12 | import RxSwift
13 |
14 | final class MockSearchRepository: SearchRepository {
15 |
16 | private(set) var searchHistory: [String] = ["Test1", "Test2", "Test3"]
17 | private(set) var bookmarks: [Bookmark] = Bookmark.stubList()
18 |
19 | func fetchSearchHistory() -> Single<[String]> {
20 | return .just(searchHistory)
21 | }
22 |
23 | func eraseAllSearchHistory() -> Single {
24 | searchHistory = []
25 |
26 | return .create { [weak self] observer in
27 | if self?.searchHistory.isEmpty ?? false {
28 | observer(.success(()))
29 | } else {
30 | observer(.failure(SearchError.failEraseAllSearchHistory))
31 | }
32 |
33 | return Disposables.create()
34 | }
35 | }
36 |
37 | func fetchSearchResult(query: String) -> Single<[Bookmark]> {
38 | if query.isEmpty {
39 | return .error(SearchError.emptySearchQuery)
40 | }
41 |
42 | return .create { [weak self] observer in
43 | if let searchedBookmarks = self?.bookmarks.filter({ bookmark in bookmark.name.contains(query) }) {
44 | observer(.success(searchedBookmarks))
45 | } else {
46 | observer(.success([]))
47 | }
48 |
49 | return Disposables.create()
50 | }
51 | }
52 |
53 | func fetchSearchResult(requestDTO: BookmarkListRequest) -> Single {
54 | if requestDTO.keyword?.isEmpty ?? true {
55 | return .error(SearchError.emptySearchQuery)
56 | }
57 |
58 | return .create { [weak self] observer in
59 | if let searchedBookmarks = self?.bookmarks.filter({ $0.name.contains(requestDTO.keyword ?? "") }) {
60 | observer(.success((bookmarks: searchedBookmarks, currentPage: 0, isLastPage: true)))
61 | } else {
62 | observer(.success((bookmarks: [], currentPage: 0, isLastPage: true)))
63 | }
64 |
65 | return Disposables.create()
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Tests/Mocks/Mocks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Mocks.swift
3 | // Tidify
4 | //
5 | // Created by Ian on 2022/10/28.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | @testable import TidifyDomain
12 |
13 | public extension Bookmark {
14 | static func stubList() -> [Bookmark] {
15 | return [
16 | .init(id: 0, folderID: 0, urlString: "www.google.com", name: "Google"),
17 | .init(id: 1, folderID: 0, urlString: "https://duwjdtn11.tistory.com", name: "Tistory"),
18 | .init(id: 2, folderID: nil, urlString: "https://github.com/iannealer", name: "Github")
19 | ]
20 | }
21 | }
22 |
23 | public extension Folder {
24 | static func stubList() -> [Folder] {
25 | return [
26 | .init(id: 0, title: "Folder1", color: "#242132"),
27 | .init(id: 1, title: "Folder2", color: "#44ff12")
28 | ]
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyDomain/Tests/Search/SearchUseCaseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchUseCaseTests.swift
3 | // TidifyDomainTests
4 | //
5 | // Created by Ian on 2022/10/27.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | import RxSwift
12 | @testable import TidifyDomain
13 |
14 | final class SearchUseCaseTests: XCTestCase {
15 |
16 | // MARK: - Properties
17 | private var disposeBag: DisposeBag!
18 | private var repository: SearchRepository!
19 | private var useCase: SearchUseCase!
20 |
21 | override func setUp() {
22 | super.setUp()
23 |
24 | self.disposeBag = .init()
25 | self.repository = MockSearchRepository()
26 | self.useCase = DefaultSearchUseCase(searchRepository: repository)
27 | }
28 |
29 | override func tearDown() {
30 | super.tearDown()
31 |
32 | self.disposeBag = nil
33 | self.repository = nil
34 | self.useCase = nil
35 | }
36 |
37 | func test_whenFetchSearchHistory_thenShouldNotEmpty() {
38 | useCase.fetchSearchHistory()
39 | .subscribe(onNext: { history in
40 | if history.isEmpty {
41 | XCTAssert(false)
42 | } else {
43 | XCTAssert(true)
44 | }
45 | })
46 | .disposed(by: disposeBag)
47 | }
48 |
49 | func test_whenEraseAllHistory_thenShouldBeEmpty() {
50 | useCase.eraseAllSearchHistory()
51 | .flatMapLatest { _ -> Observable<[String]> in self.useCase.fetchSearchHistory() }
52 | .subscribe(onNext: { history in
53 | XCTAssert(history.isEmpty)
54 | }, onError: { _ in
55 | XCTAssert(false)
56 | })
57 | .disposed(by: disposeBag)
58 | }
59 |
60 | func test_whenSearchWithKeyword_thenShouldReturnMappedBookmarks() {
61 | let requestDTO: BookmarkListRequest = .init(page: 0, keyword: "Github")
62 | useCase.fetchSearchResult(requestDTO: requestDTO)
63 | .subscribe(onNext: { response in
64 | XCTAssert(response.bookmarks.allSatisfy({ bookmark in bookmark.name.contains("Github") }))
65 | }, onError: { _ in
66 | XCTAssert(false)
67 | })
68 | .disposed(by: disposeBag)
69 | }
70 |
71 | func test_whenSearchWithEmptyKeyword_thenShouldReturnError() {
72 | let requestDTO: BookmarkListRequest = .init(page: 0, keyword: "")
73 |
74 | useCase.fetchSearchResult(requestDTO: requestDTO)
75 | .subscribe(onNext: { _ in
76 | XCTAssert(false)
77 | }, onError: { error in
78 | XCTAssert(SearchError.emptySearchQuery == error as! SearchError)
79 | })
80 | .disposed(by: disposeBag)
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/BookmarkCreation/BookmarkCreationCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookmarkCreationCoordinator.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/28.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import TidifyDomain
11 | import UIKit
12 |
13 | protocol BookmarkCreationCoordinator: Coordinator {}
14 |
15 | final class DefaultBookmarkCreationCoordinator: BookmarkCreationCoordinator {
16 |
17 | // MARK: - Properties
18 | weak var parentCoordinator: Coordinator?
19 | var childCoordinators: [Coordinator] = []
20 | var navigationController: UINavigationController
21 |
22 | // MARK: - Initializer
23 | init(navigationController: UINavigationController) {
24 | self.navigationController = navigationController
25 | }
26 |
27 | // MARK: - Methods
28 | func start() {}
29 |
30 | func startPush(type: CreationType, originBookmark: Bookmark? = nil) -> BookmarkCreationViewController {
31 | guard let useCase: BookmarkCreationUseCase = DIContainer.shared.resolve(type: BookmarkCreationUseCase.self) else {
32 | fatalError()
33 | }
34 |
35 | let viewModel: BookmarkCreationViewModel = .init(useCase: useCase)
36 | let viewController: BookmarkCreationViewController = .init(
37 | viewModel: viewModel,
38 | creationType: type,
39 | originBookmark: originBookmark
40 | )
41 | viewController.coordinator = self
42 |
43 | return viewController
44 | }
45 |
46 | func didFinish() {
47 | parentCoordinator?.removeChild(self)
48 | navigationController.popViewController(animated: true)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/BaseViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseViewController.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/08/29.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import UIKit
11 |
12 | class BaseViewController: UIViewController {
13 |
14 | // MARK: Properties
15 | var cancellable: Set = []
16 |
17 | // MARK: LifeCycle
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 |
21 | setupViews()
22 | }
23 |
24 | // MARK: Methods
25 | func setupViews() {
26 | view.backgroundColor = .t_background()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Coordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Coordinator.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/06.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import UIKit
11 |
12 | protocol Coordinatable: AnyObject {
13 | associatedtype CoordinatorType: Coordinator
14 |
15 | var coordinator: CoordinatorType? { get }
16 | }
17 |
18 | /// Protocol that all coordinator must be conform
19 | public protocol Coordinator: AnyObject {
20 |
21 | // MARK: - Properties
22 | var parentCoordinator: Coordinator? { get set }
23 | var childCoordinators: [Coordinator] { get set }
24 | var navigationController: UINavigationController { get set }
25 |
26 | // MARK: - Methods
27 | func start()
28 | func didFinish()
29 | }
30 |
31 | // MARK: - Default Implementation
32 | public extension Coordinator {
33 | func addChild(_ child: Coordinator) {
34 | childCoordinators.append(child)
35 | }
36 |
37 | func transitionToLogin() {
38 | let loginCoordinator: LoginCoordinator = DIContainer.shared.resolve(type: LoginCoordinator.self)!
39 | loginCoordinator.parentCoordinator = parentCoordinator
40 | parentCoordinator?.childCoordinators = [loginCoordinator]
41 | KeyChain.deleteAll()
42 | loginCoordinator.start()
43 | }
44 |
45 | func removeChild(_ child: Coordinator?) {
46 | for (idx, coordinator) in childCoordinators.enumerated() {
47 | if coordinator === child {
48 | childCoordinators.remove(at: idx)
49 | break
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/CGSize+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGSize+.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/06.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // MARK: - CGSize Extension
12 | public extension CGSize {
13 | init(w: CGFloat, h: CGFloat) {
14 | self.init(width: w, height: h)
15 | }
16 |
17 | init(w: Int, h: Int) {
18 | self.init(width: w, height: h)
19 | }
20 |
21 | init(w: Double, h: Double) {
22 | self.init(width: w, height: h)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/Publisher+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Publisher+.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 여정수 on 12/30/23.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Combine
11 |
12 | extension Publisher {
13 | public func receiveOnMain() -> Publishers.ReceiveOn {
14 | self.receive(on: DispatchQueue.main)
15 | }
16 |
17 | public func withUnretained(_ object: T) -> Publishers.CompactMap {
18 | compactMap { [weak object] output in
19 | guard let object else {
20 | return nil
21 | }
22 |
23 | return (object, output)
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UICollectionView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/06.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // MARK: - UICollectionView Extension
12 | public extension UICollectionView {
13 | func t_registerCellClass(cellType: UICollectionViewCell.Type) {
14 | let identifer: String = "\(cellType)"
15 | register(cellType, forCellWithReuseIdentifier: identifer)
16 | }
17 |
18 | func t_registerHeaderClass(viewType: UICollectionReusableView.Type) {
19 | let identifier: String = "\(viewType)"
20 | register(viewType, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: identifier)
21 | }
22 |
23 | func t_registerFooterClass(viewType: UICollectionReusableView.Type) {
24 | let identifier: String = "\(viewType)"
25 | register(viewType, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: identifier)
26 | }
27 |
28 | // swiftlint:disable force_cast
29 | func t_dequeueReusableCell(cellType: T.Type = T.self, indexPath: IndexPath) -> T {
30 | return dequeueReusableCell(withReuseIdentifier: "\(cellType)", for: indexPath) as! T
31 | }
32 |
33 | func t_dequeueReusableHeader(viewType: T.Type = T.self, indexPath: IndexPath) -> T {
34 | return dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "\(viewType)", for: indexPath) as! T
35 | }
36 |
37 | func t_dequeueReusableFooter(viewType: T.Type = T.self, indexPath: IndexPath) -> T {
38 | return dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "\(viewType)", for: indexPath) as! T
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIFont+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIFont+.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/06.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // MARK: - UIFont Extension
12 | public extension UIFont {
13 | static func t_R(_ size: CGFloat) -> UIFont {
14 | systemFont(ofSize: size)
15 | }
16 |
17 | static func t_B(_ size: CGFloat) -> UIFont {
18 | systemFont(ofSize: size, weight: .bold)
19 | }
20 |
21 | static func t_SB(_ size: CGFloat) -> UIFont {
22 | systemFont(ofSize: size, weight: .semibold)
23 | }
24 |
25 | static func t_EB(_ size: CGFloat) -> UIFont {
26 | systemFont(ofSize: size, weight: .heavy)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIImage+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 여정수 on 2023/05/20.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIImage {
12 | static var symbolImage: UIImage {
13 | return .init(named: "ogIcon")!
14 | }
15 |
16 | func isSame(with image: UIImage) -> Bool {
17 | guard let currentImageData = self.pngData() as? NSData, let imageData = image.pngData() else {
18 | return false
19 | }
20 |
21 | return currentImageData.isEqual(to: imageData)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIImageview+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageview+.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 여정수 on 2023/05/20.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | import Kingfisher
12 |
13 | extension UIImageView {
14 | func setImage(with urlString: String) {
15 | ImageCache.default.retrieveImage(forKey: urlString) { result in
16 | switch result {
17 | case .success(let cacheResult):
18 | if let image = cacheResult.image {
19 | self.image = image
20 | } else {
21 | DispatchQueue.global(qos: .userInteractive).async { [weak self] in
22 | guard let url = URL(string: urlString), let _ = try? Data(contentsOf: url) else {
23 | DispatchQueue.main.async {
24 | self?.image = .symbolImage
25 | }
26 | return
27 | }
28 |
29 | let resource = ImageResource(downloadURL: url, cacheKey: urlString)
30 | DispatchQueue.main.async {
31 | self?.kf.setImage(with: resource)
32 | }
33 | }
34 | }
35 |
36 | case .failure:
37 | self.image = .symbolImage
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UITableView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/06.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // MARK: - UITableView Extension
12 | public extension UITableView {
13 | func t_registerCellClass(cellType: UITableViewCell.Type) {
14 | let identifier: String = "\(cellType)"
15 | register(cellType, forCellReuseIdentifier: identifier)
16 | }
17 |
18 | func t_registerCellClasses(_ cellTypes: [UITableViewCell.Type]) {
19 | cellTypes.forEach {
20 | let identifier: String = "\($0)"
21 | register($0, forCellReuseIdentifier: identifier)
22 | }
23 | }
24 |
25 | func t_registerHeaderFooterClass(viewType: UITableViewHeaderFooterView.Type) {
26 | let identifier: String = "\(viewType)"
27 | register(viewType, forHeaderFooterViewReuseIdentifier: identifier)
28 | }
29 |
30 | // swiftlint:disable force_cast
31 | func t_dequeueReusableCell(cellType: T.Type = T.self, indexPath: IndexPath) -> T {
32 | return dequeueReusableCell(withIdentifier: "\(cellType)", for: indexPath) as! T
33 | }
34 |
35 | func t_dequeueReusableHeaderFooterView(viewType: T.Type = T.self) -> T {
36 | return dequeueReusableHeaderFooterView(withIdentifier: "\(viewType)") as! T
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UITextField+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITextField+.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/11/22.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import UIKit
11 |
12 | extension UITextField {
13 | var publisher: AnyPublisher {
14 | NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: self)
15 | .compactMap { $0.object as? UITextField }
16 | .map { $0.text ?? "" }
17 | .eraseToAnyPublisher()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIViewController+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/06.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import UIKit
11 |
12 | import RxCocoa
13 | import RxSwift
14 |
15 | public extension UIViewController {
16 | static var viewHeight: CGFloat {
17 | guard let window = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
18 | return 0
19 | }
20 | return window.screen.bounds.height
21 | }
22 |
23 | static var viewWidth: CGFloat {
24 | guard let window = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
25 | return 0
26 | }
27 | return window.screen.bounds.width
28 | }
29 |
30 | static var topPadding: CGFloat {
31 | guard let window = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
32 | return 0
33 | }
34 | return window.windows.first?.safeAreaInsets.top ?? 0
35 | }
36 | }
37 |
38 | public extension Reactive where Base: UIViewController {
39 | var viewDidLoad: Observable {
40 | self.methodInvoked(#selector(UIViewController.viewDidLoad))
41 | .mapToVoid()
42 | }
43 |
44 | var viewWillAppear: Observable {
45 | self.methodInvoked(#selector(UIViewController.viewWillAppear(_:)))
46 | .mapToVoid()
47 | }
48 |
49 | var viewDidAppear: Observable {
50 | self.methodInvoked(#selector(UIViewController.viewDidAppear(_:)))
51 | .mapToVoid()
52 | }
53 |
54 | var viewWillDisappear: Observable {
55 | self.methodInvoked(#selector(UIViewController.viewWillDisappear(_:)))
56 | .mapToVoid()
57 | }
58 |
59 | var viewDidDisappear: Observable {
60 | self.methodInvoked(#selector(UIViewController.viewDidDisappear(_:)))
61 | .mapToVoid()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/MainCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainCoordinator.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/06.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import UIKit
11 |
12 | public protocol MainCoordinator: Coordinator {
13 | func startSplash()
14 | }
15 |
16 | public final class DefaultMainCoordinator: MainCoordinator {
17 |
18 | // MARK: - Properties
19 | public var parentCoordinator: Coordinator?
20 | public var childCoordinators: [Coordinator] = []
21 | public var navigationController: UINavigationController
22 | private let container: DIContainer = .shared
23 |
24 | private var isFirstLaunch = UserProperties.isFirstLaunch
25 |
26 | // MARK: - Initialize
27 | public init(navigationController: UINavigationController) {
28 | navigationController.view.backgroundColor = .systemBackground
29 | self.navigationController = navigationController
30 | }
31 |
32 | // MARK: - Methods
33 | public func startSplash() {
34 | let splashViewController: SplashViewController = .init(coordinator: self)
35 | navigationController.pushViewController(splashViewController, animated: false)
36 | }
37 |
38 | public func start() {
39 | navigationController.popViewController(animated: false)
40 |
41 | if isFirstLaunch {
42 | UserProperties.isFirstLaunch = false
43 | KeyChain.deleteAll()
44 | startOnboarding()
45 | return
46 | }
47 |
48 | if KeyChain.load(key: .accessToken) != nil {
49 | startTabBar()
50 | return
51 | }
52 |
53 | startSignIn()
54 | }
55 |
56 | public func didFinish() {}
57 | }
58 |
59 | private extension DefaultMainCoordinator {
60 | func startOnboarding() {
61 | guard let onboardingCoordinator = container.resolve(type: OnboardingCoordinator.self) else {
62 | return
63 | }
64 |
65 | onboardingCoordinator.parentCoordinator = self
66 | addChild(onboardingCoordinator)
67 |
68 | onboardingCoordinator.start()
69 | }
70 |
71 | func startSignIn() {
72 | guard let loginCoordinator = container.resolve(type: LoginCoordinator.self) else {
73 | return
74 | }
75 |
76 | loginCoordinator.parentCoordinator = self
77 | addChild(loginCoordinator)
78 |
79 | loginCoordinator.start()
80 | }
81 |
82 | func startTabBar() {
83 | guard let tabBarCoordinator = container.resolve(type: TabBarCoordinator.self) else {
84 | return
85 | }
86 |
87 | tabBarCoordinator.parentCoordinator = self
88 | addChild(tabBarCoordinator)
89 |
90 | tabBarCoordinator.start()
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Protocol/LoadingIndicatable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoadingIndicatable.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/12/06.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol LoadingIndicatable {
12 | var indicatorView: UIActivityIndicatorView { get set }
13 | }
14 |
15 | extension LoadingIndicatable where Self: UIViewController {
16 | func setIndicatorView(isLoading: Bool) {
17 | if isLoading {
18 | if indicatorView.isHidden {
19 | indicatorView.isHidden = false
20 | }
21 |
22 | indicatorView.startAnimating()
23 | } else {
24 | indicatorView.stopAnimating()
25 | indicatorView.isHidden = true
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Protocol/VIewModelType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VIewModelType.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/08/25.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol ViewModelType {
12 |
13 | // MARK: associatedtype
14 | associatedtype Action
15 | associatedtype State
16 |
17 | // MARK: Properties
18 | var state: State { get }
19 |
20 | // MARK: Methods
21 | func action(_ action: Action)
22 | }
23 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/SplashViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplashViewController.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/04/27.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | import Lottie
12 | import SnapKit
13 |
14 | final class SplashViewController: UIViewController {
15 | private let animationView: LottieAnimationView = .init(name: "splashLottie")
16 | private weak var coordinator: MainCoordinator?
17 |
18 | // MARK: Initializer
19 | init(coordinator: MainCoordinator) {
20 | self.coordinator = coordinator
21 | super.init(nibName: nil, bundle: nil)
22 | }
23 |
24 | required init?(coder: NSCoder) {
25 | fatalError("init(coder:) has not been implemented")
26 | }
27 |
28 | // MARK: Life Cycle
29 | override func viewDidLoad() {
30 | super.viewDidLoad()
31 |
32 | setupAnimationView()
33 | }
34 |
35 | override func viewWillAppear(_ animated: Bool) {
36 | super.viewWillAppear(animated)
37 |
38 | cooridnateAfterAnimation()
39 | }
40 | }
41 |
42 | // MARK: - Private
43 | private extension SplashViewController {
44 | func setupAnimationView() {
45 | view.backgroundColor = .white
46 |
47 | animationView.contentMode = .scaleAspectFit
48 | animationView.loopMode = .repeat(1)
49 | animationView.isHidden = true
50 | view.addSubview(animationView)
51 |
52 | animationView.snp.makeConstraints {
53 | $0.width.equalTo(view.frame.width * 0.533)
54 | $0.height.equalTo(animationView.snp.width).multipliedBy(0.5)
55 |
56 | $0.center.equalToSuperview()
57 | }
58 | }
59 |
60 | func cooridnateAfterAnimation() {
61 | animationView.isHidden = false
62 |
63 | animationView.play { [weak self] _ in
64 | self?.coordinator?.start()
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/View/TidifyNavigationBar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TidifyNavigationBar.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2022/08/13.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | import SnapKit
12 |
13 | final class TidifyNavigationBar: UIView {
14 |
15 | // MARK: Properties
16 | private let leftButtonStackView: UIStackView
17 | private let settingButton: UIButton
18 |
19 | init(leftButtonStackView: UIStackView, settingButton: UIButton) {
20 | self.leftButtonStackView = leftButtonStackView
21 | self.settingButton = settingButton
22 | super.init(frame: .zero)
23 |
24 | setupUI()
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 | }
31 |
32 | private extension TidifyNavigationBar {
33 | func setupUI() {
34 | backgroundColor = .clear
35 |
36 | addSubview(leftButtonStackView)
37 | addSubview(settingButton)
38 |
39 | leftButtonStackView.snp.makeConstraints {
40 | $0.leading.equalToSuperview().offset(20)
41 | $0.top.bottom.equalToSuperview().inset(10)
42 | }
43 |
44 | settingButton.snp.makeConstraints {
45 | $0.trailing.equalToSuperview().inset(26)
46 | $0.top.bottom.equalToSuperview().inset(11)
47 | $0.width.equalTo(settingButton.snp.height)
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Common/Base/View/TidifyRightButtonTextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TidifyRightButtonTextField.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/10/19.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | import RxSwift
12 |
13 | final class TidifyRightButtonTextField: UIView {
14 |
15 | // MARK: - Properties
16 | private let rightButton: UIButton = .init()
17 | private let textField: UITextField = .init()
18 | private let rightButtonImage: UIImage?
19 |
20 | private let placeholder: String
21 |
22 | init(
23 | placeholder: String,
24 | rightButtonImage: UIImage? = nil
25 | ) {
26 | self.placeholder = placeholder
27 | self.rightButtonImage = rightButtonImage
28 |
29 | super.init(frame: .zero)
30 |
31 | setupUI()
32 | }
33 |
34 | required init?(coder: NSCoder) {
35 | fatalError("init(coder:) has not been implemented")
36 | }
37 | }
38 |
39 | extension TidifyRightButtonTextField {
40 | var rightButtonTap: Observable {
41 | rightButton.rx.tap
42 | .asObservable()
43 | }
44 |
45 | func setText(text: String) {
46 | textField.text = text
47 | }
48 |
49 | func setColor(color: UIColor) {
50 | textField.textColor = color
51 | }
52 |
53 | func getColorString() -> String {
54 | guard let textColor = textField.textColor else { return .init() }
55 |
56 | return textColor.toColorString()
57 | }
58 | }
59 |
60 | // MARK: - Private
61 | private extension TidifyRightButtonTextField {
62 | func setupUI() {
63 | addSubview(textField)
64 | addSubview(rightButton)
65 |
66 | textField.leftView = .init(frame: .init(x: 0, y: 0, width: 20, height: 0))
67 | textField.leftViewMode = .always
68 | textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [.foregroundColor: UIColor.gray])
69 | textField.backgroundColor = .white
70 | textField.cornerRadius(radius: 16)
71 | textField.font = .t_SB(16)
72 | textField.textColor = .black
73 | textField.isUserInteractionEnabled = false
74 |
75 | if let rightButtonImage {
76 | rightButton.setImage(rightButtonImage, for: .normal)
77 | rightButton.imageView?.contentMode = .scaleAspectFill
78 |
79 | }
80 |
81 | rightButton.backgroundColor = .clear
82 |
83 | textField.snp.makeConstraints {
84 | $0.edges.equalToSuperview()
85 | }
86 |
87 | rightButton.snp.makeConstraints {
88 | $0.trailing.equalToSuperview().offset(-20)
89 | $0.centerY.equalToSuperview()
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Folder/Cell/FolderCreationCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderCreationCollectionViewCell.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/11/22.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 | import UIKit
11 |
12 | import SnapKit
13 |
14 | final class FolderCreationCollectionViewCell: UICollectionViewCell {
15 |
16 | // MARK: Properties
17 | private let colorView: UIView = .init()
18 |
19 | private let checkImageView: UIImageView = {
20 | let imageView: UIImageView = .init(image: .init(named: "colorSelectIcon"))
21 | imageView.isHidden = true
22 | return imageView
23 | }()
24 |
25 | // MARK: Initializer
26 | override init(frame: CGRect) {
27 | super.init(frame: frame)
28 |
29 | setupViews()
30 | setupLayoutConstraints()
31 | }
32 |
33 | required init?(coder: NSCoder) {
34 | fatalError("init(coder:) has not been implemented")
35 | }
36 |
37 | override func prepareForReuse() {
38 | super.prepareForReuse()
39 |
40 | checkImageView.isHidden = true
41 | }
42 |
43 | // MARK: Methods
44 | func configure(color: UIColor) {
45 | colorView.backgroundColor = color
46 | colorView.cornerRadius(radius: contentView.frame.height / 2)
47 | }
48 |
49 | func setSelected(isSelected: Bool) {
50 | checkImageView.isHidden = !isSelected
51 | }
52 | }
53 |
54 | // MARK: - Private
55 | private extension FolderCreationCollectionViewCell {
56 | func setupViews() {
57 | contentView.addSubview(colorView)
58 | colorView.addSubview(checkImageView)
59 | }
60 |
61 | func setupLayoutConstraints() {
62 | colorView.snp.makeConstraints {
63 | $0.edges.equalToSuperview()
64 | }
65 |
66 | checkImageView.snp.makeConstraints {
67 | $0.edges.equalToSuperview().inset(9)
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Folder/FolderCreationCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderCreationCoordinator.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/11/18.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import TidifyDomain
11 | import UIKit
12 |
13 | protocol FolderCreationCoordinator: Coordinator {}
14 |
15 | final class DefaultFolderCreationCoordinator: FolderCreationCoordinator {
16 |
17 | // MARK: - Properties
18 | weak var parentCoordinator: Coordinator?
19 | var childCoordinators: [Coordinator] = []
20 | var navigationController: UINavigationController
21 |
22 | // MARK: - Initializer
23 | init(navigationController: UINavigationController) {
24 | self.navigationController = navigationController
25 | }
26 |
27 | // MARK: - Methods
28 | func start() {}
29 |
30 | func startPush(type: CreationType, originFolder: Folder? = nil) -> FolderCreationViewController {
31 | guard let useCase: FolderCreationUseCase = DIContainer.shared.resolve(type: FolderCreationUseCase.self) else {
32 | fatalError()
33 | }
34 |
35 | let viewModel: FolderCreationViewModel = .init(useCase: useCase)
36 | let viewController: FolderCreationViewController = .init(
37 | viewModel: viewModel,
38 | creationType: type,
39 | originFolder: originFolder
40 | )
41 | viewController.coordinator = self
42 |
43 | return viewController
44 | }
45 |
46 | func didFinish() {
47 | parentCoordinator?.removeChild(self)
48 | navigationController.popViewController(animated: true)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Folder/FolderDetailCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderDetailCoordinator.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/12/14.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 |
10 | import TidifyCore
11 | import TidifyDomain
12 | import UIKit
13 |
14 | protocol FolderDetailCoordinator: Coordinator {
15 | func pushEditBookmarkScene(bookmark: Bookmark)
16 | func startWebView(bookmark: Bookmark)
17 | }
18 |
19 | final class DefaultFolderDetailCoordinator: FolderDetailCoordinator {
20 |
21 | // MARK: - Properties
22 | weak var parentCoordinator: Coordinator?
23 | var childCoordinators: [Coordinator] = []
24 | var navigationController: UINavigationController
25 |
26 | // MARK: - Initializer
27 | init(navigationController: UINavigationController) {
28 | self.navigationController = navigationController
29 | }
30 |
31 | // MARK: - Methods
32 | func start() {}
33 |
34 | func startPush(folder: Folder) -> FolderDetailViewController {
35 | guard let useCase: FolderDetailUseCase = DIContainer.shared.resolve(type: FolderDetailUseCase.self) else {
36 | fatalError()
37 | }
38 |
39 | let viewModel: FolderDetailViewModel = .init(useCase: useCase)
40 | let viewController: FolderDetailViewController = .init(viewModel: viewModel, folder: folder)
41 | viewController.coordinator = self
42 |
43 | return viewController
44 | }
45 |
46 | func pushEditBookmarkScene(bookmark: Bookmark) {
47 | guard let bookmarkCreationCoordinator = DIContainer.shared.resolve(
48 | type: BookmarkCreationCoordinator.self) as? DefaultBookmarkCreationCoordinator else { return }
49 |
50 | let bookmarkEditViewController = bookmarkCreationCoordinator.startPush(type: .edit, originBookmark: bookmark)
51 | bookmarkCreationCoordinator.parentCoordinator = self
52 | addChild(bookmarkCreationCoordinator)
53 |
54 | navigationController.pushViewController(bookmarkEditViewController, animated: true)
55 | }
56 |
57 | func startWebView(bookmark: Bookmark) {
58 | let webViewController: WebViewController = .init(bookmark: bookmark)
59 | webViewController.modalPresentationStyle = .fullScreen
60 | navigationController.present(webViewController, animated: false)
61 | }
62 |
63 | func didFinish() {
64 | parentCoordinator?.removeChild(self)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Folder/ViewModel/FolderCreationViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderCreationViewModel.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/11/22.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import TidifyDomain
11 |
12 | final class FolderCreationViewModel: ViewModelType {
13 | typealias UseCase = FolderCreationUseCase
14 |
15 | enum Action {
16 | case didTapCreateFolderButton(_ requestDTO: FolderRequestDTO)
17 | case didTapUpdateFolderButton(id: Int, requestDTO: FolderRequestDTO)
18 | }
19 |
20 | struct State: Equatable {
21 | var errorType: FolderCreationError?
22 | var isSuccess: Bool
23 | }
24 |
25 | let useCase: UseCase
26 | @Published var state: State
27 |
28 | init(useCase: UseCase) {
29 | self.useCase = useCase
30 | state = .init(errorType: nil, isSuccess: false)
31 | }
32 |
33 | func action(_ action: Action) {
34 | state.errorType = nil
35 |
36 | switch action {
37 | case .didTapCreateFolderButton(let requestDTO):
38 | createFolder(requestDTO)
39 | case .didTapUpdateFolderButton(let id, let requestDTO):
40 | updateFolder(id: id, requestDTO: requestDTO)
41 | }
42 | }
43 | }
44 |
45 | private extension FolderCreationViewModel {
46 | func createFolder(_ requestDTO: FolderRequestDTO) {
47 | Task {
48 | do {
49 | try await useCase.createFolder(request: requestDTO)
50 | state.isSuccess = true
51 | } catch {
52 | state.errorType = .failCreateFolder
53 | state.isSuccess = false
54 | }
55 | }
56 | }
57 |
58 | func updateFolder(id: Int, requestDTO: FolderRequestDTO) {
59 | Task {
60 | do {
61 | try await useCase.updateFolder(id: id, request: requestDTO)
62 | state.isSuccess = true
63 | } catch {
64 | state.errorType = .failUpdateFolder
65 | state.isSuccess = false
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Folder/ViewModel/FolderDetailViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderDetailViewModel.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/12/14.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import TidifyDomain
11 |
12 | final class FolderDetailViewModel: ViewModelType {
13 | typealias UseCase = FolderDetailUseCase
14 |
15 | enum Action {
16 | case initialize(folderID: Int)
17 | case didTapDelete(_ bookmarkID: Int)
18 | case didTapStarButton(_ bookmarkID: Int)
19 | }
20 |
21 | struct State: Equatable {
22 | var isLoading: Bool
23 | var bookmarks: [Bookmark]
24 | var errorType: BookmarkListError?
25 | }
26 |
27 | let useCase: UseCase
28 | @Published var state: State
29 |
30 | init(useCase: UseCase) {
31 | self.useCase = useCase
32 | state = .init(isLoading: false, bookmarks: [])
33 | }
34 |
35 | func action(_ action: Action) {
36 | state.errorType = nil
37 |
38 | switch action {
39 | case .initialize(let folderID):
40 | setupInitailBookmarks(folderID: folderID)
41 | case .didTapDelete(let bookmarkID):
42 | deleteBookmark(bookmarkID)
43 | case .didTapStarButton(let bookmarkID):
44 | didTapStarButton(bookmarkID)
45 | }
46 | }
47 | }
48 |
49 | private extension FolderDetailViewModel {
50 | func setupInitailBookmarks(folderID: Int) {
51 | Task {
52 | do {
53 | state.isLoading = true
54 | let fetchBookmarkListResponse = try await useCase.fetchBookmarkListInFolder(id: folderID)
55 | state.bookmarks = fetchBookmarkListResponse.bookmarks
56 | state.isLoading = false
57 | } catch {
58 | state.errorType = .failFetchBookmarks
59 | state.isLoading = false
60 | }
61 | }
62 | }
63 |
64 | func deleteBookmark(_ bookmarkID: Int) {
65 | guard let index = state.bookmarks.firstIndex(where: { $0.id == bookmarkID }) else {
66 | return
67 | }
68 |
69 | Task {
70 | do {
71 | try await useCase.deleteBookmark(bookmarkID: bookmarkID)
72 | state.bookmarks.remove(at: index)
73 | } catch {
74 | state.errorType = .failDeleteBookmark
75 | }
76 | }
77 | }
78 |
79 | func didTapStarButton(_ bookmarkID: Int) {
80 | Task {
81 | do {
82 | try await useCase.favoriteBookmark(id: bookmarkID)
83 | } catch {
84 | state.errorType = .failFavoriteBookmark
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Home/View/SearchHistoryCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchHistoryCell.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/09/27.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol SearchHistoryCellDelegate: AnyObject {
12 | func didTapSearchTitle(_ title: String)
13 | }
14 |
15 | final class SearchHistoryCell: UITableViewCell {
16 |
17 | // MARK: - Properties
18 | private let titleLabel: UILabel = {
19 | let label: UILabel = .init()
20 | label.font = .t_B(15)
21 | label.textColor = .black
22 | label.translatesAutoresizingMaskIntoConstraints = false
23 | return label
24 | }()
25 |
26 | weak var delegate: SearchHistoryCellDelegate?
27 |
28 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
29 | super.init(style: style, reuseIdentifier: reuseIdentifier)
30 |
31 | setupUI()
32 |
33 | let gestureRecognizer = contentView.addTap()
34 | gestureRecognizer.addTarget(self, action: #selector(tapSearchHistoryCell))
35 | }
36 |
37 | required init?(coder: NSCoder) {
38 | fatalError("init(coder:) has not been implemented")
39 | }
40 |
41 | override func prepareForReuse() {
42 | super.prepareForReuse()
43 |
44 | titleLabel.text = nil
45 | }
46 |
47 | func configure(title: String) {
48 | titleLabel.text = title
49 | }
50 | }
51 |
52 | // MARK: - Private
53 | private extension SearchHistoryCell {
54 | func setupUI() {
55 | contentView.addSubview(titleLabel)
56 | selectionStyle = .none
57 |
58 | NSLayoutConstraint.activate([
59 | titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
60 | titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
61 | titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: contentView.trailingAnchor, constant: -10)
62 | ])
63 | }
64 |
65 | @objc func tapSearchHistoryCell() {
66 | delegate?.didTapSearchTitle(titleLabel.text ?? "")
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Onboarding/OnboardingCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OnboardingCoordinator.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/06.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol OnboardingCoordinator: Coordinator {
12 | var parentCoordinator: Coordinator? { get set }
13 |
14 | func showNextScene()
15 | func startEmptyGuide()
16 | }
17 |
18 | final class DefaultOnboardingCoordinator: OnboardingCoordinator {
19 |
20 | // MARK: - Properties
21 | weak var parentCoordinator: Coordinator?
22 | var childCoordinators: [Coordinator] = []
23 | var navigationController: UINavigationController
24 | private var isEmptyGuide: Bool = false
25 |
26 | // MARK: - Initialize
27 | init(navigationController: UINavigationController) {
28 | self.navigationController = navigationController
29 | }
30 |
31 | // MARK: - Methods
32 | func start() {
33 | navigationController.pushViewController(getViewController(), animated: true)
34 | }
35 |
36 | func startEmptyGuide() {
37 | isEmptyGuide = true
38 | start()
39 | }
40 |
41 | func showNextScene() {
42 | if isEmptyGuide {
43 | navigationController.popViewController(animated: true)
44 | return
45 | }
46 | let loginCoordinator: DefaultLoginCoordinator = .init(
47 | navigationController: navigationController
48 | )
49 | loginCoordinator.parentCoordinator = parentCoordinator
50 | parentCoordinator?.addChild(loginCoordinator)
51 | loginCoordinator.start()
52 | }
53 |
54 | func didFinish() {
55 | parentCoordinator?.removeChild(self)
56 | }
57 | }
58 |
59 | private extension DefaultOnboardingCoordinator {
60 | func getViewController() -> OnboardingViewController {
61 | let viewController: OnboardingViewController = .init(nibName: nil, bundle: nil)
62 | viewController.coordinator = self
63 |
64 | return viewController
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Onboarding/View/Cell/OnboardingCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OnboardingCollectionViewCell.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/06.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 | import UIKit
11 |
12 | import SnapKit
13 |
14 | final class OnboardingCollectionViewCell: UICollectionViewCell {
15 |
16 | // MARK: - Constants
17 | static let identifer: String = "\(OnboardingCollectionViewCell.self)"
18 |
19 | // MARK: - Properties
20 | private weak var imageView: UIImageView!
21 |
22 | override init(frame: CGRect) {
23 | super.init(frame: frame)
24 |
25 | setupViews()
26 | setupLayoutConstraints()
27 | }
28 |
29 | required init?(coder: NSCoder) {
30 | fatalError("init(coder:) has not been implemented")
31 | }
32 |
33 | override func prepareForReuse() {
34 | super.prepareForReuse()
35 |
36 | imageView.image = nil
37 | }
38 |
39 | func configure(_ onboarding: Onboarding) {
40 | imageView.image = UIImage(named: onboarding.imageName)
41 | }
42 | }
43 |
44 | private extension OnboardingCollectionViewCell {
45 | func setupViews() {
46 | imageView = {
47 | let imageView: UIImageView = .init()
48 | imageView.contentMode = .scaleAspectFill
49 | return imageView
50 | }()
51 |
52 | contentView.addSubview(imageView)
53 | }
54 |
55 | func setupLayoutConstraints() {
56 | imageView.snp.makeConstraints {
57 | $0.top.equalToSuperview().offset(10)
58 | $0.leading.trailing.equalToSuperview()
59 | $0.bottom.equalToSuperview()
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/PresentationAssembly.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresentationAssembly.swift
3 | // Tidify
4 | //
5 | // Created by Ian on 2022/08/16.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import TidifyDomain
11 |
12 | import UIKit
13 |
14 |
15 | public struct PresentationAssembly: Assemblable {
16 |
17 | // MARK: - Properties
18 | private let navigationController: UINavigationController
19 |
20 | // MARK: - Initializer
21 | public init(navigationController: UINavigationController) {
22 | self.navigationController = navigationController
23 | }
24 |
25 | // MARK: - Methods
26 | public func assemble(container: DIContainer) {
27 | container.register(type: OnboardingCoordinator.self) { _ in
28 | DefaultOnboardingCoordinator(navigationController: navigationController)
29 | }
30 |
31 | container.register(type: LoginCoordinator.self) { _ in
32 | DefaultLoginCoordinator(navigationController: navigationController)
33 | }
34 |
35 | container.register(type: TabBarCoordinator.self) { _ in
36 | DefaultTabBarCoordinator(navigationController: navigationController)
37 | }
38 |
39 | container.register(type: HomeCoordinator.self) { _ in
40 | DefaultHomeCoordinator(navigationController: navigationController)
41 | }
42 |
43 | container.register(type: SettingCoordinator.self) { _ in
44 | DefaultSettingCoordinator(navigationController: navigationController)
45 | }
46 |
47 | container.register(type: BookmarkCreationCoordinator.self) { _ in
48 | DefaultBookmarkCreationCoordinator(navigationController: navigationController)
49 | }
50 |
51 | container.register(type: SearchCoordinator.self) { _ in
52 | DefaultSearchCoordinator(navigationController: navigationController)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Search/Cell/SearchTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchTableViewCell.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/12/15.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class SearchTableViewCell: UITableViewCell {
12 |
13 | // MARK: Properties
14 | private let titleLabel: UILabel = {
15 | let label: UILabel = .init()
16 | label.font = .t_B(15)
17 | label.textColor = .t_ashBlue(weight: 800)
18 | return label
19 | }()
20 |
21 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
22 | super.init(style: style, reuseIdentifier: reuseIdentifier)
23 |
24 | setupUI()
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 |
31 | // MARK: - Methods
32 | func configure(title: String) {
33 | titleLabel.text = title
34 | }
35 | }
36 |
37 | // MARK: - Private
38 | private extension SearchTableViewCell {
39 | func setupUI() {
40 | selectionStyle = .none
41 | backgroundColor = .white
42 |
43 | contentView.addSubview(titleLabel)
44 | titleLabel.snp.makeConstraints {
45 | $0.leading.trailing.equalToSuperview().offset(20)
46 | $0.top.bottom.equalToSuperview().inset(17)
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Search/SearchCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchCoordinator.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2022/08/11.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import TidifyDomain
11 | import UIKit
12 |
13 | protocol SearchCoordinator: Coordinator {
14 | func startWebView(bookmark: Bookmark)
15 | }
16 |
17 | final class DefaultSearchCoordinator: SearchCoordinator {
18 | weak var parentCoordinator: Coordinator?
19 | var childCoordinators: [Coordinator] = []
20 | var navigationController: UINavigationController
21 |
22 | // MARK: - Initialize
23 | init(navigationController: UINavigationController) {
24 | self.navigationController = navigationController
25 | }
26 |
27 | // MARK: - Methods
28 | func start() {}
29 |
30 | func startPush() -> SearchViewController {
31 | guard let useCase: SearchListUseCase = DIContainer.shared.resolve(type: SearchListUseCase.self) else {
32 | fatalError()
33 | }
34 |
35 | let viewModel: SearchViewModel = .init(useCase: useCase)
36 | let viewController: SearchViewController = .init(viewModel: viewModel)
37 | viewController.coordinator = self
38 |
39 | return viewController
40 | }
41 |
42 | func startWebView(bookmark: Bookmark) {
43 | let webViewController: WebViewController = .init(bookmark: bookmark)
44 | webViewController.modalPresentationStyle = .fullScreen
45 | navigationController.present(webViewController, animated: false)
46 | }
47 |
48 | func didFinish() {
49 | parentCoordinator?.removeChild(self)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Search/View/SearchTableViewHeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchTableViewHeaderView.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/12/15.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol EraseHistoryButtonDelegate: AnyObject {
12 | func didTapEraseButton()
13 | }
14 |
15 | final class SearchTableViewHeaderView: UIView {
16 |
17 | // MARK: Properties
18 | weak var delegate: EraseHistoryButtonDelegate?
19 |
20 | private let titleLabel: UILabel = {
21 | let label: UILabel = .init()
22 | label.font = .t_EB(20)
23 | label.textColor = .black
24 | label.text = "검색기록"
25 | return label
26 | }()
27 |
28 | private lazy var eraseButton: UIButton = {
29 | let button: UIButton = .init()
30 | button.setTitleColor(.t_gray(weight: 500), for: .normal)
31 | button.setTitle("모두 지우기", for: .normal)
32 | button.titleLabel?.font = .t_B(14)
33 | button.addTarget(self, action: #selector(didTapEraseButton), for: .touchUpInside)
34 | return button
35 | }()
36 |
37 | init() {
38 | super.init(frame: .zero)
39 |
40 | setupUI()
41 | }
42 |
43 | required init?(coder: NSCoder) {
44 | fatalError("init(coder:) has not been implemented")
45 | }
46 | }
47 |
48 | private extension SearchTableViewHeaderView {
49 | func setupUI() {
50 | backgroundColor = .white
51 | addSubview(titleLabel)
52 | addSubview(eraseButton)
53 |
54 | titleLabel.snp.makeConstraints {
55 | $0.leading.equalToSuperview().offset(20)
56 | $0.centerY.equalToSuperview()
57 | }
58 |
59 | eraseButton.snp.makeConstraints {
60 | $0.trailing.equalToSuperview().inset(20)
61 | $0.centerY.equalToSuperview()
62 | }
63 | }
64 |
65 | @objc func didTapEraseButton() {
66 | delegate?.didTapEraseButton()
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Setting/SettingCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingCoordinator.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/28.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import TidifyDomain
11 | import UIKit
12 |
13 | protocol SettingCoordinator: Coordinator {}
14 |
15 | final class DefaultSettingCoordinator: SettingCoordinator {
16 |
17 | // MARK: - Properties
18 | weak var parentCoordinator: Coordinator?
19 | var childCoordinators: [Coordinator] = []
20 | var navigationController: UINavigationController
21 |
22 | // MARK: - Initializer
23 | init(navigationController: UINavigationController) {
24 | self.navigationController = navigationController
25 | }
26 |
27 | // MARK: - Methods
28 | func start() {}
29 |
30 | func startPush() -> SettingViewController{
31 | guard let useCase: UserUseCase = DIContainer.shared.resolve(type: UserUseCase.self) else {
32 | fatalError()
33 | }
34 |
35 | let viewModel: SettingViewModel = .init(useCase: useCase)
36 | let viewController: SettingViewController = .init(viewModel: viewModel)
37 | viewController.coordinator = self
38 |
39 | return viewController
40 | }
41 |
42 | func didFinish() {
43 | parentCoordinator?.removeChild(self)
44 | }
45 |
46 | func resetCoordinator() {
47 | guard !(parentCoordinator is MainCoordinator) else {
48 | return
49 | }
50 | parentCoordinator = parentCoordinator?.parentCoordinator
51 | resetCoordinator()
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Setting/VIew/Cells/SettingCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingCell.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/10/22.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class SettingCell: UITableViewCell {
12 |
13 | // MARK: - Properties
14 | private let titleLabel: UILabel = {
15 | let label: UILabel = .init()
16 | label.font = .t_B(15)
17 | label.textColor = .t_ashBlue(weight: 800)
18 | return label
19 | }()
20 |
21 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
22 | super.init(style: style, reuseIdentifier: reuseIdentifier)
23 |
24 | setupUI()
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 |
31 | // MARK: - Methods
32 | func configure(title: String, isLastIndex: Bool) {
33 | titleLabel.text = title
34 |
35 | titleLabel.snp.makeConstraints {
36 | $0.leading.equalToSuperview().offset(20)
37 | $0.top.equalToSuperview().inset(17)
38 | $0.bottom.equalToSuperview().inset(isLastIndex ? 27 : 17)
39 | }
40 | }
41 | }
42 |
43 | // MARK: - Private
44 | private extension SettingCell {
45 | func setupUI() {
46 | selectionStyle = .none
47 | backgroundColor = .white
48 |
49 | contentView.addSubview(titleLabel)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Setting/VIew/Headers/SettingSectionHeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingSectionHeaderView.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/10/22.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class SettingSectionHeaderView: UIView {
12 |
13 | // MARK: - Properties
14 | private let titleLabel: UILabel = {
15 | let label: UILabel = .init()
16 | label.font = .t_EB(20)
17 | label.textColor = .black
18 | return label
19 | }()
20 |
21 | init(section: SettingViewController.Sections) {
22 | titleLabel.text = section.sectionTitle
23 | super.init(frame: .zero)
24 |
25 | setupUI()
26 | }
27 |
28 | required init?(coder: NSCoder) {
29 | fatalError("init(coder:) has not been implemented")
30 | }
31 | }
32 |
33 | private extension SettingSectionHeaderView {
34 | func setupUI() {
35 | backgroundColor = .white
36 | addSubview(titleLabel)
37 |
38 | cornerRadius([.topLeft, .topRight], radius: 15)
39 |
40 | titleLabel.snp.makeConstraints {
41 | $0.leading.equalToSuperview().offset(20)
42 | $0.centerY.equalToSuperview()
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/Setting/ViewModel/SettingViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingViewModel.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/12/14.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import TidifyDomain
11 |
12 | final class SettingViewModel: ViewModelType {
13 | typealias UseCase = UserUseCase
14 |
15 | enum Action: Equatable {
16 | case didTapSignOutButton
17 | }
18 |
19 | struct State: Equatable {
20 | var isSuccess: Bool
21 | var error: UserError?
22 | }
23 |
24 | let useCase: UseCase
25 | @Published var state: State
26 |
27 | init(useCase: UseCase) {
28 | self.useCase = useCase
29 | state = .init(isSuccess: false, error: nil)
30 | }
31 |
32 | func action(_ action: Action) {
33 | state.error = nil
34 |
35 | switch action {
36 | case .didTapSignOutButton:
37 | signOut()
38 | }
39 | }
40 | }
41 |
42 | private extension SettingViewModel {
43 | func signOut() {
44 | Task {
45 | do {
46 | try await useCase.signOut()
47 | state.isSuccess = true
48 | } catch {
49 | state.error = .failSignOut
50 | state.isSuccess = false
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/SignIn/LoginCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginCoordinator.swift
3 | // TidifyPresentation
4 | //
5 | // Created by Ian on 2022/08/07.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyCore
10 | import TidifyDomain
11 | import UIKit
12 |
13 | protocol LoginCoordinator: Coordinator {
14 | func didSuccessLogin()
15 | }
16 |
17 | final class DefaultLoginCoordinator: LoginCoordinator {
18 |
19 | // MARK: - Properties
20 | weak var parentCoordinator: Coordinator?
21 | var childCoordinators: [Coordinator] = []
22 | var navigationController: UINavigationController
23 |
24 | // MARK: - Initialize
25 | init(navigationController: UINavigationController) {
26 | self.navigationController = navigationController
27 | }
28 |
29 | // MARK: - Methods
30 | func start() {
31 | let vc = getViewController()
32 | navigationController.setViewControllers([vc], animated: false)
33 | }
34 |
35 | func didFinish() {
36 | parentCoordinator?.removeChild(self)
37 | }
38 |
39 | func didSuccessLogin() {
40 | guard let tabBarCoordinator = DIContainer.shared.resolve(type: TabBarCoordinator.self)
41 | as? DefaultTabBarCoordinator else { return }
42 | tabBarCoordinator.parentCoordinator = parentCoordinator
43 | parentCoordinator?.addChild(tabBarCoordinator)
44 | tabBarCoordinator.start()
45 | }
46 | }
47 |
48 | // MARK: - Private
49 | private extension DefaultLoginCoordinator {
50 | func getViewController() -> LoginViewController {
51 | guard let useCase = DIContainer.shared.resolve(type: UserUseCase.self) else {
52 | fatalError()
53 | }
54 | let viewModel = LoginViewModel(useCase: useCase)
55 | let viewController = LoginViewController(viewModel: viewModel)
56 | viewController.coordinator = self
57 | return viewController
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/SignIn/ViewModel/LoginViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewModel.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2023/08/25.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import TidifyCore
11 | import TidifyDomain
12 |
13 | final class LoginViewModel: ViewModelType {
14 | typealias UseCase = UserUseCase
15 |
16 | enum Action: Equatable {
17 | case tryAppleLogin(token: String)
18 | case tryKakaoLogin
19 | }
20 |
21 | struct State: Equatable {
22 | var isLoading: Bool
23 | var isEntered: Bool
24 | var errorType: UserError?
25 | }
26 |
27 | let useCase: UseCase
28 | @Published var state: State
29 |
30 | init(useCase: UseCase) {
31 | self.useCase = useCase
32 | state = .init(isLoading: false, isEntered: false, errorType: nil)
33 | }
34 |
35 | func action(_ action: Action) {
36 | state.errorType = nil
37 |
38 | Task {
39 | do {
40 | state.isLoading = true
41 | let userToken: UserToken = try await tryLogin(action: action)
42 | saveTokens(userToken: userToken)
43 | state.isLoading = false
44 | state.isEntered = true
45 | } catch {
46 | state.errorType = action == .tryKakaoLogin ? .failKakaoLogin : .failAppleLogin
47 | state.isLoading = false
48 | }
49 | }
50 | }
51 | }
52 |
53 | private extension LoginViewModel {
54 |
55 | // MARK: Methods
56 | func saveTokens(userToken: UserToken) {
57 | if let accessTokenData = userToken.accessToken.data(using: .utf8) {
58 | KeyChain.save(key: .accessToken, data: accessTokenData)
59 | }
60 | if let refreshTokenData = userToken.refreshToken.data(using: .utf8) {
61 | KeyChain.save(key: .refreshToken, data: refreshTokenData)
62 | }
63 | }
64 |
65 | func tryLogin(action: Action) async throws -> UserToken {
66 | switch action {
67 | case .tryAppleLogin(let token):
68 | return try await useCase.appleLogin(token: token)
69 | case .tryKakaoLogin:
70 | return try await useCase.kakaoLogin()
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Sources/TabBar/View/TabBarController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabBarController.swift
3 | // TidifyPresentation
4 | //
5 | // Created by 한상진 on 2022/08/11.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | import SnapKit
12 |
13 | final class TabBarController: UITabBarController, Coordinatable {
14 |
15 | // MARK: - Properties
16 | private let tidifyTabBar: TidifyTabBar = .init()
17 | weak var coordinator: DefaultTabBarCoordinator?
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 | tidifyTabBar.delegate = self
22 | setupUI()
23 | }
24 | }
25 |
26 | // MARK: - Methods
27 | private extension TabBarController {
28 | func setupUI() {
29 | view.addSubview(tidifyTabBar)
30 |
31 | tidifyTabBar.snp.makeConstraints {
32 | $0.leading.trailing.equalToSuperview()
33 | $0.height.greaterThanOrEqualTo(Self.viewHeight * 0.016 + 32)
34 | $0.bottom.equalToSuperview()
35 | }
36 | }
37 | }
38 |
39 | extension TabBarController: TidifyTabBarDelegate {
40 | func didSelectTab(_ item: TabBarItem) {
41 | if item == .bookmarkCreation {
42 | coordinator?.pushBookmarkCreationScene()
43 | return
44 | }
45 |
46 | selectedIndex = item.index
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Tests/Home/MockHomeCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockHomeCoordinator.swift
3 | // TidifyPresentationTests
4 | //
5 | // Created by Ian on 2022/08/29.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 | import UIKit
11 |
12 | @testable import TidifyPresentation
13 | final class MockHomeCoordinator: HomeCoordinator {
14 |
15 | var childCoordinators: [Coordinator] = []
16 | var navigationController: UINavigationController = .init()
17 |
18 | // MARK: - Properties
19 | private(set) var pushWebViewCalled: Bool = false
20 | private(set) var pushSettingSceneCalled: Bool = false
21 | private(set) var pushBookmarkCreateionSceneCalled: Bool = false
22 | private(set) var pushEditBookmarkSceneCalled: Bool = false
23 |
24 | func pushWebView(bookmark: Bookmark) {
25 | pushWebViewCalled = true
26 | }
27 |
28 | func pushSettingScene() {
29 | pushSettingSceneCalled = true
30 | }
31 |
32 | func pushBookmarkCreationScene() {
33 | pushBookmarkCreateionSceneCalled = true
34 | }
35 |
36 | func pushEditBookmarkScene(bookmark: TidifyDomain.Bookmark) {
37 | pushEditBookmarkSceneCalled = true
38 | }
39 |
40 | func start() {
41 | print("Start")
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Tidify/Targets/TidifyPresentation/Tests/Search/MockSearchCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockSearchCoordinator.swift
3 | // TidifyPresentationTests
4 | //
5 | // Created by 여정수 on 2023/06/01.
6 | // Copyright © 2023 Tidify. All rights reserved.
7 | //
8 |
9 | import TidifyDomain
10 | import UIKit
11 |
12 | @testable import TidifyPresentation
13 | final class MockSearchCoordinator: SearchCoordinator {
14 |
15 | var childCoordinators: [Coordinator] = []
16 | var navigationController: UINavigationController = .init()
17 |
18 | private(set) var pushWebViewCalled: Bool = false
19 |
20 | func pushWebView(bookmark: Bookmark) {
21 | pushWebViewCalled = true
22 | }
23 |
24 | func start() {
25 | print("Start")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Tidify/Tidify.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.applesignin
6 |
7 | Default
8 |
9 | com.apple.security.application-groups
10 |
11 | group.com.ian.Tidify.share
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Tidify/TidifyShareExtension/Resources/MainInterface.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Tidify/TidifyShareExtension/Sources/ShareViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShareViewController.swift
3 | // Tidify_ShareExtension
4 | //
5 | // Created by Ian on 2022/11/04.
6 | // Copyright © 2022 Tidify. All rights reserved.
7 | //
8 |
9 | import MobileCoreServices
10 | import Social
11 | import UIKit
12 |
13 | final class ShareViewController: SLComposeServiceViewController {
14 | override func isContentValid() -> Bool {
15 | return contentText.isEmpty == false
16 | }
17 |
18 | override func didSelectPost() {
19 | let userDefault = UserDefaults(suiteName: "group.com.ian.Tidify.share")!
20 | if let text = textView.text {
21 | userDefault.set(text, forKey: "SharedText")
22 | }
23 |
24 | if let item = extensionContext?.inputItems.first as? NSExtensionItem {
25 | if let attachments = item.attachments {
26 | for attachment: NSItemProvider in attachments {
27 | if attachment.hasItemConformingToTypeIdentifier("public.url") {
28 | attachment.loadItem(forTypeIdentifier: "public.url", options: nil, completionHandler: { (url, error) in
29 | if let shareURL = url as? NSURL {
30 | userDefault.set(shareURL.relativeString, forKey: "SharedURL")
31 | }
32 | self.extensionContext?.completeRequest(returningItems: [])
33 | })
34 | }
35 | }
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Tidify/TidifyShareExtension/TidifyShareExtension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.application-groups
6 |
7 | group.com.ian.Tidify.share
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Tidify/Tuist/Config.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 |
3 | let config = Config(
4 | plugins: [
5 | .local(path: .relativeToManifest("../../Plugins/Tidify")),
6 | ]
7 | )
8 |
--------------------------------------------------------------------------------
/Tidify/Tuist/Dependencies.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 |
3 | let dependencies = Dependencies(
4 | swiftPackageManager: .init(
5 | [
6 | .remote(url: "https://github.com/kakao/kakao-ios-sdk-rx.git", requirement: .upToNextMajor(from: "2.0.0")),
7 | .remote(url: "https://github.com/SnapKit/SnapKit.git", requirement: .upToNextMinor(from: "5.0.0")),
8 | .remote(url: "https://github.com/ReactiveX/RxSwift.git", requirement: .upToNextMinor(from: "6.5.0")),
9 | .remote(url: "https://github.com/onevcat/Kingfisher.git", requirement: .upToNextMinor(from: "6.3.0")),
10 | .remote(url: "https://github.com/Moya/Moya.git", requirement: .upToNextMinor(from: "15.0.0")),
11 | .remote(url: "https://github.com/ReactorKit/ReactorKit.git", requirement: .upToNextMinor(from: "3.2.0")),
12 | .remote(url: "https://github.com/RxSwiftCommunity/RxNimble.git", requirement: .upToNextMajor(from: "5.0.0")),
13 | .remote(url: "https://github.com/airbnb/lottie-ios.git", requirement: .upToNextMajor(from: "4.1.0"))
14 | ]
15 | ),
16 | platforms: [.iOS]
17 | )
18 |
--------------------------------------------------------------------------------