diff --git a/app/metro-now/Core/Map/MapStationAnnotationView.swift b/app/Common/Components/MapAnnotation/MetroAnnotation/MetroStationAnnotation.swift similarity index 71% rename from app/metro-now/Core/Map/MapStationAnnotationView.swift rename to app/Common/Components/MapAnnotation/MetroAnnotation/MetroStationAnnotation.swift index ac76d628..2c2cd244 100644 --- a/app/metro-now/Core/Map/MapStationAnnotationView.swift +++ b/app/Common/Components/MapAnnotation/MetroAnnotation/MetroStationAnnotation.swift @@ -7,6 +7,26 @@ import MapKit import SwiftUI +struct MetroStationIcon: View { + let metroLine: String + + var body: some View { + Image( + systemName: + getMetroLineIcon(metroLine) + ) + .imageScale(.medium) + .padding(5) + .foregroundStyle(.white) + .background(getMetroLineColor(metroLine)) + .clipShape(.rect(cornerRadius: 6)) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(.white, lineWidth: 2) + ) + } +} + struct MapMetroStationView: View { let metroLines: [String] @@ -16,20 +36,8 @@ struct MapMetroStationView: View { index, metroLine in let offset: CGFloat = index == 0 ? 0 : (-16 * CGFloat(index)) - Image( - systemName: - getMetroLineIcon(metroLine) - ) - .imageScale(.medium) - .padding(5) - .foregroundStyle(.white) - .background(getMetroLineColor(metroLine)) - .clipShape(.rect(cornerRadius: 6)) - .overlay( - RoundedRectangle(cornerRadius: 6) - .stroke(.white, lineWidth: 2) - ) - .offset(x: offset, y: offset) + MetroStationIcon(metroLine: metroLine) + .offset(x: offset, y: offset) } } } diff --git a/app/Common/Components/MetroDeparture/Card.swift b/app/Common/Components/MetroDeparture/Card.swift new file mode 100644 index 00000000..cf156cdd --- /dev/null +++ b/app/Common/Components/MetroDeparture/Card.swift @@ -0,0 +1,57 @@ + +import SwiftUI + +struct MetroDepartureCard: View { + let backgroundColor: Color + let content: Content + + init(backgroundColor: Color, @ViewBuilder content: () -> Content) { + self.backgroundColor = backgroundColor + self.content = content() + } + + var body: some View { + HStack { + content + }.padding(.horizontal, 20) + .padding(.vertical, 10) + .background( + LinearGradient( + colors: [ + backgroundColor, + backgroundColor.opacity(0.8), + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .clipShape(.rect(cornerRadius: 15)) + } +} + +#Preview { + MetroDepartureCard( + backgroundColor: getMetroLineColor("A")) + { + HStack { + MetroDepartureCardLabel(direction: "Nemocnice Motol", metroLine: "A") + Spacer() + } + } + MetroDepartureCard( + backgroundColor: getMetroLineColor("B")) + { + HStack { + MetroDepartureCardLabel(direction: "Černý Most", metroLine: "B") + Spacer() + } + } + MetroDepartureCard( + backgroundColor: getMetroLineColor("C")) + { + HStack { + MetroDepartureCardLabel(direction: "Haje", metroLine: "C") + Spacer() + } + } +} diff --git a/app/Common/Components/MetroDeparture/FirstDeparture.swift b/app/Common/Components/MetroDeparture/FirstDeparture.swift new file mode 100644 index 00000000..591e117a --- /dev/null +++ b/app/Common/Components/MetroDeparture/FirstDeparture.swift @@ -0,0 +1,18 @@ + +import SwiftUI + +struct MetroDepartureCardFirstDeparture: View { + let departureDate: Date + + var body: some View { + Text( + .currentDate, format: .reference( + to: departureDate, + allowedFields: [.second, .minute, .hour] + ) + ) + .fontWeight(.bold) + .foregroundStyle(.white) + .foregroundStyle(.white) + } +} diff --git a/app/Common/Components/MetroDeparture/Label.swift b/app/Common/Components/MetroDeparture/Label.swift new file mode 100644 index 00000000..3476dab4 --- /dev/null +++ b/app/Common/Components/MetroDeparture/Label.swift @@ -0,0 +1,21 @@ + +import SwiftUI + +struct MetroDepartureCardLabel: View { + let direction: String + let metroLine: String + + var body: some View { + Label( + title: { + Text(direction) + }, + icon: { + Image(systemName: getMetroLineIcon(metroLine)) + } + ) + .fontWeight(.bold) + .font(.headline) + .foregroundStyle(.white) + } +} diff --git a/app/Common/Components/MetroDeparture/MetroDeparture.swift b/app/Common/Components/MetroDeparture/MetroDeparture.swift new file mode 100644 index 00000000..fa7b0b53 --- /dev/null +++ b/app/Common/Components/MetroDeparture/MetroDeparture.swift @@ -0,0 +1,29 @@ + +import SwiftUI + +struct MetroDeparture: View { + @State var direction: String + + /// only first two items from array are shown + /// this view doesn't handle logic of deciding which departures are outdated (shouldn't be shown) + @State var departureDates: [Date] + + @State var metroLine: String + + var body: some View { + MetroDepartureCard(backgroundColor: getMetroLineColor(metroLine)) { + MetroDepartureCardLabel(direction: direction, metroLine: metroLine) + + Spacer() + + VStack { + if departureDates.count >= 1 { + MetroDepartureCardFirstDeparture(departureDate: departureDates[0]) + } + if departureDates.count >= 2 { + MetroDepartureCardSecondDeparture(departureDate: departureDates[1]) + } + } + } + } +} diff --git a/app/Common/Components/MetroDeparture/SecondDeparture.swift b/app/Common/Components/MetroDeparture/SecondDeparture.swift new file mode 100644 index 00000000..e04417a8 --- /dev/null +++ b/app/Common/Components/MetroDeparture/SecondDeparture.swift @@ -0,0 +1,22 @@ + +import SwiftUI + +struct MetroDepartureCardSecondDeparture: View { + let departureDate: Date + + var body: some View { + Text( + "Also in " + ).font(.caption2) + .fontWeight(.bold) + .foregroundStyle(.white) + .opacity(0.9) + Text( + .currentDate, format: .reference(to: departureDate, allowedFields: [.second, .minute, .hour]) + ) + .font(.caption2) + .fontWeight(.bold) + .foregroundStyle(.white) + .opacity(0.9) + } +} diff --git a/app/metro-now.xcodeproj/project.pbxproj b/app/metro-now.xcodeproj/project.pbxproj index 039041f6..1bfce890 100644 --- a/app/metro-now.xcodeproj/project.pbxproj +++ b/app/metro-now.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -21,7 +21,6 @@ 2D1B2C502BFAD8ED007ED5EB /* metroRoutesTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D1B2C4E2BFAD8ED007ED5EB /* metroRoutesTypes.swift */; }; 2D1B2C522BFAD90B007ED5EB /* metroStationsTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D1B2C512BFAD90B007ED5EB /* metroStationsTypes.swift */; }; 2D1B2C532BFAD90B007ED5EB /* metroStationsTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D1B2C512BFAD90B007ED5EB /* metroStationsTypes.swift */; }; - 2D350E672BFBE50600F68039 /* MapStationAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D350E662BFBE50600F68039 /* MapStationAnnotationView.swift */; }; 2D373CC22C0825790025BCDF /* ListWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D373CC12C0825790025BCDF /* ListWidgetView.swift */; }; 2D4486862BFAA10A005C59CE /* metro_now_watchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4486852BFAA10A005C59CE /* metro_now_watchApp.swift */; }; 2D4486882BFAA10A005C59CE /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4486872BFAA10A005C59CE /* ContentView.swift */; }; @@ -35,7 +34,6 @@ 2DC639DC2BF3CCBA00A72C7F /* metro_nowApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC639DB2BF3CCBA00A72C7F /* metro_nowApp.swift */; }; 2DC639E02BF3CCBC00A72C7F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2DC639DF2BF3CCBC00A72C7F /* Assets.xcassets */; }; 2DC639E32BF3CCBC00A72C7F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2DC639E22BF3CCBC00A72C7F /* Preview Assets.xcassets */; }; - 2DC639ED2BF3CFCF00A72C7F /* PlatformListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC639EC2BF3CFCF00A72C7F /* PlatformListItem.swift */; }; 2DC63A042BF4C1E200A72C7F /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC63A032BF4C1E200A72C7F /* MainTabView.swift */; }; 2DC63A082BF4C25B00A72C7F /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC63A072BF4C25B00A72C7F /* MapView.swift */; }; 2DC63A242BF5266700A72C7F /* metroUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC63A232BF5266700A72C7F /* metroUtilsTests.swift */; }; @@ -128,7 +126,6 @@ 2D1B2C4A2BFAD807007ED5EB /* fileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = fileUtils.swift; sourceTree = ""; }; 2D1B2C4E2BFAD8ED007ED5EB /* metroRoutesTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = metroRoutesTypes.swift; sourceTree = ""; }; 2D1B2C512BFAD90B007ED5EB /* metroStationsTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = metroStationsTypes.swift; sourceTree = ""; }; - 2D350E662BFBE50600F68039 /* MapStationAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapStationAnnotationView.swift; sourceTree = ""; }; 2D373CC12C0825790025BCDF /* ListWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListWidgetView.swift; sourceTree = ""; }; 2D4486832BFAA10A005C59CE /* metro-now-watch Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "metro-now-watch Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D4486852BFAA10A005C59CE /* metro_now_watchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = metro_now_watchApp.swift; sourceTree = ""; }; @@ -143,7 +140,6 @@ 2DC639DB2BF3CCBA00A72C7F /* metro_nowApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = metro_nowApp.swift; sourceTree = ""; }; 2DC639DF2BF3CCBC00A72C7F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2DC639E22BF3CCBC00A72C7F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 2DC639EC2BF3CFCF00A72C7F /* PlatformListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformListItem.swift; sourceTree = ""; }; 2DC63A032BF4C1E200A72C7F /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; 2DC63A072BF4C25B00A72C7F /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; 2DC63A182BF50E8F00A72C7F /* metro-now-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "metro-now-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -170,6 +166,32 @@ 2DF66D972BFD39B000B31FA2 /* test-coordinates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "test-coordinates.swift"; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 2D5BA5E22C382D8D0055F12A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Card.swift, + FirstDeparture.swift, + Label.swift, + MetroDeparture.swift, + SecondDeparture.swift, + ); + target = 2DC639D72BF3CCBA00A72C7F /* metro-now */; + }; + 2D5BA5EB2C382FEA0055F12A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + MetroAnnotation/MetroStationAnnotation.swift, + ); + target = 2DC639D72BF3CCBA00A72C7F /* metro-now */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 2D5BA5DE2C382D6A0055F12A /* MetroDeparture */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (2D5BA5E22C382D8D0055F12A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = MetroDeparture; sourceTree = ""; }; + 2D5BA5EA2C382FC30055F12A /* MapAnnotation */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (2D5BA5EB2C382FEA0055F12A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = MapAnnotation; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 2D4486802BFAA10A005C59CE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -259,6 +281,8 @@ 2D1B2C5B2BFADAAA007ED5EB /* Components */ = { isa = PBXGroup; children = ( + 2D5BA5EA2C382FC30055F12A /* MapAnnotation */, + 2D5BA5DE2C382D6A0055F12A /* MetroDeparture */, ); path = Components; sourceTree = ""; @@ -329,20 +353,11 @@ isa = PBXGroup; children = ( 2DC63A062BF4C24C00A72C7F /* Map */, - 2DC639EA2BF3CD4700A72C7F /* PlatformList */, 2DC63A052BF4C1E800A72C7F /* TabBar */, ); path = Core; sourceTree = ""; }; - 2DC639EA2BF3CD4700A72C7F /* PlatformList */ = { - isa = PBXGroup; - children = ( - 2DC639EC2BF3CFCF00A72C7F /* PlatformListItem.swift */, - ); - path = PlatformList; - sourceTree = ""; - }; 2DC63A052BF4C1E800A72C7F /* TabBar */ = { isa = PBXGroup; children = ( @@ -354,10 +369,9 @@ 2DC63A062BF4C24C00A72C7F /* Map */ = { isa = PBXGroup; children = ( - 2D65850D2C251761002D7F45 /* StationDetailView.swift */, 2D65850F2C253944002D7F45 /* DetailedMapView.swift */, 2DC63A072BF4C25B00A72C7F /* MapView.swift */, - 2D350E662BFBE50600F68039 /* MapStationAnnotationView.swift */, + 2D65850D2C251761002D7F45 /* StationDetailView.swift */, ); path = Map; sourceTree = ""; @@ -632,7 +646,6 @@ 2D1B2C4B2BFAD807007ED5EB /* fileUtils.swift in Sources */, 2D1B2C482BFAD7F2007ED5EB /* metroUtils.swift in Sources */, 2D1B2C3B2BFAD6CC007ED5EB /* LocationModel.swift in Sources */, - 2D350E672BFBE50600F68039 /* MapStationAnnotationView.swift in Sources */, 2D1B2C422BFAD72C007ED5EB /* jsonUtils.swift in Sources */, 2DC639DC2BF3CCBA00A72C7F /* metro_nowApp.swift in Sources */, 2D6585102C253944002D7F45 /* DetailedMapView.swift in Sources */, @@ -642,7 +655,6 @@ 2DF48A712C02514E002F754E /* departuresResponseTypes.swift in Sources */, 2D1B2C4F2BFAD8ED007ED5EB /* metroRoutesTypes.swift in Sources */, 2D1B2C452BFAD7DB007ED5EB /* mapUtils.swift in Sources */, - 2DC639ED2BF3CFCF00A72C7F /* PlatformListItem.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/app/metro-now/Core/Map/StationDetailView.swift b/app/metro-now/Core/Map/StationDetailView.swift index b58ab581..c0f9aec4 100644 --- a/app/metro-now/Core/Map/StationDetailView.swift +++ b/app/metro-now/Core/Map/StationDetailView.swift @@ -108,7 +108,7 @@ struct StationDetailView: View { let d = departures[k]! if d.count > 0 { - PlatformListItemView( + MetroDeparture( direction: d[0].heading, departureDates: d.map(\.departure), metroLine: d[0].line diff --git a/app/metro-now/Core/PlatformList/PlatformListItem.swift b/app/metro-now/Core/PlatformList/PlatformListItem.swift deleted file mode 100644 index 6b5be9a5..00000000 --- a/app/metro-now/Core/PlatformList/PlatformListItem.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// metro-now -// -// Created by Kryštof Krátký on 14.05.2024. -// - -import SwiftUI - -struct PlatformListItemView: View { - @State var direction: String - - /// only first two items from array are shown - /// this view doesn't handle logic of deciding which departures are outdated (shouldn't be shown) - @State var departureDates: [Date] - - @State var metroLine: String - - var body: some View { - HStack { - Label( - title: { Text(direction) }, - icon: { Image(systemName: getMetroLineIcon(metroLine)) } - ) - .fontWeight(.bold) - .font(.headline) - .foregroundStyle(.white) - - Spacer() - - VStack { - if departureDates.count >= 1 { - Text( - .currentDate, format: .reference(to: departureDates[0], allowedFields: [.second, .minute, .hour]) - ) - .fontWeight(.bold) - .foregroundStyle(.white) - .foregroundStyle(.white) - } - if departureDates.count >= 2 { - Text( - "Also in " - ).font(.caption2) - .fontWeight(.bold) - .foregroundStyle(.white) - .opacity(0.9) - Text( - .currentDate, format: .reference(to: departureDates[1], allowedFields: [.second, .minute, .hour]) - ) - .font(.caption2) - .fontWeight(.bold) - .foregroundStyle(.white) - .opacity(0.9) - } - } - } - .padding(.horizontal, 20) - .padding(.vertical, 10) - .background( - LinearGradient( - colors: [ - getMetroLineColor(metroLine), - getMetroLineColor(metroLine).opacity(0.8), - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - - .clipShape(.rect(cornerRadius: 15)) - } -} diff --git a/app/metro-now/Core/TabBar/MainTabView.swift b/app/metro-now/Core/TabBar/MainTabView.swift index 04ba61c7..3cbb86b6 100644 --- a/app/metro-now/Core/TabBar/MainTabView.swift +++ b/app/metro-now/Core/TabBar/MainTabView.swift @@ -30,7 +30,6 @@ struct MainTabView: View { } } .onReceive(locationModel.$location) { location in - guard let location else { print("Unknown location") return