├── .github
├── ISSUE_TEMPLATE
│ └── feature-template.md
└── pull_request_template.md
├── .gitignore
├── .swiftlint.yml
├── README.md
├── TOASTER-iOS.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
├── xcshareddata
│ └── xcschemes
│ │ └── TOASTER-iOS.xcscheme
└── xcuserdata
│ └── yeahh.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── TOASTER-iOS
├── Application
│ ├── AppDelegate.swift
│ ├── Coordinator
│ │ ├── Coordinator.swift
│ │ ├── CoordinatorFinishOutput.swift
│ │ ├── Coordinators
│ │ │ ├── AddLinkCoordinator.swift
│ │ │ ├── AppCoordinator.swift
│ │ │ ├── ClipCoordinator.swift
│ │ │ ├── HomeCoordinator.swift
│ │ │ ├── LoginCoordinator.swift
│ │ │ ├── SearchCoordinator.swift
│ │ │ ├── TabBarCoordinator.swift
│ │ │ └── TimerCoordinator.swift
│ │ ├── Factory
│ │ │ ├── CoordinatorFactory.swift
│ │ │ └── ViewControllerFactory.swift
│ │ └── Router.swift
│ └── SceneDelegate.swift
├── Global
│ ├── Components
│ │ ├── .gitkeep
│ │ ├── ToasterBottomSheet
│ │ │ ├── BottomType.swift
│ │ │ └── ToasterBottomSheetViewController.swift
│ │ ├── ToasterLoadingView
│ │ │ └── ToasterLoadingView.swift
│ │ ├── ToasterNavigationController
│ │ │ ├── ToasterNavigationController.swift
│ │ │ └── ToasterNavigationType.swift
│ │ ├── ToasterPopup
│ │ │ └── ToasterPopupViewController.swift
│ │ ├── ToasterTipView
│ │ │ ├── TipPathView.swift
│ │ │ ├── TipUserDefaults.swift
│ │ │ └── ToasterTipView.swift
│ │ └── ToasterToastMessage
│ │ │ ├── ToastStatus.swift
│ │ │ └── ToasterToastMessageView.swift
│ ├── Consts
│ │ ├── FontLiterals.swift
│ │ ├── Notification.swift
│ │ └── StringLiterals.swift
│ ├── Extensions
│ │ ├── Combine+
│ │ │ ├── CancelBag.swift
│ │ │ ├── Publisher+Driver.swift
│ │ │ ├── Publisher+Operator.swift
│ │ │ ├── Publisher+UIBarButtonItem.swift
│ │ │ ├── Publisher+UIButton.swift
│ │ │ ├── Publisher+UIControl.swift
│ │ │ └── Publisher+UIGesture.swift
│ │ ├── Foundation+
│ │ │ └── NSObject+.swift
│ │ └── UIKit+
│ │ │ ├── UIButton+.swift
│ │ │ ├── UIColor+.swift
│ │ │ ├── UILabel+.swift
│ │ │ ├── UIStackView+.swift
│ │ │ ├── UITextField+.swift
│ │ │ ├── UIView+.swift
│ │ │ └── UIViewController+.swift
│ ├── Protocols
│ │ ├── .gitkeep
│ │ ├── AuthenticationAdapterProtocol.swift
│ │ └── ViewModelType.swift
│ ├── Resources
│ │ ├── Assets
│ │ │ ├── Assets.xcassets
│ │ │ │ ├── AccentColor.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── AppIcon.appiconset
│ │ │ │ │ ├── 100.png
│ │ │ │ │ ├── 1024.png
│ │ │ │ │ ├── 114.png
│ │ │ │ │ ├── 120.png
│ │ │ │ │ ├── 128.png
│ │ │ │ │ ├── 144.png
│ │ │ │ │ ├── 152.png
│ │ │ │ │ ├── 16.png
│ │ │ │ │ ├── 167.png
│ │ │ │ │ ├── 172.png
│ │ │ │ │ ├── 180.png
│ │ │ │ │ ├── 196.png
│ │ │ │ │ ├── 20.png
│ │ │ │ │ ├── 216.png
│ │ │ │ │ ├── 256.png
│ │ │ │ │ ├── 29.png
│ │ │ │ │ ├── 32.png
│ │ │ │ │ ├── 40.png
│ │ │ │ │ ├── 48.png
│ │ │ │ │ ├── 50.png
│ │ │ │ │ ├── 512.png
│ │ │ │ │ ├── 55.png
│ │ │ │ │ ├── 57.png
│ │ │ │ │ ├── 58.png
│ │ │ │ │ ├── 60.png
│ │ │ │ │ ├── 64.png
│ │ │ │ │ ├── 66.png
│ │ │ │ │ ├── 72.png
│ │ │ │ │ ├── 76.png
│ │ │ │ │ ├── 80.png
│ │ │ │ │ ├── 87.png
│ │ │ │ │ ├── 88.png
│ │ │ │ │ ├── 92.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Contents.json
│ │ │ │ ├── EmptyView
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── clip_empty.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── img_clip.png
│ │ │ │ │ │ ├── img_clip@2x.png
│ │ │ │ │ │ └── img_clip@3x.png
│ │ │ │ │ ├── detail_clip_empty.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── img_link.png
│ │ │ │ │ │ ├── img_link@2x.png
│ │ │ │ │ │ └── img_link@3x.png
│ │ │ │ │ ├── img_alarm.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── img_alarm.png
│ │ │ │ │ │ ├── img_alarm@2x.png
│ │ │ │ │ │ └── img_alarm@3x.png
│ │ │ │ │ ├── img_alarm_ios.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── img_alarm_ios.png
│ │ │ │ │ │ ├── img_alarm_ios@2x.png
│ │ │ │ │ │ └── img_alarm_ios@3x.png
│ │ │ │ │ ├── img_search.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── img_search.png
│ │ │ │ │ │ ├── img_search@2x.png
│ │ │ │ │ │ └── img_search@3x.png
│ │ │ │ │ └── img_timer.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── img_timer.png
│ │ │ │ │ │ ├── img_timer@2x.png
│ │ │ │ │ │ └── img_timer@3x.png
│ │ │ │ ├── Icons_18
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── ic_alert_18_dark.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_alert_18_dark.svg
│ │ │ │ │ ├── ic_alert_18_white.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── ic_alert_18_white.png
│ │ │ │ │ │ ├── ic_alert_18_white@2x.png
│ │ │ │ │ │ └── ic_alert_18_white@3x.png
│ │ │ │ │ ├── ic_arrow_18.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_arrow_18.svg
│ │ │ │ │ ├── ic_check_18.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_check_18.svg
│ │ │ │ │ ├── ic_check_18_white.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── ic_check_18_white.png
│ │ │ │ │ │ ├── ic_check_18_white@2x.png
│ │ │ │ │ │ └── ic_check_18_white@3x.png
│ │ │ │ │ ├── ic_plus_18_orange.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_plus_18_orange.svg
│ │ │ │ │ └── ic_search_cancle.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_search_cancle.svg
│ │ │ │ ├── Icons_20
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── alarm_disabled_20.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── alarm_disabled_20.svg
│ │ │ │ │ ├── ic_arrow_20.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_arrow_20.svg
│ │ │ │ │ └── ic_search_20.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_search_20.svg
│ │ │ │ ├── Icons_24
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── ic_alarm_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_alarm_24.svg
│ │ │ │ │ ├── ic_all_clip_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_all_clip_24.svg
│ │ │ │ │ ├── ic_apple_login_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_apple_login_24.svg
│ │ │ │ │ ├── ic_arrow2_back_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_arrow2_24.svg
│ │ │ │ │ ├── ic_arrow2_forward_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_arrow2_24.svg
│ │ │ │ │ ├── ic_arrow_left_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_arrow_left_24.svg
│ │ │ │ │ ├── ic_cancle_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_cancle_24.svg
│ │ │ │ │ ├── ic_clip_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_clip_24.svg
│ │ │ │ │ ├── ic_clip_24_black.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_clip_24_black.svg
│ │ │ │ │ ├── ic_clip_full_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_clip_full_24.svg
│ │ │ │ │ ├── ic_close_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_close_24.svg
│ │ │ │ │ ├── ic_edit_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_edit_24.svg
│ │ │ │ │ ├── ic_home_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_home_24.svg
│ │ │ │ │ ├── ic_kakao_login_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_kakao_login_24.svg
│ │ │ │ │ ├── ic_more_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_more_24.svg
│ │ │ │ │ ├── ic_my_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_my_24.svg
│ │ │ │ │ ├── ic_pin_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_pin_24.svg
│ │ │ │ │ ├── ic_plus_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_plus_24.svg
│ │ │ │ │ ├── ic_read.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_read.svg
│ │ │ │ │ ├── ic_reload_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_reload_24.svg
│ │ │ │ │ ├── ic_safari_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_safari_24.svg
│ │ │ │ │ ├── ic_search_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_search_24.svg
│ │ │ │ │ ├── ic_settings_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_settings_24.svg
│ │ │ │ │ ├── ic_share.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_share.svg
│ │ │ │ │ └── ic_timer_24.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_timer_24.svg
│ │ │ │ ├── Icons_plus
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── btn_plus.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── btn_plus.svg
│ │ │ │ │ ├── fab_plus.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── fab_plus.svg
│ │ │ │ │ └── ic_delete_28.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── ic_delete_28.svg
│ │ │ │ └── Image
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── img_bmsite.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── img_bmsite.png
│ │ │ │ │ ├── img_bmsite@2x.png
│ │ │ │ │ └── img_bmsite@3x.png
│ │ │ │ │ ├── img_indicator.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── img_indicator.svg
│ │ │ │ │ ├── img_indicator_selected.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── img_indicator_selected.svg
│ │ │ │ │ ├── img_login.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── img_login.png
│ │ │ │ │ ├── img_login@2x.png
│ │ │ │ │ └── img_login@3x.png
│ │ │ │ │ ├── img_onboarding.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── img_onboarding.png
│ │ │ │ │ ├── img_onboarding@2x.png
│ │ │ │ │ └── img_onboarding@3x.png
│ │ │ │ │ ├── img_onboarding2.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── img_onboarding2.png
│ │ │ │ │ ├── img_onboarding2@2x.png
│ │ │ │ │ └── img_onboarding2@3x.png
│ │ │ │ │ ├── img_onboarding3.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── img_onboarding3.png
│ │ │ │ │ ├── img_onboarding3@2x.png
│ │ │ │ │ └── img_onboarding3@3x.png
│ │ │ │ │ ├── img_thumbnail.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── img_thumbnail.png
│ │ │ │ │ ├── img_thumbnail@2x.png
│ │ │ │ │ └── img_thumbnail@3x.png
│ │ │ │ │ ├── loginTitle.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── loginTitle.png
│ │ │ │ │ ├── loginTitle@2x.png
│ │ │ │ │ └── loginTitle@3x.png
│ │ │ │ │ ├── profile.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── profile.png
│ │ │ │ │ ├── profile@2x.png
│ │ │ │ │ └── profile@3x.png
│ │ │ │ │ ├── symbol.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── symbol.png
│ │ │ │ │ ├── symbol@2x.png
│ │ │ │ │ └── symbol@3x.png
│ │ │ │ │ └── wordmark.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── wordmark.png
│ │ │ │ │ ├── wordmark@2x.png
│ │ │ │ │ └── wordmark@3x.png
│ │ │ └── Colors.xcassets
│ │ │ │ ├── Contents.json
│ │ │ │ ├── black850.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── black900.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── gray100.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── gray150.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── gray200.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── gray300.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── gray400.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── gray50.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── gray500.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── gray600.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── gray700.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── gray800.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── loginKakao.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toaster100.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toaster200.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toaster300.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toaster400.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toaster50.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toaster800.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toasterBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toasterBlack.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toasterError.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toasterPrimary.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toasterSuccess.colorset
│ │ │ │ └── Contents.json
│ │ │ │ ├── toasterWarning.colorset
│ │ │ │ └── Contents.json
│ │ │ │ └── toasterWhite.colorset
│ │ │ │ └── Contents.json
│ │ ├── Base.lproj
│ │ │ └── LaunchScreen.storyboard
│ │ ├── Config.swift
│ │ └── Fonts
│ │ │ ├── .gitkeep
│ │ │ ├── SUIT-Bold.otf
│ │ │ ├── SUIT-ExtraBold.otf
│ │ │ ├── SUIT-Medium.otf
│ │ │ ├── SUIT-Regular.otf
│ │ │ └── SUIT-SemiBold.otf
│ └── Supporting Files
│ │ └── Info.plist
├── Network
│ ├── .gitkeep
│ ├── Auth
│ │ ├── AuthAPIService.swift
│ │ ├── AuthTargetType.swift
│ │ └── DTO
│ │ │ ├── Request
│ │ │ └── PostSocialLoginRequestDTO.swift
│ │ │ └── Response
│ │ │ ├── PostRefreshTokenResponseDTO.swift
│ │ │ ├── PostSocialLoginResponseDTO.swift
│ │ │ └── PostTokenHealthResponseDTO.swift
│ ├── Base
│ │ ├── APIInterceptor.swift
│ │ ├── BaseAPIService.swift
│ │ ├── BaseTargetType.swift
│ │ ├── KeyChainService.swift
│ │ ├── MoyaPlugin.swift
│ │ ├── NetworkResult.swift
│ │ ├── NetworkService.swift
│ │ └── NoneDataResponseDTO.swift
│ ├── Clip
│ │ ├── ClipAPIService.swift
│ │ ├── ClipTargetType.swift
│ │ └── DTO
│ │ │ ├── Request
│ │ │ ├── PatchEditNameCategoryRequestDTO.swift
│ │ │ ├── PatchEditPriorityCategoryRequestDTO.swift
│ │ │ └── PostAddCategoryRequestDTO.swift
│ │ │ └── Response
│ │ │ ├── GetAllCategoryResponseDTO.swift
│ │ │ ├── GetCheckCategoryResponseDTO.swift
│ │ │ └── GetDetailCategoryResponseDTO.swift
│ ├── Popup
│ │ ├── DTO
│ │ │ ├── GetPopupInfoResponseDTO.swift
│ │ │ ├── PatchPopupHiddenRequestDTO.swift
│ │ │ └── PatchPopupHiddenResponseDTO.swift
│ │ ├── PopupAPIService.swift
│ │ └── PopupTargetType.swift
│ ├── Search
│ │ ├── DTO
│ │ │ ├── GetMainPageSearchResponseDTO.swift
│ │ │ └── GetRecommendSiteResponseDTO.swift
│ │ ├── SearchAPIService.swift
│ │ └── SearchTargetType.swift
│ ├── Timer
│ │ ├── DTO
│ │ │ ├── Request
│ │ │ │ ├── PatchEditTimerRequestDTO.swift
│ │ │ │ ├── PatchEditTimerTitleRequestDTO.swift
│ │ │ │ └── PostCreateTimerRequestDTO.swift
│ │ │ └── Response
│ │ │ │ ├── GetDetailTimerResponseDTO.swift
│ │ │ │ └── GetTimerMainpageResponseDTO.swift
│ │ ├── TimerAPIService.swift
│ │ └── TimerTargetType.swift
│ ├── Toaster
│ │ ├── DTO
│ │ │ ├── Request
│ │ │ │ ├── PatchChangeCategoryRequestDTO.swift
│ │ │ │ ├── PatchEditLinkTitleRequestDTO.swift
│ │ │ │ ├── PatchOpenLinkRequestDTO.swift
│ │ │ │ └── PostSaveLinkRequestDTO.swift
│ │ │ └── Response
│ │ │ │ ├── GetRecentLinkResponseDTO.swift
│ │ │ │ ├── GetWeeksLinkResponseDTO.swift
│ │ │ │ ├── PatchChangeCategoryResponseDTO.swift
│ │ │ │ ├── PatchEditLinkTitleResponseDTO.swift
│ │ │ │ └── PatchOpenLinkResponseDTO.swift
│ │ ├── ToasterAPIService.swift
│ │ └── ToasterTargetType.swift
│ └── User
│ │ ├── DTO
│ │ ├── Request
│ │ │ └── PatchPushAlarmRequestDTO.swift
│ │ └── Response
│ │ │ ├── GetMainPageResponseDTO.swift
│ │ │ ├── GetMyPageResponseDTO.swift
│ │ │ ├── GetSettingPageResponseDTO.swift
│ │ │ └── PatchPushAlarmResponseDTO.swift
│ │ ├── UserAPIService.swift
│ │ └── UserTargetType.swift
├── Present
│ ├── AddLink
│ │ ├── LinkEmbed
│ │ │ ├── Model
│ │ │ │ └── SaveLinkModel.swift
│ │ │ ├── View
│ │ │ │ ├── AddLinkView.swift
│ │ │ │ └── AddLinkViewController.swift
│ │ │ └── ViewModel
│ │ │ │ └── AddLinkViewModel.swift
│ │ └── SelectClip
│ │ │ ├── Model
│ │ │ └── SelectClipModel.swift
│ │ │ ├── View
│ │ │ ├── SelectClipHeaderView.swift
│ │ │ └── SelectClipViewController.swift
│ │ │ └── ViewModel
│ │ │ └── SelectClipViewModel.swift
│ ├── Clip
│ │ ├── Model
│ │ │ └── ClipModel.swift
│ │ ├── View
│ │ │ ├── Cell
│ │ │ │ └── ClipListCollectionViewCell.swift
│ │ │ ├── ClipViewController.swift
│ │ │ └── Component
│ │ │ │ ├── AddClipBottomSheetView.swift
│ │ │ │ ├── ClipCollectionHeaderView.swift
│ │ │ │ └── ClipEmptyView.swift
│ │ └── ViewModel
│ │ │ └── ClipViewModel.swift
│ ├── DetailClip
│ │ ├── Model
│ │ │ └── DetailClipModel.swift
│ │ ├── View
│ │ │ ├── Cell
│ │ │ │ └── DetailClipListCollectionViewCell.swift
│ │ │ ├── Component
│ │ │ │ ├── ChangeClipBottomSheetView.swift
│ │ │ │ ├── DetailClipEmptyView.swift
│ │ │ │ ├── DetailClipSegmentedControlView.swift
│ │ │ │ ├── DetailEditLinkBottomSheet.swift
│ │ │ │ ├── EditLinkBottomSheetView.swift
│ │ │ │ └── LinkOptionBottomSheetView.swift
│ │ │ └── DetailClipViewController.swift
│ │ └── ViewModel
│ │ │ ├── DetailClipPropertyType.swift
│ │ │ └── DetailClipViewModel.swift
│ ├── EditClip
│ │ ├── Model
│ │ │ └── ClipPriorityEditModel.swift
│ │ ├── View
│ │ │ ├── EditClipCollectionViewCell.swift
│ │ │ ├── EditClipNoticeView.swift
│ │ │ └── EditClipViewController.swift
│ │ └── ViewModel
│ │ │ └── EditClipViewModel.swift
│ ├── Home
│ │ ├── HomeViewController.swift
│ │ ├── Model
│ │ │ ├── MainInfoModel.swift
│ │ │ ├── PopupInfoModel.swift
│ │ │ ├── RecentLinkModel.swift
│ │ │ ├── RecommendSiteModel.swift
│ │ │ └── WeeklyLinkModel.swift
│ │ ├── View
│ │ │ ├── Cell
│ │ │ │ ├── HomeFooterCollectionView.swift
│ │ │ │ ├── HomeHeaderCollectionView.swift
│ │ │ │ ├── MainCollectionViewCell.swift
│ │ │ │ ├── UserClipCollectionViewCell.swift
│ │ │ │ ├── UserClipEmptyCollectionViewCell.swift
│ │ │ │ ├── WeeklyLinkCollectionViewCell.swift
│ │ │ │ └── WeeklyRecommendCollectionViewCell.swift
│ │ │ ├── Component
│ │ │ │ └── CompositioinalFactory.swift
│ │ │ ├── HomeView.swift
│ │ │ └── HomeViewController.swift
│ │ └── ViewModel
│ │ │ └── HomeViewModel.swift
│ ├── LinkWeb
│ │ ├── Model
│ │ │ └── LinkReadEditModel.swift
│ │ ├── View
│ │ │ ├── LinkWebNavigationView.swift
│ │ │ └── LinkWebToolBarView.swift
│ │ ├── ViewController
│ │ │ └── LinkWebViewController.swift
│ │ └── ViewModel
│ │ │ └── LinkWebViewModel.swift
│ ├── Login
│ │ ├── Adapter
│ │ │ ├── AppleAuthenticateAdapter.swift
│ │ │ └── KakaoAuthenticateAdapter.swift
│ │ ├── LoginError.swift
│ │ ├── LoginViewController.swift
│ │ ├── Model
│ │ │ └── SocialLoginTokenModel.swift
│ │ ├── OnboardingType.swift
│ │ ├── OnboardingViewController.swift
│ │ ├── SocialLoginType.swift
│ │ ├── UseCase
│ │ │ ├── LoginUseCase.swift
│ │ │ └── LogoutUseCase.swift
│ │ └── View
│ │ │ ├── CustomPageIndicatorView.swift
│ │ │ └── SocialLoginButtonView.swift
│ ├── Remind
│ │ ├── Model
│ │ │ └── RemindModel.swift
│ │ ├── View
│ │ │ ├── Cell
│ │ │ │ ├── CompleteTimerCollectionViewCell.swift
│ │ │ │ ├── CompleteTimerEmptyCollectionViewCell.swift
│ │ │ │ ├── RemindCollectionFooterView.swift
│ │ │ │ ├── RemindCollectionHeaderView.swift
│ │ │ │ └── WaitTimerCollectionViewCell.swift
│ │ │ ├── Component
│ │ │ │ ├── AlarmOffStateButton.swift
│ │ │ │ ├── RemindAlarmOffBottomSheetView.swift
│ │ │ │ ├── RemindAlarmOffView.swift
│ │ │ │ ├── RemindAlarmOffViewType.swift
│ │ │ │ ├── RemindTimerEditBottomSheetView.swift
│ │ │ │ └── RemindTimerEmptyView.swift
│ │ │ └── RemindViewController.swift
│ │ └── ViewModel
│ │ │ └── RemindViewModel.swift
│ ├── RemindAdd
│ │ ├── ClipAdd
│ │ │ ├── Model
│ │ │ │ └── RemindClipModel.swift
│ │ │ ├── View
│ │ │ │ ├── Cell
│ │ │ │ │ └── RemindSelectClipCollectionViewCell.swift
│ │ │ │ └── RemindSelectClipViewController.swift
│ │ │ └── ViewModel
│ │ │ │ └── RemindSelectClipViewModel.swift
│ │ └── TimerAdd
│ │ │ ├── Model
│ │ │ └── RemindTimerAddModel.swift
│ │ │ ├── RemindTimerAddViewController.swift
│ │ │ ├── TimerRepeatDate.swift
│ │ │ ├── View
│ │ │ ├── Cell
│ │ │ │ └── TimerRepeatCollectionViewCell.swift
│ │ │ └── TimerRepeatBottomSheetView.swift
│ │ │ └── ViewModel
│ │ │ └── RemindTimerAddViewModel.swift
│ ├── Search
│ │ ├── Model
│ │ │ └── SearchResultModel.swift
│ │ ├── View
│ │ │ ├── SearchEmptyResultView.swift
│ │ │ └── SearchViewController.swift
│ │ └── ViewModel
│ │ │ └── SearchViewModel.swift
│ ├── Setting
│ │ ├── Model
│ │ │ └── MypageUserModel.swift
│ │ └── View
│ │ │ ├── Cell
│ │ │ └── SettingTableViewCell.swift
│ │ │ ├── MypageHeaderView.swift
│ │ │ ├── SettingView.swift
│ │ │ └── SettingViewController.swift
│ ├── TabBar
│ │ ├── CustomTabBar.swift
│ │ ├── TabBarController.swift
│ │ └── TabBarItem.swift
│ ├── UpdateAlert
│ │ ├── UpdateAlertManager.swift
│ │ └── UpdateAlertType.swift
│ └── ViewController.swift
└── TOASTER-iOS.entitlements
└── ToasterShareExtension
├── Info.plist
├── MoyaPlugin.swift
├── ShareViewController.swift
├── ShareViewModel.swift
└── ToasterShareExtension.entitlements
/.github/ISSUE_TEMPLATE/feature-template.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Template
3 | about: "기능 이슈 템플릿입니다 \U0001F4AA\U0001F3FB"
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## 👀 이슈 (issue)
11 |
12 |
13 | ## 🚀 to-do
14 |
15 | - [ ] 할 것
16 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## ✨ 해결한 이슈
2 |
3 | - Resolved: #이슈번호
4 |
5 | ## 🛠️ 작업내용
6 |
7 |
8 | | 구현 내용 | SE | 13 mini | 15 pro |
9 | | :-------------: | :----------: | :----------: | :----------: |
10 | | GIF |
|
|
|
11 |
12 | ## 🖥️ 주요 코드 설명
13 |
14 | `HomeView`
15 | - 쏼라쏼라
16 | ```swift
17 | // 코드는 이 사이에 작성하면 됩니다.
18 | ```
19 |
20 | ## 📂 참고한 내용
21 |
22 |
23 | ## ✅ Checklist
24 | - [ ] 필요없는 주석, 프린트문 제거했는지 확인
25 | - [ ] 컨벤션 지켰는지 확인
26 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | # 실행에서 제외할 룰 식별자
2 | disabled_rules:
3 | - line_length
4 | - trailing_whitespace
5 | - function_parameter_count
6 | - file_length
7 | - identifier_name
8 | - function_body_length
9 | - nesting
10 | - cyclomatic_complexity
11 | # 린트 과정에서 무시할 파일 경로
12 | excluded:
13 | - TOASTER-iOS/Application/AppDelegate.swift
14 | - TOASTER-iOS/Application/SceneDelegate.swift
15 | - TOASTER-iOS/Present/ViewController.swift
16 |
--------------------------------------------------------------------------------
/TOASTER-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TOASTER-iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Application/Coordinator/Coordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Coordinator.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 1/7/25.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol Coordinator: AnyObject {
11 | var childCoordinators: [Coordinator] { get set }
12 | func start()
13 | }
14 |
15 | class BaseCoordinator: Coordinator {
16 | var childCoordinators = [Coordinator]()
17 |
18 | func addDependency(_ coordinator: Coordinator) {
19 | guard !childCoordinators.contains(where: { $0 === coordinator }) else { return }
20 | childCoordinators.append(coordinator)
21 | }
22 |
23 | func removeDependency(_ coordinator: Coordinator?) {
24 | guard let index = childCoordinators.firstIndex(where: { $0 === coordinator }) else { return }
25 | childCoordinators.remove(at: index)
26 | }
27 |
28 | func start() {}
29 | }
30 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Application/Coordinator/CoordinatorFinishOutput.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoordinatorFinishOutput.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 1/10/25.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol CoordinatorFinishOutput {
11 | var onFinish: (() -> Void)? { get set }
12 | }
13 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Application/Coordinator/Coordinators/AddLinkCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AddLinkCoordinator.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 1/15/25.
6 | //
7 |
8 | import Foundation
9 |
10 | final class AddLinkCoordinator: BaseCoordinator, CoordinatorFinishOutput {
11 |
12 | var onFinish: (() -> Void)?
13 |
14 | private let router: RouterProtocol
15 | private let viewControllerFactory: ViewControllerFactoryProtocol
16 | private let coordinatorFactory: CoordinatorFactoryProtocol
17 | private let isNavigationBarHidden: Bool
18 |
19 | init(
20 | router: RouterProtocol,
21 | viewControllerFactory: ViewControllerFactoryProtocol,
22 | coordinatorFactory: CoordinatorFactoryProtocol,
23 | isNavigationBarHidden: Bool
24 | ) {
25 | self.router = router
26 | self.viewControllerFactory = viewControllerFactory
27 | self.coordinatorFactory = coordinatorFactory
28 | self.isNavigationBarHidden = isNavigationBarHidden
29 | }
30 |
31 | override func start() {
32 | showAddLinkVC()
33 | }
34 | }
35 |
36 | private extension AddLinkCoordinator {
37 | func showAddLinkVC() {
38 | let vc = viewControllerFactory.makeAddLinkVC(isNavigationBarHidden: isNavigationBarHidden)
39 | vc.onLinkInputCompleted = { [weak self] linkURL in
40 | self?.showSelectClipVC(linkURL: linkURL)
41 | }
42 | vc.onPopToRoot = { [weak self] in
43 | self?.router.dismiss(animated: false, completion: {
44 | self?.onFinish?()
45 | })
46 | }
47 | router.setRoot(vc, animated: true, hideBottomBarWhenPushed: true)
48 | }
49 |
50 | func showSelectClipVC(linkURL: String) {
51 | let vc = ViewControllerFactory.shared.makeSelectClipVC(isNavigationBarHidden: isNavigationBarHidden)
52 | vc.linkURL = linkURL
53 | vc.onPopToRoot = { [weak self] in
54 | self?.router.dismiss(animated: false, completion: {
55 | self?.onFinish?()
56 | })
57 | }
58 | router.push(vc, animated: true)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Application/Coordinator/Coordinators/LoginCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginCoordinator.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 1/10/25.
6 | //
7 |
8 | import UIKit
9 |
10 | final class LoginCoordinator: BaseCoordinator, CoordinatorFinishOutput {
11 |
12 | var onFinish: (() -> Void)?
13 |
14 | private let router: RouterProtocol
15 | private let viewControllerFactory: ViewControllerFactoryProtocol
16 | private let coordinatorFactory: CoordinatorFactoryProtocol
17 |
18 | init(
19 | router: RouterProtocol,
20 | viewControllerFactory: ViewControllerFactoryProtocol,
21 | coordinatorFactory: CoordinatorFactoryProtocol
22 | ) {
23 | self.router = router
24 | self.viewControllerFactory = viewControllerFactory
25 | self.coordinatorFactory = coordinatorFactory
26 | }
27 |
28 | override func start() {
29 | showLoginVC()
30 | }
31 | }
32 |
33 | private extension LoginCoordinator {
34 | func showLoginVC() {
35 | let vc = viewControllerFactory.makeLoginVC()
36 | vc.onLoginCompleted = { [weak self] in
37 | self?.onFinish?()
38 | }
39 | router.setRoot(vc, animated: false)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Application/Coordinator/Coordinators/SearchCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchCoordinator.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 1/8/25.
6 | //
7 |
8 | import Foundation
9 |
10 | final class SearchCoordinator: BaseCoordinator, CoordinatorFinishOutput {
11 |
12 | var onFinish: (() -> Void)?
13 |
14 | private let router: RouterProtocol
15 | private let viewControllerFactory: ViewControllerFactoryProtocol
16 | private let coordinatorFactory: CoordinatorFactoryProtocol
17 |
18 | init(
19 | router: RouterProtocol,
20 | viewControllerFactory: ViewControllerFactoryProtocol,
21 | coordinatorFactory: CoordinatorFactoryProtocol
22 | ) {
23 | self.router = router
24 | self.viewControllerFactory = viewControllerFactory
25 | self.coordinatorFactory = coordinatorFactory
26 | }
27 |
28 | override func start() {
29 | showSearchVC()
30 | }
31 | }
32 |
33 | private extension SearchCoordinator {
34 | func showSearchVC() {
35 | let vc = viewControllerFactory.makeSearchVC()
36 | vc.onLinkItemSelected = { [weak self] linkURL, isRead, id in
37 | self?.showLinkWebVC(linkURL: linkURL, isRead: isRead, id: id)
38 | }
39 | vc.onClipItemSelected = { [weak self] id, name in
40 | self?.showDetailClipVC(id: id, name: name)
41 | }
42 | router.setRoot(vc, animated: false)
43 | }
44 |
45 | func showDetailClipVC(id: Int, name: String) {
46 | let vc = viewControllerFactory.makeDetailClipVC()
47 | vc.setupCategory(id: id, name: name)
48 | vc.onLinkSelected = { [weak self] linkURL, isRead, id in
49 | self?.showLinkWebVC(linkURL: linkURL, isRead: isRead, id: id)
50 | }
51 | router.push(vc, animated: true, hideBottomBarWhenPushed: true)
52 | }
53 |
54 | func showLinkWebVC(linkURL: String, isRead: Bool, id: Int) {
55 | let vc = viewControllerFactory.makeLinkWebVC()
56 | vc.setupDataBind(linkURL: linkURL, isRead: isRead, id: id)
57 | vc.onBack = { [weak self] in
58 | self?.router.pop(animated: true)
59 | }
60 | router.push(vc, animated: true, hideBottomBarWhenPushed: true)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Global/Components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Link-MIND/TOASTER-iOS/784eacf9d0648469e0c42879b78f73e36cc5792d/TOASTER-iOS/Global/Components/.gitkeep
--------------------------------------------------------------------------------
/TOASTER-iOS/Global/Components/ToasterBottomSheet/BottomType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BottomType.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 1/3/24.
6 | //
7 |
8 | import UIKit
9 |
10 | enum BottomType {
11 | case white, gray
12 |
13 | var color: UIColor {
14 | switch self {
15 | case .white:
16 | return .toasterWhite
17 | case .gray:
18 | return .gray50
19 | }
20 | }
21 |
22 | var alignment: NSTextAlignment {
23 | return .left
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Global/Components/ToasterNavigationController/ToasterNavigationType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToasterNavigationType.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 김다예 on 1/6/24.
6 | //
7 |
8 | import UIKit
9 |
10 | struct ToasterNavigationType {
11 | var hasBackButton: Bool // backButton 존재 여부
12 | var hasRightButton: Bool // rightButton 존재 여부
13 |
14 | var mainTitle: StringOrImageType // mainTitle
15 | var rightButton: StringOrImageType // rightButton
16 | var rightButtonAction: () -> Void // rightButton의 액션
17 | }
18 |
19 | enum StringOrImageType {
20 | case string(String)
21 | case image(UIImage)
22 | }
23 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Global/Components/ToasterTipView/TipUserDefaults.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipUserDefaults.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 10/29/24.
6 | //
7 |
8 | import Foundation
9 |
10 | enum TipUserDefaults {
11 | static let isShowHomeViewToolTip = "homeViewToolTip"
12 | static let isShowDetailClipViewToolTip = "detailClipViewToolTip"
13 | static let isShowLinkWebViewToolTip = "linkWebViewToolTip"
14 | }
15 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Global/Components/ToasterToastMessage/ToastStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToasterToastMessage.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 1/2/24.
6 | //
7 |
8 | import UIKit
9 |
10 | enum ToastStatus {
11 | case check, warning
12 |
13 | var icon: UIImage {
14 | switch self {
15 | case .check:
16 | return .icCheck18White
17 | case .warning:
18 | return .icAlert18White
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Global/Components/ToasterToastMessage/ToasterToastMessageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToasterToastMessageView.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 1/3/24.
6 | //
7 |
8 | import UIKit
9 |
10 | import SnapKit
11 | import Then
12 |
13 | final class ToasterToastMessageView: UIView {
14 |
15 | // MARK: - UI Components
16 |
17 | private let toastImage = UIImageView().then {
18 | $0.tintColor = .toasterWhite
19 | }
20 |
21 | private let toastLabel = UILabel().then {
22 | $0.textColor = .toasterWhite
23 | $0.font = .suitBold(size: 14)
24 | }
25 |
26 | // MARK: - Life Cycles
27 |
28 | override init(frame: CGRect) {
29 | super.init(frame: frame)
30 |
31 | setupStyle()
32 | setupHierarchy()
33 | setupLayout()
34 | }
35 |
36 | @available(*, unavailable)
37 | required init?(coder: NSCoder) {
38 | fatalError("init(coder:) has not been implemented")
39 | }
40 | }
41 |
42 | // MARK: - Extensions
43 |
44 | extension ToasterToastMessageView {
45 | func setupDataBind(message: String, status: ToastStatus) {
46 | toastImage.image = status.icon
47 | toastLabel.text = message
48 | }
49 | }
50 |
51 | // MARK: - Private Extensions
52 |
53 | private extension ToasterToastMessageView {
54 | func setupStyle() {
55 | self.backgroundColor = .gray800
56 | self.makeRounded(radius: 22)
57 | }
58 |
59 | func setupHierarchy() {
60 | addSubviews(toastImage, toastLabel)
61 | }
62 |
63 | func setupLayout() {
64 | toastImage.snp.makeConstraints {
65 | $0.leading.equalToSuperview().inset(22)
66 | $0.centerY.equalToSuperview()
67 | $0.size.equalTo(18)
68 | }
69 | toastLabel.snp.makeConstraints {
70 | $0.leading.equalTo(toastImage.snp.trailing).offset(10)
71 | $0.centerY.equalToSuperview()
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Global/Consts/FontLiterals.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FontLiterals.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 12/31/23.
6 | //
7 |
8 | import UIKit
9 |
10 | enum FontName: String {
11 | case suitExtraBold = "SUIT-ExtraBold"
12 | case suitBold = "SUIT-Bold"
13 | case suitSemiBold = "SUIT-SemiBold"
14 | case suitMedium = "SUIT-Medium"
15 | case suitRegular = "SUIT-Regular"
16 | }
17 |
18 | extension UIFont {
19 | @nonobjc class func suitExtraBold(size: CGFloat) -> UIFont {
20 | return UIFont(name: FontName.suitExtraBold.rawValue, size: size)!
21 | }
22 |
23 | @nonobjc class func suitBold(size: CGFloat) -> UIFont {
24 | return UIFont(name: FontName.suitBold.rawValue, size: size)!
25 | }
26 |
27 | @nonobjc class func suitSemiBold(size: CGFloat) -> UIFont {
28 | return UIFont(name: FontName.suitSemiBold.rawValue, size: size)!
29 | }
30 |
31 | @nonobjc class func suitMedium(size: CGFloat) -> UIFont {
32 | return UIFont(name: FontName.suitMedium.rawValue, size: size)!
33 | }
34 |
35 | @nonobjc class func suitRegular(size: CGFloat) -> UIFont {
36 | return UIFont(name: FontName.suitRegular.rawValue, size: size)!
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Global/Consts/Notification.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Notification.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by mini on 6/4/25.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Notification.Name {
11 | static let refreshTokenExpired = Notification.Name("refreshTokenExpired")
12 | }
13 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Global/Consts/StringLiterals.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringLiterals.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by Gahyun Kim on 2024/01/01.
6 | //
7 |
8 | import Foundation
9 |
10 | enum StringLiterals {
11 | enum Login {
12 | static let onboarding1 = "복사한 링크를\n손쉽게 저장하고"
13 | static let onboarding2 = "타이머를 설정하고\n링크를 리마인드 받아요"
14 | static let onboarding3 = "나의 링크 열람 현황까지\n한 눈에!"
15 | static let subTitle = "더 이상 링크를 태우지 마세요\n토스트 먹듯이 간편하게,"
16 | static let appleButton = "Apple 계정으로 시작하기"
17 | static let kakaoButton = "카카오 계정으로 시작하기"
18 | }
19 |
20 | enum Tabbar {
21 | static let home = "HOME"
22 | static let clip = "CLIP"
23 | static let timer = "TIMER"
24 | static let search = "SEARCH"
25 | }
26 |
27 | enum Button {
28 | static let okay = "확인"
29 | static let complete = "완료"
30 | static let delete = "삭제"
31 | static let close = "닫기"
32 | static let cancel = "취소"
33 | static let next = "다음"
34 | static let editTitle = "제목 편집"
35 | }
36 |
37 | enum Placeholder {
38 | static let search = "검색어를 입력해주세요"
39 | static let copyLink = "복사한 링크를 붙여 넣어 주세요"
40 | static let addClip = "새로운 클립의 이름을 입력해주세요"
41 | }
42 |
43 | enum ToastMessage {
44 | static let noticeMaxClip = "클립 추가는 15개까지 가능해요"
45 | static let completeAddClip = "클립 생성 완료!"
46 | static let completeEditClip = "클립 수정 완료!"
47 | static let completeDeleteClip = "클립 삭제 완료"
48 | static let completeDeleteLink = "링크 삭제 완료"
49 | static let completeReadLink = "링크 열람 완료"
50 | static let cancelReadLink = "링크 열람 취소"
51 | static let completeSetTimer = "타이머 설정 완료!"
52 | static let completeEditTimer = "타이머 수정 완료!"
53 | static let completeDeleteTimer = "타이머 삭제 완료"
54 | static let completeEditTitle = "제목 편집 완료"
55 | static let noticeSetTimer = "한 클립당 하나의 타이머만 설정 가능해요"
56 | static let noticeMaxTimer = "타이머는 최대 다섯 개까지 설정 가능해요"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Global/Extensions/Combine+/CancelBag.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CancelBag.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 9/29/24.
6 | //
7 |
8 | import Combine
9 |
10 | class CancelBag {
11 | var cancellables = Set()
12 |
13 | deinit {
14 | cancellables.forEach { $0.cancel() }
15 | cancellables.removeAll()
16 | }
17 | }
18 |
19 | extension AnyCancellable {
20 | func store(in cancelBag: CancelBag) {
21 | cancelBag.cancellables.insert(self)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/TOASTER-iOS/Global/Extensions/Combine+/Publisher+Driver.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Publisher+Driver.swift
3 | // TOASTER-iOS
4 | //
5 | // Created by 민 on 9/30/24.
6 | //
7 |
8 | import Combine
9 | import Foundation
10 |
11 | public typealias Driver = AnyPublisher
12 |
13 | public extension Publisher {
14 | func asDriver() -> Driver