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