diff --git a/apps/mobile/metro-now/metro-now Watch App/ContentView.swift b/apps/mobile/metro-now/metro-now Watch App/ContentView.swift index c947372d..76384271 100644 --- a/apps/mobile/metro-now/metro-now Watch App/ContentView.swift +++ b/apps/mobile/metro-now/metro-now Watch App/ContentView.swift @@ -13,12 +13,11 @@ struct ContentView: View { var body: some View { VStack { - if - let location = locationManager.location, - let stops, - let closestStop = findClosestStop(to: location, stops: stops) + if let location = locationManager.location, + let stops, + let closestStop = findClosestStop(to: location, stops: stops) { - MainPage( + StopDeparturesView( title: closestStop.name, platforms: closestStop.platforms.map { platform in @@ -31,7 +30,6 @@ struct ContentView: View { ) } ) - } else { ProgressView() } diff --git a/apps/mobile/metro-now/metro-now Watch App/pages/departure-placeholder.swift b/apps/mobile/metro-now/metro-now Watch App/pages/departure-placeholder.swift deleted file mode 100644 index 4247b234..00000000 --- a/apps/mobile/metro-now/metro-now Watch App/pages/departure-placeholder.swift +++ /dev/null @@ -1,33 +0,0 @@ -// metro-now -// https://github.com/krystxf/metro-now - -import SwiftUI - -struct DeparturePlaceholder: View { - let color: Color - - init( - color: Color? - ) { - self.color = color ?? Color.gray.opacity(0.3) - } - - var body: some View { - VStack(alignment: .trailing) { - HStack { - Text("Loading...") - Spacer() - Text("--s") - } - HStack { - Spacer() - Text("also in --m --s") - }.font(.system(size: 12)) - } - .redacted(reason: .placeholder) - .padding(.vertical, 10) - .padding(.horizontal, 5) - .background(color) - .clipShape(.rect(cornerRadius: 10)) - } -} diff --git a/apps/mobile/metro-now/metro-now Watch App/pages/main-page.swift b/apps/mobile/metro-now/metro-now Watch App/pages/main-page.swift deleted file mode 100644 index 279aa2e9..00000000 --- a/apps/mobile/metro-now/metro-now Watch App/pages/main-page.swift +++ /dev/null @@ -1,78 +0,0 @@ -// metro-now -// https://github.com/krystxf/metro-now - -import SwiftUI - -struct Departure { - let id: String - let headsing: String -} - -struct MainPagePlatform { - let id: String - let metroLine: MetroLine? - let departures: [ApiDeparture]? -} - -struct MainPage: View { - let title: String - let platforms: [MainPagePlatform] - - var body: some View { - NavigationView { - ScrollView { - VStack { - ForEach(platforms, id: \.id) { platform in - let itemColor = getMetroLineColor(platform.metroLine) - - if let departures = platform.departures, departures.count > 0 { - let hasNextDeparture = departures.count > 1 - - DepartureListItem( - color: itemColor, - headsign: departures[0].headsign, - departure: departures[0].departure.predicted, - nextHeadsign: hasNextDeparture ? departures[1].headsign : nil, - nextDeparture: hasNextDeparture ? departures[1].departure.predicted : nil - ) - } else { - DeparturePlaceholder( - color: itemColor - ) - } - } - } - } - - .navigationTitle(shortenStopName(title)) - } - } -} - -#Preview { - MainPage( - title: "Muzeum", - platforms: [ - MainPagePlatform( - id: "1", - metroLine: .A, - departures: nil - ), - MainPagePlatform( - id: "2", - metroLine: .A, - departures: nil - ), - MainPagePlatform( - id: "3", - metroLine: .C, - departures: nil - ), - MainPagePlatform( - id: "4", - metroLine: .C, - departures: nil - ), - ] - ) -} diff --git a/apps/mobile/metro-now/metro-now Watch App/pages/platform/next-departure.view.swift b/apps/mobile/metro-now/metro-now Watch App/pages/platform/next-departure.view.swift new file mode 100644 index 00000000..cc787afb --- /dev/null +++ b/apps/mobile/metro-now/metro-now Watch App/pages/platform/next-departure.view.swift @@ -0,0 +1,53 @@ +// metro-now +// https://github.com/krystxf/metro-now + +import SwiftUI + +struct PlatformDetailNextDepartureView: View { + let headsign: String + let departure: Date + let nextHeadsign: String? + let nextDeparture: Date? + + var body: some View { + VStack(spacing: 40) { + VStack { + Spacer() + Label( + shortenStopName(headsign), + systemImage: "arrowshape.forward" + ).font(.title2) + .fontWeight(.semibold) + .symbolVariant(.fill) + .imageScale(.small) + .foregroundStyle(.secondary) + + CountdownView( + targetDate: departure + ) + .font(.largeTitle) + .foregroundStyle(.primary) + } + + if let nextHeadsign, let nextDeparture { + if headsign == nextHeadsign { + CountdownView( + targetDate: nextDeparture + ) { + "Also in \($0)" + }.foregroundStyle(.tertiary) + } else { + VStack { + Text( + shortenStopName(nextHeadsign) + ) + + CountdownView( + targetDate: nextDeparture + ) + }.foregroundStyle(.secondary) + } + } + } + } +} diff --git a/apps/mobile/metro-now/metro-now Watch App/pages/platform/platform-departures-list.view.swift b/apps/mobile/metro-now/metro-now Watch App/pages/platform/platform-departures-list.view.swift new file mode 100644 index 00000000..dea17afb --- /dev/null +++ b/apps/mobile/metro-now/metro-now Watch App/pages/platform/platform-departures-list.view.swift @@ -0,0 +1,16 @@ +// metro-now +// https://github.com/krystxf/metro-now + +import SwiftUI + +struct PlatformDetailDeparturesListView: View { + let departures: [ApiDeparture] + + var body: some View { List(departures, id: \.departure.predicted) { departure in + HStack { + Text(departure.headsign) + Spacer() + CountdownView(targetDate: departure.departure.predicted) + } + }} +} diff --git a/apps/mobile/metro-now/metro-now Watch App/pages/platform/platform-detail.view.swift b/apps/mobile/metro-now/metro-now Watch App/pages/platform/platform-detail.view.swift new file mode 100644 index 00000000..4c5ad42b --- /dev/null +++ b/apps/mobile/metro-now/metro-now Watch App/pages/platform/platform-detail.view.swift @@ -0,0 +1,98 @@ +// metro-now +// https://github.com/krystxf/metro-now + +import SwiftUI + +struct PlatformDetailView: View { + let platformId: String + let metroLine: MetroLine? + @State var departures: [ApiDeparture]? = nil + private let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect() + + init( + platformId: String, + metroLine: MetroLine? = nil, + departures: [ApiDeparture]? = nil + + ) { + self.platformId = platformId + self.departures = departures + self.metroLine = metroLine + } + + var body: some View { + TabView { + if let departures, departures.count > 0 { + let backgroundColor = getMetroLineColor( + metroLine ?? MetroLine(rawValue: departures[0].route) + ) ?? .clear + + PlatformDetailNextDepartureView( + headsign: departures[0].headsign, + departure: departures[0].departure.scheduled, + nextHeadsign: departures[1].headsign, + nextDeparture: departures[1].departure.predicted + ) + .containerBackground(backgroundColor.gradient, for: .tabView) + + PlatformDetailDeparturesListView(departures: departures) + .containerBackground(backgroundColor.gradient, for: .tabView) + + } else { + ProgressView() + } + } + + .toolbar { + if let metroLineName = metroLine?.rawValue { + ToolbarItem( + placement: .confirmationAction) + { + Text(metroLineName) + .overlay( + Circle() + .size(width: 32, height: 32, anchor: .center) + .stroke(.white.opacity(0.6), lineWidth: 3) + ) + .fontWeight(.semibold) + .foregroundStyle(.white.opacity(0.6)) + } + + } + } + + .tabViewStyle(.verticalPage(transitionStyle: .identity)) + .onAppear { + getDepartures() + } + .onReceive(timer) { _ in + getDepartures() + } + } + + func getDepartures() { + NetworkManager.shared + .getDepartures(stopIds: [], platformIds: [platformId]) { result in + DispatchQueue.main.async { + switch result { + case let .success(departures): + + self.departures = departures + print(departures) + + case let .failure(error): + print(error.localizedDescription) + } + } + } + } +} + +#Preview { + NavigationStack { + PlatformDetailView( + platformId: "U1040Z101P", + metroLine: MetroLine.B + ) + } +} diff --git a/apps/mobile/metro-now/metro-now Watch App/pages/stop/list-item-placeholder.view.swift b/apps/mobile/metro-now/metro-now Watch App/pages/stop/list-item-placeholder.view.swift new file mode 100644 index 00000000..73c85c08 --- /dev/null +++ b/apps/mobile/metro-now/metro-now Watch App/pages/stop/list-item-placeholder.view.swift @@ -0,0 +1,41 @@ +// metro-now +// https://github.com/krystxf/metro-now + +import SwiftUI + +struct StopDepartureListItemPlaceholderView: View { + let color: Color + + init(color: Color? + ) { + self.color = color ?? Color.gray.opacity(0.3) + } + + var body: some View { + StopDepartureListItemView( + color: color, + headsign: "Loading", + departure: .now, + nextHeadsign: "Loading", + nextDeparture: .now + ) + .redacted(reason: .placeholder) + } +} + +#Preview { + ScrollView { + StopDepartureListItemPlaceholderView( + color: .red + ) + StopDepartureListItemPlaceholderView( + color: .red + ) + StopDepartureListItemPlaceholderView( + color: .green + ) + StopDepartureListItemPlaceholderView( + color: .green + ) + } +} diff --git a/apps/mobile/metro-now/metro-now Watch App/pages/departure-list-item.swift b/apps/mobile/metro-now/metro-now Watch App/pages/stop/list-item.view.swift similarity index 84% rename from apps/mobile/metro-now/metro-now Watch App/pages/departure-list-item.swift rename to apps/mobile/metro-now/metro-now Watch App/pages/stop/list-item.view.swift index 1869fe75..c6363fb8 100644 --- a/apps/mobile/metro-now/metro-now Watch App/pages/departure-list-item.swift +++ b/apps/mobile/metro-now/metro-now Watch App/pages/stop/list-item.view.swift @@ -3,7 +3,7 @@ import SwiftUI -struct DepartureListItem: View { +struct StopDepartureListItemView: View { let color: Color let headsign: String @@ -45,13 +45,20 @@ struct DepartureListItem: View { CountdownView(targetDate: nextDeparture) { nextHeadsign == headsign ? "also in \($0)" : $0 } - }.font(.system(size: 12)) } } .padding(.vertical, 10) .padding(.horizontal, 5) .background(color) - .clipShape(.rect(cornerRadius: 10)) + .clipShape(.rect(cornerRadius: 14)) + .listRowInsets( + EdgeInsets( + top: 0, + leading: 0, + bottom: 0, + trailing: 0 + ) + ) } } diff --git a/apps/mobile/metro-now/metro-now Watch App/pages/stop/stop-detail.view.swift b/apps/mobile/metro-now/metro-now Watch App/pages/stop/stop-detail.view.swift new file mode 100644 index 00000000..e6e0e504 --- /dev/null +++ b/apps/mobile/metro-now/metro-now Watch App/pages/stop/stop-detail.view.swift @@ -0,0 +1,94 @@ +// metro-now +// https://github.com/krystxf/metro-now + +import SwiftUI + +struct Departure { + let id: String + let headsing: String +} + +struct MainPagePlatform { + let id: String + let metroLine: MetroLine? + let departures: [ApiDeparture]? +} + +struct StopDeparturesView: View { + let title: String + let platforms: [MainPagePlatform] + + @State var selectedPlatformId: String? = nil + + var body: some View { + NavigationSplitView { + List(platforms, id: \.id, selection: $selectedPlatformId) { platform in + let itemColor = getMetroLineColor(platform.metroLine) + + if let departures = platform.departures, departures.count > 0 { + let hasNextDeparture = departures.count > 1 + + StopDepartureListItemView( + color: itemColor, + headsign: departures[0].headsign, + departure: departures[0].departure.predicted, + nextHeadsign: hasNextDeparture ? departures[1].headsign : nil, + nextDeparture: hasNextDeparture ? departures[1].departure.predicted : nil + ) + + } else { + StopDepartureListItemPlaceholderView( + color: itemColor + ) + } + } + + .navigationTitle(shortenStopName(title)) + + } detail: { + let platform = platforms.first( + where: { + $0.id == selectedPlatformId + + }) + + if let platform { + PlatformDetailView( + platformId: platform.id, + metroLine: platform.metroLine, + departures: platform.departures + ) + } else { + EmptyView() + } + } + } +} + +#Preview { + StopDeparturesView( + title: "Florenc", + platforms: [ + MainPagePlatform( + id: "U689Z101P", + metroLine: .B, + departures: nil + ), + MainPagePlatform( + id: "U689Z102P", + metroLine: .B, + departures: nil + ), + MainPagePlatform( + id: "U689Z121P", + metroLine: .C, + departures: nil + ), + MainPagePlatform( + id: "U689Z122P", + metroLine: .C, + departures: nil + ), + ] + ) +} diff --git a/apps/mobile/metro-now/metro-now.xcodeproj/project.pbxproj b/apps/mobile/metro-now/metro-now.xcodeproj/project.pbxproj index d27ca171..dc39206f 100644 --- a/apps/mobile/metro-now/metro-now.xcodeproj/project.pbxproj +++ b/apps/mobile/metro-now/metro-now.xcodeproj/project.pbxproj @@ -393,7 +393,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 0.2; PRODUCT_BUNDLE_IDENTIFIER = "com.krystof.metro-now.watchkitapp"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; @@ -401,7 +401,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 9; + WATCHOS_DEPLOYMENT_TARGET = 11; }; name = Debug; }; @@ -424,7 +424,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 0.2; PRODUCT_BUNDLE_IDENTIFIER = "com.krystof.metro-now.watchkitapp"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; @@ -433,7 +433,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; VALIDATE_PRODUCT = YES; - WATCHOS_DEPLOYMENT_TARGET = 9; + WATCHOS_DEPLOYMENT_TARGET = 11; }; name = Release; }; @@ -454,8 +454,8 @@ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -488,8 +488,8 @@ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)",