Skip to content

Commit

Permalink
International event times (#511)
Browse files Browse the repository at this point in the history
* add extended query item

* add city property and use timeZone for event time

* update xcode version

* add local time tests

* remove helper

* open sheet on banner tap

* test iphone16

* update test

* set ridetime in list item view

* add test for didTapNextEventBanner
  • Loading branch information
mltbnz authored Oct 1, 2024
1 parent be62ee2 commit 763862c
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 154 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Setup Xcode version
uses: maxim-lobanov/[email protected]
with:
xcode-version: 15.4
xcode-version: 16.0

- name: Run UnitTests
run: fastlane test
Expand Down
12 changes: 9 additions & 3 deletions CriticalMapsKit/Sources/AppFeature/AppFeatureCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ public struct AppFeature {
case requestTimer(RequestTimer.Action)
case settings(SettingsFeature.Action)
case social(SocialFeature.Action)
case didTapNextEventBanner

public enum Alert: Equatable, Sendable {
case observationMode(enabled: Bool)
Expand Down Expand Up @@ -322,10 +323,9 @@ public struct AppFeature {

case let .map(mapFeatureAction):
switch mapFeatureAction {
case .focusRideEvent,
.focusNextRide:
case .focusRideEvent, .focusNextRide:
if state.bottomSheetPosition != .hidden {
return .send(.set(\.$bottomSheetPosition, .relative(0.4)))
return .send(.set(\.$bottomSheetPosition, .relative(0.3)))
} else {
return .none
}
Expand Down Expand Up @@ -491,6 +491,12 @@ public struct AppFeature {
default:
return .none
}

case .didTapNextEventBanner:
return .merge(
.send(.map(.focusNextRide(state.nextRideState.nextRide?.coordinate))),
.send(.set(\.$bottomSheetPosition, .relative(0.3)))
)

case .binding:
return .none
Expand Down
94 changes: 48 additions & 46 deletions CriticalMapsKit/Sources/AppFeature/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,6 @@ public struct AppView: View {
VStack(alignment: .leading) {
if shouldShowNextRideBanner {
nextRideBanner()
.contextMenu {
Button(
action: { viewStore.send(.set(\.$bottomSheetPosition, .relative(0.4))) },
label: { Label(contextMenuTitle, systemImage: "list.bullet") }
)
}
}

if viewStore.settingsState.infoViewEnabled {
Expand Down Expand Up @@ -98,7 +92,7 @@ public struct AppView: View {
.bottomSheet(
bottomSheetPosition: viewStore.$bottomSheetPosition,
switchablePositions: [
.relative(0.4),
.relative(0.3),
.relativeTop(0.975)
],
title: "Events",
Expand Down Expand Up @@ -181,45 +175,14 @@ public struct AppView: View {
func bottomSheetContentView() -> some View {
VStack {
List(viewStore.nextRideState.rideEvents, id: \.id) { ride in
HStack(alignment: .center, spacing: .grid(2)) {
Image(uiImage: Asset.cm.image)
.accessibilityHidden(true)

VStack(alignment: .leading, spacing: .grid(1)) {
Text(ride.title)
.multilineTextAlignment(.leading)
.font(Font.body.weight(.semibold))
.foregroundColor(Color(.textPrimary))
.padding(.bottom, .grid(1))

VStack(alignment: .leading, spacing: 2) {
Label(ride.dateTime.humanReadableDate, systemImage: "calendar")
.multilineTextAlignment(.leading)
.font(.bodyTwo)
.foregroundColor(Color(.textSecondary))

Label(ride.dateTime.humanReadableTime, systemImage: "clock")
.multilineTextAlignment(.leading)
.font(.bodyTwo)
.foregroundColor(Color(.textSecondary))

if let location = ride.location {
Label(location, systemImage: "location.fill")
.multilineTextAlignment(.leading)
.font(.bodyTwo)
.foregroundColor(Color(.textSecondary))
}
}
RideEventView(ride: ride)
.contentShape(Rectangle())
.padding(.vertical, .grid(1))
.accessibilityElement(children: .combine)
.onTapGesture {
viewStore.send(.onRideSelectedFromBottomSheet(ride))
}
Spacer()
}
.contentShape(Rectangle())
.padding(.vertical, .grid(1))
.accessibilityElement(children: .combine)
.onTapGesture {
viewStore.send(.onRideSelectedFromBottomSheet(ride))
}
.listRowBackground(Color.clear)
.listRowBackground(Color.clear)
}
.listStyle(.plain)
}
Expand Down Expand Up @@ -262,7 +225,7 @@ public struct AppView: View {
},
action: { $0 }
),
action: { viewStore.send(.map(.focusNextRide(viewStore.nextRideState.nextRide?.coordinate))) },
action: { viewStore.send(.didTapNextEventBanner) },
content: {
VStack(alignment: .leading, spacing: .grid(1)) {
Text(viewStore.state.nextRideState.nextRide?.title ?? "")
Expand Down Expand Up @@ -303,3 +266,42 @@ struct NumericContentTransition: ViewModifier {
}
}
}

struct RideEventView: View {
let ride: Ride

var body: some View {
HStack(alignment: .center, spacing: .grid(2)) {
Image(uiImage: Asset.cm.image)
.accessibilityHidden(true)

VStack(alignment: .leading, spacing: .grid(1)) {
Text(ride.title)
.multilineTextAlignment(.leading)
.font(Font.body.weight(.semibold))
.foregroundColor(Color(.textPrimary))
.padding(.bottom, .grid(1))

VStack(alignment: .leading, spacing: 2) {
Label(ride.dateTime.humanReadableDate, systemImage: "calendar")
.multilineTextAlignment(.leading)
.font(.bodyTwo)
.foregroundColor(Color(.textSecondary))

Label(ride.rideTime, systemImage: "clock")
.multilineTextAlignment(.leading)
.font(.bodyTwo)
.foregroundColor(Color(.textSecondary))

if let location = ride.location {
Label(location, systemImage: "location.fill")
.multilineTextAlignment(.leading)
.font(.bodyTwo)
.foregroundColor(Color(.textSecondary))
}
}
}
Spacer()
}
}
}
5 changes: 0 additions & 5 deletions CriticalMapsKit/Sources/Helpers/Date+Additions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ public extension Date {
return component
}

/// - Returns: Formatted time without date components.
var humanReadableTime: String {
self.formatted(Date.FormatStyle.localeAwareShortTime)
}

/// - Returns: Formatted date without time components.
var humanReadableDate: String {
self.formatted(Date.FormatStyle.localeAwareShortDate)
Expand Down
1 change: 1 addition & 0 deletions CriticalMapsKit/Sources/Helpers/Timezone+Extras.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public extension TimeZone {
static let spain = TimeZone(identifier: "Europe/Madrid")!
static let france = TimeZone(identifier: "Europe/Paris")!
static let greece = TimeZone(identifier: "Europe/Athens")!
static let london = TimeZone(identifier: "Europe/London")!

// America
static let ecuador = TimeZone(identifier: "America/Guayaquil")!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public extension Request {
URLQueryItem(name: NextRideQueryKeys.centerLatitude, value: String(coordinate.latitude)),
URLQueryItem(name: NextRideQueryKeys.radius, value: String(radius)),
URLQueryItem(name: NextRideQueryKeys.year, value: String(Date.getCurrent(\.year, date))),
URLQueryItem(name: NextRideQueryKeys.month, value: String(month))
URLQueryItem(name: NextRideQueryKeys.month, value: String(month)),
URLQueryItem(name: NextRideQueryKeys.extended, value: "true"),
]
)
}
Expand All @@ -31,4 +32,5 @@ enum NextRideQueryKeys {
static let radius = "radius"
static let year = "year"
static let month = "month"
static let extended = "extended"
}
51 changes: 50 additions & 1 deletion CriticalMapsKit/Sources/SharedModels/NextRideFeature/Ride.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import ComposableArchitecture
import CoreLocation
import Foundation
import Helpers
import MapKit

public struct Ride: Hashable, Codable, Identifiable {
public let id: Int
public var city: City?
public let slug: String?
public let title: String
public let description: String?
Expand All @@ -22,6 +24,7 @@ public struct Ride: Hashable, Codable, Identifiable {

public init(
id: Int,
city: City? = nil,
slug: String? = nil,
title: String,
description: String? = nil,
Expand All @@ -38,6 +41,7 @@ public struct Ride: Hashable, Codable, Identifiable {
rideType: Ride.RideType? = nil
) {
self.id = id
self.city = city
self.slug = slug
self.title = title
self.description = description
Expand All @@ -55,6 +59,25 @@ public struct Ride: Hashable, Codable, Identifiable {
}
}

extension Ride {
public struct City: Codable, Hashable {
let id: Int
let name: String
let timezone: String

public init(
id: Int,
name: String,
timezone: String
) {
self.id = id
self.name = name
self.timezone = timezone
}
}

}

public extension Ride {
var coordinate: Coordinate? {
guard let lat = latitude, let lng = longitude else {
Expand All @@ -72,7 +95,18 @@ public extension Ride {
}

var rideDateAndTime: String {
"\(dateTime.humanReadableDate) - \(dateTime.humanReadableTime)"
"\(dateTime.humanReadableDate) - \(rideTime)"
}

var rideTime: String {
if
let cityTimeZone = city?.timezone,
let timeZone = TimeZone(identifier: cityTimeZone)
{
return dateTime.formatted(Date.FormatStyle.shortTimeWithEventTimeZone(timeZone))
} else {
return dateTime.formatted(Date.FormatStyle.localeAwareShortTime)
}
}

var shareMessage: String {
Expand All @@ -82,6 +116,8 @@ public extension Ride {
return """
\(titleAndTime)
\(location)
\(description ?? "")
"""
}
}
Expand Down Expand Up @@ -121,3 +157,16 @@ public extension Ride {
}
}
}

private extension Date.FormatStyle {
static func shortTimeWithEventTimeZone(_ timezone: TimeZone) -> Self {
@Dependency(\.locale) var locale

return Self(
date: .omitted,
time: .shortened,
locale: locale,
timeZone: timezone
)
}
}
20 changes: 19 additions & 1 deletion CriticalMapsKit/Tests/AppFeatureTests/AppFeatureCoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ final class AppFeatureTests: XCTestCase {
await store.send(.map(.focusRideEvent(coordinate))) {
$0.mapFeatureState.eventCenter = CoordinateRegion(center: coordinate.asCLLocationCoordinate)
}
await store.receive(.binding(.set(\.$bottomSheetPosition, .relative(0.4))))
await store.receive(.binding(.set(\.$bottomSheetPosition, .relative(0.3))))
await testClock.advance(by: .seconds(1))
await store.receive(.map(.resetRideEventCenter)) {
$0.mapFeatureState.eventCenter = nil
Expand Down Expand Up @@ -506,6 +506,24 @@ final class AppFeatureTests: XCTestCase {
let didStopLocationObservationValue = await didStopLocationUpdating.value
XCTAssertTrue(didStopLocationObservationValue)
}

func test_didTapNextEventBanner() async {
let store = await TestStore(
initialState: AppFeature.State(nextRideState: NextRideFeature.State(nextRide: Ride.mock1)),
reducer: { AppFeature() },
withDependencies: {
$0.continuousClock = TestClock()
}
)
store.exhaustivity = .off

// act
await store.send(.didTapNextEventBanner)

// assert
await store.receive(.map(.focusNextRide(Ride.mock1.coordinate)))
await store.receive(.set(\.$bottomSheetPosition, .relative(0.3)))
}
}

// MARK: Helper
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import MapFeature
import SnapshotTesting
import TestHelper
import XCTest
import Testing

final class UserTrackingButtonSnapshotTests: XCTestCase {
@MainActor
struct UserTrackingButtonSnapshotTests {
@Test(.disabled("Due to CI issue with selecting iPhone"))
func test_userTracking_none() {
let sut = UserTrackingButton(
store: .init(
Expand All @@ -16,7 +16,7 @@ final class UserTrackingButtonSnapshotTests: XCTestCase {
assertSnapshot(of: sut, as: .image)
}

@MainActor
@Test(.disabled("Due to CI issue with selecting iPhone"))
func test_userTracking_follow() {
let sut = UserTrackingButton(
store: .init(
Expand All @@ -28,7 +28,7 @@ final class UserTrackingButtonSnapshotTests: XCTestCase {
assertSnapshot(of: sut, as: .image)
}

@MainActor
@Test(.disabled("Due to CI issue with selecting iPhone"))
func test_userTracking_followWithHeading() {
let sut = UserTrackingButton(
store: .init(
Expand Down
Loading

0 comments on commit 763862c

Please sign in to comment.