From 754e490218c962fa95cf1ba7cd44ae6601178514 Mon Sep 17 00:00:00 2001 From: Alex Sherstnev Date: Wed, 7 Jan 2026 21:51:26 +0100 Subject: [PATCH] show actual street view year in comparisons --- Rewind/Model/ComparisonModel.swift | 27 ++++++++++++++++++------ Rewind/Model/ImageDetailsModel.swift | 2 +- Rewind/Model/RewindRemotes.swift | 2 +- Rewind/Model/StreetViewFactory.swift | 5 +++++ Rewind/Network/Request.swift | 31 +++++++++++++++++++++++++--- Rewind/View/ComparisonScreen.swift | 4 ++-- Rewind/View/ComparisonView.swift | 5 +++-- Rewind/View/ImageDetailsView.swift | 4 ++-- Rewind/View/ImageList.swift | 2 +- 9 files changed, 64 insertions(+), 18 deletions(-) diff --git a/Rewind/Model/ComparisonModel.swift b/Rewind/Model/ComparisonModel.swift index cfda18e..43056d2 100644 --- a/Rewind/Model/ComparisonModel.swift +++ b/Rewind/Model/ComparisonModel.swift @@ -43,6 +43,7 @@ struct ComparisonState { var orientation: Orientation var alert: Identified? var shareVC: Identified? + var streetViewAvailability: StreetViewAvailability? var currentLens: Lens? var availableLens: [Lens] { @@ -84,6 +85,7 @@ enum ComparisonAction { case imageTaken(UIImage) case orientationChanged(Orientation) case shareSheetLoaded(UIViewController) + case streetViewAvailabilityLoaded(StreetViewAvailability) case setupCapture } @@ -95,7 +97,7 @@ func makeComparisonViewDeps( captureMode: ComparisonState.CaptureMode, oldUIImage: UIImage, oldImageData: Model.Image, - streetViewAvailability: Remote + streetViewAvailability: Remote ) -> ComparisonViewDeps { let orientationTracker = OrientationTracker() weak var comparisonVC: UIViewController? @@ -164,10 +166,8 @@ func makeComparisonViewDeps( case .streetView: enqueueEffect(.perform { anotherAction in do { - let isAvailable = try await streetViewAvailability.load() - if !isAvailable { - await anotherAction(.external(.alert(.presentStreetViewUnavailable))) - } + let availability = try await streetViewAvailability.load() + await anotherAction(.internal(.streetViewAvailabilityLoaded(availability))) } catch { assertionFailure() // we block user actions only if streetView is surely not available @@ -287,6 +287,11 @@ func makeComparisonViewDeps( enqueueEffect(.anotherAction(.external(.alert(.presentStreetViewError(error))))) } } + case let .streetViewAvailabilityLoaded(availability): + state.streetViewAvailability = availability + if case .unavailable = availability { + enqueueEffect(.anotherAction(.external(.alert(.presentStreetViewUnavailable)))) + } } } } @@ -301,7 +306,8 @@ func makeComparisonViewDeps( style: state.style, oldImageData: state.oldImageData, oldImage: state.oldUIImage, - captureState: state.captureState + captureState: state.captureState, + streetViewYear: state.streetViewAvailability?.year ) } ) @@ -340,3 +346,12 @@ extension ComparisonState.CaptureState? { if case .viewfinder = self { true } else { false } } } + +extension StreetViewAvailability { + fileprivate var year: Int? { + switch self { + case let .available(year): year + case .unavailable: nil + } + } +} diff --git a/Rewind/Model/ImageDetailsModel.swift b/Rewind/Model/ImageDetailsModel.swift index d65b59c..a572cfd 100644 --- a/Rewind/Model/ImageDetailsModel.swift +++ b/Rewind/Model/ImageDetailsModel.swift @@ -98,7 +98,7 @@ func makeImageDetailsModel( canOpenURL: @escaping (URL) -> Bool, urlOpener: @escaping (URL) -> Void, setOrientationLock: @escaping ResultAction, - streetViewAvailability: Remote + streetViewAvailability: Remote ) -> ImageDetailsModel { Reducer( initial: ImageDetailsState( diff --git a/Rewind/Model/RewindRemotes.swift b/Rewind/Model/RewindRemotes.swift index bdbd258..9426d59 100644 --- a/Rewind/Model/RewindRemotes.swift +++ b/Rewind/Model/RewindRemotes.swift @@ -10,7 +10,7 @@ import Foundation struct RewindRemotes { var annotations: Remote var imageDetails: Remote - var streetViewAvailability: Remote + var streetViewAvailability: Remote } struct AnnotationLoadingParams { diff --git a/Rewind/Model/StreetViewFactory.swift b/Rewind/Model/StreetViewFactory.swift index 2f6b9b4..1e97302 100644 --- a/Rewind/Model/StreetViewFactory.swift +++ b/Rewind/Model/StreetViewFactory.swift @@ -8,6 +8,11 @@ import UIKit import WebKit +enum StreetViewAvailability { + case available(year: Int) + case unavailable +} + func makeStreetView( image: Model.Image ) throws -> WKWebView { diff --git a/Rewind/Network/Request.swift b/Rewind/Network/Request.swift index 4c841dc..6c4e52b 100644 --- a/Rewind/Network/Request.swift +++ b/Rewind/Network/Request.swift @@ -36,7 +36,8 @@ extension Network.Request { Network.image(path: path, quality: quality) } - static func streetViewAvailability(coordinate: Coordinate) -> Network.Request { + static func streetViewAvailability(coordinate: Coordinate) -> Network + .Request { Network.streetViewAvailability(coordinate: coordinate) } } @@ -173,7 +174,8 @@ extension Network { } // https://developers.google.com/maps/documentation/streetview/metadata - fileprivate static func streetViewAvailability(coordinate: Coordinate) -> Request { + fileprivate static func streetViewAvailability(coordinate: Coordinate) + -> Request { struct Response: Decodable { enum Status: String, Decodable { case ok = "OK" @@ -186,6 +188,24 @@ extension Network { } let status: Status + let date: String? + } + + func extractYear(date: String?) throws -> Int { + guard let date else { throw HandlingError("Date is missing") } + let s = date.trimmingCharacters(in: .whitespacesAndNewlines) + guard + let yearStr = s.split( + separator: "-", + maxSplits: 1, + omittingEmptySubsequences: true + ).first, + yearStr.allSatisfy(\.isNumber), + let year = Int(yearStr) + else { + throw HandlingError("Invalid date format") + } + return year } return Request( @@ -208,7 +228,12 @@ extension Network { }, parseResult: { data in let response = try JSONDecoder().decode(Response.self, from: data) - return response.status == .ok + if response.status == .ok { + let year = try extractYear(date: response.date) + return .available(year: year) + } else { + return .unavailable + } } ) } diff --git a/Rewind/View/ComparisonScreen.swift b/Rewind/View/ComparisonScreen.swift index f711997..d630199 100644 --- a/Rewind/View/ComparisonScreen.swift +++ b/Rewind/View/ComparisonScreen.swift @@ -208,7 +208,7 @@ private let shutterButtonSize: CGFloat = 80 captureMode: .camera, oldUIImage: .panorama, oldImageData: .mock, - streetViewAvailability: .mock(true) + streetViewAvailability: .mock(.unavailable) ) ComparisonScreen(deps: deps) @@ -220,7 +220,7 @@ private let shutterButtonSize: CGFloat = 80 captureMode: .streetView, oldUIImage: .panorama, oldImageData: .mock, - streetViewAvailability: .mock(true) + streetViewAvailability: .mock(.available(year: 1826)) ) ComparisonScreen(deps: deps) diff --git a/Rewind/View/ComparisonView.swift b/Rewind/View/ComparisonView.swift index e9f675a..dcae726 100644 --- a/Rewind/View/ComparisonView.swift +++ b/Rewind/View/ComparisonView.swift @@ -12,6 +12,7 @@ struct ComparisonView: View { var oldImageData: Model.Image var oldImage: UIImage var captureState: ComparisonState.CaptureState? + var streetViewYear: Int? @State private var currentYear = Calendar.current.component(.year, from: .now) @@ -21,14 +22,14 @@ struct ComparisonView: View { case .sideBySide: SideBySideView( oldYear: oldImageData.date.year, - currentYear: currentYear, + currentYear: streetViewYear ?? currentYear, old: { ScaleToFillImage(image: oldImage) }, new: { cameraPreview } ) case .cardOnCard: CardOnCardView( oldYear: oldImageData.date.year, - currentYear: currentYear, + currentYear: streetViewYear ?? currentYear, oldImageAspectRatio: oldImage.size.aspectRatio ?? 3 / 4, old: { ScaleToFillImage(image: oldImage) }, new: { cameraPreview } diff --git a/Rewind/View/ImageDetailsView.swift b/Rewind/View/ImageDetailsView.swift index 0452477..cc3ac1a 100644 --- a/Rewind/View/ImageDetailsView.swift +++ b/Rewind/View/ImageDetailsView.swift @@ -351,7 +351,7 @@ extension SingleFavoriteModel { canOpenURL: { _ in true }, urlOpener: { _ in }, setOrientationLock: { _ in }, - streetViewAvailability: .mock(true) + streetViewAvailability: .mock(.unavailable) ).viewStore ImageDetailsView( @@ -375,7 +375,7 @@ extension SingleFavoriteModel { canOpenURL: { _ in true }, urlOpener: { _ in }, setOrientationLock: { _ in }, - streetViewAvailability: .mock(true) + streetViewAvailability: .mock(.unavailable) ).viewStore ImageDetailsView( diff --git a/Rewind/View/ImageList.swift b/Rewind/View/ImageList.swift index 874a5f8..c286af4 100644 --- a/Rewind/View/ImageList.swift +++ b/Rewind/View/ImageList.swift @@ -96,7 +96,7 @@ private let imageDetailsFactoryMock: ImageDetailsFactory = { _, source in canOpenURL: { _ in false }, urlOpener: { _ in }, setOrientationLock: { _ in }, - streetViewAvailability: .mock(true) + streetViewAvailability: .mock(.unavailable) ) }