Skip to content

Commit

Permalink
refactor(watchos): file structure
Browse files Browse the repository at this point in the history
  • Loading branch information
krystxf committed Nov 16, 2024
1 parent 9df3038 commit e40e655
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 168 deletions.
168 changes: 1 addition & 167 deletions apps/mobile/metro-now/metro-now Watch App/ContentView.swift
Original file line number Diff line number Diff line change
@@ -1,177 +1,11 @@
// metro-now
// https://github.com/krystxf/metro-now

import Alamofire
import CoreLocation
import Foundation
import SwiftUI

private let REFETCH_INTERVAL: TimeInterval = 3 // seconds
private let SECONDS_BEFORE: TimeInterval = 3 // how many seconds after departure will it still be visible

class ClosestStopPageViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
@Published var location: CLLocation?

@Published var stops: [ApiStop]?
@Published var closestStop: ApiStop?
@Published var departures: [ApiDeparture]?

private var refreshTimer: Timer?

override init() {
super.init()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()

getStops()
startPeriodicRefresh()
}

deinit {
stopPeriodicRefresh()
}

private func startPeriodicRefresh() {
stopPeriodicRefresh() // Stop any existing timer to avoid duplication.
refreshTimer = Timer.scheduledTimer(
withTimeInterval: REFETCH_INTERVAL,
repeats: true
) { [weak self] _ in

guard let self, let closestStop else {
return
}

getDepartures(stopsIds: [closestStop.id])
}
}

private func stopPeriodicRefresh() {
refreshTimer?.invalidate()
refreshTimer = nil
}

func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
self.location = location

updateClosestStop()
}

func updateClosestStop() {
guard let location else {
return
}

if
let stops,
let nextValue = findClosestStop(to: location, stops: stops),
nextValue.id != self.closestStop?.id
{
closestStop = nextValue
getDepartures(stopsIds: [nextValue.id])
}
}

private func getStops() {
let request = AF.request(
"\(ENDPOINT)/v1/stop/all",
method: .get,
parameters: ["metroOnly": String(true)]
)

request
.validate()
.responseDecodable(of: [ApiStop].self) { response in
switch response.result {
case let .success(fetchedStops):
DispatchQueue.main.async {
self.stops = fetchedStops
print("Fetched \(fetchedStops.count) metro stops")

self.updateClosestStop()
}
case let .failure(error):
print("Error fetching metroStops: \(error)")
}
}
}

private func getDepartures(stopsIds: [String]) {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

let request = AF.request(
"\(ENDPOINT)/v2/departure",
method: .get,
parameters: [
"stop": stopsIds,
"limit": 20,
"totalLimit": 80,
"minutesBefore": 1,
]
)

request
.validate()
.responseDecodable(of: [ApiDeparture].self, decoder: decoder) { response in
switch response.result {
case let .success(fetchedDepartures):
DispatchQueue.main.async {
if let oldDepartures = self.departures {
self.departures = uniqueBy(
array: oldDepartures + fetchedDepartures,
by: \.id
)
.filter {
$0.departure.predicted > Date.now - SECONDS_BEFORE
}
.sorted(by: {
$0.departure.scheduled < $1.departure.scheduled
})
} else {
self.departures = fetchedDepartures
}

print("Fetched \(fetchedDepartures.count) departures")
}
case let .failure(error):
print("Error fetching stops: \(error)")
}
}
}
}

