├── .github ├── ISSUE_TEMPLATE │ ├── bug-report-.md │ ├── custom.md │ └── new-feature.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── swift.yml ├── .gitignore ├── BBus ├── AlarmSettingViewModelTests │ ├── AlarmSettingViewModelTests.swift │ └── MOCKArrInfo.json ├── BBus.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── BBus.xcscheme ├── BBus │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120-1.png │ │ │ ├── 120.png │ │ │ ├── 180.png │ │ │ ├── 29.png │ │ │ ├── 40.png │ │ │ ├── 57.png │ │ │ ├── 58-1.png │ │ │ ├── 60.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ │ ├── Color │ │ │ ├── Contents.json │ │ │ ├── bbusAlarmGray.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusBackground.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusCongestionHigh.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusCongestionLow.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusCongestionMedium.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusCongestionRed.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusGray.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusGray6.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusLightGray.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusLikeYellow.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusSearchRed.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusTypeBlue.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusTypeCirculation.colorset │ │ │ │ └── Contents.json │ │ │ ├── bbusTypeGreen.colorset │ │ │ │ └── Contents.json │ │ │ └── bbusTypeRed.colorset │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Image │ │ │ ├── Contents.json │ │ │ ├── alarmOff.imageset │ │ │ ├── Contents.json │ │ │ ├── alarmOff-1.png │ │ │ ├── alarmOff-2.png │ │ │ └── alarmOff.png │ │ │ ├── alarmOn.imageset │ │ │ ├── Contents.json │ │ │ ├── alarmOn-1.png │ │ │ ├── alarmOn-2.png │ │ │ └── alarmOn.png │ │ │ ├── alarmOnTint.imageset │ │ │ ├── Contents.json │ │ │ ├── alarmOnTint-1.png │ │ │ ├── alarmOnTint-2.png │ │ │ └── alarmOnTint.png │ │ │ ├── blueBusIcon.imageset │ │ │ ├── Contents.json │ │ │ ├── blueBusIcon-1.png │ │ │ ├── blueBusIcon-2.png │ │ │ └── blueBusIcon.png │ │ │ ├── blueBusWithBooduck.imageset │ │ │ ├── BusIconWithBooDuck-1.png │ │ │ ├── BusIconWithBooDuck-2.png │ │ │ ├── BusIconWithBooDuck.png │ │ │ └── Contents.json │ │ │ ├── booDuck.imageset │ │ │ ├── BooDuck-1.png │ │ │ ├── BooDuck-2.png │ │ │ ├── BooDuck.png │ │ │ └── Contents.json │ │ │ ├── busGraySymbol.imageset │ │ │ ├── Contents.json │ │ │ ├── grayBusIcon-1.png │ │ │ ├── grayBusIcon-2.png │ │ │ └── grayBusIcon.png │ │ │ ├── busTagMaxSize.imageset │ │ │ ├── BusTagMaxSize-1.png │ │ │ ├── BusTagMaxSize-2.png │ │ │ ├── BusTagMaxSize.png │ │ │ └── Contents.json │ │ │ ├── busTagMinSize.imageset │ │ │ ├── BusTagMinSize-1.png │ │ │ ├── BusTagMinSize-2.png │ │ │ ├── BusTagMinSize.png │ │ │ └── Contents.json │ │ │ ├── circulationBusIcon.imageset │ │ │ ├── Contents.json │ │ │ ├── circulationBusIcon-1.png │ │ │ ├── circulationBusIcon-2.png │ │ │ └── circulationBusIcon.png │ │ │ ├── circulationBusWithBooduck.imageset │ │ │ ├── Contents.json │ │ │ ├── circulationBusWithBooduck-1.png │ │ │ ├── circulationBusWithBooduck-2.png │ │ │ └── circulationBusWithBooduck.png │ │ │ ├── getOff.imageset │ │ │ ├── Contents.json │ │ │ ├── GetOff-1.png │ │ │ ├── GetOff-2.png │ │ │ └── GetOff.png │ │ │ ├── getOn.imageset │ │ │ ├── Contents.json │ │ │ ├── GetOn-1.png │ │ │ ├── GetOn-2.png │ │ │ └── GetOn.png │ │ │ ├── greenBusIcon.imageset │ │ │ ├── Contents.json │ │ │ ├── greenBusIcon-1.png │ │ │ ├── greenBusIcon-2.png │ │ │ └── greenBusIcon.png │ │ │ ├── greenBusWithBooduck.imageset │ │ │ ├── Contents.json │ │ │ ├── greenBusWithBooduck-1.png │ │ │ ├── greenBusWithBooduck-2.png │ │ │ └── greenBusWithBooduck.png │ │ │ ├── homeFavoriteEmpty.imageset │ │ │ ├── Contents.json │ │ │ ├── homeFavoriteEmpty-1.png │ │ │ ├── homeFavoriteEmpty-2.png │ │ │ └── homeFavoriteEmpty.png │ │ │ ├── locationSymbol.imageset │ │ │ ├── Contents.json │ │ │ ├── locationIcon-1.png │ │ │ ├── locationIcon-2.png │ │ │ └── locationIcon.png │ │ │ ├── redBusIcon.imageset │ │ │ ├── Contents.json │ │ │ ├── redBusIcon-1.png │ │ │ ├── redBusIcon-2.png │ │ │ └── redBusIcon.png │ │ │ ├── redBusWithBooduck.imageset │ │ │ ├── Contents.json │ │ │ ├── redBusWithBooduck-1.png │ │ │ ├── redBusWithBooduck-2.png │ │ │ └── redBusWithBooduck.png │ │ │ ├── speechBubble.imageset │ │ │ ├── Contents.json │ │ │ ├── SpeechBubble-1.png │ │ │ ├── SpeechBubble-2.png │ │ │ └── SpeechBubble.png │ │ │ ├── stationCenterCircle.imageset │ │ │ ├── Contents.json │ │ │ ├── StationCenterCircle-1.png │ │ │ ├── StationCenterCircle-2.png │ │ │ └── StationCenterCircle.png │ │ │ └── uturn.imageset │ │ │ ├── Contents.json │ │ │ ├── Uturn-1.png │ │ │ ├── Uturn-2.png │ │ │ └── Uturn.png │ ├── Background │ │ ├── GetOffAlarm │ │ │ ├── GetOffAlarmController.swift │ │ │ ├── Interactor │ │ │ │ └── GetOffAlarmInteractor.swift │ │ │ └── Model │ │ │ │ └── GetOffAlarmStatus.swift │ │ └── GetOnAlarm │ │ │ ├── GetOnAlarmController.swift │ │ │ ├── Interactor │ │ │ └── GetOnAlarmInteractor.swift │ │ │ ├── Model │ │ │ ├── AlarmStartResult.swift │ │ │ ├── BusApproachStatus.swift │ │ │ └── GetOnAlarmStatus.swift │ │ │ └── UseCase │ │ │ ├── GetOnAlarmAPIUseCase.swift │ │ │ └── GetOnAlarmCalculateUsecase.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Foreground │ │ ├── AlarmSetting │ │ │ ├── AlarmSettingCoordinator.swift │ │ │ ├── AlarmSettingViewController.swift │ │ │ ├── Model │ │ │ │ ├── AlarmSettingBusArriveInfo.swift │ │ │ │ └── AlarmSettingBusArriveInfos.swift │ │ │ ├── UseCase │ │ │ │ ├── AlarmSettingAPIUsable.swift │ │ │ │ ├── AlarmSettingAPIUseCase.swift │ │ │ │ ├── AlarmSettingCalculatable.swift │ │ │ │ └── AlarmSettingCalculateUseCase.swift │ │ │ ├── View │ │ │ │ ├── AlarmSettingButton.swift │ │ │ │ ├── AlarmSettingView.swift │ │ │ │ ├── GetOffTableViewCell.swift │ │ │ │ ├── GetOnStatusCell.swift │ │ │ │ └── NoneInfoTableViewCell.swift │ │ │ └── ViewModel │ │ │ │ └── AlarmSettingViewModel.swift │ │ ├── BusRoute │ │ │ ├── BusRouteCoordinator.swift │ │ │ ├── BusRouteViewController.swift │ │ │ ├── Model │ │ │ │ └── BusRouteModel.swift │ │ │ ├── UseCase │ │ │ │ ├── BusRouteAPIUsable.swift │ │ │ │ └── BusRouteAPIUseCase.swift │ │ │ ├── View │ │ │ │ ├── BusRouteHeaderView.swift │ │ │ │ ├── BusRouteTableViewCell.swift │ │ │ │ ├── BusRouteView.swift │ │ │ │ ├── BusStationTableViewCell.swift │ │ │ │ └── BusTagView.swift │ │ │ └── ViewModel │ │ │ │ └── BusRouteViewModel.swift │ │ ├── Home │ │ │ ├── HomeCoordinator.swift │ │ │ ├── HomeViewController.swift │ │ │ ├── Model │ │ │ │ └── HomeModel.swift │ │ │ ├── UseCase │ │ │ │ ├── HomeAPIUsable.swift │ │ │ │ ├── HomeAPIUseCase.swift │ │ │ │ ├── HomeCalculatable.swift │ │ │ │ └── HomeCalculateUseCase.swift │ │ │ ├── View │ │ │ │ ├── BusCellTrailingView.swift │ │ │ │ ├── EmptyFavoriteNoticeView.swift │ │ │ │ ├── FavoriteCollectionHeaderView.swift │ │ │ │ ├── FavoriteCollectionViewCell.swift │ │ │ │ ├── HomeNavigationView.swift │ │ │ │ ├── HomeView.swift │ │ │ │ ├── RemainCongestionBadgeLabel.swift │ │ │ │ └── SourceFooterView.swift │ │ │ └── ViewModel │ │ │ │ └── HomeViewModel.swift │ │ ├── MovingStatus │ │ │ ├── Model │ │ │ │ └── MovingStatusModel.swift │ │ │ ├── MovingStatusFoldUnfoldDelegate.swift │ │ │ ├── MovingStatusViewController.swift │ │ │ ├── UseCase │ │ │ │ ├── MovingStatusAPIUsable.swift │ │ │ │ ├── MovingStatusAPIUseCase.swift │ │ │ │ ├── MovingStatusCalculatable.swift │ │ │ │ └── MovingStatusCalculateUseCase.swift │ │ │ ├── View │ │ │ │ ├── MovingStatusBusTagView.swift │ │ │ │ ├── MovingStatusTableViewCell.swift │ │ │ │ └── MovingStatusView.swift │ │ │ └── ViewModel │ │ │ │ └── MovingStatusViewModel.swift │ │ ├── Search │ │ │ ├── Model │ │ │ │ ├── BusSearchResult.swift │ │ │ │ ├── SearchResults.swift │ │ │ │ └── StationSearchResult.swift │ │ │ ├── SearchCoordinator.swift │ │ │ ├── SearchViewController.swift │ │ │ ├── UseCase │ │ │ │ ├── SearchAPIUsable.swift │ │ │ │ ├── SearchAPIUseCase.swift │ │ │ │ ├── SearchCalculatable.swift │ │ │ │ └── SearchCalculateUseCase.swift │ │ │ ├── View │ │ │ │ ├── EmptySearchResultNoticeView.swift │ │ │ │ ├── KeyboardAccessoryView.swift │ │ │ │ ├── SearchNavigationView.swift │ │ │ │ ├── SearchResultCollectionViewCell.swift │ │ │ │ ├── SearchResultScrollView.swift │ │ │ │ ├── SearchView.swift │ │ │ │ └── SimpleCollectionHeaderView.swift │ │ │ └── ViewModel │ │ │ │ └── SearchViewModel.swift │ │ └── Station │ │ │ ├── Model │ │ │ ├── BusArriveInfos.swift │ │ │ ├── BusCongestion.swift │ │ │ ├── BusRemainTime.swift │ │ │ └── BusSectionKeys.swift │ │ │ ├── StationCoordinator.swift │ │ │ ├── StationViewController.swift │ │ │ ├── UseCase │ │ │ ├── StationAPIUsable.swift │ │ │ ├── StationAPIUseCase.swift │ │ │ ├── StationCalculatable.swift │ │ │ └── StationCalculateUseCase.swift │ │ │ ├── View │ │ │ ├── StationBodyCollectionViewCell.swift │ │ │ ├── StationHeaderView.swift │ │ │ └── StationView.swift │ │ │ └── ViewModel │ │ │ └── StationViewModel.swift │ ├── Global │ │ ├── Base │ │ │ ├── BaseUseCase.swift │ │ │ └── BaseViewController.swift │ │ ├── Constant │ │ │ ├── BBusColor.swift │ │ │ ├── BBusImage.swift │ │ │ └── BBusRouteType.swift │ │ ├── Coordinator │ │ │ ├── AppCoordinator.swift │ │ │ ├── Coordinator.swift │ │ │ └── Pushables.swift │ │ ├── CustomView │ │ │ ├── CustomNavigationBar.swift │ │ │ ├── NavigatableView.swift │ │ │ ├── RefreshButton.swift │ │ │ ├── RefreshableView.swift │ │ │ └── ThrottleButton.swift │ │ ├── DTO │ │ │ ├── ArrInfoByRouteDTO.swift │ │ │ ├── BusPosByRtidDTO.swift │ │ │ ├── BusPosByVehicleIdDTO.swift │ │ │ ├── BusRouteDTO.swift │ │ │ ├── FavoriteItemDTO.swift │ │ │ ├── JsonDTO.swift │ │ │ ├── StationByRouteListDTO.swift │ │ │ ├── StationByUidItemDTO.swift │ │ │ └── StationDTO.swift │ │ ├── DeviceConfig │ │ │ └── AlarmCenter.swift │ │ ├── Extension │ │ │ ├── NotificationNameExtension.swift │ │ │ ├── PublisherExtension.swift │ │ │ ├── StringExtension.swift │ │ │ ├── UINavigationControllerExtension.swift │ │ │ └── UIViewExtension.swift │ │ ├── Network │ │ │ ├── AccessKey.xcconfig │ │ │ ├── BBusAPIError.swift │ │ │ ├── BBusAPIUseCases │ │ │ │ ├── BBusAPIUseCases.swift │ │ │ │ ├── CreateFavoriteItemUseCase.swift │ │ │ │ ├── DeleteFavoriteItemUseCase.swift │ │ │ │ ├── GetArrInfoByRouteListUseCase.swift │ │ │ │ ├── GetBusPosByRtidUseCase.swift │ │ │ │ ├── GetBusPosByVehIdUseCase.swift │ │ │ │ ├── GetFavoriteItemListUseCase.swift │ │ │ │ ├── GetRouteListUseCase.swift │ │ │ │ ├── GetStationByUidItemUseCase.swift │ │ │ │ ├── GetStationListUseCase.swift │ │ │ │ └── GetStationsByRouteListUseCase.swift │ │ │ ├── Fetcher │ │ │ │ ├── CreateFavoriteItemFetcher.swift │ │ │ │ ├── DeleteFavoriteItemFetchable.swift │ │ │ │ ├── GetArrInfoByRouteListFetcher.swift │ │ │ │ ├── GetBusPosByRtidFetcher.swift │ │ │ │ ├── GetBusPosByVehIdFetcher.swift │ │ │ │ ├── GetFavoriteItemListFetcher.swift │ │ │ │ ├── GetRouteInfoItemFetcher.swift │ │ │ │ ├── GetRouteListFetcher.swift │ │ │ │ ├── GetStationByUidItemFetcher.swift │ │ │ │ ├── GetStationListFetcher.swift │ │ │ │ ├── GetStationsByRouteListFetcher.swift │ │ │ │ ├── PersistencetFetchable.swift │ │ │ │ └── ServiceFetchable.swift │ │ │ ├── NetworkService.swift │ │ │ ├── PersistenceStorage.swift │ │ │ ├── PersistentStorage.swift │ │ │ ├── RequestFactory.swift │ │ │ ├── RequestUseCases │ │ │ │ ├── CreateFavoriteItemUsable.swift │ │ │ │ ├── DeleteFavoriteItemUsable.swift │ │ │ │ ├── GetArrInfoByRouteListUsable.swift │ │ │ │ ├── GetBusPosByRtidUsable.swift │ │ │ │ ├── GetBusPosByVehIdUsable.swift │ │ │ │ ├── GetFavoriteItemListUsable.swift │ │ │ │ ├── GetRouteListUsable.swift │ │ │ │ ├── GetStationByUidItemUsable.swift │ │ │ │ ├── GetStationListUsable.swift │ │ │ │ └── GetStationsByRouteListUsable.swift │ │ │ └── TokenManager.swift │ │ ├── Resource │ │ │ ├── BusRouteList.json │ │ │ └── StationList.json │ │ └── UseCase │ │ │ └── AverageSectionTimeCalculatable.swift │ ├── Info.plist │ └── SceneDelegate.swift ├── BBusTests │ └── BBusTests.swift ├── BusRouteViewModelTests │ └── BusRouteViewModelTests.swift ├── HomeViewModelTests │ └── HomeViewModelTests.swift ├── MovingStatusViewModelTests │ └── MovingStatusViewModelTests.swift ├── NetworkServiceTests │ └── NetworkServiceTests.swift ├── PersistenceStorageTests │ └── PersistenceStorageTests.swift ├── RequestFactoryTests │ └── RequestFactoryTests.swift ├── SearchViewModelTests │ └── SearchViewModelTests.swift ├── StationViewModelTests │ ├── DummyJsonStringStationByUidItemDTO.json │ └── StationViewModelTests.swift └── TokenManagerTests │ └── TokenManagerTests.swift └── README.md /.github/ISSUE_TEMPLATE/bug-report-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'bug report ' 3 | about: 버그에 대해 작성하는 이슈 4 | title: '' 5 | labels: "\U0001F41B fix" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 발생 일시 11 | 12 | ## 발생 위치 13 | 14 | ## 증상 15 | 16 | ## 재현 방법 17 | 18 | ## 스크린샷 19 | 20 | ## 레퍼런스 21 | 없음. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new-feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: new feature 3 | about: 새로운 기능 4 | title: "[에픽 이름] Task 내용" 5 | labels: "\U0001F9F0 feature" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 작업 내용 11 | - [ ] 12 | 13 | ## 레퍼런스 14 | 없음. 15 | 16 | ## 기타 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 작업 내용 2 | 3 | ## 시연 방법 4 | 5 | ## 기타 (고민과 해결, 리뷰 포인트 등) 6 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | push: 5 | branches: [ feature/**, issue/**, test/** ] 6 | pull_request: 7 | branches: [ develop, feature/** ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | env: 14 | API_ACCESS_KEY1: ${{ secrets.API_ACCESS_KEY }} 15 | API_ACCESS_KEY2: ${{ secrets.API_ACCESS_KEY2 }} 16 | API_ACCESS_KEY3: ${{ secrets.API_ACCESS_KEY3 }} 17 | API_ACCESS_KEY4: ${{ secrets.API_ACCESS_KEY4 }} 18 | API_ACCESS_KEY5: ${{ secrets.API_ACCESS_KEY5 }} 19 | API_ACCESS_KEY6: ${{ secrets.API_ACCESS_KEY6 }} 20 | API_ACCESS_KEY7: ${{ secrets.API_ACCESS_KEY7 }} 21 | API_ACCESS_KEY8: ${{ secrets.API_ACCESS_KEY8 }} 22 | API_ACCESS_KEY9: ${{ secrets.API_ACCESS_KEY9 }} 23 | API_ACCESS_KEY10: ${{ secrets.API_ACCESS_KEY10 }} 24 | API_ACCESS_KEY11: ${{ secrets.API_ACCESS_KEY11 }} 25 | API_ACCESS_KEY12: ${{ secrets.API_ACCESS_KEY12 }} 26 | API_ACCESS_KEY13: ${{ secrets.API_ACCESS_KEY13 }} 27 | API_ACCESS_KEY14: ${{ secrets.API_ACCESS_KEY14 }} 28 | API_ACCESS_KEY15: ${{ secrets.API_ACCESS_KEY15 }} 29 | API_ACCESS_KEY16: ${{ secrets.API_ACCESS_KEY16 }} 30 | API_ACCESS_KEY17: ${{ secrets.API_ACCESS_KEY17 }} 31 | 32 | 33 | steps: 34 | - uses: actions/checkout@v2 35 | - name: Build 36 | run: | 37 | xcodebuild test -project BBus/BBus.xcodeproj -scheme BBus -destination 'platform=iOS Simulator,name=iPhone 12,OS=latest' \ 38 | API_ACCESS_KEY1=$API_ACCESS_KEY1 \ 39 | API_ACCESS_KEY2=$API_ACCESS_KEY2 \ 40 | API_ACCESS_KEY3=$API_ACCESS_KEY3 \ 41 | API_ACCESS_KEY4=$API_ACCESS_KEY4 \ 42 | API_ACCESS_KEY5=$API_ACCESS_KEY5 \ 43 | API_ACCESS_KEY6=$API_ACCESS_KEY6 \ 44 | API_ACCESS_KEY7=$API_ACCESS_KEY7 \ 45 | API_ACCESS_KEY8=$API_ACCESS_KEY8 \ 46 | API_ACCESS_KEY9=$API_ACCESS_KEY9 \ 47 | API_ACCESS_KEY10=$API_ACCESS_KEY10 \ 48 | API_ACCESS_KEY11=$API_ACCESS_KEY11 \ 49 | API_ACCESS_KEY12=$API_ACCESS_KEY12 \ 50 | API_ACCESS_KEY13=$API_ACCESS_KEY13 \ 51 | API_ACCESS_KEY14=$API_ACCESS_KEY14 \ 52 | API_ACCESS_KEY15=$API_ACCESS_KEY15 \ 53 | API_ACCESS_KEY16=$API_ACCESS_KEY16 \ 54 | API_ACCESS_KEY17=$API_ACCESS_KEY17 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/xcode,swift,macos 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=xcode,swift,macos 4 | 5 | ### macOS ### 6 | # General 7 | .DS_Store 8 | 9 | # Thumbnails 10 | ._* 11 | 12 | # Files that might appear in the root of a volume 13 | .DocumentRevisions-V100 14 | .fseventsd 15 | .Spotlight-V100 16 | .TemporaryItems 17 | .Trashes 18 | .VolumeIcon.icns 19 | .com.apple.timemachine.donotpresent 20 | 21 | # Directories potentially created on remote AFP share 22 | .AppleDB 23 | .AppleDesktop 24 | Network Trash Folder 25 | Temporary Items 26 | .apdisk 27 | 28 | ### Swift ### 29 | # Xcode 30 | # 31 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 32 | 33 | ## User settings 34 | xcuserdata/ 35 | 36 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 37 | *.xcscmblueprint 38 | *.xccheckout 39 | 40 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 41 | build/ 42 | DerivedData/ 43 | *.moved-aside 44 | *.pbxuser 45 | !default.pbxuser 46 | *.mode1v3 47 | !default.mode1v3 48 | *.mode2v3 49 | !default.mode2v3 50 | *.perspectivev3 51 | !default.perspectivev3 52 | 53 | ## Obj-C/Swift specific 54 | *.hmap 55 | 56 | ## App packaging 57 | *.ipa 58 | *.dSYM.zip 59 | *.dSYM 60 | 61 | ## Playgrounds 62 | timeline.xctimeline 63 | playground.xcworkspace 64 | 65 | # Swift Package Manager 66 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 67 | # Packages/ 68 | # Package.pins 69 | # Package.resolved 70 | # *.xcodeproj 71 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 72 | # hence it is not needed unless you have added a package configuration file to your project 73 | # .swiftpm 74 | 75 | .build/ 76 | 77 | # Accio dependency management 78 | Dependencies/ 79 | .accio/ 80 | 81 | # Code Injection 82 | # After new code Injection tools there's a generated folder /iOSInjectionProject 83 | # https://github.com/johnno1962/injectionforxcode 84 | 85 | iOSInjectionProject/ 86 | 87 | ### Xcode ### 88 | # Xcode 89 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 90 | 91 | ## Gcc Patch 92 | /*.gcno 93 | 94 | ### Xcode Patch ### 95 | *.xcodeproj/* 96 | !*.xcodeproj/project.pbxproj 97 | !*.xcodeproj/xcshareddata/ 98 | !*.xcworkspace/contents.xcworkspacedata 99 | **/xcshareddata/WorkspaceSettings.xcsettings 100 | 101 | ### Access Key ### 102 | # *.xcconfig 103 | 104 | # End of https://www.toptal.com/developers/gitignore/api/xcode,swift,macos 105 | -------------------------------------------------------------------------------- /BBus/AlarmSettingViewModelTests/MOCKArrInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrmsg1": "곧 도착", 3 | "arrmsg2": "7분9초후[3번째 전]", 4 | "reride_Num1": "3", 5 | "reride_Num2": "4", 6 | "stationNm1": "모두의학교.금천문화예술정보학교", 7 | "stationNm2": "호림박물관", 8 | "plainNo1": "서울71사1535", 9 | "plainNo2": "서울71사1519", 10 | "vehId1": "117020066", 11 | "vehId2": "117020207" 12 | } 13 | -------------------------------------------------------------------------------- /BBus/BBus.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BBus/BBus.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BBus/BBus/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/10/26. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | final class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | UNUserNotificationCenter.current().delegate = self 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | extension AppDelegate: UNUserNotificationCenterDelegate { 39 | func userNotificationCenter(_ center: UNUserNotificationCenter, 40 | didReceive response: UNNotificationResponse, 41 | withCompletionHandler completionHandler: @escaping () -> Void) { 42 | completionHandler() 43 | } 44 | 45 | func userNotificationCenter(_ center: UNUserNotificationCenter, 46 | willPresent notification: UNNotification, 47 | withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { 48 | completionHandler([.banner, .sound]) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/120-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/58-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/58-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58-1.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120-1.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "57.png", 47 | "idiom" : "iphone", 48 | "scale" : "1x", 49 | "size" : "57x57" 50 | }, 51 | { 52 | "filename" : "114.png", 53 | "idiom" : "iphone", 54 | "scale" : "2x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "120.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "60x60" 62 | }, 63 | { 64 | "filename" : "180.png", 65 | "idiom" : "iphone", 66 | "scale" : "3x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "scale" : "1x", 72 | "size" : "20x20" 73 | }, 74 | { 75 | "idiom" : "ipad", 76 | "scale" : "2x", 77 | "size" : "20x20" 78 | }, 79 | { 80 | "idiom" : "ipad", 81 | "scale" : "1x", 82 | "size" : "29x29" 83 | }, 84 | { 85 | "idiom" : "ipad", 86 | "scale" : "2x", 87 | "size" : "29x29" 88 | }, 89 | { 90 | "idiom" : "ipad", 91 | "scale" : "1x", 92 | "size" : "40x40" 93 | }, 94 | { 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "40x40" 98 | }, 99 | { 100 | "idiom" : "ipad", 101 | "scale" : "1x", 102 | "size" : "76x76" 103 | }, 104 | { 105 | "idiom" : "ipad", 106 | "scale" : "2x", 107 | "size" : "76x76" 108 | }, 109 | { 110 | "idiom" : "ipad", 111 | "scale" : "2x", 112 | "size" : "83.5x83.5" 113 | }, 114 | { 115 | "filename" : "1024.png", 116 | "idiom" : "ios-marketing", 117 | "scale" : "1x", 118 | "size" : "1024x1024" 119 | } 120 | ], 121 | "info" : { 122 | "author" : "xcode", 123 | "version" : 1 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusAlarmGray.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.784", 9 | "green" : "0.784", 10 | "red" : "0.784" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xF0", 9 | "green" : "0xEE", 10 | "red" : "0xED" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusCongestionHigh.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.310", 9 | "green" : "0.333", 10 | "red" : "0.871" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusCongestionLow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.251", 9 | "green" : "0.808", 10 | "red" : "0.376" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusCongestionMedium.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.267", 9 | "green" : "0.769", 10 | "red" : "0.929" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusCongestionRed.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.337", 9 | "green" : "0.435", 10 | "red" : "0.875" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.337", 27 | "green" : "0.435", 28 | "red" : "0.875" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusGray.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.745", 9 | "green" : "0.745", 10 | "red" : "0.745" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.745", 27 | "green" : "0.745", 28 | "red" : "0.745" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusGray6.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.969", 9 | "green" : "0.949", 10 | "red" : "0.949" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusLightGray.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.969", 9 | "green" : "0.969", 10 | "red" : "0.969" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.969", 27 | "green" : "0.969", 28 | "red" : "0.969" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusLikeYellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.071", 9 | "green" : "0.816", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.071", 27 | "green" : "0.816", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusSearchRed.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.227", 9 | "green" : "0.333", 10 | "red" : "0.929" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.227", 27 | "green" : "0.333", 28 | "red" : "0.929" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusTypeBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xEC", 9 | "green" : "0x74", 10 | "red" : "0x64" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusTypeCirculation.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x27", 9 | "green" : "0xAE", 10 | "red" : "0xE0" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusTypeGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x3F", 9 | "green" : "0xBF", 10 | "red" : "0x6F" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Color/bbusTypeRed.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x4D", 9 | "green" : "0x69", 10 | "red" : "0xE1" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOff.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "alarmOff.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "alarmOff-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "alarmOff-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOff.imageset/alarmOff-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/alarmOff.imageset/alarmOff-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOff.imageset/alarmOff-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/alarmOff.imageset/alarmOff-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOff.imageset/alarmOff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/alarmOff.imageset/alarmOff.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "alarmOn.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "alarmOn-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "alarmOn-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOn.imageset/alarmOn-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/alarmOn.imageset/alarmOn-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOn.imageset/alarmOn-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/alarmOn.imageset/alarmOn-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOn.imageset/alarmOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/alarmOn.imageset/alarmOn.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOnTint.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "alarmOnTint.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "alarmOnTint-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "alarmOnTint-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOnTint.imageset/alarmOnTint-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/alarmOnTint.imageset/alarmOnTint-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOnTint.imageset/alarmOnTint-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/alarmOnTint.imageset/alarmOnTint-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/alarmOnTint.imageset/alarmOnTint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/alarmOnTint.imageset/alarmOnTint.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/blueBusIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "blueBusIcon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "blueBusIcon-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "blueBusIcon-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/blueBusIcon.imageset/blueBusIcon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/blueBusIcon.imageset/blueBusIcon-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/blueBusIcon.imageset/blueBusIcon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/blueBusIcon.imageset/blueBusIcon-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/blueBusIcon.imageset/blueBusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/blueBusIcon.imageset/blueBusIcon.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/blueBusWithBooduck.imageset/BusIconWithBooDuck-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/blueBusWithBooduck.imageset/BusIconWithBooDuck-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/blueBusWithBooduck.imageset/BusIconWithBooDuck-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/blueBusWithBooduck.imageset/BusIconWithBooDuck-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/blueBusWithBooduck.imageset/BusIconWithBooDuck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/blueBusWithBooduck.imageset/BusIconWithBooDuck.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/blueBusWithBooduck.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BusIconWithBooDuck.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "BusIconWithBooDuck-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "BusIconWithBooDuck-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/booDuck.imageset/BooDuck-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/booDuck.imageset/BooDuck-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/booDuck.imageset/BooDuck-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/booDuck.imageset/BooDuck-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/booDuck.imageset/BooDuck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/booDuck.imageset/BooDuck.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/booDuck.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BooDuck.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "BooDuck-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "BooDuck-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busGraySymbol.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "grayBusIcon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "grayBusIcon-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "grayBusIcon-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busGraySymbol.imageset/grayBusIcon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/busGraySymbol.imageset/grayBusIcon-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busGraySymbol.imageset/grayBusIcon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/busGraySymbol.imageset/grayBusIcon-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busGraySymbol.imageset/grayBusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/busGraySymbol.imageset/grayBusIcon.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busTagMaxSize.imageset/BusTagMaxSize-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/busTagMaxSize.imageset/BusTagMaxSize-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busTagMaxSize.imageset/BusTagMaxSize-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/busTagMaxSize.imageset/BusTagMaxSize-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busTagMaxSize.imageset/BusTagMaxSize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/busTagMaxSize.imageset/BusTagMaxSize.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busTagMaxSize.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BusTagMaxSize.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "BusTagMaxSize-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "BusTagMaxSize-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busTagMinSize.imageset/BusTagMinSize-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/busTagMinSize.imageset/BusTagMinSize-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busTagMinSize.imageset/BusTagMinSize-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/busTagMinSize.imageset/BusTagMinSize-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busTagMinSize.imageset/BusTagMinSize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/busTagMinSize.imageset/BusTagMinSize.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/busTagMinSize.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BusTagMinSize.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "BusTagMinSize-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "BusTagMinSize-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/circulationBusIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "circulationBusIcon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "circulationBusIcon-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "circulationBusIcon-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/circulationBusIcon.imageset/circulationBusIcon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/circulationBusIcon.imageset/circulationBusIcon-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/circulationBusIcon.imageset/circulationBusIcon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/circulationBusIcon.imageset/circulationBusIcon-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/circulationBusIcon.imageset/circulationBusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/circulationBusIcon.imageset/circulationBusIcon.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/circulationBusWithBooduck.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "circulationBusWithBooduck.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "circulationBusWithBooduck-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "circulationBusWithBooduck-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/circulationBusWithBooduck.imageset/circulationBusWithBooduck-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/circulationBusWithBooduck.imageset/circulationBusWithBooduck-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/circulationBusWithBooduck.imageset/circulationBusWithBooduck-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/circulationBusWithBooduck.imageset/circulationBusWithBooduck-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/circulationBusWithBooduck.imageset/circulationBusWithBooduck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/circulationBusWithBooduck.imageset/circulationBusWithBooduck.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/getOff.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "GetOff.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "GetOff-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "GetOff-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/getOff.imageset/GetOff-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/getOff.imageset/GetOff-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/getOff.imageset/GetOff-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/getOff.imageset/GetOff-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/getOff.imageset/GetOff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/getOff.imageset/GetOff.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/getOn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "GetOn.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "GetOn-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "GetOn-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/getOn.imageset/GetOn-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/getOn.imageset/GetOn-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/getOn.imageset/GetOn-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/getOn.imageset/GetOn-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/getOn.imageset/GetOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/getOn.imageset/GetOn.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/greenBusIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "greenBusIcon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "greenBusIcon-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "greenBusIcon-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/greenBusIcon.imageset/greenBusIcon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/greenBusIcon.imageset/greenBusIcon-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/greenBusIcon.imageset/greenBusIcon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/greenBusIcon.imageset/greenBusIcon-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/greenBusIcon.imageset/greenBusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/greenBusIcon.imageset/greenBusIcon.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/greenBusWithBooduck.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "greenBusWithBooduck.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "greenBusWithBooduck-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "greenBusWithBooduck-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/greenBusWithBooduck.imageset/greenBusWithBooduck-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/greenBusWithBooduck.imageset/greenBusWithBooduck-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/greenBusWithBooduck.imageset/greenBusWithBooduck-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/greenBusWithBooduck.imageset/greenBusWithBooduck-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/greenBusWithBooduck.imageset/greenBusWithBooduck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/greenBusWithBooduck.imageset/greenBusWithBooduck.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/homeFavoriteEmpty.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "homeFavoriteEmpty.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "homeFavoriteEmpty-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "homeFavoriteEmpty-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/homeFavoriteEmpty.imageset/homeFavoriteEmpty-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/homeFavoriteEmpty.imageset/homeFavoriteEmpty-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/homeFavoriteEmpty.imageset/homeFavoriteEmpty-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/homeFavoriteEmpty.imageset/homeFavoriteEmpty-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/homeFavoriteEmpty.imageset/homeFavoriteEmpty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/homeFavoriteEmpty.imageset/homeFavoriteEmpty.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/locationSymbol.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "locationIcon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "locationIcon-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "locationIcon-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/locationSymbol.imageset/locationIcon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/locationSymbol.imageset/locationIcon-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/locationSymbol.imageset/locationIcon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/locationSymbol.imageset/locationIcon-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/locationSymbol.imageset/locationIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/locationSymbol.imageset/locationIcon.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/redBusIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "redBusIcon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "redBusIcon-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "redBusIcon-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/redBusIcon.imageset/redBusIcon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/redBusIcon.imageset/redBusIcon-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/redBusIcon.imageset/redBusIcon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/redBusIcon.imageset/redBusIcon-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/redBusIcon.imageset/redBusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/redBusIcon.imageset/redBusIcon.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/redBusWithBooduck.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "redBusWithBooduck.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "redBusWithBooduck-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "redBusWithBooduck-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/redBusWithBooduck.imageset/redBusWithBooduck-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/redBusWithBooduck.imageset/redBusWithBooduck-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/redBusWithBooduck.imageset/redBusWithBooduck-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/redBusWithBooduck.imageset/redBusWithBooduck-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/redBusWithBooduck.imageset/redBusWithBooduck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/redBusWithBooduck.imageset/redBusWithBooduck.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/speechBubble.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "SpeechBubble.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "SpeechBubble-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "SpeechBubble-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/speechBubble.imageset/SpeechBubble-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/speechBubble.imageset/SpeechBubble-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/speechBubble.imageset/SpeechBubble-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/speechBubble.imageset/SpeechBubble-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/speechBubble.imageset/SpeechBubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/speechBubble.imageset/SpeechBubble.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/stationCenterCircle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "StationCenterCircle.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "StationCenterCircle-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "StationCenterCircle-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/stationCenterCircle.imageset/StationCenterCircle-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/stationCenterCircle.imageset/StationCenterCircle-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/stationCenterCircle.imageset/StationCenterCircle-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/stationCenterCircle.imageset/StationCenterCircle-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/stationCenterCircle.imageset/StationCenterCircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/stationCenterCircle.imageset/StationCenterCircle.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/uturn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Uturn.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Uturn-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Uturn-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/uturn.imageset/Uturn-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/uturn.imageset/Uturn-1.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/uturn.imageset/Uturn-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/uturn.imageset/Uturn-2.png -------------------------------------------------------------------------------- /BBus/BBus/Assets.xcassets/Image/uturn.imageset/Uturn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2021/iOS09-BBus/cbbc6e3d63ee82c21add28c1b4b66b5bbdb6699c/BBus/BBus/Assets.xcassets/Image/uturn.imageset/Uturn.png -------------------------------------------------------------------------------- /BBus/BBus/Background/GetOffAlarm/GetOffAlarmController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetOffAlarmController.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/24. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | import CoreLocation 11 | 12 | final class GetOffAlarmController { 13 | 14 | static let shared = GetOffAlarmController(alarmCenter: AlarmCenter()) 15 | 16 | @Published private(set) var viewModel: GetOffAlarmInteractor? 17 | private let alarmCenter: AlarmDetailConfigurable 18 | 19 | private init(alarmCenter: AlarmDetailConfigurable) { 20 | self.alarmCenter = alarmCenter 21 | } 22 | 23 | func start(targetOrd: Int, busRouteId: Int, arsId: String) -> AlarmStartResult { 24 | if let viewModel = viewModel { 25 | return viewModel.causesStartFail(targetOrd: targetOrd, busRouteId: busRouteId) 26 | } 27 | else { 28 | let getOffAlarmStatus = GetOffAlarmStatus(targetOrd: targetOrd, 29 | busRouteId: busRouteId, 30 | arsId: arsId) 31 | self.viewModel = GetOffAlarmInteractor(currentStatus: getOffAlarmStatus) 32 | return .success 33 | } 34 | } 35 | 36 | func stop() { 37 | self.viewModel = nil 38 | } 39 | 40 | func configureAlarmPermission(_ delegate: CLLocationManagerDelegate) { 41 | self.alarmCenter.configurePermission() 42 | self.alarmCenter.configureLocationDetail(delegate) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /BBus/BBus/Background/GetOffAlarm/Interactor/GetOffAlarmInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetOffAlarmViewModel.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/24. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | class GetOffAlarmInteractor { 12 | 13 | private(set) var getOffAlarmStatus: GetOffAlarmStatus 14 | 15 | init(currentStatus: GetOffAlarmStatus) { 16 | self.getOffAlarmStatus = currentStatus 17 | } 18 | 19 | func causesStartFail(targetOrd: Int, busRouteId: Int) -> AlarmStartResult { 20 | if isSameAlarm(targetOrd: targetOrd, busRouteId: busRouteId) { 21 | return .sameAlarm 22 | } 23 | else { 24 | return .duplicated 25 | } 26 | } 27 | 28 | private func isSameAlarm(targetOrd: Int, busRouteId: Int) -> Bool { 29 | return self.getOffAlarmStatus.targetOrd == targetOrd && self.getOffAlarmStatus.busRouteId == busRouteId 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BBus/BBus/Background/GetOffAlarm/Model/GetOffAlarmStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetOffAlarmStatus.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct GetOffAlarmStatus { 11 | let targetOrd: Int 12 | let busRouteId: Int 13 | let arsId: String 14 | 15 | init(targetOrd: Int, busRouteId: Int, arsId: String) { 16 | self.targetOrd = targetOrd 17 | self.busRouteId = busRouteId 18 | self.arsId = arsId 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Background/GetOnAlarm/Interactor/GetOnAlarmInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetOnAlarmViewModel.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/22. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | final class GetOnAlarmInteractor { 12 | 13 | private let useCase: GetOnAlarmAPIUseCase 14 | private(set) var getOnAlarmStatus: GetOnAlarmStatus 15 | private var cancellables: Set 16 | @Published private(set) var busApproachStatus: BusApproachStatus? 17 | private(set) var message: String? 18 | @Published private(set) var networkErrorMessage: (title: String, body: String)? 19 | 20 | init(useCase: GetOnAlarmAPIUseCase, currentStatus: GetOnAlarmStatus) { 21 | self.useCase = useCase 22 | self.getOnAlarmStatus = currentStatus 23 | self.message = nil 24 | self.networkErrorMessage = nil 25 | self.cancellables = [] 26 | self.busApproachStatus = nil 27 | self.execute() 28 | self.configureObserver() 29 | } 30 | 31 | private func configureObserver() { 32 | NotificationCenter.default.addObserver(forName: .fifteenSecondsPassed, object: nil, queue: .main) { [weak self] _ in 33 | self?.fetch() 34 | } 35 | } 36 | 37 | private func execute() { 38 | self.bindBusPosition() 39 | self.bindNetworkErrorMessage() 40 | } 41 | 42 | func bindBusPosition() { 43 | self.useCase.$busPosition 44 | .receive(on: DispatchQueue.global()) 45 | .sink { [weak self] position in 46 | guard let self = self, 47 | let position = position, 48 | let stationOrd = Int(position.stationOrd) else { return } 49 | 50 | if let status = GetOnAlarmCalculateUsecase().busApproachStatus(currentOrd: stationOrd, 51 | beforeOrd: self.getOnAlarmStatus.currentBusOrd ?? stationOrd, 52 | targetOrd: self.getOnAlarmStatus.targetOrd) { 53 | self.makeMessage(with: status) 54 | self.busApproachStatus = status 55 | } 56 | self.getOnAlarmStatus = self.getOnAlarmStatus.withCurrentBusOrd(stationOrd) 57 | } 58 | .store(in: &self.cancellables) 59 | } 60 | 61 | func bindNetworkErrorMessage() { 62 | self.useCase.$networkError 63 | .receive(on: DispatchQueue.global()) 64 | .sink(receiveValue: { [weak self] error in 65 | guard let _ = error else { return } 66 | self?.networkErrorMessage = ("승차 알람", "네트워크 에러가 발생하여 알람이 취소됩니다.") 67 | }) 68 | .store(in: &self.cancellables) 69 | } 70 | 71 | func fetch() { 72 | self.useCase.fetch(withVehId: "\(self.getOnAlarmStatus.vehicleId)") 73 | } 74 | 75 | private func makeMessage(with status: BusApproachStatus) { 76 | self.message = "\(self.getOnAlarmStatus.busName)번 버스가 \(status.rawValue)번째 전 정류장에 도착하였습니다." 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /BBus/BBus/Background/GetOnAlarm/Model/AlarmStartResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetOnStartResult.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | enum AlarmStartResult { 11 | case success 12 | case sameAlarm 13 | case duplicated 14 | } 15 | -------------------------------------------------------------------------------- /BBus/BBus/Background/GetOnAlarm/Model/BusApproachStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BusApproachStatus.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | enum BusApproachStatus: Int { 11 | case oneStationLeft = 1, twoStationLeft 12 | } 13 | -------------------------------------------------------------------------------- /BBus/BBus/Background/GetOnAlarm/Model/GetOnAlarmStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetOnAlarmStatus.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct GetOnAlarmStatus { 11 | let currentBusOrd: Int? 12 | let targetOrd: Int 13 | let vehicleId: Int 14 | let busName: String 15 | let busRouteId: Int 16 | let stationId: Int 17 | 18 | init(currentBusOrd: Int?, targetOrd: Int, vehicleId: Int, busName: String, busRouteId: Int, stationId: Int) { 19 | self.currentBusOrd = currentBusOrd 20 | self.targetOrd = targetOrd 21 | self.vehicleId = vehicleId 22 | self.busName = busName 23 | self.busRouteId = busRouteId 24 | self.stationId = stationId 25 | } 26 | 27 | func withCurrentBusOrd(_ ord: Int) -> GetOnAlarmStatus { 28 | return GetOnAlarmStatus(currentBusOrd: ord, 29 | targetOrd: self.targetOrd, 30 | vehicleId: self.vehicleId, 31 | busName: self.busName, 32 | busRouteId: self.busRouteId, 33 | stationId: self.stationId) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BBus/BBus/Background/GetOnAlarm/UseCase/GetOnAlarmAPIUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetOnAlarmUsecase.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/22. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetOnAlarmAPIUsable: BaseUseCase { 12 | func fetch(withVehId vehId: String) 13 | } 14 | 15 | final class GetOnAlarmAPIUseCase: GetOnAlarmAPIUsable { 16 | 17 | private let useCases: GetBusPosByVehIdUsable 18 | @Published private(set) var networkError: Error? 19 | @Published private(set) var busPosition: BusPosByVehicleIdDTO? 20 | 21 | init(useCases: GetBusPosByVehIdUsable) { 22 | self.useCases = useCases 23 | self.networkError = nil 24 | self.busPosition = nil 25 | } 26 | 27 | func fetch(withVehId vehId: String) { 28 | self.useCases.getBusPosByVehId(vehId) 29 | .decode(type: JsonMessage.self, decoder: JSONDecoder()) 30 | .catchError({ error in 31 | self.networkError = error 32 | }) 33 | .map({ item in 34 | item.msgBody.itemList.first 35 | }) 36 | .assign(to: &self.$busPosition) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BBus/BBus/Background/GetOnAlarm/UseCase/GetOnAlarmCalculateUsecase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BusApproachCheckUsecase.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol GetOnAlarmCalculatable: BaseUseCase { 11 | func busApproachStatus(currentOrd: Int, beforeOrd: Int, targetOrd: Int) -> BusApproachStatus? 12 | } 13 | 14 | struct GetOnAlarmCalculateUsecase: GetOnAlarmCalculatable { 15 | 16 | func busApproachStatus(currentOrd: Int, beforeOrd: Int, targetOrd: Int) -> BusApproachStatus? { 17 | guard currentOrd != beforeOrd else { return nil } 18 | return BusApproachStatus(rawValue: targetOrd - currentOrd) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/AlarmSetting/AlarmSettingCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlarmSettingCoordinator.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/03. 6 | // 7 | 8 | import UIKit 9 | 10 | final class AlarmSettingCoordinator: Coordinator { 11 | weak var delegate: CoordinatorDelegate? 12 | weak var movingStatusDelegate: MovingStatusOpenCloseDelegate? 13 | var navigationPresenter: UINavigationController 14 | 15 | init(presenter: UINavigationController) { 16 | self.navigationPresenter = presenter 17 | } 18 | 19 | func start(stationId: Int, busRouteId: Int, stationOrd: Int, arsId: String, routeType: RouteType?, busName: String) { 20 | let apiUseCases = BBusAPIUseCases(networkService: NetworkService(), 21 | persistenceStorage: PersistenceStorage(), 22 | tokenManageType: TokenManager.self, 23 | requestFactory: RequestFactory()) 24 | let apiUseCase = AlarmSettingAPIUseCase(useCases: apiUseCases) 25 | let calculateUseCase = AlarmSettingCalculateUseCase() 26 | let viewModel = AlarmSettingViewModel(apiUseCase: apiUseCase, 27 | calculateUseCase: calculateUseCase, 28 | stationId: stationId, 29 | busRouteId: busRouteId, 30 | stationOrd: stationOrd, 31 | arsId: arsId, 32 | routeType: routeType, 33 | busName: busName) 34 | let viewController = AlarmSettingViewController(viewModel: viewModel) 35 | viewController.coordinator = self 36 | self.navigationPresenter.pushViewController(viewController, animated: true) 37 | } 38 | 39 | func terminate() { 40 | self.navigationPresenter.popViewController(animated: true) 41 | self.coordinatorDidFinish() 42 | } 43 | 44 | func openMovingStatus(busRouteId: Int, fromArsId: String, toArsId: String) { 45 | self.movingStatusDelegate?.open(busRouteId: busRouteId, fromArsId: fromArsId, toArsId: toArsId) 46 | } 47 | 48 | func resetMovingStatus(busRouteId: Int, fromArsId: String, toArsId: String) { 49 | self.movingStatusDelegate?.reset(busRouteId: busRouteId, fromArsId: fromArsId, toArsId: toArsId) 50 | } 51 | 52 | func closeMovingStatus() { 53 | self.movingStatusDelegate?.close() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/AlarmSetting/Model/AlarmSettingBusArriveInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlarmSettingBusArriveInfo.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import Foundation 9 | 10 | struct AlarmSettingBusArriveInfo { 11 | static let estimatedTimeFormatter: DateFormatter = { 12 | let formatter = DateFormatter() 13 | formatter.timeZone = TimeZone.current 14 | formatter.locale = Locale(identifier: "ko_KR") 15 | formatter.dateFormat = "a hh시 mm분 도착 예정" 16 | return formatter 17 | }() 18 | 19 | var arriveRemainTime: BusRemainTime? 20 | let estimatedArrivalTime: String? 21 | let relativePosition: String? 22 | let congestion: BusCongestion? 23 | let currentStation: String 24 | let plainNumber: String 25 | let vehicleId: Int 26 | 27 | init(busArriveRemainTime: String, congestion: Int, currentStation: String, plainNumber: String, vehicleId: Int) { 28 | let timeAndPositionInfo = Self.seperateTimeAndPositionInfo(with: busArriveRemainTime) 29 | self.arriveRemainTime = timeAndPositionInfo.time.checkInfo() ? timeAndPositionInfo.time : nil 30 | self.relativePosition = timeAndPositionInfo.position 31 | self.congestion = BusCongestion(rawValue: congestion) 32 | self.currentStation = currentStation 33 | self.plainNumber = plainNumber 34 | self.vehicleId = vehicleId 35 | 36 | if let estimatedTime = timeAndPositionInfo.time.estimateArrivalTime() { 37 | self.estimatedArrivalTime = Self.estimatedTimeFormatter.string(from: estimatedTime) 38 | } 39 | else { 40 | self.estimatedArrivalTime = nil 41 | } 42 | } 43 | 44 | static func seperateTimeAndPositionInfo(with info: String) -> (time: BusRemainTime, position: String?) { 45 | let components = info.replacingOccurrences(of: " ", with: "").components(separatedBy: ["[", "]"]) 46 | let exceptions = ["차고지출발"] 47 | if components.count > 4 { // 막차 48 | return (time: BusRemainTime(arriveRemainTime: components[2]), position: components[3]) 49 | } 50 | else if components.count > 1 { 51 | if exceptions.contains(components[1]) { 52 | return (time: BusRemainTime(arriveRemainTime: components[1]), position: nil) 53 | } 54 | return (time: BusRemainTime(arriveRemainTime: components[0]), position: components[1]) 55 | } 56 | else { 57 | return (time: BusRemainTime(arriveRemainTime: components[0]), position: nil) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/AlarmSetting/Model/AlarmSettingBusArriveInfos.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlarmSettingBusArriveInfos.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/18. 6 | // 7 | 8 | import Foundation 9 | 10 | typealias AlarmSettingBusStationInfo = (arsId: String, name: String, estimatedTime: Int, ord: Int) 11 | 12 | struct AlarmSettingBusArriveInfos { 13 | var arriveInfos: [AlarmSettingBusArriveInfo] 14 | var changedByTimer: Bool 15 | 16 | var count: Int { 17 | return self.arriveInfos.count 18 | } 19 | 20 | var first: AlarmSettingBusArriveInfo? { 21 | return self.arriveInfos.first 22 | } 23 | 24 | subscript(index: Int) -> AlarmSettingBusArriveInfo? { 25 | guard index >= 0 && index < self.count else { return nil } 26 | return self.arriveInfos[index] 27 | } 28 | 29 | mutating func desend() { 30 | self.arriveInfos = self.arriveInfos.map { 31 | var arriveInfo = $0 32 | arriveInfo.arriveRemainTime?.descend() 33 | return arriveInfo 34 | } 35 | self.changedByTimer = true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/AlarmSetting/UseCase/AlarmSettingAPIUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlarmSettingAPIUsable.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol AlarmSettingAPIUsable: BaseUseCase { 12 | func busArriveInfoWillLoaded(stId: String, busRouteId: String, ord: String) -> AnyPublisher 13 | func busStationsInfoWillLoaded(busRouetId: String, arsId: String) -> AnyPublisher<[StationByRouteListDTO]?, Error> 14 | } 15 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/AlarmSetting/UseCase/AlarmSettingAPIUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlarmSettingUseCase.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | final class AlarmSettingAPIUseCase: AlarmSettingAPIUsable { 12 | typealias AlarmSettingUseCases = GetArrInfoByRouteListUsable & GetStationsByRouteListUsable 13 | 14 | private let useCases: AlarmSettingUseCases 15 | @Published private(set) var networkError: Error? 16 | 17 | init(useCases: AlarmSettingUseCases) { 18 | self.useCases = useCases 19 | self.networkError = nil 20 | } 21 | 22 | func busArriveInfoWillLoaded(stId: String, busRouteId: String, ord: String) -> AnyPublisher { 23 | return self.useCases.getArrInfoByRouteList(stId: stId, 24 | busRouteId: busRouteId, 25 | ord: ord) 26 | .decode(type: ArrInfoByRouteResult.self, decoder: JSONDecoder()) 27 | .tryMap({ item -> ArrInfoByRouteDTO in 28 | let result = item.msgBody.itemList 29 | guard let item = result.first else { throw BBusAPIError.wrongFormatError } 30 | return item 31 | }) 32 | .eraseToAnyPublisher() 33 | } 34 | 35 | func busStationsInfoWillLoaded(busRouetId: String, arsId: String) -> AnyPublisher<[StationByRouteListDTO]?, Error> { 36 | return self.useCases.getStationsByRouteList(busRoutedId: busRouetId) 37 | .decode(type: StationByRouteResult.self, decoder: JSONDecoder()) 38 | .map({ item -> [StationByRouteListDTO]? in 39 | let result = item.msgBody.itemList 40 | guard let index = result.firstIndex(where: { $0.arsId == arsId }) else { return nil } 41 | return Array(result[index.. AnyPublisher 13 | func fetchRouteList(busRouteId: Int) -> AnyPublisher<[StationByRouteListDTO], Error> 14 | func fetchBusPosList(busRouteId: Int) -> AnyPublisher<[BusPosByRtidDTO], Error> 15 | } 16 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/BusRoute/UseCase/BusRouteAPIUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BusRouteUseCase.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | final class BusRouteAPIUseCase: BusRouteAPIUsable { 12 | 13 | private let useCases: GetRouteListUsable & GetStationsByRouteListUsable & GetBusPosByRtidUsable 14 | 15 | init(useCases: GetRouteListUsable & GetStationsByRouteListUsable & GetBusPosByRtidUsable) { 16 | self.useCases = useCases 17 | } 18 | 19 | func searchHeader(busRouteId: Int) -> AnyPublisher { 20 | return self.useCases.getRouteList() 21 | .decode(type: [BusRouteDTO].self, decoder: JSONDecoder()) 22 | .tryMap({ routeList -> BusRouteDTO? in 23 | let header = routeList.filter { $0.routeID == busRouteId }.first 24 | return header 25 | }) 26 | .eraseToAnyPublisher() 27 | } 28 | 29 | func fetchRouteList(busRouteId: Int) -> AnyPublisher<[StationByRouteListDTO], Error> { 30 | return self.useCases.getStationsByRouteList(busRoutedId: "\(busRouteId)") 31 | .decode(type: StationByRouteResult.self, decoder: JSONDecoder()) 32 | .map({ item in 33 | item.msgBody.itemList 34 | }) 35 | .eraseToAnyPublisher() 36 | } 37 | 38 | func fetchBusPosList(busRouteId: Int) -> AnyPublisher<[BusPosByRtidDTO], Error> { 39 | return self.useCases.getBusPosByRtid(busRoutedId: "\(busRouteId)") 40 | .decode(type: BusPosByRtidResult.self, decoder: JSONDecoder()) 41 | .tryMap({ item in 42 | return item.msgBody.itemList 43 | }) 44 | .eraseToAnyPublisher() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Home/HomeCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeCoordinator.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/02. 6 | // 7 | 8 | import UIKit 9 | 10 | final class HomeCoordinator: SearchPushable, BusRoutePushable, AlarmSettingPushable, StationPushable { 11 | weak var delegate: CoordinatorDelegate? 12 | var navigationPresenter: UINavigationController 13 | 14 | init(presenter: UINavigationController) { 15 | self.navigationPresenter = presenter 16 | } 17 | 18 | func start(statusBarHeight: CGFloat?) { 19 | let apiUseCases = BBusAPIUseCases(networkService: NetworkService(), 20 | persistenceStorage: PersistenceStorage(), 21 | tokenManageType: TokenManager.self, 22 | requestFactory: RequestFactory()) 23 | let apiUseCase = HomeAPIUseCase(useCases: apiUseCases) 24 | let calculateUseCase = HomeCalculateUseCase() 25 | let viewModel = HomeViewModel(apiUseCase: apiUseCase, calculateUseCase: calculateUseCase) 26 | let viewController = HomeViewController(viewModel: viewModel, statusBarHegiht: statusBarHeight) 27 | viewController.coordinator = self 28 | 29 | navigationPresenter.pushViewController(viewController, animated: false) // present 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Home/UseCase/HomeAPIUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeAPIUsable.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol HomeAPIUsable: BaseUseCase { 12 | typealias HomeUseCases = GetFavoriteItemListUsable & CreateFavoriteItemUsable & GetStationListUsable & GetRouteListUsable & GetArrInfoByRouteListUsable 13 | 14 | func fetchFavoriteData() -> AnyPublisher<[FavoriteItemDTO], Error> 15 | func fetchBusRemainTime(favoriteItem: FavoriteItemDTO) -> AnyPublisher 16 | func fetchStation() -> AnyPublisher<[StationDTO], Error> 17 | func fetchBusRoute() -> AnyPublisher<[BusRouteDTO], Error> 18 | } 19 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Home/UseCase/HomeAPIUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeUseCase.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | final class HomeAPIUseCase: HomeAPIUsable { 12 | 13 | private let useCases: HomeUseCases 14 | 15 | init(useCases: HomeUseCases) { 16 | self.useCases = useCases 17 | } 18 | 19 | func fetchFavoriteData() -> AnyPublisher<[FavoriteItemDTO], Error> { 20 | return self.useCases.getFavoriteItemList() 21 | .decode(type: [FavoriteItemDTO]?.self, decoder: PropertyListDecoder()) 22 | .tryMap({ item in 23 | guard let item = item else { throw BBusAPIError.wrongFormatError } 24 | return item 25 | }) 26 | .eraseToAnyPublisher() 27 | } 28 | 29 | func fetchBusRemainTime(favoriteItem: FavoriteItemDTO) -> AnyPublisher { 30 | return self.useCases.getArrInfoByRouteList(stId: favoriteItem.stId, 31 | busRouteId: favoriteItem.busRouteId, 32 | ord: favoriteItem.ord) 33 | .decode(type: ArrInfoByRouteResult.self, decoder: JSONDecoder()) 34 | .tryMap({ item in 35 | let result = item.msgBody.itemList 36 | guard let item = result.first else { throw BBusAPIError.wrongFormatError } 37 | let homeFavoriteInfo: HomeFavoriteInfo 38 | homeFavoriteInfo.favoriteItem = favoriteItem 39 | homeFavoriteInfo.arriveInfo = HomeArriveInfo(arrInfoByRouteDTO: item) 40 | return homeFavoriteInfo 41 | }) 42 | .eraseToAnyPublisher() 43 | } 44 | 45 | func fetchStation() -> AnyPublisher<[StationDTO], Error> { 46 | self.useCases.getStationList() 47 | .decode(type: [StationDTO]?.self, decoder: JSONDecoder()) 48 | .tryMap({ item in 49 | guard let item = item else { throw BBusAPIError.wrongFormatError } 50 | return item 51 | }) 52 | .eraseToAnyPublisher() 53 | } 54 | 55 | func fetchBusRoute() -> AnyPublisher<[BusRouteDTO], Error> { 56 | return self.useCases.getRouteList() 57 | .decode(type: [BusRouteDTO]?.self, decoder: JSONDecoder()) 58 | .tryMap({ item in 59 | guard let item = item else { throw BBusAPIError.wrongFormatError } 60 | return item 61 | }) 62 | .eraseToAnyPublisher() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Home/UseCase/HomeCalculatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeCalculatable.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol HomeCalculateUsable: BaseUseCase { 11 | func findStationName(in list: [StationDTO]?, by stationId: String) -> String? 12 | func findBusName(in list: [BusRouteDTO]?, by busRouteId: String) -> String? 13 | func findBusType(in list: [BusRouteDTO]?, by busName: String) -> RouteType? 14 | } 15 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Home/UseCase/HomeCalculateUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeCalculateUseCase.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | 10 | struct HomeCalculateUseCase: HomeCalculateUsable { 11 | func findStationName(in list: [StationDTO]?, by stationId: String) -> String? { 12 | guard let stationId = Int(stationId), 13 | let stationName = list?.first(where: { $0.stationID == stationId })?.stationName else { return nil } 14 | 15 | return stationName 16 | } 17 | 18 | func findBusName(in list: [BusRouteDTO]?, by busRouteId: String) -> String? { 19 | guard let busRouteId = Int(busRouteId), 20 | let busName = list?.first(where: { $0.routeID == busRouteId })?.busRouteName else { return nil } 21 | 22 | return busName 23 | } 24 | 25 | func findBusType(in list: [BusRouteDTO]?, by busName: String) -> RouteType? { 26 | return list?.first(where: { $0.busRouteName == busName } )?.routeType 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Home/View/EmptyFavoriteNoticeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyFavoriteNoticeView.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/18. 6 | // 7 | 8 | import UIKit 9 | 10 | final class EmptyFavoriteNoticeView: UIView { 11 | 12 | private lazy var noticeImage: UIImageView = { 13 | let imageView = UIImageView(image: BBusImage.homeFavoriteEmpty) 14 | imageView.contentMode = .scaleAspectFit 15 | return imageView 16 | }() 17 | private lazy var noticeLabel: UILabel = { 18 | let label = UILabel() 19 | label.text = "다음버스 도착시간까지 알고 싶다면 즐겨찾기를 추가해보세요." 20 | label.numberOfLines = 2 21 | label.lineBreakMode = .byWordWrapping 22 | label.textColor = BBusColor.bbusGray 23 | label.textAlignment = .center 24 | label.font = UIFont.systemFont(ofSize: 15) 25 | return label 26 | }() 27 | private lazy var sourceLabel: UILabel = { 28 | let label = UILabel() 29 | label.text = "출처 : 서울특별시" 30 | label.font = UIFont.systemFont(ofSize: 15) 31 | label.textColor = BBusColor.bbusGray 32 | return label 33 | }() 34 | 35 | convenience init() { 36 | self.init(frame: CGRect()) 37 | } 38 | 39 | override init(frame: CGRect) { 40 | super.init(frame: frame) 41 | self.configureLayout() 42 | } 43 | 44 | required init?(coder: NSCoder) { 45 | super.init(coder: coder) 46 | self.configureLayout() 47 | } 48 | 49 | private func configureLayout() { 50 | let half: CGFloat = 0.5 51 | let centerYInterval: CGFloat = -30 52 | 53 | self.addSubviews(self.noticeImage, self.noticeLabel, self.sourceLabel) 54 | 55 | NSLayoutConstraint.activate([ 56 | self.noticeImage.centerXAnchor.constraint(equalTo: self.centerXAnchor), 57 | self.noticeImage.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: centerYInterval), 58 | self.noticeImage.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: half), 59 | self.noticeImage.heightAnchor.constraint(equalTo: self.noticeImage.widthAnchor) 60 | ]) 61 | 62 | NSLayoutConstraint.activate([ 63 | self.noticeLabel.topAnchor.constraint(equalTo: self.noticeImage.bottomAnchor, constant: 15), 64 | self.noticeLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor), 65 | self.noticeLabel.widthAnchor.constraint(equalTo: self.noticeImage.widthAnchor, multiplier: 1.2) 66 | ]) 67 | 68 | let sourceLabelBottominterval: CGFloat = -30 69 | NSLayoutConstraint.activate([ 70 | self.sourceLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: sourceLabelBottominterval), 71 | self.sourceLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor) 72 | ]) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Home/View/RemainCongestionBadgeLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemainCongestionBadgeLabel.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/07. 6 | // 7 | 8 | import UIKit 9 | 10 | final class RemainCongestionBadgeLabel: UILabel { 11 | 12 | let padding = UIEdgeInsets(top: 2, left: 4, bottom: 2, right: 4) 13 | 14 | override func drawText(in rect: CGRect) { 15 | let paddingRect = rect.inset(by: padding) 16 | super.drawText(in: paddingRect) 17 | } 18 | 19 | override var intrinsicContentSize: CGSize { 20 | var contentSize = super.intrinsicContentSize 21 | contentSize.height += padding.top + padding.bottom 22 | contentSize.width += padding.left + padding.right 23 | return contentSize 24 | } 25 | 26 | override init(frame: CGRect) { 27 | super.init(frame: frame) 28 | self.configureUI() 29 | } 30 | 31 | required init?(coder: NSCoder) { 32 | super.init(coder: coder) 33 | self.configureUI() 34 | } 35 | 36 | convenience init() { 37 | self.init(frame: CGRect()) 38 | } 39 | 40 | private func configureUI() { 41 | self.layer.borderColor = BBusColor.bbusLightGray?.cgColor 42 | self.layer.borderWidth = 2 43 | self.layer.cornerRadius = 3 44 | self.font = UIFont.systemFont(ofSize: 11) 45 | self.textColor = BBusColor.bbusGray 46 | self.textAlignment = .center 47 | } 48 | 49 | func configure(remaining: String?, congestion: String?) { 50 | if remaining == nil && congestion == nil { 51 | self.isHidden = true 52 | } 53 | else { 54 | let remaining = remaining ?? "" 55 | let congestion = congestion ?? "" 56 | let description = remaining + " " + congestion 57 | let redRange = (description as NSString).range(of: congestion) 58 | let attributedString = NSMutableAttributedString(string: description) 59 | attributedString.addAttribute(.foregroundColor, 60 | value: BBusColor.bbusCongestionRed as Any, 61 | range: redRange) 62 | self.attributedText = attributedString 63 | self.isHidden = false 64 | } 65 | self.sizeToFit() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Home/View/SourceFooterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionReusableView.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/25. 6 | // 7 | 8 | import UIKit 9 | 10 | final class SourceFooterView: UICollectionReusableView { 11 | 12 | static let identifier = "SourceFooterView" 13 | static let height: CGFloat = 100 14 | 15 | private lazy var sourceLabel: UILabel = { 16 | let label = UILabel() 17 | label.text = "출처 : 서울특별시" 18 | label.font = UIFont.systemFont(ofSize: 15) 19 | label.textColor = BBusColor.bbusGray 20 | return label 21 | }() 22 | 23 | required override init(frame: CGRect) { 24 | super.init(frame: frame) 25 | self.configureLayout() 26 | } 27 | 28 | required init?(coder: NSCoder) { 29 | super.init(coder: coder) 30 | self.configureLayout() 31 | } 32 | 33 | private func configureLayout() { 34 | self.addSubviews(self.sourceLabel) 35 | 36 | let half: CGFloat = 0.5 37 | NSLayoutConstraint.activate([ 38 | self.sourceLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -Self.height * half), 39 | self.sourceLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor) 40 | ]) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/MovingStatus/Model/MovingStatusModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovingStatusModel.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import Foundation 9 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/MovingStatus/MovingStatusFoldUnfoldDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovingStatusFoldUnfoldDelegate.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/09. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol MovingStatusOpenCloseDelegate: AnyObject { 11 | func open(busRouteId: Int, fromArsId: String, toArsId: String) 12 | func reset(busRouteId: Int, fromArsId: String, toArsId: String) 13 | func close() 14 | } 15 | 16 | protocol MovingStatusFoldUnfoldDelegate { 17 | func fold() 18 | func unfold() 19 | } 20 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/MovingStatus/UseCase/MovingStatusAPIUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovingStatusAPIUsable.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol MovingStatusAPIUsable: BaseUseCase { 12 | func searchHeader(busRouteId: Int) -> AnyPublisher 13 | func fetchRouteList(busRouteId: Int) -> AnyPublisher<[StationByRouteListDTO], Error> 14 | func fetchBusPosList(busRouteId: Int) -> AnyPublisher<[BusPosByRtidDTO], Error> 15 | } 16 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/MovingStatus/UseCase/MovingStatusAPIUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovingStatusAPIUseCase.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | final class MovingStatusAPIUseCase: MovingStatusAPIUsable { 12 | 13 | private let useCases: GetRouteListUsable & GetStationsByRouteListUsable & GetBusPosByRtidUsable 14 | 15 | init(useCases: GetRouteListUsable & GetStationsByRouteListUsable & GetBusPosByRtidUsable) { 16 | self.useCases = useCases 17 | } 18 | 19 | func searchHeader(busRouteId: Int) -> AnyPublisher { 20 | return self.useCases.getRouteList() 21 | .decode(type: [BusRouteDTO].self, decoder: JSONDecoder()) 22 | .tryMap({ routeList in 23 | let headers = routeList.filter({ $0.routeID == busRouteId }) 24 | if let header = headers.first { 25 | return header 26 | } 27 | else { 28 | throw BBusAPIError.wrongFormatError 29 | } 30 | }) 31 | .eraseToAnyPublisher() 32 | } 33 | 34 | func fetchRouteList(busRouteId: Int) -> AnyPublisher<[StationByRouteListDTO], Error> { 35 | return self.useCases.getStationsByRouteList(busRoutedId: "\(busRouteId)") 36 | .decode(type: StationByRouteResult.self, decoder: JSONDecoder()) 37 | .map({ item in 38 | item.msgBody.itemList 39 | }) 40 | .eraseToAnyPublisher() 41 | } 42 | 43 | func fetchBusPosList(busRouteId: Int) -> AnyPublisher<[BusPosByRtidDTO], Error> { 44 | return self.useCases.getBusPosByRtid(busRoutedId: "\(busRouteId)") 45 | .decode(type: BusPosByRtidResult.self, decoder: JSONDecoder()) 46 | .tryMap ({ item in 47 | return item.msgBody.itemList 48 | }) 49 | .eraseToAnyPublisher() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/MovingStatus/UseCase/MovingStatusCalculatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovingStatusCalculatable.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol MovingStatusCalculatable: AverageSectionTimeCalculatable { 11 | func filteredBuses(from buses: [BusPosByRtidDTO], startOrd: Int, currentOrd: Int, count: Int) -> [BusPosByRtidDTO] 12 | func convertBusInfo(header: BusRouteDTO) -> BusInfo 13 | func remainStation(bus: BusPosByRtidDTO, startOrd: Int, count: Int) -> Int 14 | func pushAlarmMessage(remainStation: Int) -> (message: String?, terminated: Bool) 15 | func remainTime(bus: BusPosByRtidDTO, stations: [StationInfo], startOrd: Int, boardedBus: BoardedBus) -> Int 16 | func convertBusPos(startOrd: Int, order: Int, sect: String, fullSect: String) -> Double 17 | func isOnBoard(gpsY: Double, gpsX: Double, busY: Double, busX: Double) -> Bool 18 | func stationIndex(with targetId: String, with stations: [StationByRouteListDTO]) -> Int? 19 | func filteredStations(from stations: [StationByRouteListDTO]) -> (stations: [StationInfo], time: Int) 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Search/Model/BusSearchResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BusSearchResult.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/14. 6 | // 7 | 8 | import Foundation 9 | 10 | struct BusSearchResult { 11 | let routeID: Int 12 | let busRouteName: String 13 | let routeType: RouteType 14 | 15 | init(busRouteDTO: BusRouteDTO) { 16 | self.routeID = busRouteDTO.routeID 17 | self.busRouteName = busRouteDTO.busRouteName 18 | self.routeType = busRouteDTO.routeType 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Search/Model/SearchResults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResult.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/14. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SearchResults { 11 | var busSearchResults: [BusSearchResult] 12 | var stationSearchResults: [StationSearchResult] 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Search/Model/StationSearchResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationSearchResult.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import Foundation 9 | 10 | struct StationSearchResult { 11 | let stationName: String 12 | let arsId: String 13 | let stationNameMatchRanges: [NSRange] 14 | let arsIdMatchRanges: [NSRange] 15 | 16 | init(stationName: String, arsId: String, stationNameMatchRanges: [Range], arsIdMatchRanges: [Range]) { 17 | self.stationName = stationName 18 | self.arsId = arsId 19 | self.stationNameMatchRanges = stationNameMatchRanges.map { NSRange($0, in: stationName) } 20 | self.arsIdMatchRanges = arsIdMatchRanges.map { NSRange($0, in: arsId) } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Search/SearchCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchCoordinator.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/02. 6 | // 7 | 8 | import UIKit 9 | 10 | final class SearchCoordinator: BusRoutePushable, StationPushable { 11 | weak var delegate: CoordinatorDelegate? 12 | var navigationPresenter: UINavigationController 13 | 14 | init(presenter: UINavigationController) { 15 | self.navigationPresenter = presenter 16 | } 17 | 18 | func start() { 19 | let apiUseCases = BBusAPIUseCases(networkService: NetworkService(), 20 | persistenceStorage: PersistenceStorage(), 21 | tokenManageType: TokenManager.self, 22 | requestFactory: RequestFactory()) 23 | let apiUseCase = SearchAPIUseCase(useCases: apiUseCases) 24 | let calculateUseCase = SearchCalculateUseCase() 25 | let viewModel = SearchViewModel(apiUseCase: apiUseCase, calculateUseCase: calculateUseCase) 26 | let viewController = SearchViewController(viewModel: viewModel) 27 | viewController.coordinator = self 28 | self.navigationPresenter.pushViewController(viewController, animated: true) 29 | } 30 | 31 | func terminate() { 32 | self.navigationPresenter.popViewController(animated: true) 33 | self.coordinatorDidFinish() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Search/UseCase/SearchAPIUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchAPIUsable.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol SearchAPIUsable: BaseUseCase { 12 | func loadBusRouteList() -> AnyPublisher<[BusRouteDTO], Error> 13 | func loadStationList() -> AnyPublisher<[StationDTO], Error> 14 | } 15 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Search/UseCase/SearchAPIUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchAPIUseCase.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | final class SearchAPIUseCase: SearchAPIUsable { 12 | 13 | private let useCases: GetRouteListUsable & GetStationListUsable 14 | private var cancellables: Set 15 | @Published var networkError: Error? 16 | 17 | init(useCases: GetRouteListUsable & GetStationListUsable) { 18 | self.useCases = useCases 19 | self.cancellables = [] 20 | } 21 | 22 | func loadBusRouteList() -> AnyPublisher<[BusRouteDTO], Error> { 23 | self.useCases.getRouteList() 24 | .decode(type: [BusRouteDTO].self, decoder: JSONDecoder()) 25 | .eraseToAnyPublisher() 26 | } 27 | 28 | func loadStationList() -> AnyPublisher<[StationDTO], Error> { 29 | self.useCases.getStationList() 30 | .decode(type: [StationDTO].self, decoder: JSONDecoder()) 31 | .eraseToAnyPublisher() 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Search/UseCase/SearchCalculatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchCalculatable.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol SearchCalculatable { 11 | func searchBus(by keyword: String, at routeList: [BusRouteDTO]) -> [BusSearchResult] 12 | func searchStation(by keyword: String, at stationList: [StationDTO]) -> [StationSearchResult] 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Search/UseCase/SearchCalculateUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchCalculateUseCase.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/30. 6 | // 7 | 8 | import Foundation 9 | 10 | final class SearchCalculateUseCase: SearchCalculatable { 11 | func searchBus(by keyword: String, at routeList: [BusRouteDTO]) -> [BusSearchResult] { 12 | if keyword.isEmpty { 13 | return [] 14 | } 15 | else { 16 | return routeList 17 | .filter { $0.busRouteName.hasPrefix(keyword) } 18 | .map { BusSearchResult(busRouteDTO: $0) } 19 | } 20 | } 21 | 22 | func searchStation(by keyword: String, at stationList: [StationDTO]) -> [StationSearchResult] { 23 | if keyword.isEmpty { 24 | return [] 25 | } 26 | else { 27 | return stationList 28 | .map { StationSearchResult(stationName: $0.stationName, 29 | arsId: $0.arsID, 30 | stationNameMatchRanges: $0.stationName.ranges(of: keyword), 31 | arsIdMatchRanges: $0.arsID.ranges(of: keyword)) } 32 | .filter { !($0.arsIdMatchRanges.isEmpty && $0.stationNameMatchRanges.isEmpty) } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Search/View/SimpleCollectionHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleCollectionHeaderView.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/04. 6 | // 7 | 8 | import UIKit 9 | 10 | final class SimpleCollectionHeaderView: UICollectionReusableView { 11 | 12 | static let identifier = "SearchResultHeaderView" 13 | static let height: CGFloat = 20 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | self.configureUI() 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | super.init(coder: coder) 22 | self.configureUI() 23 | } 24 | 25 | private lazy var title: UILabel = { 26 | let label = UILabel() 27 | label.text = "" 28 | label.textColor = BBusColor.gray 29 | label.font = UIFont.systemFont(ofSize: 12) 30 | return label 31 | }() 32 | 33 | // MARK: - Configuration 34 | func configureLayout() { 35 | self.addSubviews(self.title) 36 | 37 | let titleLeftPadding: CGFloat = 10 38 | NSLayoutConstraint.activate([ 39 | self.title.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: titleLeftPadding), 40 | self.title.centerYAnchor.constraint(equalTo: self.centerYAnchor) 41 | ]) 42 | } 43 | 44 | private func configureUI() { 45 | self.backgroundColor = BBusColor.bbusLightGray 46 | } 47 | 48 | func configure(title: String) { 49 | self.title.text = title 50 | self.title.sizeToFit() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Search/ViewModel/SearchViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchViewModel.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | final class SearchViewModel { 12 | 13 | private let apiUseCase: SearchAPIUsable 14 | private let calculateUseCase: SearchCalculatable 15 | private var busRouteList: [BusRouteDTO] 16 | private var stationList: [StationDTO] 17 | @Published private var keyword: String 18 | @Published private(set) var searchResults: SearchResults 19 | @Published private(set) var networkError: Error? 20 | private var cancellables: Set 21 | 22 | init(apiUseCase: SearchAPIUsable, calculateUseCase: SearchCalculatable) { 23 | self.apiUseCase = apiUseCase 24 | self.calculateUseCase = calculateUseCase 25 | self.keyword = "" 26 | self.busRouteList = [] 27 | self.stationList = [] 28 | self.searchResults = SearchResults(busSearchResults: [], stationSearchResults: []) 29 | self.cancellables = [] 30 | 31 | self.bind() 32 | } 33 | 34 | func configure(keyword: String) { 35 | self.keyword = keyword 36 | } 37 | 38 | private func bind() { 39 | self.bindBusRouteList() 40 | self.bindStationList() 41 | self.bindKeyword() 42 | } 43 | 44 | private func bindBusRouteList() { 45 | self.apiUseCase.loadBusRouteList() 46 | .catchError { [weak self] error in 47 | self?.networkError = error 48 | } 49 | .sink { [weak self] busRouteList in 50 | self?.busRouteList = busRouteList 51 | } 52 | .store(in: &self.cancellables) 53 | } 54 | 55 | private func bindStationList() { 56 | self.apiUseCase.loadStationList() 57 | .catchError { [weak self] error in 58 | self?.networkError = error 59 | } 60 | .sink { [weak self] stationList in 61 | self?.stationList = stationList 62 | } 63 | .store(in: &self.cancellables) 64 | } 65 | 66 | private func bindKeyword() { 67 | self.$keyword 68 | .debounce(for: .milliseconds(400), scheduler: DispatchQueue.global()) 69 | .sink { [weak self] keyword in 70 | guard let self = self else { return } 71 | 72 | let busSearchResults = self.calculateUseCase.searchBus(by: keyword, at: self.busRouteList) 73 | let stationSearchResults = self.calculateUseCase.searchStation(by: keyword, at: self.stationList) 74 | self.searchResults = SearchResults(busSearchResults: busSearchResults, stationSearchResults: stationSearchResults) 75 | } 76 | .store(in: &self.cancellables) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Station/Model/BusArriveInfos.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BusArriveInfos.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/25. 6 | // 7 | 8 | import Foundation 9 | 10 | typealias BusArriveInfo = (firstBusArriveRemainTime: BusRemainTime?, firstBusRelativePosition: String?, firstBusCongestion: BusCongestion?, secondBusArriveRemainTime: BusRemainTime?, secondBusRelativePosition: String?, secondBusCongestion: BusCongestion?, stationOrd: Int, busRouteId: Int, nextStation: String, busNumber: String, routeType: BBusRouteType) 11 | 12 | struct BusArriveInfos { 13 | 14 | private var infos: [BusArriveInfo] 15 | 16 | subscript(item: Int) -> BusArriveInfo? { 17 | guard 0.. BusArriveInfos { 26 | let newInfos = lhs.infos + rhs.infos 27 | return BusArriveInfos(infos: newInfos) 28 | } 29 | 30 | func descended() -> BusArriveInfos { 31 | let newInfos = self.infos.map { (info) -> BusArriveInfo in 32 | var result = info 33 | result.firstBusArriveRemainTime?.descend() 34 | result.secondBusArriveRemainTime?.descend() 35 | return result 36 | } 37 | return BusArriveInfos(infos: newInfos) 38 | } 39 | 40 | func count() -> Int { 41 | return self.infos.count 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Station/Model/BusCongestion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BusCongestion.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import Foundation 9 | 10 | enum BusCongestion: Int { 11 | case relaxed = 3, normal, confusion, veryCrowded 12 | 13 | func toString() -> String { 14 | switch self { 15 | case .relaxed : return "여유" 16 | case .normal : return "보통" 17 | case .confusion : return "혼잡" 18 | case .veryCrowded : return "매우 혼잡" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Station/Model/BusRemainTime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BusRemainTime.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/16. 6 | // 7 | 8 | import Foundation 9 | 10 | struct BusRemainTime { 11 | var seconds: Int? 12 | let message: String? 13 | 14 | init(arriveRemainTime: String) { 15 | let times = arriveRemainTime.components(separatedBy: ["분", "초"]) 16 | switch times.count { 17 | case 3 : 18 | self.seconds = 60 * (Int(times[0]) ?? 0) + (Int(times[1]) ?? 0) 19 | self.message = nil 20 | case 2: 21 | if arriveRemainTime.contains("분") { 22 | self.seconds = 60 * (Int(times[0]) ?? 0) 23 | } 24 | else { 25 | self.seconds = Int(times[1]) ?? 0 26 | } 27 | self.message = nil 28 | default : 29 | self.seconds = nil 30 | self.message = arriveRemainTime 31 | } 32 | } 33 | 34 | func toString() -> String? { 35 | if let time = self.seconds { 36 | let minutes = time / 60 37 | let seconds = time % 60 38 | return minutes >= 1 ? "\(minutes)분 \(seconds)초" : "\(max(seconds, 0))초" 39 | } 40 | else { 41 | return self.checkInfo() ? self.message : nil 42 | } 43 | } 44 | 45 | func estimateArrivalTime() -> Date? { 46 | guard self.checkInfo() else { return nil } 47 | guard let seconds = self.seconds else { return Date() } 48 | return Date(timeIntervalSinceNow: TimeInterval(seconds)) 49 | } 50 | 51 | func checkInfo() -> Bool { 52 | guard let message = self.message else { return true } 53 | let noInfoMessages = ["운행종료", "출발대기"] 54 | return !noInfoMessages.contains(message) 55 | } 56 | 57 | mutating func descend() { 58 | if let second = self.seconds { 59 | self.seconds = second - 1 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Station/Model/BusSectionKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BusSectionKeys.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/25. 6 | // 7 | 8 | import Foundation 9 | 10 | struct BusSectionKeys { 11 | 12 | private var keys: [BBusRouteType] 13 | 14 | subscript(section: Int) -> BBusRouteType? { 15 | guard 0.. BusSectionKeys { 20 | return Self.init(keys: lhs.keys + rhs.keys) 21 | } 22 | 23 | init(keys: [BBusRouteType] = []) { 24 | self.keys = keys 25 | } 26 | 27 | func count() -> Int { 28 | return self.keys.count 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Station/StationCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationCoordinator.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/03. 6 | // 7 | 8 | import UIKit 9 | 10 | final class StationCoordinator: BusRoutePushable, AlarmSettingPushable { 11 | weak var delegate: CoordinatorDelegate? 12 | var navigationPresenter: UINavigationController 13 | 14 | init(presenter: UINavigationController) { 15 | self.navigationPresenter = presenter 16 | } 17 | 18 | func start(arsId: String) { 19 | let apiUseCases = BBusAPIUseCases(networkService: NetworkService(), 20 | persistenceStorage: PersistenceStorage(), 21 | tokenManageType: TokenManager.self, 22 | requestFactory: RequestFactory()) 23 | let apiUseCase = StationAPIUseCase(useCases: apiUseCases) 24 | let calculateUseCase = StationCalculateUseCase() 25 | let viewModel = StationViewModel(apiUseCase: apiUseCase, 26 | calculateUseCase: calculateUseCase, 27 | arsId: arsId) 28 | let viewController = StationViewController(viewModel: viewModel) 29 | viewController.coordinator = self 30 | self.navigationPresenter.pushViewController(viewController, animated: true) 31 | } 32 | 33 | func terminate() { 34 | self.navigationPresenter.popViewController(animated: true) 35 | self.coordinatorDidFinish() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Station/UseCase/StationAPIUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationAPIUsable.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol StationAPIUsable: BaseUseCase { 12 | func loadStationList() -> AnyPublisher<[StationDTO], Error> 13 | func refreshInfo(about arsId: String) -> AnyPublisher<[StationByUidItemDTO], Error> 14 | func add(favoriteItem: FavoriteItemDTO) -> AnyPublisher 15 | func remove(favoriteItem: FavoriteItemDTO) -> AnyPublisher 16 | func getFavoriteItems() -> AnyPublisher<[FavoriteItemDTO], Error> 17 | func loadRoute() -> AnyPublisher<[BusRouteDTO], Error> 18 | } 19 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Station/UseCase/StationAPIUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationAPIUseCase.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | final class StationAPIUseCase: StationAPIUsable { 12 | typealias StationUseCases = GetStationByUidItemUsable & GetStationListUsable & CreateFavoriteItemUsable & DeleteFavoriteItemUsable & GetFavoriteItemListUsable & GetRouteListUsable 13 | 14 | private let useCases: StationUseCases 15 | private var cancellables: Set 16 | 17 | init(useCases: StationUseCases) { 18 | self.useCases = useCases 19 | self.cancellables = [] 20 | } 21 | 22 | func loadStationList() -> AnyPublisher<[StationDTO], Error> { 23 | self.useCases.getStationList() 24 | .decode(type: [StationDTO].self, decoder: JSONDecoder()) 25 | .eraseToAnyPublisher() 26 | } 27 | 28 | func refreshInfo(about arsId: String) -> AnyPublisher<[StationByUidItemDTO], Error> { 29 | return self.useCases.getStationByUidItem(arsId: arsId) 30 | .decode(type: StationByUidItemResult.self, decoder: JSONDecoder()) 31 | .tryMap({ item in 32 | item.msgBody.itemList 33 | }) 34 | .eraseToAnyPublisher() 35 | } 36 | 37 | func add(favoriteItem: FavoriteItemDTO) -> AnyPublisher { 38 | return self.useCases.createFavoriteItem(param: favoriteItem) 39 | .eraseToAnyPublisher() 40 | } 41 | 42 | func remove(favoriteItem: FavoriteItemDTO) -> AnyPublisher { 43 | return self.useCases.deleteFavoriteItem(param: favoriteItem) 44 | .eraseToAnyPublisher() 45 | } 46 | 47 | func getFavoriteItems() -> AnyPublisher<[FavoriteItemDTO], Error> { 48 | return self.useCases.getFavoriteItemList() 49 | .decode(type: [FavoriteItemDTO].self, decoder: PropertyListDecoder()) 50 | .eraseToAnyPublisher() 51 | } 52 | 53 | func loadRoute() -> AnyPublisher<[BusRouteDTO], Error> { 54 | return self.useCases.getRouteList() 55 | .decode(type: [BusRouteDTO].self, decoder: JSONDecoder()) 56 | .eraseToAnyPublisher() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Station/UseCase/StationCalculatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationCalculatable.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol StationCalculatable: BaseUseCase { 11 | func findStation(in stations: [StationDTO], with arsId: String) -> StationDTO? 12 | } 13 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Station/UseCase/StationCalculateUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationCalculateUseCase.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/30. 6 | // 7 | 8 | import Foundation 9 | 10 | final class StationCalculateUseCase: StationCalculatable { 11 | func findStation(in stations: [StationDTO], with arsId: String) -> StationDTO? { 12 | let station = stations.filter() { $0.arsID == arsId } 13 | return station.first 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /BBus/BBus/Foreground/Station/View/StationHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationHeaderView.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/02. 6 | // 7 | 8 | import UIKit 9 | 10 | final class StationHeaderView: UIView { 11 | 12 | static let headerHeight: CGFloat = 170 13 | 14 | private lazy var stationIdLabel: UILabel = { 15 | let typeLabelFontSize: CGFloat = 15 16 | 17 | let label = UILabel() 18 | label.textColor = BBusColor.white 19 | label.font = UIFont.systemFont(ofSize: typeLabelFontSize) 20 | label.textAlignment = .center 21 | return label 22 | }() 23 | private lazy var stationNameLabel: UILabel = { 24 | let numberLabelFontSize: CGFloat = 22 25 | 26 | let label = UILabel() 27 | label.textColor = BBusColor.white 28 | label.font = UIFont.boldSystemFont(ofSize: numberLabelFontSize) 29 | label.textAlignment = .center 30 | return label 31 | }() 32 | private lazy var directionLabel: UILabel = { 33 | let fromStationLabelFontSize: CGFloat = 15 34 | 35 | let label = UILabel() 36 | label.textColor = BBusColor.white 37 | label.font = UIFont.systemFont(ofSize: fromStationLabelFontSize) 38 | label.textAlignment = .center 39 | return label 40 | }() 41 | 42 | convenience init() { 43 | self.init(frame: CGRect()) 44 | 45 | self.configureLayout() 46 | } 47 | 48 | // MARK: - Configure 49 | func configureLayout() { 50 | let stationIdLabelYaxisMargin: CGFloat = 10 51 | let stationIdLabelBottomMargin: CGFloat = -5 52 | let directionLabelTopMargin: CGFloat = 7 53 | 54 | self.addSubviews(self.stationNameLabel, self.stationIdLabel, self.directionLabel) 55 | 56 | NSLayoutConstraint.activate([ 57 | self.stationNameLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor), 58 | self.stationNameLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: stationIdLabelYaxisMargin) 59 | ]) 60 | 61 | NSLayoutConstraint.activate([ 62 | self.stationIdLabel.centerXAnchor.constraint(equalTo: self.stationNameLabel.centerXAnchor), 63 | self.stationIdLabel.bottomAnchor.constraint(equalTo: self.stationNameLabel.topAnchor, constant: stationIdLabelBottomMargin) 64 | ]) 65 | 66 | NSLayoutConstraint.activate([ 67 | self.directionLabel.centerXAnchor.constraint(equalTo: self.stationNameLabel.centerXAnchor), 68 | self.directionLabel.topAnchor.constraint(equalTo: self.stationNameLabel.bottomAnchor, constant: directionLabelTopMargin) 69 | ]) 70 | } 71 | 72 | func configureStationInfo(stationId: String, stationName: String) { 73 | self.stationIdLabel.text = stationId 74 | self.stationNameLabel.text = stationName 75 | } 76 | 77 | func configure(nextStationName: String) { 78 | let suffix = "방면" 79 | self.directionLabel.text = "\(nextStationName) \(suffix)" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Base/BaseUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseUseCase.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol BaseUseCase { } 11 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Base/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/29. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol BaseViewControllerType: UIViewController { 11 | 12 | func viewDidLoad() 13 | 14 | // MARK: configure 15 | func configureLayout() 16 | func configureDelegate() 17 | func refresh() 18 | 19 | // MARK: bind 20 | func bindAll() 21 | } 22 | 23 | extension BaseViewControllerType { 24 | func baseViewDidLoad() { 25 | self.configureLayout() 26 | self.configureDelegate() 27 | self.bindAll() 28 | } 29 | 30 | func baseViewWillAppear() { 31 | self.refresh() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Constant/BBusColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BBusColor.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/09. 6 | // 7 | 8 | import UIKit 9 | 10 | enum BBusColor { 11 | static let gray = UIColor.gray 12 | static let white = UIColor.white 13 | static let black = UIColor.black 14 | static let red = UIColor.red 15 | static let clear = UIColor.clear 16 | static let green = UIColor.green 17 | static let yellow = UIColor.yellow 18 | static let systemGray = UIColor.systemGray 19 | static let systemGray5 = UIColor.systemGray5 20 | static let bbusGray6 = UIColor(named: "bbusGray6") 21 | static let darkGray = UIColor.darkGray 22 | static let bbusBackground = UIColor(named: "bbusBackground") 23 | static let bbusLightGray = UIColor(named: "bbusLightGray") 24 | static let bbusGray = UIColor(named: "bbusGray") 25 | static let bbusTypeBlue = UIColor(named: "bbusTypeBlue") 26 | static let bbusTypeRed = UIColor(named: "bbusTypeRed") 27 | static let bbusTypeGreen = UIColor(named: "bbusTypeGreen") 28 | static let bbusTypeCirculation = UIColor(named: "bbusTypeCirculation") 29 | static let bbusSearchRed = UIColor(named: "bbusSearchRed") 30 | static let bbusCongestionRed = UIColor(named: "bbusCongestionRed") 31 | static let bbusLikeYellow = UIColor(named: "bbusLikeYellow") 32 | static let iconColor = UIColor(named: "bbusAlarmGray") 33 | static let alarmTint = UIColor(named: "bbusGray") 34 | static let bbusCongestionHigh = UIColor(named: "bbusCongestionHigh") 35 | static let bbusCongestionMedium = UIColor(named: "bbusCongestionMedium") 36 | static let bbusCongestionLow = UIColor(named: "bbusCongestionLow") 37 | } 38 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Constant/BBusRouteType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BBusRouteType.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/09. 6 | // 7 | 8 | import Foundation 9 | 10 | enum BBusRouteType: Int { 11 | case shared = 0, airport, town, gansun, jisun, circular, wideArea, incheon, gyeonggi, closed, lateNight 12 | 13 | func toString() -> String { 14 | let common = "버스" 15 | switch self { 16 | case .shared: return "공용" + common 17 | case .airport: return "공항" + common 18 | case .town: return "마을" + common 19 | case .gansun: return "간선" + common 20 | case .jisun: return "지선" + common 21 | case .circular: return "순환" + common 22 | case .wideArea: return "광역" + common 23 | case .incheon: return "인천" + common 24 | case .gyeonggi: return "경기" + common 25 | case .closed: return "폐지" + common 26 | case .lateNight: return "심야" + common 27 | } 28 | } 29 | 30 | func toRouteType() -> RouteType? { 31 | switch self { 32 | case .shared: return RouteType.customized 33 | case .airport: return RouteType.airport 34 | case .town: return RouteType.town 35 | case .gansun: return RouteType.mainLine 36 | case .jisun: return RouteType.localLine 37 | case .circular: return RouteType.circulation 38 | case .wideArea: return RouteType.broadArea 39 | case .incheon: return nil 40 | case .gyeonggi: return nil 41 | case .closed: return nil 42 | case .lateNight: return RouteType.lateNight 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Coordinator/Coordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coordinator.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/02. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol CoordinatorFinishDelegate: AnyObject { 11 | func coordinatorDidFinish() 12 | func removeChildCoordinator(_ coordinator: Coordinator) 13 | } 14 | 15 | protocol CoordinatorCreateDelegate: AnyObject { 16 | func pushSearch() 17 | func pushBusRoute(busRouteId: Int) 18 | func pushAlarmSetting(stationId: Int, busRouteId: Int, stationOrd: Int, arsId: String, routeType: RouteType?, busName: String) 19 | func pushStation(arsId: String) 20 | } 21 | 22 | protocol AlertCreateToNavigationDelegate: AnyObject { 23 | func presentAlertToNavigation(controller: UIAlertController, completion: (() -> Void)?) 24 | } 25 | 26 | protocol AlertCreateToMovingStatusDelegate: AnyObject { 27 | func presentAlertToMovingStatus(controller: UIAlertController, completion: (() -> Void)?) 28 | } 29 | 30 | typealias CoordinatorDelegate = (CoordinatorFinishDelegate & CoordinatorCreateDelegate & AlertCreateToNavigationDelegate) 31 | 32 | protocol Coordinator: AnyObject { 33 | var navigationPresenter: UINavigationController { get set } 34 | var delegate: CoordinatorDelegate? { get set } 35 | } 36 | 37 | extension Coordinator { 38 | func coordinatorDidFinish() { 39 | self.delegate?.removeChildCoordinator(self) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Coordinator/Pushables.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pushables.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/09. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | protocol BusRoutePushable: Coordinator { 12 | func pushToBusRoute(busRouteId: Int) 13 | } 14 | 15 | extension BusRoutePushable { 16 | func pushToBusRoute(busRouteId: Int) { 17 | self.delegate?.pushBusRoute(busRouteId: busRouteId) 18 | } 19 | } 20 | 21 | protocol SearchPushable: Coordinator { 22 | func pushToSearch() 23 | } 24 | 25 | extension SearchPushable { 26 | func pushToSearch() { 27 | self.delegate?.pushSearch() 28 | } 29 | } 30 | 31 | protocol AlarmSettingPushable: Coordinator { 32 | func pushToAlarmSetting(stationId: Int, busRouteId: Int, stationOrd: Int, arsId: String, routeType: RouteType?, busName: String) 33 | } 34 | 35 | extension AlarmSettingPushable { 36 | func pushToAlarmSetting(stationId: Int, busRouteId: Int, stationOrd: Int, arsId: String, routeType: RouteType?, busName: String) { 37 | self.delegate?.pushAlarmSetting(stationId: stationId, 38 | busRouteId: busRouteId, 39 | stationOrd: stationOrd, 40 | arsId: arsId, 41 | routeType: routeType, 42 | busName: busName) 43 | } 44 | } 45 | 46 | protocol StationPushable: Coordinator { 47 | func pushToStation(arsId: String) 48 | } 49 | 50 | extension StationPushable { 51 | func pushToStation(arsId: String) { 52 | self.delegate?.pushStation(arsId: arsId) 53 | } 54 | } 55 | 56 | protocol AlertPresentable: Coordinator { 57 | func presentAlert(controller: UIAlertController, completion: (() -> Void)?) 58 | } 59 | 60 | extension AlertPresentable { 61 | func presentAlert(controller: UIAlertController, completion: (() -> Void)? = nil) { 62 | self.delegate?.presentAlertToNavigation(controller: controller, completion: completion) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /BBus/BBus/Global/CustomView/NavigatableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigatableView.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/30. 6 | // 7 | 8 | import UIKit 9 | 10 | class NavigatableView: RefreshableView { 11 | 12 | lazy var navigationBar = CustomNavigationBar() 13 | 14 | override func configureLayout() { 15 | self.addSubviews(self.navigationBar) 16 | 17 | NSLayoutConstraint.activate([ 18 | self.navigationBar.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor), 19 | self.navigationBar.leadingAnchor.constraint(equalTo: self.leadingAnchor), 20 | self.navigationBar.trailingAnchor.constraint(equalTo: self.trailingAnchor) 21 | ]) 22 | 23 | super.configureLayout() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BBus/BBus/Global/CustomView/RefreshButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshButton.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/29. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol RefreshButtonDelegate: AnyObject { 11 | func buttonTapped() 12 | } 13 | 14 | final class RefreshButton: ThrottleButton { 15 | 16 | static let refreshButtonWidth: CGFloat = 50 17 | private weak var delegate: RefreshButtonDelegate? { 18 | didSet { 19 | self.addTouchUpEventWithThrottle(delay: ThrottleButton.refreshInterval) { 20 | self.delegate?.buttonTapped() 21 | } 22 | } 23 | } 24 | 25 | convenience init() { 26 | self.init(frame: CGRect()) 27 | self.configureUI() 28 | } 29 | 30 | private func configureUI() { 31 | self.setImage(BBusImage.refresh, for: .normal) 32 | self.layer.cornerRadius = Self.refreshButtonWidth / 2 33 | self.tintColor = BBusColor.white 34 | self.backgroundColor = BBusColor.darkGray 35 | } 36 | 37 | func configureDelegate(_ delegate: RefreshButtonDelegate) { 38 | self.delegate = delegate 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /BBus/BBus/Global/CustomView/RefreshableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshableView.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/29. 6 | // 7 | 8 | import UIKit 9 | 10 | class RefreshableView: UIView { 11 | 12 | lazy var refreshButton = RefreshButton() 13 | 14 | func configureLayout() { 15 | self.addSubviews(self.refreshButton) 16 | 17 | let refreshTrailingBottomInterval: CGFloat = -16 18 | NSLayoutConstraint.activate([ 19 | self.refreshButton.widthAnchor.constraint(equalToConstant: RefreshButton.refreshButtonWidth), 20 | self.refreshButton.heightAnchor.constraint(equalTo: self.refreshButton.widthAnchor), 21 | self.refreshButton.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: refreshTrailingBottomInterval), 22 | self.refreshButton.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: refreshTrailingBottomInterval) 23 | ]) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BBus/BBus/Global/CustomView/ThrottleButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThrottleButton.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/17. 6 | // 7 | 8 | import UIKit 9 | 10 | class ThrottleButton: UIButton { 11 | 12 | static let refreshInterval: Double = 3 13 | 14 | private var workItem: DispatchWorkItem? 15 | private var delay: Double = 0 16 | private var callback: (() -> Void)? 17 | 18 | func addTouchUpEventWithThrottle(delay: Double, callback: @escaping (() -> Void)) { 19 | self.delay = delay 20 | self.callback = callback 21 | 22 | self.addTarget(self, action: #selector(self.touchUpInside(_:)), for: .touchUpInside) 23 | } 24 | 25 | @objc private func touchUpInside(_ sender: UIButton) { 26 | if self.workItem == nil { 27 | self.callback?() 28 | 29 | let workItem = DispatchWorkItem(block: { [weak self] in 30 | self?.workItem?.cancel() 31 | self?.workItem = nil 32 | }) 33 | self.workItem = workItem 34 | 35 | DispatchQueue.global().asyncAfter(deadline: .now() + self.delay, execute: workItem) 36 | } 37 | } 38 | 39 | deinit { 40 | self.removeTarget(self, action: #selector(self.touchUpInside(_:)), for: .touchUpInside) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /BBus/BBus/Global/DTO/ArrInfoByRouteDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArrInfoByRouteDTO.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/11. 6 | // 7 | 8 | import Foundation 9 | 10 | // JSON 11 | struct ArrInfoByRouteResult: Codable { 12 | let msgHeader: MessageHeader 13 | let msgBody: ArrInfoByRouteBody 14 | } 15 | 16 | struct ArrInfoByRouteBody: Codable { 17 | let itemList: [ArrInfoByRouteDTO] 18 | } 19 | 20 | struct ArrInfoByRouteDTO: Codable { 21 | let firstBusArriveRemainTime: String 22 | let secondBusArriveRemainTime: String 23 | let firstBusCongestion: Int 24 | let secondBusCongestion: Int 25 | let firstBusCurrentStation: String 26 | let secondBusCurrentStation: String 27 | let firstBusPlainNumber: String 28 | let secondBusPlainNumber: String 29 | let firstBusVehicleId: Int 30 | let secondBusVehicleId: Int 31 | 32 | enum CodingKeys: String, CodingKey { 33 | case firstBusArriveRemainTime = "arrmsg1" 34 | case secondBusArriveRemainTime = "arrmsg2" 35 | case firstBusCongestion = "reride_Num1" 36 | case secondBusCongestion = "reride_Num2" 37 | case firstBusCurrentStation = "stationNm1" 38 | case secondBusCurrentStation = "stationNm2" 39 | case firstBusPlainNumber = "plainNo1" 40 | case secondBusPlainNumber = "plainNo2" 41 | case firstBusVehicleId = "vehId1" 42 | case secondBusVehicleId = "vehId2" 43 | } 44 | 45 | init(from decoder: Decoder) throws { 46 | let container = try decoder.container(keyedBy: CodingKeys.self) 47 | self.firstBusArriveRemainTime = (try? container.decode(String.self, forKey: .firstBusArriveRemainTime)) ?? "" 48 | self.secondBusArriveRemainTime = (try? container.decode(String.self, forKey: .secondBusArriveRemainTime)) ?? "" 49 | self.firstBusCongestion = Int((try? container.decode(String.self, forKey: .firstBusCongestion)) ?? "") ?? 0 50 | self.secondBusCongestion = Int((try? container.decode(String.self, forKey: .secondBusCongestion)) ?? "") ?? 0 51 | self.firstBusCurrentStation = (try? container.decode(String.self, forKey: .firstBusCurrentStation)) ?? "" 52 | self.secondBusCurrentStation = (try? container.decode(String.self, forKey: .secondBusCurrentStation)) ?? "" 53 | self.firstBusPlainNumber = (try? container.decode(String.self, forKey: .firstBusPlainNumber)) ?? "" 54 | self.secondBusPlainNumber = (try? container.decode(String.self, forKey: .secondBusPlainNumber)) ?? "" 55 | self.firstBusVehicleId = Int((try? container.decode(String.self, forKey: .firstBusVehicleId)) ?? "") ?? 0 56 | self.secondBusVehicleId = Int((try? container.decode(String.self, forKey: .secondBusVehicleId)) ?? "") ?? 0 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /BBus/BBus/Global/DTO/BusPosByRtidDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BusPosByRtidDTO.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | 10 | // JSON 11 | struct BusPosByRtidResult: Codable { 12 | let msgHeader: MessageHeader 13 | let msgBody: BusPosByRtidBody 14 | } 15 | 16 | struct BusPosByRtidBody: Codable { 17 | let itemList: [BusPosByRtidDTO] 18 | } 19 | 20 | struct BusPosByRtidDTO: Codable { 21 | let busType: Int 22 | let congestion: Int 23 | let plainNumber: String 24 | let sectionOrder: Int 25 | let fullSectDist: String 26 | let sectDist: String 27 | let gpsY: Double 28 | let gpsX: Double 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case busType 32 | case congestion 33 | case plainNumber = "plainNo" 34 | case sectionOrder = "sectOrd" 35 | case fullSectDist 36 | case sectDist 37 | case gpsY 38 | case gpsX 39 | } 40 | 41 | init(from decoder: Decoder) throws { 42 | let container = try decoder.container(keyedBy: CodingKeys.self) 43 | self.busType = Int((try? container.decode(String.self, forKey: .busType)) ?? "") ?? 0 44 | self.congestion = Int((try? container.decode(String.self, forKey: .congestion)) ?? "") ?? 0 45 | self.plainNumber = (try? container.decode(String.self, forKey: .plainNumber)) ?? "" 46 | self.sectionOrder = Int((try? container.decode(String.self, forKey: .sectionOrder)) ?? "") ?? 0 47 | self.fullSectDist = (try? container.decode(String.self, forKey: .fullSectDist)) ?? "" 48 | self.sectDist = (try? container.decode(String.self, forKey: .sectDist)) ?? "" 49 | self.gpsY = Double((try? container.decode(String.self, forKey: .gpsY)) ?? "") ?? 0 50 | self.gpsX = Double((try? container.decode(String.self, forKey: .gpsX)) ?? "") ?? 0 51 | } 52 | 53 | init(busType: Int, congestion: Int, plainNumber: String, sectionOrder: Int, fullSectDist: String, sectDist: String, gpsY: Double, gpsX: Double) { 54 | self.busType = busType 55 | self.congestion = congestion 56 | self.plainNumber = plainNumber 57 | self.sectionOrder = sectionOrder 58 | self.fullSectDist = fullSectDist 59 | self.sectDist = sectDist 60 | self.gpsY = gpsY 61 | self.gpsX = gpsX 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /BBus/BBus/Global/DTO/BusPosByVehicleIdDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BusPosByVehicleId.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct BusPosByVehicleIdDTO: Codable { 11 | 12 | let stationOrd: String 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case stationOrd = "stOrd" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /BBus/BBus/Global/DTO/BusRouteDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BusRouteDTO.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | 10 | struct BusRouteDTO: Codable { 11 | let routeID: Int 12 | let busRouteName: String 13 | let routeType: RouteType 14 | let startStation: String 15 | let endStation: String 16 | } 17 | 18 | enum RouteType: String, Codable { 19 | case mainLine = "간선" 20 | case broadArea = "광역" 21 | case customized = "맞춤" 22 | case circulation = "순환" 23 | case lateNight = "심야" 24 | case localLine = "지선" 25 | case airport = "공항" 26 | case town = "마을" 27 | } 28 | -------------------------------------------------------------------------------- /BBus/BBus/Global/DTO/FavoriteItemDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavoriteItemDTO.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/14. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FavoriteItemDTO: Codable, Equatable { 11 | let stId: String 12 | let busRouteId: String 13 | let ord: String 14 | let arsId: String 15 | } 16 | -------------------------------------------------------------------------------- /BBus/BBus/Global/DTO/JsonDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JsonDTO.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | 10 | struct JsonHeader: Codable { 11 | let msgHeader: MessageHeader 12 | } 13 | 14 | struct JsonMessage: Codable { 15 | let msgHeader: MessageHeader 16 | let msgBody: MessageBody 17 | } 18 | 19 | struct MessageHeader: Codable { 20 | let headerMessage, headerCD: String 21 | let itemCount: Int 22 | 23 | enum CodingKeys: String, CodingKey { 24 | case headerMessage = "headerMsg" 25 | case headerCD = "headerCd" 26 | case itemCount 27 | } 28 | } 29 | 30 | struct MessageBody: Codable { 31 | let itemList: [T] 32 | } 33 | -------------------------------------------------------------------------------- /BBus/BBus/Global/DTO/StationByRouteListDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationByRouteListDTO.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | 10 | // JSON 11 | struct StationByRouteResult: Codable { 12 | let msgHeader: MessageHeader 13 | let msgBody: StationByRouteBody 14 | } 15 | 16 | struct StationByRouteBody: Codable { 17 | let itemList: [StationByRouteListDTO] 18 | } 19 | 20 | struct StationByRouteListDTO: Codable { 21 | let sectionSpeed: Int 22 | let sequence: Int 23 | let stationName: String 24 | let fullSectionDistance: Int 25 | let arsId: String 26 | let beginTm: String 27 | let lastTm: String 28 | let transYn: String 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case sectionSpeed = "sectSpd" 32 | case sequence = "seq" 33 | case stationName = "stationNm" 34 | case fullSectionDistance = "fullSectDist" 35 | case arsId = "arsId" 36 | case beginTm = "beginTm" 37 | case lastTm = "lastTm" 38 | case transYn = "transYn" 39 | } 40 | 41 | init(from decoder: Decoder) throws { 42 | let container = try decoder.container(keyedBy: CodingKeys.self) 43 | self.sectionSpeed = Int((try? container.decode(String.self, forKey: .sectionSpeed)) ?? "") ?? 0 44 | self.sequence = Int((try? container.decode(String.self, forKey: .sequence)) ?? "") ?? 0 45 | self.stationName = (try? container.decode(String.self, forKey: .stationName)) ?? "" 46 | self.fullSectionDistance = Int((try? container.decode(String.self, forKey: .fullSectionDistance)) ?? "") ?? 0 47 | self.arsId = (try? container.decode(String.self, forKey: .arsId)) ?? "" 48 | self.beginTm = (try? container.decode(String.self, forKey: .beginTm)) ?? "" 49 | self.lastTm = (try? container.decode(String.self, forKey: .lastTm)) ?? "" 50 | self.transYn = (try? container.decode(String.self, forKey: .transYn)) ?? "" 51 | } 52 | 53 | init(sectionSpeed: Int, sequence: Int, stationName: String, fullSectionDistance: Int, arsId: String, beginTm: String, lastTm: String, transYn: String) { 54 | self.sectionSpeed = sectionSpeed 55 | self.sequence = sequence 56 | self.stationName = stationName 57 | self.fullSectionDistance = fullSectionDistance 58 | self.arsId = arsId 59 | self.beginTm = beginTm 60 | self.lastTm = lastTm 61 | self.transYn = transYn 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /BBus/BBus/Global/DTO/StationByUidItemDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationByUidItemDTO.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | 10 | // JSON 11 | struct StationByUidItemResult: Codable { 12 | let msgHeader: MessageHeader 13 | let msgBody: StationByUidItemBody 14 | } 15 | 16 | struct StationByUidItemBody: Codable { 17 | let itemList: [StationByUidItemDTO] 18 | } 19 | 20 | struct StationByUidItemDTO: Codable { 21 | let firstBusArriveRemainTime: String 22 | let secondBusArriveRemainTime: String 23 | let arsId: String 24 | let stationOrd: Int 25 | let busRouteId: Int 26 | let congestion: Int 27 | let nextStation: String 28 | let busNumber: String 29 | let routeType: String 30 | 31 | enum CodingKeys: String, CodingKey { 32 | case firstBusArriveRemainTime = "arrmsg1" 33 | case secondBusArriveRemainTime = "arrmsg2" 34 | case arsId 35 | case stationOrd = "staOrd" 36 | case busRouteId 37 | case congestion 38 | case nextStation = "nxtStn" 39 | case busNumber = "rtNm" 40 | case routeType 41 | } 42 | 43 | init(from decoder: Decoder) throws { 44 | let container = try decoder.container(keyedBy: CodingKeys.self) 45 | self.firstBusArriveRemainTime = (try? container.decode(String.self, forKey: .firstBusArriveRemainTime)) ?? "" 46 | self.secondBusArriveRemainTime = (try? container.decode(String.self, forKey: .secondBusArriveRemainTime)) ?? "" 47 | self.arsId = (try? container.decode(String.self, forKey: .arsId)) ?? "" 48 | self.stationOrd = Int((try? container.decode(String.self, forKey: .stationOrd)) ?? "") ?? 0 49 | self.busRouteId = Int((try? container.decode(String.self, forKey: .busRouteId)) ?? "") ?? 0 50 | self.congestion = Int((try? container.decode(String.self, forKey: .congestion)) ?? "") ?? 0 51 | self.nextStation = (try? container.decode(String.self, forKey: .nextStation)) ?? "" 52 | self.busNumber = (try? container.decode(String.self, forKey: .busNumber)) ?? "" 53 | self.routeType = (try? container.decode(String.self, forKey: .routeType)) ?? "" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /BBus/BBus/Global/DTO/StationDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationDTO.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | 10 | struct StationDTO: Codable { 11 | let stationID: Int 12 | let arsID: String 13 | let stationName: String 14 | } 15 | -------------------------------------------------------------------------------- /BBus/BBus/Global/DeviceConfig/AlarmCenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionManager.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/30. 6 | // 7 | 8 | import CoreLocation 9 | import UserNotifications 10 | import UIKit 11 | 12 | protocol AlarmManagable { 13 | func configurePermission() 14 | func pushAlarm(in identifier: String, title: String, message: String) 15 | } 16 | 17 | protocol AlarmDetailConfigurable: AlarmManagable { 18 | func configureLocationDetail(_: CLLocationManagerDelegate) 19 | } 20 | 21 | final class AlarmCenter: AlarmDetailConfigurable { 22 | 23 | private lazy var locationManager: CLLocationManager = CLLocationManager() 24 | private lazy var userNotificationCenter: UNUserNotificationCenter = UNUserNotificationCenter.current() 25 | 26 | init() {} 27 | 28 | func configurePermission() { 29 | self.configureLocationManager() 30 | self.configureUserNotificationCenter() 31 | } 32 | 33 | func configureLocationDetail(_ delegate: CLLocationManagerDelegate) { 34 | self.locationManager.desiredAccuracy = kCLLocationAccuracyBest 35 | self.locationManager.delegate = delegate 36 | } 37 | 38 | private func configureLocationManager() { 39 | self.locationManager.requestAlwaysAuthorization() 40 | self.locationManager.allowsBackgroundLocationUpdates = true 41 | self.locationManager.startUpdatingLocation() 42 | self.locationUnauthorizedHandler(self.locationManager.authorizationStatus) 43 | } 44 | 45 | private func configureUserNotificationCenter() { 46 | self.userNotificationCenter.requestAuthorization(options: [.alert, .sound, .badge], completionHandler: { [weak self] didAllow, error in 47 | guard didAllow == false else { return } 48 | self?.openSetting() 49 | }) 50 | } 51 | 52 | func pushAlarm(in identifier: String, title: String, message: String) { 53 | let content = UNMutableNotificationContent() 54 | content.title = title 55 | content.body = message 56 | content.badge = Int(truncating: content.badge ?? 0) + 1 as NSNumber 57 | 58 | let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil) 59 | 60 | self.userNotificationCenter.add(request, withCompletionHandler: nil) 61 | } 62 | 63 | private func openSetting() { 64 | guard let settingUrl = URL(string: UIApplication.openSettingsURLString) else { return } 65 | DispatchQueue.main.async { 66 | UIApplication.shared.open(settingUrl) 67 | } 68 | } 69 | 70 | private func locationUnauthorizedHandler(_ status: CLAuthorizationStatus) { 71 | guard status == .denied else { return } 72 | self.openSetting() 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Extension/NotificationNameExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationNameExtension.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/16. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Notification.Name { 11 | static let oneSecondPassed = Self.init(rawValue: "oneSecondPassed") 12 | static let thirtySecondPassed = Self.init(rawValue: "thirtySecondPassed") 13 | static let fifteenSecondsPassed = Self.init(rawValue: "fifteenSecondsPassed") 14 | } 15 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Extension/PublisherExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PublisherExtension.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/18. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | extension Publisher where Output == Data, Failure == Error { 12 | func mapJsonBBusAPIError(with removeAccessKeyHandler: @escaping () -> Void ) -> AnyPublisher { 13 | self.tryMap({ data -> Data in 14 | guard let json = try? JSONDecoder().decode(JsonHeader.self, from: data), 15 | let statusCode = Int(json.msgHeader.headerCD), 16 | let error = BBusAPIError(errorCode: statusCode) else { return data } 17 | switch error { 18 | case .noneAccessKeyError, .noneRegisteredKeyError, .suspendedKeyError, .exceededKeyError: 19 | removeAccessKeyHandler() 20 | default: 21 | break 22 | } 23 | throw error 24 | }).eraseToAnyPublisher() 25 | } 26 | } 27 | 28 | extension Publisher where Failure == Error { 29 | func catchError(_ handler: @escaping (Error) -> Void) -> AnyPublisher { 30 | self.catch({ error -> AnyPublisher in 31 | handler(error) 32 | let publisher = PassthroughSubject() 33 | DispatchQueue.global().async { 34 | publisher.send(completion: .finished) 35 | } 36 | return publisher.eraseToAnyPublisher() 37 | }).eraseToAnyPublisher() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Extension/StringExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringExtension.swift 3 | // BBus 4 | // 5 | // Created by 최수정 on 2021/11/11. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | func ranges(of substring: String) -> [Range] { 12 | var ranges = [Range]() 13 | var lowerBound = self.startIndex 14 | let upperBound = self.endIndex 15 | 16 | while let range = self.range(of: substring, 17 | options: [], 18 | range: lowerBound.. String { 27 | var result = "" 28 | for character in self { 29 | if character.isNumber { 30 | result += String(character) 31 | } 32 | else { 33 | break 34 | } 35 | } 36 | return result 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Extension/UINavigationControllerExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationControllerExtension.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/18. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UINavigationController: ObservableObject, UIGestureRecognizerDelegate { 11 | override open func viewDidLoad() { 12 | super.viewDidLoad() 13 | navigationBar.isHidden = true 14 | interactivePopGestureRecognizer?.delegate = self 15 | } 16 | 17 | public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 18 | return viewControllers.count > 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Extension/UIViewExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewExtension.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/16. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | extension UIView { 12 | func addSubviews(_ views : UIView...) { 13 | views.forEach() { view in 14 | self.addSubview(view) 15 | view.translatesAutoresizingMaskIntoConstraints = false 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/AccessKey.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // StationInfoAccessKey.xcconfig 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/10. 6 | // 7 | 8 | // Configuration settings file format documentation can be found at: 9 | // https://help.apple.com/xcode/#/dev745c5c974 10 | 11 | API_ACCESS_KEY2 = aaa 12 | API_ACCESS_KEY = aaa 13 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BBusAPIError.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/18. 6 | // 7 | 8 | import Foundation 9 | 10 | enum BBusAPIError: Error { 11 | case systemError, noneParamError, wrongParamError, noneResultError, noneAccessKeyError, noneRegisteredKeyError, suspendedKeyError, exceededKeyError, wrongRequestError, wrongRouteIdError, wrongStationError, noneBusArriveInfoError, wrongStartStationIdError, wrongEndStationIdError, preparingAPIError, wrongFormatError, noMoreAccessKeyError, 12 | trafficExceed, invalidStationError 13 | 14 | init?(errorCode: Int) { 15 | switch errorCode { 16 | case 1: 17 | self = Self.systemError 18 | case 2: 19 | self = Self.noneParamError 20 | case 3: 21 | self = Self.wrongParamError 22 | case 4: 23 | self = Self.noneResultError 24 | case 5: 25 | self = Self.noneAccessKeyError 26 | case 6: 27 | self = Self.noneRegisteredKeyError 28 | case 7: 29 | self = Self.suspendedKeyError 30 | case 8: 31 | self = Self.exceededKeyError 32 | case 20: 33 | self = Self.wrongRequestError 34 | case 21: 35 | self = Self.wrongRouteIdError 36 | case 22: 37 | self = Self.wrongStationError 38 | case 23: 39 | self = Self.noneBusArriveInfoError 40 | case 31: 41 | self = Self.wrongStartStationIdError 42 | case 32: 43 | self = Self.wrongEndStationIdError 44 | case 99: 45 | self = Self.preparingAPIError 46 | default : 47 | return nil 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIUseCases/BBusAPIUseCases.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BBusAPIUsecases.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | struct BBusAPIUseCases { 12 | let tokenManageType: TokenManagable.Type 13 | 14 | let networkService: NetworkServiceProtocol 15 | let persistenceStorage: PersistenceStorageProtocol 16 | let requestFactory: Requestable 17 | 18 | init(networkService: NetworkServiceProtocol, persistenceStorage: PersistenceStorageProtocol, tokenManageType: TokenManagable.Type, requestFactory: Requestable) { 19 | self.networkService = networkService 20 | self.persistenceStorage = persistenceStorage 21 | self.tokenManageType = tokenManageType 22 | self.requestFactory = requestFactory 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIUseCases/CreateFavoriteItemUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateFavoriteItemUseCase.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | extension BBusAPIUseCases: CreateFavoriteItemUsable { 12 | func createFavoriteItem(param: FavoriteItemDTO) -> AnyPublisher { 13 | let fetcher: CreateFavoriteItemFetchable = PersistenceCreateFavoriteItemFetcher(persistenceStorage: self.persistenceStorage) 14 | return fetcher 15 | .fetch(param: param) 16 | .tryCatch({ error -> AnyPublisher in 17 | return fetcher 18 | .fetch(param: param) 19 | }) 20 | .retry(TokenManager.maxTokenCount) 21 | .eraseToAnyPublisher() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIUseCases/DeleteFavoriteItemUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteFavoriteItemUseCase.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | extension BBusAPIUseCases: DeleteFavoriteItemUsable { 12 | func deleteFavoriteItem(param: FavoriteItemDTO) -> AnyPublisher { 13 | let fetcher: DeleteFavoriteItemFetchable = PersistenceDeleteFavoriteItemFetcher(persistenceStorage: self.persistenceStorage) 14 | return fetcher 15 | .fetch(param: param) 16 | .tryCatch({ error -> AnyPublisher in 17 | return fetcher 18 | .fetch(param: param) 19 | }) 20 | .retry(TokenManager.maxTokenCount) 21 | .eraseToAnyPublisher() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIUseCases/GetArrInfoByRouteListUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetArrInfoByRouteListUseCase.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | extension BBusAPIUseCases: GetArrInfoByRouteListUsable { 12 | func getArrInfoByRouteList(stId: String, busRouteId: String, ord: String) -> AnyPublisher { 13 | let param = ["stId": stId, "busRouteId": busRouteId, "ord": ord, "resultType": "json"] 14 | let fetcher: GetArrInfoByRouteListFetchable = ServiceGetArrInfoByRouteListFetcher(networkService: self.networkService, 15 | tokenManager: self.tokenManageType.init(), 16 | requestFactory: self.requestFactory) 17 | return fetcher 18 | .fetch(param: param) 19 | .tryCatch({ error -> AnyPublisher in 20 | return fetcher 21 | .fetch(param: param) 22 | }) 23 | .retry(TokenManager.maxTokenCount) 24 | .eraseToAnyPublisher() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIUseCases/GetBusPosByRtidUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetBusPosByRtidUseCase.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | extension BBusAPIUseCases: GetBusPosByRtidUsable { 12 | func getBusPosByRtid(busRoutedId: String) -> AnyPublisher { 13 | let param = ["busRouteId": busRoutedId, "resultType": "json"] 14 | let fetcher: GetBusPosByRtidFetchable = ServiceGetBusPosByRtidFetcher(networkService: self.networkService, 15 | tokenManager: TokenManager(), 16 | requestFactory: self.requestFactory) 17 | return fetcher 18 | .fetch(param: param) 19 | .tryCatch({ error -> AnyPublisher in 20 | return fetcher 21 | .fetch(param: param) 22 | }) 23 | .retry(TokenManager.maxTokenCount) 24 | .eraseToAnyPublisher() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIUseCases/GetBusPosByVehIdUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetBusPosByVehIdUseCase.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | extension BBusAPIUseCases: GetBusPosByVehIdUsable { 12 | func getBusPosByVehId(_ vehId: String) -> AnyPublisher { 13 | let param = ["vehId": vehId, "resultType": "json"] 14 | let fetcher: GetBusPosByVehIdFetchable = ServiceGetBusPosByVehIdFetcher(networkService: self.networkService, 15 | tokenManager: TokenManager(), 16 | requestFactory: self.requestFactory) 17 | return fetcher 18 | .fetch(param: param) 19 | .tryCatch({ error -> AnyPublisher in 20 | return fetcher 21 | .fetch(param: param) 22 | }) 23 | .retry(TokenManager.maxTokenCount) 24 | .eraseToAnyPublisher() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIUseCases/GetFavoriteItemListUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetFavoriteItemListUseCase.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | extension BBusAPIUseCases: GetFavoriteItemListUsable { 12 | func getFavoriteItemList() -> AnyPublisher { 13 | let fetcher: GetFavoriteItemListFetchable = PersistenceGetFavoriteItemListFetcher(persistenceStorage: self.persistenceStorage) 14 | return fetcher 15 | .fetch() 16 | .tryCatch({ error -> AnyPublisher in 17 | return fetcher 18 | .fetch() 19 | }) 20 | .retry(TokenManager.maxTokenCount) 21 | .eraseToAnyPublisher() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIUseCases/GetRouteListUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetRouteListUseCase.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | extension BBusAPIUseCases: GetRouteListUsable { 12 | func getRouteList() -> AnyPublisher { 13 | let fetcher: GetRouteListFetchable = PersistenceGetRouteListFetcher(persistenceStorage: self.persistenceStorage) 14 | return fetcher 15 | .fetch() 16 | .tryCatch({ error -> AnyPublisher in 17 | return fetcher 18 | .fetch() 19 | }) 20 | .retry(TokenManager.maxTokenCount) 21 | .eraseToAnyPublisher() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIUseCases/GetStationByUidItemUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetStationByUidItemUseCase.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | extension BBusAPIUseCases: GetStationByUidItemUsable { 12 | func getStationByUidItem(arsId: String) -> AnyPublisher { 13 | let param = ["arsId": arsId, "resultType": "json"] 14 | let fetcher: GetStationByUidItemFetchable = ServiceGetStationByUidItemFetcher(networkService: self.networkService, 15 | tokenManager: TokenManager(), 16 | requestFactory: self.requestFactory) 17 | return fetcher 18 | .fetch(param: param) 19 | .tryCatch({ error -> AnyPublisher in 20 | return fetcher 21 | .fetch(param: param) 22 | }) 23 | .retry(TokenManager.maxTokenCount) 24 | .eraseToAnyPublisher() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIUseCases/GetStationListUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetStationListUseCase.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | extension BBusAPIUseCases: GetStationListUsable { 12 | func getStationList() -> AnyPublisher { 13 | let fetcher: GetStationListFetchable = PersistenceGetStationListFetcher(persistenceStorage: self.persistenceStorage) 14 | return fetcher 15 | .fetch() 16 | .tryCatch({ error -> AnyPublisher in 17 | return fetcher 18 | .fetch() 19 | }) 20 | .retry(TokenManager.maxTokenCount) 21 | .eraseToAnyPublisher() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/BBusAPIUseCases/GetStationsByRouteListUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetStationsByRouteListUseCase.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/12/01. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | extension BBusAPIUseCases: GetStationsByRouteListUsable { 12 | func getStationsByRouteList(busRoutedId: String) -> AnyPublisher { 13 | let param = ["busRouteId": busRoutedId, "resultType": "json"] 14 | let fetcher: GetStationsByRouteListFetchable = ServiceGetStationsByRouteListFetcher(networkService: self.networkService, 15 | tokenManager: TokenManager(), 16 | requestFactory: self.requestFactory) 17 | return fetcher 18 | .fetch(param: param) 19 | .tryCatch({ error -> AnyPublisher in 20 | return fetcher 21 | .fetch(param: param) 22 | }) 23 | .retry(TokenManager.maxTokenCount) 24 | .eraseToAnyPublisher() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/CreateFavoriteItemFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateFavoriteItemFetcher.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol CreateFavoriteItemFetchable { 12 | func fetch(param: FavoriteItemDTO) -> AnyPublisher 13 | } 14 | 15 | struct PersistenceCreateFavoriteItemFetcher: PersistenceFetchable, CreateFavoriteItemFetchable { 16 | private(set) var persistenceStorage: PersistenceStorageProtocol 17 | 18 | func fetch(param: FavoriteItemDTO) -> AnyPublisher { 19 | return self.persistenceStorage.create(key: "FavoriteItems", param: param) 20 | .eraseToAnyPublisher() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/DeleteFavoriteItemFetchable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteFavoriteItemFetchable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol DeleteFavoriteItemFetchable: PersistenceFetchable { 12 | func fetch(param: FavoriteItemDTO) -> AnyPublisher 13 | } 14 | 15 | struct PersistenceDeleteFavoriteItemFetcher: DeleteFavoriteItemFetchable { 16 | private(set) var persistenceStorage: PersistenceStorageProtocol 17 | 18 | func fetch(param: FavoriteItemDTO) -> AnyPublisher { 19 | return self.persistenceStorage.delete(key: "FavoriteItems", param: param) 20 | .eraseToAnyPublisher() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/GetArrInfoByRouteListFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetArrInfoByRouteListFetcher.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetArrInfoByRouteListFetchable { 12 | func fetch(param: [String: String]) -> AnyPublisher 13 | } 14 | 15 | struct ServiceGetArrInfoByRouteListFetcher: ServiceFetchable, GetArrInfoByRouteListFetchable { 16 | private(set) var networkService: NetworkServiceProtocol 17 | private(set) var tokenManager: TokenManagable 18 | private(set) var requestFactory: Requestable 19 | 20 | func fetch(param: [String: String]) -> AnyPublisher { 21 | let url = "http://ws.bus.go.kr/api/rest/arrive/getArrInfoByRoute" 22 | return self.fetch(url: url, param: param) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/GetBusPosByRtidFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetBusPosByRtidFetcher.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetBusPosByRtidFetchable { 12 | func fetch(param: [String: String]) -> AnyPublisher 13 | } 14 | 15 | struct ServiceGetBusPosByRtidFetcher: ServiceFetchable, GetBusPosByRtidFetchable { 16 | private(set) var networkService: NetworkServiceProtocol 17 | private(set) var tokenManager: TokenManagable 18 | private(set) var requestFactory: Requestable 19 | 20 | func fetch(param: [String : String]) -> AnyPublisher { 21 | let url = "http://ws.bus.go.kr/api/rest/buspos/getBusPosByRtid" 22 | return self.fetch(url: url, param: param) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/GetBusPosByVehIdFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetBusPosByVehIdFetcher.swift 3 | // BBus 4 | // 5 | // Created by 김태훈 on 2021/11/22. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetBusPosByVehIdFetchable { 12 | func fetch(param: [String: String]) -> AnyPublisher 13 | } 14 | 15 | struct ServiceGetBusPosByVehIdFetcher: ServiceFetchable, GetBusPosByVehIdFetchable { 16 | private(set) var networkService: NetworkServiceProtocol 17 | private(set) var tokenManager: TokenManagable 18 | private(set) var requestFactory: Requestable 19 | 20 | func fetch(param: [String: String]) -> AnyPublisher { 21 | let url = "http://ws.bus.go.kr/api/rest/buspos/getBusPosByVehId" 22 | return self.fetch(url: url, param: param) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/GetFavoriteItemListFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetFavoriteItemListFetcher.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetFavoriteItemListFetchable { 12 | func fetch() -> AnyPublisher 13 | } 14 | 15 | struct PersistenceGetFavoriteItemListFetcher: PersistenceFetchable, GetFavoriteItemListFetchable { 16 | private(set) var persistenceStorage: PersistenceStorageProtocol 17 | 18 | func fetch() -> AnyPublisher { 19 | return self.persistenceStorage.getFromUserDefaults(key: "FavoriteItems") 20 | .eraseToAnyPublisher() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/GetRouteInfoItemFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetRouteInfoItemFetcher.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetRouteInfoItemFetchable { 12 | func fetch(param: [String: String]) -> AnyPublisher 13 | } 14 | 15 | struct ServiceGetRouteInfoItemFetcher: ServiceFetchable, GetRouteInfoItemFetchable { 16 | private(set) var networkService: NetworkServiceProtocol 17 | private(set) var tokenManager: TokenManagable 18 | private(set) var requestFactory: Requestable 19 | 20 | func fetch(param: [String : String]) -> AnyPublisher { 21 | let url = "http://ws.bus.go.kr/api/rest/busRouteInfo/getRouteInfo" 22 | return self.fetch(url: url, param: param) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/GetRouteListFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetRouteListFetcher.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetRouteListFetchable { 12 | func fetch() -> AnyPublisher 13 | } 14 | 15 | struct PersistenceGetRouteListFetcher: PersistenceFetchable, GetRouteListFetchable { 16 | private(set) var persistenceStorage: PersistenceStorageProtocol 17 | 18 | func fetch() -> AnyPublisher { 19 | return self.persistenceStorage.get(file: "BusRouteList", type: "json") 20 | .eraseToAnyPublisher() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/GetStationByUidItemFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetStationByUidItemFetcher.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetStationByUidItemFetchable { 12 | func fetch(param: [String: String]) -> AnyPublisher 13 | } 14 | 15 | struct ServiceGetStationByUidItemFetcher: ServiceFetchable, GetStationByUidItemFetchable { 16 | private(set) var networkService: NetworkServiceProtocol 17 | private(set) var tokenManager: TokenManagable 18 | private(set) var requestFactory: Requestable 19 | 20 | func fetch(param: [String : String]) -> AnyPublisher { 21 | let url = "http://ws.bus.go.kr/api/rest/stationinfo/getStationByUid" 22 | return self.fetch(url: url, param: param) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/GetStationListFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetStationListFetcher.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetStationListFetchable { 12 | func fetch() -> AnyPublisher 13 | } 14 | 15 | struct PersistenceGetStationListFetcher: PersistenceFetchable, GetStationListFetchable { 16 | private(set) var persistenceStorage: PersistenceStorageProtocol 17 | 18 | func fetch() -> AnyPublisher { 19 | return self.persistenceStorage.get(file: "StationList", type: "json") 20 | .eraseToAnyPublisher() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/GetStationsByRouteListFetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetStationsByRouteListFetcher.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetStationsByRouteListFetchable { 12 | func fetch(param: [String: String]) -> AnyPublisher 13 | } 14 | 15 | struct ServiceGetStationsByRouteListFetcher: ServiceFetchable, GetStationsByRouteListFetchable { 16 | private(set) var networkService: NetworkServiceProtocol 17 | private(set) var tokenManager: TokenManagable 18 | private(set) var requestFactory: Requestable 19 | 20 | func fetch(param: [String : String]) -> AnyPublisher { 21 | let url = "http://ws.bus.go.kr/api/rest/busRouteInfo/getStaionByRoute" 22 | return self.fetch(url: url, param: param) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/PersistencetFetchable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersistencetFetchable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol PersistenceFetchable { 11 | var persistenceStorage: PersistenceStorageProtocol { get } 12 | } 13 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/Fetcher/ServiceFetchable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceFetcher.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol ServiceFetchable { 12 | var networkService: NetworkServiceProtocol { get } 13 | var tokenManager: TokenManagable { get } 14 | var requestFactory: Requestable { get } 15 | 16 | func fetch(url: String, param: [String: String]) -> AnyPublisher 17 | } 18 | 19 | extension ServiceFetchable { 20 | func fetch(url: String, param: [String: String]) -> AnyPublisher { 21 | guard let key = try? self.tokenManager.randomAccessKey() else { return BBusAPIError.noMoreAccessKeyError.publisher } 22 | guard let request = 23 | self.requestFactory.request(url: url, accessKey: key.key, params: param) else { return NetworkError.urlError.publisher } 24 | return networkService.get(request: request) 25 | .mapJsonBBusAPIError { 26 | self.tokenManager.removeAccessKey(at: key.index) 27 | } 28 | } 29 | } 30 | 31 | fileprivate extension Error { 32 | var publisher: AnyPublisher { 33 | let publisher = CurrentValueSubject(nil) 34 | publisher.send(completion: .failure(self)) 35 | return publisher.compactMap({$0}) 36 | .eraseToAnyPublisher() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/NetworkService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkService.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/10. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | enum NetworkError: Error { 12 | case accessKeyError, urlError, unknownError, noDataError, noResponseError, responseError 13 | } 14 | 15 | protocol NetworkServiceProtocol { 16 | func get(request: URLRequest) -> AnyPublisher 17 | } 18 | 19 | struct NetworkService: NetworkServiceProtocol { 20 | func get(request: URLRequest) -> AnyPublisher { 21 | return URLSession.shared.dataTaskPublisher(for: request) 22 | .mapError({ $0 as Error }) 23 | .tryMap { data, response -> Data in 24 | guard let response = response as? HTTPURLResponse else { 25 | throw NetworkError.noResponseError 26 | } 27 | if response.statusCode != 200 { 28 | throw NetworkError.responseError 29 | } 30 | return data 31 | } 32 | .eraseToAnyPublisher() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/RequestFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestFactory.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol Requestable { 11 | func request(url: String, accessKey: String, params: [String: String]) -> URLRequest? 12 | } 13 | 14 | struct RequestFactory: Requestable { 15 | func request(url: String, accessKey: String, params: [String: String]) -> URLRequest? { 16 | guard var components = URLComponents(string: url) else { return nil } 17 | var items: [URLQueryItem] = [] 18 | params.forEach() { item in 19 | items.append(URLQueryItem(name: item.key, value: item.value)) 20 | } 21 | components.queryItems = items 22 | guard let query = components.percentEncodedQuery else { return nil } 23 | components.percentEncodedQuery = query + "&serviceKey=" + accessKey 24 | guard let url = components.url else { return nil } 25 | var request = URLRequest(url: url) 26 | request.httpMethod = "GET" 27 | return request 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/RequestUseCases/CreateFavoriteItemUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateFavoriteItemUsable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol CreateFavoriteItemUsable { 12 | func createFavoriteItem(param: FavoriteItemDTO) -> AnyPublisher 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/RequestUseCases/DeleteFavoriteItemUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteFavoriteItemUsable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol DeleteFavoriteItemUsable { 12 | func deleteFavoriteItem(param: FavoriteItemDTO) -> AnyPublisher 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/RequestUseCases/GetArrInfoByRouteListUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetArrInfoByRouteListUsable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetArrInfoByRouteListUsable { 12 | func getArrInfoByRouteList(stId: String, busRouteId: String, ord: String) -> AnyPublisher 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/RequestUseCases/GetBusPosByRtidUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetBusPosByRtidUsable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetBusPosByRtidUsable { 12 | func getBusPosByRtid(busRoutedId: String) -> AnyPublisher 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/RequestUseCases/GetBusPosByVehIdUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetBusPosByVehIdUsable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetBusPosByVehIdUsable { 12 | func getBusPosByVehId(_ vehId: String) -> AnyPublisher 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/RequestUseCases/GetFavoriteItemListUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetFavoriteItemListUsable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetFavoriteItemListUsable { 12 | func getFavoriteItemList() -> AnyPublisher 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/RequestUseCases/GetRouteListUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetRouteListUsable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetRouteListUsable { 12 | func getRouteList() -> AnyPublisher 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/RequestUseCases/GetStationByUidItemUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetStationByUidItemUsable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetStationByUidItemUsable { 12 | func getStationByUidItem(arsId: String) -> AnyPublisher 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/RequestUseCases/GetStationListUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetStationListUsable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetStationListUsable { 12 | func getStationList() -> AnyPublisher 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/RequestUseCases/GetStationsByRouteListUsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetStationsByRouteListUsable.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | protocol GetStationsByRouteListUsable { 12 | func getStationsByRouteList(busRoutedId: String) -> AnyPublisher 13 | } 14 | -------------------------------------------------------------------------------- /BBus/BBus/Global/Network/TokenManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenManager.swift 3 | // BBus 4 | // 5 | // Created by 이지수 on 2021/11/29. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol TokenManagable: AnyObject { 11 | init() 12 | func removeAccessKey(at order: Int) 13 | func randomAccessKey() throws -> (index: Int, key: String) 14 | } 15 | 16 | class TokenManager: TokenManagable { 17 | static let maxTokenCount: Int = 17 18 | 19 | private(set) var accessKeys: [String] = { () -> [String] in 20 | let keys = [Bundle.main.infoDictionary?["API_ACCESS_KEY1"] as? String, Bundle.main.infoDictionary?["API_ACCESS_KEY2"] as? String, Bundle.main.infoDictionary?["API_ACCESS_KEY3"] as? String, Bundle.main.infoDictionary?["API_ACCESS_KEY4"] as? String, Bundle.main.infoDictionary?["API_ACCESS_KEY5"] as? String, Bundle.main.infoDictionary?["API_ACCESS_KEY6"] as? String, Bundle.main.infoDictionary?["API_ACCESS_KEY7"] as? String, Bundle.main.infoDictionary?["API_ACCESS_KEY8"] as? String, Bundle.main.infoDictionary?["API_ACCESS_KEY9"] as? String, 21 | Bundle.main.infoDictionary?["API_ACCESS_KEY10"] as? String, 22 | Bundle.main.infoDictionary?["API_ACCESS_KEY11"] as? String, 23 | Bundle.main.infoDictionary?["API_ACCESS_KEY12"] as? String, 24 | Bundle.main.infoDictionary?["API_ACCESS_KEY13"] as? String, 25 | Bundle.main.infoDictionary?["API_ACCESS_KEY14"] as? String, 26 | Bundle.main.infoDictionary?["API_ACCESS_KEY15"] as? String, 27 | Bundle.main.infoDictionary?["API_ACCESS_KEY16"] as? String, 28 | Bundle.main.infoDictionary?["API_ACCESS_KEY17"] as? String] 29 | return keys.compactMap({ $0 }).filter({ $0 != "" }) 30 | }() 31 | 32 | private var keys: [Int] 33 | 34 | required init() { 35 | self.keys = Array(0.. (index: Int, key: String) { 43 | guard self.keys.count != 0, 44 | let order = self.keys.randomElement() else { 45 | throw BBusAPIError.noMoreAccessKeyError 46 | } 47 | return (order, accessKeys[order]) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /BBus/BBus/Global/UseCase/AverageSectionTimeCalculatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AverageSectionTimeCalculatable.swift 3 | // BBus 4 | // 5 | // Created by Kang Minsang on 2021/11/30. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol AverageSectionTimeCalculatable: BaseUseCase { 11 | func averageSectionTime(speed: Int, distance: Int) -> Int 12 | } 13 | 14 | extension AverageSectionTimeCalculatable { 15 | func averageSectionTime(speed: Int, distance: Int) -> Int { 16 | let averageBusSpeed: Double = 21 17 | let metterToKilometter: Double = 0.06 18 | 19 | let result = Double(distance)/averageBusSpeed*metterToKilometter 20 | return Int(ceil(result)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BBus/BBus/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSLocationAlwaysAndWhenInUseUsageDescription 6 | 알람을 위해 앱을 사용중이지 않을 때도 사용자의 GPS 위치를 추적해야합니다. 7 | NSLocationWhenInUseUsageDescription 8 | 알람을 위해 사용자의 GPS 위치를 추적해야합니다. 9 | NSLocationUsageDescription 10 | 하차 알람을 위해서 사용자의 GPS 위치를 추적해야합니다. 11 | UISupportedInterfaceOrientations~iphone 12 | 13 | UIInterfaceOrientationPortrait 14 | 15 | API_ACCESS_KEY1 16 | ${API_ACCESS_KEY1} 17 | API_ACCESS_KEY10 18 | ${API_ACCESS_KEY10} 19 | API_ACCESS_KEY11 20 | ${API_ACCESS_KEY11} 21 | API_ACCESS_KEY12 22 | ${API_ACCESS_KEY12} 23 | API_ACCESS_KEY13 24 | ${API_ACCESS_KEY13} 25 | API_ACCESS_KEY14 26 | ${API_ACCESS_KEY14} 27 | API_ACCESS_KEY15 28 | ${API_ACCESS_KEY15} 29 | API_ACCESS_KEY16 30 | ${API_ACCESS_KEY16} 31 | API_ACCESS_KEY17 32 | ${API_ACCESS_KEY17} 33 | API_ACCESS_KEY2 34 | ${API_ACCESS_KEY2} 35 | API_ACCESS_KEY3 36 | ${API_ACCESS_KEY3} 37 | API_ACCESS_KEY4 38 | ${API_ACCESS_KEY4} 39 | API_ACCESS_KEY5 40 | ${API_ACCESS_KEY5} 41 | API_ACCESS_KEY6 42 | ${API_ACCESS_KEY6} 43 | API_ACCESS_KEY7 44 | ${API_ACCESS_KEY7} 45 | API_ACCESS_KEY8 46 | ${API_ACCESS_KEY8} 47 | API_ACCESS_KEY9 48 | ${API_ACCESS_KEY9} 49 | NSAppTransportSecurity 50 | 51 | NSAllowsArbitraryLoads 52 | 53 | NSExceptionDomains 54 | 55 | ws.bus.go.kr 56 | 57 | NSTemporaryExceptionAllowsInsecureHTTPLoads 58 | 59 | 60 | 61 | 62 | UIApplicationSceneManifest 63 | 64 | UIApplicationSupportsMultipleScenes 65 | 66 | UISceneConfigurations 67 | 68 | UIWindowSceneSessionRoleApplication 69 | 70 | 71 | UISceneConfigurationName 72 | Default Configuration 73 | UISceneDelegateClassName 74 | $(PRODUCT_MODULE_NAME).SceneDelegate 75 | 76 | 77 | 78 | 79 | UIBackgroundModes 80 | 81 | location 82 | 83 | UIStatusBarStyle 84 | UIStatusBarStyleDarkContent 85 | UIViewControllerBasedStatusBarAppearance 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /BBus/BBusTests/BBusTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BBusTests.swift 3 | // BBusTests 4 | // 5 | // Created by 김태훈 on 2021/11/01. 6 | // 7 | 8 | import XCTest 9 | 10 | final class BBusTests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | } 15 | 16 | override func tearDownWithError() throws { 17 | // Put teardown code here. This method is called after the invocation of each test method in the class. 18 | } 19 | 20 | func testExample() throws { 21 | // This is an example of a functional test case. 22 | // Use XCTAssert and related functions to verify your tests produce the correct results. 23 | } 24 | 25 | func testPerformanceExample() throws { 26 | // This is an example of a performance test case. 27 | measure { 28 | // Put the code you want to measure the time of here. 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /BBus/HomeViewModelTests/HomeViewModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewModelTests.swift 3 | // HomeViewModelTests 4 | // 5 | // Created by 김태훈 on 2021/11/30. 6 | // 7 | 8 | import XCTest 9 | 10 | class HomeViewModelTests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | } 15 | 16 | override func tearDownWithError() throws { 17 | // Put teardown code here. This method is called after the invocation of each test method in the class. 18 | } 19 | 20 | func testExample() throws { 21 | // This is an example of a functional test case. 22 | // Use XCTAssert and related functions to verify your tests produce the correct results. 23 | } 24 | 25 | func testPerformanceExample() throws { 26 | // This is an example of a performance test case. 27 | measure { 28 | // Put the code you want to measure the time of here. 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /BBus/NetworkServiceTests/NetworkServiceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkServiceTests.swift 3 | // NetworkServiceTests 4 | // 5 | // Created by 김태훈 on 2021/11/30. 6 | // 7 | 8 | import XCTest 9 | import Combine 10 | 11 | class NetworkServiceTests: XCTestCase { 12 | 13 | private let timeout: TimeInterval = 10 14 | private var successRequest: URLRequest? 15 | private var redirectFailRequest: URLRequest? 16 | private var cancellables: Set = [] 17 | 18 | override func setUpWithError() throws { 19 | guard let successRequest = self.makeSuccessRequest(), 20 | let redirectFailRequest = self.makeRedirectRequest() else { throw NetworkError.urlError } 21 | self.successRequest = successRequest 22 | self.redirectFailRequest = redirectFailRequest 23 | } 24 | 25 | private func makeSuccessRequest() -> URLRequest? { 26 | guard let key = Bundle.main.infoDictionary?["API_ACCESS_KEY1"] as? String, 27 | let url = URL(string: "http://ws.bus.go.kr/api/rest/arrive/getLowArrInfoByStId?stId=100&resultType=json&serviceKey=\(key)") 28 | else { return nil } 29 | return URLRequest(url: url) 30 | } 31 | 32 | private func makeRedirectRequest() -> URLRequest? { 33 | guard let url = URL(string: "http://www.naver.com/.png") else { return nil } 34 | return URLRequest(url: url) 35 | } 36 | 37 | func test_get_요청_성공() throws { 38 | // given 39 | let expectation = self.expectation(description: "get 요청이 성공되어야 한다.") 40 | guard let successRequest = self.successRequest else { return } 41 | let networkService = NetworkService() 42 | var data: Data? = nil 43 | 44 | // when 45 | networkService.get(request: successRequest) 46 | .sink(receiveCompletion: { _ in 47 | expectation.fulfill() 48 | }, receiveValue: { result in 49 | data = result 50 | }) 51 | .store(in: &self.cancellables) 52 | 53 | waitForExpectations(timeout: self.timeout) 54 | 55 | // then 56 | XCTAssertNotNil(data) 57 | } 58 | 59 | func test_get_리다이렉트로_요청_실패() throws { 60 | // given 61 | let expectation = self.expectation(description: "get 요청이 실패되어야 한다.") 62 | guard let redirectFailRequest = self.redirectFailRequest else { return } 63 | let networkService = NetworkService() 64 | var error: Error? = nil 65 | 66 | // when 67 | networkService.get(request: redirectFailRequest) 68 | .sink(receiveCompletion: { result in 69 | if case .failure(let resultError) = result { 70 | error = resultError 71 | } 72 | expectation.fulfill() 73 | }, receiveValue: { data in 74 | return 75 | }) 76 | .store(in: &self.cancellables) 77 | 78 | waitForExpectations(timeout: self.timeout) 79 | 80 | // then 81 | XCTAssertNotNil(error) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /BBus/RequestFactoryTests/RequestFactoryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestFactoryTests.swift 3 | // RequestFactoryTests 4 | // 5 | // Created by 김태훈 on 2021/11/30. 6 | // 7 | 8 | import XCTest 9 | 10 | class RequestFactoryTests: XCTestCase { 11 | 12 | private var requestFactory: Requestable? 13 | 14 | override func setUpWithError() throws { 15 | super.setUp() 16 | self.requestFactory = RequestFactory() 17 | } 18 | 19 | override func tearDownWithError() throws { 20 | super.tearDown() 21 | self.requestFactory = nil 22 | } 23 | 24 | func test_request_파라미터2개_생성_일치() throws { 25 | // given 26 | let mockUrl = "http://ws.bus.go.kr/testUrl" 27 | let mockAccessKey = "uAtMsUVNMLIM%2FM9%3D%3D" 28 | let mockParam = ["stId": "10001", "busRouteId": "1001001"] 29 | let answer1 = URL(string: "http://ws.bus.go.kr/testUrl?stId=10001&busRouteId=1001001&serviceKey=uAtMsUVNMLIM%2FM9%3D%3D") 30 | let answer2 = URL(string: "http://ws.bus.go.kr/testUrl?busRouteId=1001001&stId=10001&serviceKey=uAtMsUVNMLIM%2FM9%3D%3D") 31 | let answers = [answer1, answer2] 32 | 33 | // when 34 | guard let requestResult = self.requestFactory?.request(url: mockUrl, accessKey: mockAccessKey, params: mockParam) else { 35 | XCTFail("request result is nil") 36 | return 37 | } 38 | 39 | // then 40 | XCTAssertNotNil(requestResult) 41 | XCTAssertTrue(answers.contains(requestResult.url)) 42 | } 43 | 44 | func test_request_파라미터3개_생성_일치() throws { 45 | //given 46 | let mockUrl = "http://www.BBus.test" 47 | let mockAccessKey = "uAtMsUVNMLIM%2FM9%3D%3D" 48 | let mockParam = ["stId": "10001", "routeId": "1001001", "ord": "1"] 49 | let answer1 = URL(string: "http://www.BBus.test?stId=10001&routeId=1001001&ord=1&serviceKey=uAtMsUVNMLIM%2FM9%3D%3D") 50 | let answer2 = URL(string: "http://www.BBus.test?routeId=1001001&stId=10001&ord=1&serviceKey=uAtMsUVNMLIM%2FM9%3D%3D") 51 | let answer3 = URL(string: "http://www.BBus.test?ord=1&routeId=1001001&stId=10001&serviceKey=uAtMsUVNMLIM%2FM9%3D%3D") 52 | let answer4 = URL(string: "http://www.BBus.test?stId=10001&ord=1&routeId=1001001&serviceKey=uAtMsUVNMLIM%2FM9%3D%3D") 53 | let answer5 = URL(string: "http://www.BBus.test?routeId=1001001&ord=1&stId=10001&serviceKey=uAtMsUVNMLIM%2FM9%3D%3D") 54 | let answer6 = URL(string: "http://www.BBus.test?ord=1&stId=10001&routeId=1001001&serviceKey=uAtMsUVNMLIM%2FM9%3D%3D") 55 | let answers = [answer1, answer2, answer3, answer4, answer5, answer6] 56 | 57 | // when 58 | guard let requestResult = self.requestFactory?.request(url: mockUrl, accessKey: mockAccessKey, params: mockParam) else { 59 | XCTFail("request result is nil") 60 | return 61 | } 62 | 63 | // then 64 | XCTAssertNotNil(requestResult) 65 | XCTAssertTrue(answers.contains(requestResult.url)) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /BBus/TokenManagerTests/TokenManagerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenManagerTests.swift 3 | // TokenManagerTests 4 | // 5 | // Created by 김태훈 on 2021/11/30. 6 | // 7 | 8 | import XCTest 9 | 10 | class TokenManagerTests: XCTestCase { 11 | var tokenManager: TokenManager! 12 | var accessKeyList: [String]! 13 | 14 | enum APIAccessKeyError: Error { 15 | case cannotFindAccessKey 16 | } 17 | 18 | override func setUpWithError() throws { 19 | self.tokenManager = TokenManager() 20 | self.accessKeyList = try self.loadAllAccessKeys() 21 | 22 | super.setUp() 23 | } 24 | 25 | override func tearDownWithError() throws { 26 | self.tokenManager = nil 27 | self.accessKeyList = nil 28 | super.tearDown() 29 | } 30 | 31 | func loadAllAccessKeys() throws -> [String] { 32 | var accessKeyList = [String]() 33 | 34 | for i in (1...TokenManager.maxTokenCount) { 35 | guard let accessKey = Bundle.main.infoDictionary?["API_ACCESS_KEY\(i)"] as? String else { throw APIAccessKeyError.cannotFindAccessKey } 36 | accessKeyList.append(accessKey) 37 | } 38 | 39 | return accessKeyList 40 | } 41 | 42 | func test_randomAccessKey_리턴_성공() { 43 | // given 44 | let expectedMinIndex = 0 45 | let expectedMaxIndex = 16 46 | 47 | // when then 48 | do { 49 | let randomAccessKey = try self.tokenManager.randomAccessKey() 50 | let index = randomAccessKey.index 51 | let key = randomAccessKey.key 52 | 53 | XCTAssertGreaterThanOrEqual(index, expectedMinIndex) 54 | XCTAssertLessThanOrEqual(index, expectedMaxIndex) 55 | XCTAssertTrue(self.accessKeyList.contains(key)) 56 | } catch { 57 | XCTFail() 58 | } 59 | } 60 | 61 | func test_randomAccessKey_액세스키_없어_리턴_실패() { 62 | // given 63 | for i in (0..