Skip to content

Commit

Permalink
Feat/#245 홈 / 알람 쓰로틀, 무한스크롤 적용 (#312)
Browse files Browse the repository at this point in the history
* [Feat] 알람 -> 정류장: 쓰로틀 작업 (#261)

* [Feat] #260 BusStop Refresh 제한 설정

* [Fix] refreshLoading 옵셔널 변경

* [Feat] viewWillAppear Flow에 대한 분기처리 추가

* [Feat] BusStopFeature Refresh Throttle 15초 처리

* Revert "[Feat] BusStopFeature Refresh Throttle 15초 처리"

This reverts commit 44b2df4.

* Feat/#267 BusStopFeature Refresh Throttle 처리 (#268)

* [Feat] BusStopFeature Refresh Throttle 15초 처리

* [Add]  viewModel.flw 분기처리 수정

* [Chore] refresh 쓰로틀 시간 3초로 변경

* [Add] willEnterForegroundNotification시점 추가

* [Style] 쓰로틀 상태 Enum 추가

* [Chore] Camel Case 수정

* Feat/#255 즐겨찾기 Fetch 페이지네이션 처리 (#265)

* [Feat] #251 즐겨찾기 정보 Fetch 요청 쓰로틀 작업

* [Remove] VC 레거시 코드 제거

* [Fix] RefreshController 애니메이션 시작 / 종료 버그 수정

* [Fix] 홈 화면 타이머 버그 수정

* [Feat] 페이지네이션 fetch 함수 구현

* [Add] 페이지네이션 fetch 범위 수정

* [Chore] 디버깅 로그 수정

* [Feat] 즐겨찾기 Fetch 페이지네이션 적용

* [Fix] BCTimer 로직 수정

* [Fix] 버스도착정보 모델 date 추가 및 시간정보 update 로직 수정

* [Chore] pagenation 후 중복 제거 코드 삭제

* [Fix] 업데이트 버튼 시간 정보 버그 수정

* [Chore] Lint에러 수정

* [Fix] 즐겨찾기 Fetch 방식 수정, 홈 화면 즐겨찾기된 정류장 표시 버그 수정

* [Fix] #265 중복된 즐겨찾기 버그 수정

* [Fix] #265 중복된 즐겨찾기 버그 수정

* [Chore] 레거시 함수 제거 및 주석 추가

* [Fix] #265 정류장 Fetch 버그 수정

#265 (comment)

* [Remove] 레거시 코드 제거

* [Fix] FavoritesRepository 마이그레이션 로직 수정

* [Fix] #265 홈화면 즐겨찾기 변화에 따른 FakeFetching 로직 수정

* [Chore] #265 즐겨찾기 스크롤이벤트 변수명 수정

* [Fix] #265 버스도착정보 시간정보 업데이트 로직 수정

* [Chore] 디버그 print문 삭제

---------

Co-authored-by: MUKER-WON <[email protected]>

---------

Co-authored-by: Kang Muk <[email protected]>
  • Loading branch information
gnksbm and MUKER-WON authored May 26, 2024
1 parent e95d3d8 commit cc4221a
Show file tree
Hide file tree
Showing 21 changed files with 538 additions and 355 deletions.
6 changes: 3 additions & 3 deletions Projects/App/Widget/ArrivalInfo/ArrivalInfoUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ final class ArrivalInfoUseCase {
forKey: "arrivalResponse"
) as? [Data]
else { return }
responses = datas.compactMap {
return try? $0.decode(type: BusStopArrivalInfoResponse.self)
}
// responses = datas.compactMap {
// return try? $0.decode(type: BusStopArrivalInfoResponse.self)
// }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,21 @@ struct FavoritesOptionProvider: DynamicOptionsProvider {
}