struct ContentView: View {
@StateObject private var viewModel = ClosestStopPageViewModel()

var body: some View {
VStack {
if
let closestStop = viewModel.closestStop
{
let platforms = closestStop.platforms.filter { $0.routes.count > 0 }

StopDeparturesView(
title: closestStop.name,
platforms: platforms.map { platform in
MainPagePlatform(
id: platform.id,
metroLine: MetroLine(rawValue: platform.routes[0].name),
departures: viewModel.departures?.filter { departure in
departure.platformId == platform.id
}
)
}
)
} else {
ProgressView()
}
}
ClosestStopListView()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// metro-now
// https://github.com/krystxf/metro-now

import Alamofire
import CoreLocation

private let REFETCH_INTERVAL: TimeInterval = 3 // seconds
private let SECONDS_BEFORE: TimeInterval = 3 // how many seconds after departure will it still be visible

class ClosestStopListViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
@Published var location: CLLocation?

@Published var stops: [ApiStop]?
@Published var closestStop: ApiStop?
@Published var departures: [ApiDeparture]?

private var refreshTimer: Timer?

override init() {
super.init()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()

getStops()
startPeriodicRefresh()
}

deinit {
stopPeriodicRefresh()
}

private func startPeriodicRefresh() {
stopPeriodicRefresh() // Stop any existing timer to avoid duplication.
refreshTimer = Timer.scheduledTimer(
withTimeInterval: REFETCH_INTERVAL,
repeats: true
) { [weak self] _ in

guard let self, let closestStop else {
return
}

getDepartures(stopsIds: [closestStop.id])
}
}

private func stopPeriodicRefresh() {
refreshTimer?.invalidate()
refreshTimer = nil
}

func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
self.location = location

updateClosestStop()
}

func updateClosestStop() {
guard let location else {
return
}

if
let stops,
let nextValue = findClosestStop(to: location, stops: stops),
nextValue.id != self.closestStop?.id
{
closestStop = nextValue
getDepartures(stopsIds: [nextValue.id])
}
}

private func getStops() {
let request = AF.request(
"\(ENDPOINT)/v1/stop/all",
method: .get,
parameters: ["metroOnly": String(true)]
)

request
.validate()
.responseDecodable(of: [ApiStop].self) { response in
switch response.result {
case let .success(fetchedStops):
DispatchQueue.main.async {
self.stops = fetchedStops
print("Fetched \(fetchedStops.count) metro stops")

self.updateClosestStop()
}
case let .failure(error):
print("Error fetching metroStops: \(error)")
}
}
}

private func getDepartures(stopsIds: [String]) {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

let request = AF.request(
"\(ENDPOINT)/v2/departure",
method: .get,
parameters: [
"stop": stopsIds,
"limit": 20,
"totalLimit": 80,
"minutesBefore": 1,
]
)

request
.validate()
.responseDecodable(of: [ApiDeparture].self, decoder: decoder) { response in
switch response.result {
case let .success(fetchedDepartures):
DispatchQueue.main.async {
if let oldDepartures = self.departures {
self.departures = uniqueBy(
array: oldDepartures + fetchedDepartures,
by: \.id
)
.filter {
$0.departure.predicted > Date.now - SECONDS_BEFORE
}
.sorted(by: {
$0.departure.scheduled < $1.departure.scheduled
})
} else {
self.departures = fetchedDepartures
}

print("Fetched \(fetchedDepartures.count) departures")
}
case let .failure(error):
print("Error fetching stops: \(error)")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// metro-now
// https://github.com/krystxf/metro-now


import SwiftUI

struct ClosestStopListView: View {
@StateObject private var viewModel = ClosestStopListViewModel()

var body: some View {
VStack {
if let closestStop = viewModel.closestStop {
let platforms = closestStop.platforms.filter { $0.routes.count > 0 }

StopDeparturesView(
title: closestStop.name,
platforms: platforms.map { platform in
let metroLine = MetroLine(rawValue: platform.routes[0].name)

return MainPagePlatform(
id: platform.id,
metroLine: metroLine,
departures: viewModel.departures?.filter { departure in
departure.platformId == platform.id
}
)
}
)
} else {
ProgressView()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ struct PlatformDetailView: View {
let platformId: String
let metroLine: MetroLine?
let departures: [ApiDeparture]?

init(
platformId: String,
metroLine: MetroLine? = nil,
Expand Down

0 comments on commit e40e655

Please sign in to comment.