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