func results() async throws -> [String] {
await withCheckedContinuation { continuation in
coreDataService.storeStatus
.subscribe(
onNext: { status in
if status == .loaded {
continuation.resume()
}
}
)
.disposed(by: disposeBag)
return await withCheckedContinuation { continuation in
coreDataService.fetch(
type: FavoritesBusResponse.self
)
.map { responses in
responses.map { response in
"\(response.busStopName), \(response.busName)"
}
}
.subscribe(
onNext: { result in
continuation.resume(returning: result)
}
)
.disposed(by: disposeBag)
}
return try coreDataService.fetch(
type: FavoritesBusResponse.self
)
.map { "\($0.busStopName), \($0.busName)" }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ struct ArrivalInfoSmallView: View {
.foregroundColor(.green)
Spacer()
VStack(spacing: 6) {
HStack(spacing:3) {
HStack(spacing: 3) {
Spacer()
Text(bus.firstArrivalState.toString)
.font(.nanumHeavy(13))
Expand Down
2 changes: 1 addition & 1 deletion Projects/App/Widget/Extension/ArrivalState+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extension ArrivalState {
.red
case .finished:
.red
case .arrivalTime(time: _):
case .arrivalTime:
.white
}
}
Expand Down
25 changes: 10 additions & 15 deletions Projects/Core/Sources/BCTimer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,26 @@

import Foundation

import RxSwift
import RxRelay

public final class BCTimer {
private var timer: Timer?
public var distanceFromStart = BehaviorRelay<Int>(value: 0)

private var disposeBag = DisposeBag()
public init() { }

public func start(interval: TimeInterval = 1) {
let startDate = Date()
timer = .scheduledTimer(
withTimeInterval: interval,
repeats: true
) { [weak self] timer in
self?.distanceFromStart.accept(
Int(
startDate.distance(to: timer.fireDate)
)
public func start(interval: RxTimeInterval = .seconds(1)) {
Observable
.interval(
interval,
scheduler: ConcurrentDispatchQueueScheduler(qos: .utility)
)
}
.bind(to: distanceFromStart)
.disposed(by: disposeBag)
}

public func stop() {
distanceFromStart.accept(0)
timer?.invalidate()
timer = nil
disposeBag = .init()
}
}
9 changes: 9 additions & 0 deletions Projects/CoreDataService/Sources/CoreDataDirectory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,13 @@ import Foundation

enum CoreDataDirectory: Codable {
case applicationSupport, appGroup

var description: String {
switch self {
case .applicationSupport:
return "마이그레이션 필요한 저장소"
case .appGroup:
return "마이그레이션 완료된 저장소"
}
}
}
29 changes: 16 additions & 13 deletions Projects/CoreDataService/Sources/DefaultCoreDataService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public final class DefaultCoreDataService: CoreDataService {
#if DEBUG
print(
"💾 CoreData 저장소: \(String(describing: migrationStatus))",
"[applicationSupport(마이그레이션 필요) / appGroup(마이그레이션 완)]"
"\(migrationStatus.description)"
)
#endif
loadStore()
Expand Down Expand Up @@ -117,19 +117,22 @@ public final class DefaultCoreDataService: CoreDataService {
public func fetch<T: CoreDataStorable>(
type: T.Type
) -> Observable<[T]> {
Observable.create { observer in
do {
let result = try self.fetchMO(type: type)
.compactMap { $0 as? CoreDataModelObject }
.compactMap { $0.toDomain as? T }
observer.onNext(result)
observer.onCompleted()
return Disposables.create()
} catch {
observer.onError(error)
return Disposables.create()
return storeStatus
.filter { status in
status == .loaded
}
.take(1)
.withUnretained(self)
.flatMap { coreDataService, _ in
do {
let result = try coreDataService.fetchMO(type: type)
.compactMap { $0 as? CoreDataModelObject }
.compactMap { $0.toDomain as? T }
return Observable.just(result)
} catch {
return Observable.error(error)
}
}
}
}

public func fetch<T: CoreDataStorable>(type: T.Type) throws -> [T] {
Expand Down
43 changes: 23 additions & 20 deletions Projects/Data/Sources/Repository/DefaultFavoritesRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,19 @@ public final class DefaultFavoritesRepository: FavoritesRepository {
data: favorites,
uniqueKeyPath: \.identifier
)
fetchFavorites()
let currentFavorites = try self.favorites.value()
self.favorites.onNext(currentFavorites + [favorites])
}

public func removeFavorites(favorites: FavoritesBusResponse) throws {
try coreDataService.delete(
data: favorites,
uniqueKeyPath: \.identifier
)
fetchFavorites()
let currentFavorites = try self.favorites.value()
self.favorites.onNext(
currentFavorites.filter { $0 != favorites }
)
}

private func bindStoreStatus() {
Expand All @@ -60,29 +64,28 @@ public final class DefaultFavoritesRepository: FavoritesRepository {
.disposed(by: disposeBag)
}

public func fetchFavorites() {
coreDataService.fetch(
public func fetchFavorites() -> Observable<[FavoritesBusResponse]> {
let fetchResult = coreDataService.fetch(
type: FavoritesBusResponse.self
)
.withUnretained(self)
.subscribe(
onNext: { repository, favoritesList in
repository.favorites.onNext(favoritesList)
}
)
.disposed(by: disposeBag)
.share()
fetchResult
.withUnretained(self)
.subscribe(
onNext: { repository, favoritesList in
repository.favorites.onNext(favoritesList)
}
)
.disposed(by: disposeBag)
return fetchResult
}

private func migrateFavorites() {
coreDataService.fetch(type: FavoritesBusStopResponse.self)
.withUnretained(self)
.filter { repository, legacyFavoritesList in
let needMigration = !legacyFavoritesList.isEmpty
if !needMigration {
repository.fetchFavorites()
}
return needMigration
.filter { legacyFavoritesList in
!legacyFavoritesList.isEmpty
}
.withUnretained(self)
.flatMap { repository, legacyFavoritesList in
repository.fetchLegacyFavoritesToBusStop(
legacyFavoritesList: legacyFavoritesList.filterDuplicated()
Expand All @@ -96,10 +99,10 @@ public final class DefaultFavoritesRepository: FavoritesRepository {
busStopList: tuple.0,
legacyFavoritesList: tuple.1
)
repository.fetchFavorites()
_ = repository.fetchFavorites()
},
onError: { [weak self] _ in
self?.fetchFavorites()
_ = self?.fetchFavorites()
}
)
.disposed(by: disposeBag)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@

import Foundation

public struct BusStopArrivalInfoResponse: Codable, Hashable {
public struct BusStopArrivalInfoResponse: Hashable {
public let generatedDate: Date
public let busStopId: String
public let busStopName: String
public let direction: String
public var buses: [BusArrivalInfoResponse]

public init(
generatedDate: Date = .now,
busStopId: String,
busStopName: String,
direction: String,
buses: [BusArrivalInfoResponse]
) {
self.generatedDate = generatedDate
self.busStopId = busStopId
self.busStopName = busStopName
self.direction = direction
Expand All @@ -28,6 +31,52 @@ public struct BusStopArrivalInfoResponse: Codable, Hashable {
}

public extension BusStopArrivalInfoResponse {
func replaceTime() -> Self {
let distance = Int(generatedDate.distance(to: .now))
return BusStopArrivalInfoResponse(
generatedDate: generatedDate,
busStopId: busStopId,
busStopName: busStopName,
direction: direction,
buses: buses.map { busInfo in
let newFirstArrivalState: ArrivalState
let newSecondArrivalState: ArrivalState
switch busInfo.firstArrivalState {
case .soon, .pending, .finished:
newFirstArrivalState = busInfo.firstArrivalState
case .arrivalTime(let time):
newFirstArrivalState = time - distance > 60 ?
.arrivalTime(time: time - distance):
.soon
}
switch busInfo.secondArrivalState {
case .soon, .pending, .finished:
newSecondArrivalState
= busInfo.secondArrivalState
case .arrivalTime(let time):
newSecondArrivalState = time - distance > 60 ?
.arrivalTime(time: time - distance):
.soon
}
let firstReaining = busInfo.firstArrivalRemaining
let secondReaining = busInfo.secondArrivalRemaining
return BusArrivalInfoResponse(
busId: busInfo.busId,
busName: busInfo.busName,
busType: busInfo.busType.rawValue,
nextStation: busInfo.nextStation,
firstArrivalState: newFirstArrivalState,
firstArrivalRemaining: firstReaining,
secondArrivalState: newSecondArrivalState,
secondArrivalRemaining: secondReaining,
adirection: busInfo.adirection,
isFavorites: busInfo.isFavorites,
isAlarmOn: busInfo.isAlarmOn
)
}
)
}

func updateFavoritesStatus(
favoritesList: [FavoritesBusResponse]
) -> Self {
Expand All @@ -42,6 +91,7 @@ public extension BusStopArrivalInfoResponse {
return updatedBus
}
return .init(
generatedDate: generatedDate,
busStopId: busStopId,
busStopName: busStopName,
direction: direction,
Expand All @@ -54,6 +104,7 @@ public extension BusStopArrivalInfoResponse {
busResponse.isFavorites
}
return BusStopArrivalInfoResponse(
generatedDate: generatedDate,
busStopId: busStopId,
busStopName: busStopName,
direction: direction,
Expand All @@ -63,6 +114,12 @@ public extension BusStopArrivalInfoResponse {
}

public extension Array<BusStopArrivalInfoResponse> {
func filterUnfavorites(favoritesList: [FavoritesBusResponse]) -> Self {
updateFavoritesStatus(favoritesList: favoritesList)
.map { $0.filterUnfavoritesBuses() }
.filter { !$0.buses.isEmpty }
}

func updateFavoritesStatus(
favoritesList: [FavoritesBusResponse]
) -> Self {
Expand All @@ -79,10 +136,13 @@ public extension Array<BusStopArrivalInfoResponse> {
let key = "\(busStop.busStopId)\(bus.busId)\(bus.adirection)"
if let isFavorites = favoritesDic[key] {
updatedBuses[index].isFavorites = isFavorites
} else {
updatedBuses[index].isFavorites = false
}
}

return BusStopArrivalInfoResponse(
generatedDate: busStop.generatedDate,
busStopId: busStop.busStopId,
busStopName: busStop.busStopName,
direction: busStop.direction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ public struct FavoritesBusResponse: CoreDataStorable, Equatable {
self.adirection = adirection
}
}

public extension Array<FavoritesBusResponse> {
var toBusStopIds: [String] {
map { $0.busStopId }
.removeDuplicated()
.sorted { $0 < $1 }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import RxSwift
public protocol FavoritesRepository {
var favorites: BehaviorSubject<[FavoritesBusResponse]> { get }

func fetchFavorites()
func fetchFavorites() -> Observable<[FavoritesBusResponse]>
func addFavorites(favorites: FavoritesBusResponse) throws
func removeFavorites(favorites: FavoritesBusResponse) throws
}
Loading

0 comments on commit cc4221a

Please sign in to comment.