diff --git a/Haltestellenmonitor1-DD.xcodeproj/project.pbxproj b/Haltestellenmonitor1-DD.xcodeproj/project.pbxproj index d673b7a..c4f3016 100644 --- a/Haltestellenmonitor1-DD.xcodeproj/project.pbxproj +++ b/Haltestellenmonitor1-DD.xcodeproj/project.pbxproj @@ -190,6 +190,7 @@ D0D1998229F40C4B00946D87 /* StopRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D1998129F40C4B00946D87 /* StopRow.swift */; }; D0D1998929F412DF00946D87 /* DepartureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D1998829F412DF00946D87 /* DepartureView.swift */; }; D0D1998B29F414BD00946D87 /* DepartureRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D1998A29F414BD00946D87 /* DepartureRow.swift */; }; + F1520EE32AE96E5A001E3E05 /* widgetLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1520EE12AE96E5A001E3E05 /* widgetLocationManager.swift */; }; F19FC6EE2AE9974C00F65316 /* ColorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F19FC6ED2AE9974C00F65316 /* ColorManager.swift */; }; F1A7E7CB2AEA988C00E1910F /* TripSectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A7E7CA2AEA988C00E1910F /* TripSectionViewModel.swift */; }; F1BC367D2BC3E16100629780 /* MotColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BC367C2BC3E16100629780 /* MotColors.swift */; }; @@ -347,6 +348,7 @@ D0D1998129F40C4B00946D87 /* StopRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopRow.swift; sourceTree = ""; }; D0D1998829F412DF00946D87 /* DepartureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DepartureView.swift; sourceTree = ""; }; D0D1998A29F414BD00946D87 /* DepartureRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DepartureRow.swift; sourceTree = ""; }; + F1520EE12AE96E5A001E3E05 /* widgetLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = widgetLocationManager.swift; sourceTree = ""; }; F19FC6ED2AE9974C00F65316 /* ColorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorManager.swift; sourceTree = ""; }; F1A7E7CA2AEA988C00E1910F /* TripSectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripSectionViewModel.swift; sourceTree = ""; }; F1BC367C2BC3E16100629780 /* MotColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotColors.swift; sourceTree = ""; }; @@ -572,6 +574,7 @@ D0BD668629EFE0AA004E574C /* MonitorWidget */ = { isa = PBXGroup; children = ( + F1520EE02AE96E2C001E3E05 /* Managers */, D0BD66C629EFF621004E574C /* Models */, D0BD669929EFE169004E574C /* Views */, D0BD668729EFE0AA004E574C /* MonitorWidgetBundle.swift */, @@ -662,6 +665,14 @@ path = Rows; sourceTree = ""; }; + F1520EE02AE96E2C001E3E05 /* Managers */ = { + isa = PBXGroup; + children = ( + F1520EE12AE96E5A001E3E05 /* widgetLocationManager.swift */, + ); + path = Managers; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -764,7 +775,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1430; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1540; TargetAttributes = { D00059C12A52F3800007468A = { CreatedOnToolsVersion = 14.3.1; @@ -943,6 +954,7 @@ D0C1ECC929F28C2F001D214D /* TripAttributes.swift in Sources */, D0142C3B2A0F90AA00646C94 /* DepartureBinding.swift in Sources */, D0BD66EF29F03C0D004E574C /* PartialRoute.swift in Sources */, + F1520EE32AE96E5A001E3E05 /* widgetLocationManager.swift in Sources */, D0BD66A129EFEA0D004E574C /* DepartureStatus.swift in Sources */, D0BD669E29EFEA03004E574C /* Stop.swift in Sources */, D0BD66C229EFEF0F004E574C /* DateParser.swift in Sources */, @@ -1105,10 +1117,10 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = WGL37RNHJ6; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = "dev.hanashi.Haltestellenmonitor1-DDTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; @@ -1123,10 +1135,10 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = WGL37RNHJ6; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = "dev.hanashi.Haltestellenmonitor1-DDTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; @@ -1140,6 +1152,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -1172,6 +1185,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -1200,6 +1214,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -1232,6 +1247,8 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1258,7 +1275,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Haltestellenmonitor1-DD/Haltestellenmonitor1-DD.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_ASSET_PATHS = "\"Haltestellenmonitor1-DD/Preview Content\""; DEVELOPMENT_TEAM = WGL37RNHJ6; ENABLE_PREVIEWS = YES; @@ -1274,11 +1291,12 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = "eu.Hanashi.Haltestellenmonitor1-DD"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1295,7 +1313,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Haltestellenmonitor1-DD/Haltestellenmonitor1-DD.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_ASSET_PATHS = "\"Haltestellenmonitor1-DD/Preview Content\""; DEVELOPMENT_TEAM = WGL37RNHJ6; ENABLE_PREVIEWS = YES; @@ -1311,11 +1329,12 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = "eu.Hanashi.Haltestellenmonitor1-DD"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1331,19 +1350,20 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = MonitorWidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = WGL37RNHJ6; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MonitorWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MonitorWidget; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Der Standort wird verwendet um den Abstand zur nächsten Haltestelle zu berechnen."; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = "eu.Hanashi.Haltestellenmonitor1-DD.MonitorWidget"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1361,7 +1381,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = MonitorWidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = WGL37RNHJ6; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MonitorWidget/Info.plist; @@ -1373,7 +1393,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = "eu.Hanashi.Haltestellenmonitor1-DD.MonitorWidget"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1389,7 +1409,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MonitorIntents/MonitorIntents.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = WGL37RNHJ6; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MonitorIntents/Info.plist; @@ -1401,7 +1421,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = "eu.Hanashi.Haltestellenmonitor1-DD.MonitorIntents"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1417,7 +1437,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MonitorIntents/MonitorIntents.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = WGL37RNHJ6; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MonitorIntents/Info.plist; @@ -1429,7 +1449,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = "eu.Hanashi.Haltestellenmonitor1-DD.MonitorIntents"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1446,7 +1466,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_ASSET_PATHS = "\"WatchMonitor Watch App/Preview Content\""; DEVELOPMENT_TEAM = WGL37RNHJ6; ENABLE_PREVIEWS = YES; @@ -1454,11 +1474,12 @@ INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Der Standort wird verwendet um den Abstand zur nächsten Haltestelle zu berechnen."; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "eu.Hanashi.Haltestellenmonitor1-DD"; + INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = "eu.Hanashi.Haltestellenmonitor1-DD.watchkitapp"; PRODUCT_NAME = "Haltestellenmonitor Dresden"; SDKROOT = watchos; @@ -1476,7 +1497,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_ASSET_PATHS = "\"WatchMonitor Watch App/Preview Content\""; DEVELOPMENT_TEAM = WGL37RNHJ6; ENABLE_PREVIEWS = YES; @@ -1484,11 +1505,12 @@ INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Der Standort wird verwendet um den Abstand zur nächsten Haltestelle zu berechnen."; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "eu.Hanashi.Haltestellenmonitor1-DD"; + INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.1; + MARKETING_VERSION = 2.2; PRODUCT_BUNDLE_IDENTIFIER = "eu.Hanashi.Haltestellenmonitor1-DD.watchkitapp"; PRODUCT_NAME = "Haltestellenmonitor Dresden"; SDKROOT = watchos; diff --git a/Haltestellenmonitor1-DD.xcodeproj/xcshareddata/xcschemes/MonitorWidgetExtension.xcscheme b/Haltestellenmonitor1-DD.xcodeproj/xcshareddata/xcschemes/MonitorWidgetExtension.xcscheme new file mode 100644 index 0000000..86aaf0e --- /dev/null +++ b/Haltestellenmonitor1-DD.xcodeproj/xcshareddata/xcschemes/MonitorWidgetExtension.xcscheme @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Haltestellenmonitor1-DD.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Haltestellenmonitor1-DD.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 4873383..7233ad1 100644 --- a/Haltestellenmonitor1-DD.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Haltestellenmonitor1-DD.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,12 +12,12 @@ MonitorIntents.xcscheme_^#shared#^_ orderHint - 2 + 1 MonitorWidgetExtension.xcscheme_^#shared#^_ orderHint - 1 + 2 WatchMonitor Watch App.xcscheme_^#shared#^_ diff --git a/Haltestellenmonitor1-DD/Models/FavoriteStop.swift b/Haltestellenmonitor1-DD/Models/FavoriteStop.swift index 9ea36cb..8e48372 100644 --- a/Haltestellenmonitor1-DD/Models/FavoriteStop.swift +++ b/Haltestellenmonitor1-DD/Models/FavoriteStop.swift @@ -11,7 +11,7 @@ import Foundation @Published var favorites: [Int] init() { - if let data = UserDefaults(suiteName: "group.dev.hanashi.Haltestellenmonitor")?.data(forKey: "FavoriteStops") { + if let data = UserDefaults(suiteName: "group.eu.hanashi.Haltestellenmonitor")?.data(forKey: "FavoriteStops") { if let decoded = try? JSONDecoder().decode([Int].self, from: data) { favorites = decoded return @@ -48,7 +48,12 @@ import Foundation func save() { if let encoded = try? JSONEncoder().encode(favorites) { - UserDefaults(suiteName: "group.dev.hanashi.Haltestellenmonitor")?.set(encoded, forKey: "FavoriteStops") + UserDefaults(suiteName: "group.eu.hanashi.Haltestellenmonitor")?.set(encoded, forKey: "FavoriteStops") } + // fürs Widget + + let sharedUserDefaults = UserDefaults(suiteName: "group.eu.hanashi.Haltestellenmonitor") + sharedUserDefaults?.set(favorites, forKey: "WidgetFavs") + } } diff --git a/Haltestellenmonitor1-DD/Views/ConnectionView.swift b/Haltestellenmonitor1-DD/Views/ConnectionView.swift index edd5fd3..c77fbb2 100644 --- a/Haltestellenmonitor1-DD/Views/ConnectionView.swift +++ b/Haltestellenmonitor1-DD/Views/ConnectionView.swift @@ -253,6 +253,7 @@ struct ConnectionView: View { request.httpMethod = "POST" request.httpBody = try? JSONEncoder().encode(requestData) request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Haltestellenmonitor Dresden v2", forHTTPHeaderField: "User-Agent") do { let (content, _) = try await URLSession.shared.data(for: request) diff --git a/Haltestellenmonitor1-DD/Views/DepartureView.swift b/Haltestellenmonitor1-DD/Views/DepartureView.swift index f0f4a24..bbe8bc9 100644 --- a/Haltestellenmonitor1-DD/Views/DepartureView.swift +++ b/Haltestellenmonitor1-DD/Views/DepartureView.swift @@ -147,6 +147,7 @@ struct DepartureView: View { request.httpMethod = "POST" request.httpBody = try? JSONEncoder().encode(DepartureRequest(stopid: String(stop.stopId), time: dateTime.ISO8601Format())) request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Haltestellenmonitor Dresden v2", forHTTPHeaderField: "User-Agent") do { let (content, _) = try await URLSession.shared.data(for: request) @@ -210,6 +211,7 @@ struct DepartureView: View { request.httpMethod = "POST" request.httpBody = try? JSONEncoder().encode(ActivityRequest(token: token, stopID: String(stop.stopId), tripID: departure.Id, time: departure.getDateTime().ISO8601Format(), scheduledTime: departure.ScheduledTime, realTime: departure.RealTime)) request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Haltestellenmonitor Dresden v2", forHTTPHeaderField: "User-Agent") let task = URLSession.shared.dataTask(with: request) {(data, response, error) in guard error == nil else { diff --git a/Haltestellenmonitor1-DD/Views/SingleTripView.swift b/Haltestellenmonitor1-DD/Views/SingleTripView.swift index e035d18..b3b7195 100644 --- a/Haltestellenmonitor1-DD/Views/SingleTripView.swift +++ b/Haltestellenmonitor1-DD/Views/SingleTripView.swift @@ -87,6 +87,7 @@ struct SingleTripView: View { request.httpMethod = "POST" request.httpBody = try? JSONEncoder().encode(SingleTripRequest(stopID: String(stop.stopId), tripID: departure.Id, time: departure.getDateTime().ISO8601Format())) request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Haltestellenmonitor Dresden v2", forHTTPHeaderField: "User-Agent") do { let (content, _) = try await URLSession.shared.data(for: request) @@ -141,6 +142,7 @@ struct SingleTripView: View { request.httpMethod = "POST" request.httpBody = try? JSONEncoder().encode(ActivityRequest(token: token, stopID: String(stop.stopId), tripID: departure.Id, time: departure.getDateTime().ISO8601Format(), scheduledTime: departure.ScheduledTime, realTime: departure.RealTime)) request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Haltestellenmonitor Dresden v2", forHTTPHeaderField: "User-Agent") let task = URLSession.shared.dataTask(with: request) {(data, response, error) in guard error == nil else { diff --git a/MonitorWidget/Info.plist b/MonitorWidget/Info.plist index 0f118fb..eed247e 100644 --- a/MonitorWidget/Info.plist +++ b/MonitorWidget/Info.plist @@ -2,6 +2,16 @@ + NSLocationUsageDescription + Um Dir stets die nächste favorisierte Haltestelle anzuzeigen, benötigen wir Zugriff auf Standortdaten. + CFBundleIdentifier + + NSLocationAlwaysAndWhenInUseUsageDescription + Um Dir stets die nächste favorisierte Haltestelle anzuzeigen, benötigen wir Zugriff auf Standortdaten. + NSLocationWhenInUseUsageDescription + Um Dir stets die nächste favorisierte Haltestelle anzuzeigen, benötigen wir Zugriff auf Standortdaten. + NSWidgetWantsLocation + NSExtension NSExtensionPointIdentifier diff --git a/MonitorWidget/Managers/widgetLocationManager.swift b/MonitorWidget/Managers/widgetLocationManager.swift new file mode 100644 index 0000000..de68ae7 --- /dev/null +++ b/MonitorWidget/Managers/widgetLocationManager.swift @@ -0,0 +1,49 @@ +// +// widgetLocationManager.swift +// Haltestellenmonitor1-DD +// +// Created by Tom Braune on 03.11.23 +// Credit to https://github.com/AKORA-Studios for helping with the LocationManager +// + +import Foundation +import CoreLocation + +class WidgetLocationManager: NSObject, CLLocationManagerDelegate { + var locationManager = CLLocationManager() + + private var handler: ((CLLocation) -> Void)? + private var completion: ((CLLocation) -> Void)? = nil + + @Published var llocation: CLLocation? + + override init() { + super.init() + self.locationManager.delegate = self + DispatchQueue.main.async { + if self.locationManager.authorizationStatus == .notDetermined { + self.locationManager.requestWhenInUseAuthorization() + } + } + } + + func fetchLocation(completion: @escaping (CLLocation) -> Void) async { + self.completion = completion + locationManager.requestLocation() + } + + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + guard let location = locations.first else { return } + + self.llocation = location + + if (completion != nil) { + completion!(location) + completion = nil + } + } + + func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + print("MonitorWidgetLocationManagerError ", error) + } +} diff --git a/MonitorWidget/Models/MonitorEntry.swift b/MonitorWidget/Models/MonitorEntry.swift index cef5439..dce1b38 100644 --- a/MonitorWidget/Models/MonitorEntry.swift +++ b/MonitorWidget/Models/MonitorEntry.swift @@ -3,27 +3,36 @@ // MonitorWidgetExtension // // Created by Peter Lohse on 19.04.23. +// Modified by Tom Braune on 03.11.23. // import WidgetKit import SwiftUI import Intents +import CoreLocation +import MapKit struct MonitorEntry: TimelineEntry { let date: Date let configuration: ConfigurationIntent let departureMonitor: DepartureMonitor? + let widgetLocationManager = WidgetLocationManager() - func getStopID() -> String { + func getStopID(Name : String) -> String { + if Name == "_" { + return configuration.stopType?.identifier ?? "33000028" + } + + // Retriving the stopID by the Stops' name + let stop = stops.first(where: { String($0.name) == Name }) + + if stop != nil { + return String(stop!.stopId) + } + return configuration.stopType?.identifier ?? "33000028" } - func getStopName() -> String { - let stopID = self.getStopID() - let stop = stops.first(where: { String($0.stopId) == stopID }) - return stop?.name ?? "Unbekannt" - } - func getLineFilters() -> [String]? { if (configuration.lineFilter == nil || configuration.lineFilter?.isEmpty == true) { return nil diff --git a/MonitorWidget/MonitorWidget.swift b/MonitorWidget/MonitorWidget.swift index 16c2857..9212615 100644 --- a/MonitorWidget/MonitorWidget.swift +++ b/MonitorWidget/MonitorWidget.swift @@ -3,14 +3,23 @@ // MonitorWidget // // Created by Peter Lohse on 19.04.23. +// Modiefied by Tom Braune on 03.11.23. +// Credit to https://github.com/AKORA-Studios for helping with the LocationManager // import WidgetKit import SwiftUI import Intents +import CoreLocation +import MapKit -struct Provider: IntentTimelineProvider { +class Provider: IntentTimelineProvider { + + typealias Entry = MonitorEntry + + let widgetLocationManager = WidgetLocationManager() + func placeholder(in context: Context) -> MonitorEntry { MonitorEntry(date: Date(), configuration: ConfigurationIntent(), departureMonitor: nil) @@ -20,28 +29,72 @@ struct Provider: IntentTimelineProvider { let entry = MonitorEntry(date: Date(), configuration: configuration, departureMonitor: departureM) completion(entry) } - + + func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> ()) { - let stopID = configuration.stopType?.identifier ?? "33000028" + + var stopID: String = "33000028" + var favoriteStops: [Int] = [] + + if configuration.favoriteFilter == FavoriteFilter.true { + if let data = UserDefaults(suiteName: "group.eu.hanashi.Haltestellenmonitor")?.data(forKey: "FavoriteStops") { + if let decoded = try? JSONDecoder().decode([Int].self, from: data) { + favoriteStops = decoded + } + } + + // Retrieving stop data for marked favorites + var favStops : [Stop] = stops.filter{favorite in + return favoriteStops.contains(favorite.stopId) + } + + if favStops.isEmpty { + print("No favorites found.") + stopID = "33000028" + + } else { + var favStopsLoc : [Stop] = [] + // Retrieving location data + Task() { + await widgetLocationManager.fetchLocation { llocation in + print(">>>", llocation.coordinate)} + } + // Dresden town hall GPS coordinates as default + let location = widgetLocationManager.llocation ?? CLLocation(latitude: +51.04750, longitude: +13.74035) + + // sorting by distance + favStops.forEach {stop in + var newStop = stop + newStop.distance = location.distance(from: CLLocation(latitude: stop.coordinates.latitude, longitude: stop.coordinates.longitude)) + favStopsLoc.append(newStop) + } + favStops = favStopsLoc.sorted{$0.getDistance() < $1.getDistance()} + + stopID = String(favStops[0].stopId) + } + } else { + stopID = configuration.stopType?.identifier ?? "33000028" + } let url = URL(string: "https://webapi.vvo-online.de/dm")! var request = URLRequest(url: url, timeoutInterval: 20) request.httpMethod = "POST" request.httpBody = try? JSONEncoder().encode(DepartureRequest(stopid: stopID)) request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Haltestellenmonitor Dresden v2", forHTTPHeaderField: "User-Agent") let task = URLSession.shared.dataTask(with: request) {(data, response, error) in var entries: [MonitorEntry] = [] var departureMonitor: DepartureMonitor? = nil guard error == nil else { print ("error: \(error!)") - getTimeline(for: configuration, in: context, completion: completion) + self.getTimeline(for: configuration, in: context, completion: completion) return } guard let content = data else { print("No data") - getTimeline(for: configuration, in: context, completion: completion) + self.getTimeline(for: configuration, in: context, completion: completion) return } @@ -52,9 +105,10 @@ struct Provider: IntentTimelineProvider { do { let decoder = JSONDecoder() departureMonitor = try decoder.decode(DepartureMonitor.self, from: content) + print(content) } catch { print(error) - getTimeline(for: configuration, in: context, completion: completion) + self.getTimeline(for: configuration, in: context, completion: completion) return } @@ -76,12 +130,28 @@ struct Provider: IntentTimelineProvider { struct MonitorWidget: Widget { let kind: String = "MonitorWidget" - + var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in MonitorWidgetEntryView(entry: entry) } .configurationDisplayName("Haltestellenmonitor") .description("Widget zur Anzeige der Abfahrten an einer Haltestelle.") + .contentMarginsDisabledIfAvailable() + } +} + +extension WidgetConfiguration +{ + func contentMarginsDisabledIfAvailable() -> some WidgetConfiguration + { + if #available(iOSApplicationExtension 17.0, *) + { + return self.contentMarginsDisabled() + } + else + { + return self + } } } diff --git a/MonitorWidget/Views/MonitorWidgetEntryView.swift b/MonitorWidget/Views/MonitorWidgetEntryView.swift index c9417dd..3e32ff0 100644 --- a/MonitorWidget/Views/MonitorWidgetEntryView.swift +++ b/MonitorWidget/Views/MonitorWidgetEntryView.swift @@ -3,23 +3,25 @@ // MonitorWidgetExtension // // Created by Peter Lohse on 19.04.23. +// Modified by Tom Braune on 03.11.23. // import WidgetKit import SwiftUI import Intents +import CoreLocation struct MonitorWidgetEntryView : View { @Environment(\.colorScheme) var colorScheme @Environment(\.widgetFamily) var widgetFamily var entry: Provider.Entry - + var body: some View { let prefix = (widgetFamily == .systemLarge || widgetFamily == .systemExtraLarge) ? 16 : 5 HStack(alignment: .top) { VStack(alignment: .leading) { - Text(entry.getStopName()) + Text(entry.departureMonitor?.Name ?? "") .font(.headline) .padding(.bottom, 1.0) if (entry.filterDepartures(departures: entry.departureMonitor?.Departures ?? []).isEmpty) { @@ -36,12 +38,24 @@ struct MonitorWidgetEntryView : View { } .padding([.top, .leading, .bottom]) .padding(.trailing, 5.0) - .background(colorScheme == .dark ? Color.black : Color.yellow) - .widgetURL(URL(string: "widget://stop/\(entry.getStopID().addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)")) + .widgetBackground(colorScheme == .dark ? Color.black : Color.yellow) + .widgetURL(URL(string: "widget://stop/\(entry.getStopID(Name: entry.departureMonitor?.Name ?? "-").addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)")) .dynamicTypeSize(.medium ... .large) } } +extension View { + func widgetBackground(_ backgroundView: some View) -> some View { + if #available(iOSApplicationExtension 17.0, *) { + return containerBackground(for: .widget) { + backgroundView + } + } else { + return background(backgroundView) + } + } +} + struct MonitorWidget_Previews: PreviewProvider { static var previews: some View { MonitorWidgetEntryView(entry: MonitorEntry(date: Date(), configuration: ConfigurationIntent(), departureMonitor: departureM)) diff --git a/MonitorWidget/de.lproj/MonitorWidget.intentdefinition b/MonitorWidget/de.lproj/MonitorWidget.intentdefinition index 54480d1..5860e0d 100644 --- a/MonitorWidget/de.lproj/MonitorWidget.intentdefinition +++ b/MonitorWidget/de.lproj/MonitorWidget.intentdefinition @@ -47,17 +47,60 @@ + + INEnumDisplayName + Favorite Filter + INEnumDisplayNameID + CaHjqO + INEnumGeneratesHeader + + INEnumName + FavoriteFilter + INEnumType + Regular + INEnumValues + + + INEnumValueDisplayName + unknown + INEnumValueDisplayNameID + OrxHxJ + INEnumValueName + unknown + + + INEnumValueDisplayName + Ein + INEnumValueDisplayNameID + KK7Dbe + INEnumValueIndex + 1 + INEnumValueName + true + + + INEnumValueDisplayName + Gewählte Haltestelle beibehalten + INEnumValueDisplayNameID + Hl1d2S + INEnumValueIndex + 2 + INEnumValueName + false + + + INIntentDefinitionModelVersion 1.2 INIntentDefinitionNamespace 88xZPY INIntentDefinitionSystemVersion - 22E261 + 23A344 INIntentDefinitionToolsBuildVersion - 14E222b + 15A240d INIntentDefinitionToolsVersion - 14.3 + 15.0 INIntents @@ -70,7 +113,7 @@ INIntentIneligibleForSuggestions INIntentLastParameterTag - 11 + 14 INIntentName Configuration INIntentParameters @@ -104,26 +147,6 @@ INIntentParameterPromptDialogType Primary - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} options matching ‘${stopType}’. - INIntentParameterPromptDialogFormatStringID - mpAhY8 - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${stopType}’? - INIntentParameterPromptDialogFormatStringID - uf7Ra2 - INIntentParameterPromptDialogType - Confirmation - INIntentParameterSupportsDynamicEnumeration @@ -208,6 +231,50 @@ INIntentParameterObjectTypeNamespace 88xZPY INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterSupportsMultipleValues + + INIntentParameterTag + 11 + INIntentParameterType + Object + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Automatisch nächsten Favoriten anzeigen + INIntentParameterDisplayNameID + cKrlgy + INIntentParameterDisplayPriority + 4 + INIntentParameterEnumType + FavoriteFilter + INIntentParameterEnumTypeNamespace + 88xZPY + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + false + + INIntentParameterName + favoriteFilter + INIntentParameterPromptDialogs INIntentParameterPromptDialogCustom @@ -225,9 +292,9 @@ INIntentParameterPromptDialogCustom INIntentParameterPromptDialogFormatString - There are ${count} options matching ‘${lineFilter}’. + There are 2 options matching ‘${favoriteFilter}’. INIntentParameterPromptDialogFormatStringID - 78h9NL + o6SqGz INIntentParameterPromptDialogType DisambiguationIntroduction @@ -235,21 +302,17 @@ INIntentParameterPromptDialogCustom INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${lineFilter}’? + Just to confirm, you wanted ‘${favoriteFilter}’? INIntentParameterPromptDialogFormatStringID - OjLOW6 + If7lVM INIntentParameterPromptDialogType Confirmation - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterSupportsMultipleValues - INIntentParameterTag - 11 + 14 INIntentParameterType - Object + Integer INIntentResponse diff --git a/MonitorWidgetExtension.entitlements b/MonitorWidgetExtension.entitlements index fc07546..46ca08d 100644 --- a/MonitorWidgetExtension.entitlements +++ b/MonitorWidgetExtension.entitlements @@ -2,8 +2,14 @@ + aps-environment + development com.apple.security.app-sandbox + com.apple.security.application-groups + + group.eu.hanashi.Haltestellenmonitor + com.apple.security.network.client com.apple.security.personal-information.location diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b14767 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Haltestellenmonitor-v3 +Haltestellen Navigator für Dresden + +[](https://apps.apple.com/de/app/haltestellenmonitor-dresden/id1266844674) + +### Showcase +

+ + + +

+ +## Anforderungen +* iOS: 16.4 +* Xcode 14.3* + +Für mehr Infos siehe: https://developer.apple.com/support/xcode/ + +## How to contribute +0. Erstell ein Issue mit deinem Vorsachlag +1. Fork das Projekt +2. Setzte deinen Vorschlag um +3. Erstelle eine Pullrequest +4. Warte bis ein Maintainer diese annimmt oder Feedback gibt + +Bitte achte darauf das neuer Code zum Codestyle des bisher vorhandenen Codes ähnlich ist und verständlich geschrieben ist. + +### Allgemeine Anmerkungen dazu +* Keine Binary Files (wie z.B. .DS_Store) Datein oder so pushen +* Keine Identifier vom z.B. dem Development Team des projektes in der Pullrequest ändern +* Bei Fragen zum Codestyle: [siehe hier](https://google.github.io/swift/) + +## How to Install and Run the Project +``` +git clone https://github.com/HanashiDev/Haltestellenmonitor-v3 +``` + +1. Öffne die workspace Datei in Xcode +2. Nutze CMD + R um das App Shema (Haltestellenmonitor1-DD) auszuführen c: + + +## License +Das Projekt läuft unter der GNU GENERAL PUBLIC LICENSE. [Für mehr Info siehe hier](/LICENCES.md) \ No newline at end of file diff --git a/WatchMonitor Watch App/DepartureView.swift b/WatchMonitor Watch App/DepartureView.swift index 38a31e8..c52f133 100644 --- a/WatchMonitor Watch App/DepartureView.swift +++ b/WatchMonitor Watch App/DepartureView.swift @@ -54,6 +54,7 @@ struct DepartureView: View { request.httpMethod = "POST" request.httpBody = try? JSONEncoder().encode(DepartureRequest(stopid: String(stop.stopId))) request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Haltestellenmonitor Dresden v2", forHTTPHeaderField: "User-Agent") do { let (content, _) = try await URLSession.shared.data(for: request) diff --git a/WatchMonitor Watch App/SingleTripView.swift b/WatchMonitor Watch App/SingleTripView.swift index 06b3552..f363820 100644 --- a/WatchMonitor Watch App/SingleTripView.swift +++ b/WatchMonitor Watch App/SingleTripView.swift @@ -51,6 +51,7 @@ struct SingleTripView: View { request.httpMethod = "POST" request.httpBody = try? JSONEncoder().encode(SingleTripRequest(stopID: String(stop.stopId), tripID: departure.Id, time: departure.getDateTime().ISO8601Format())) request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Haltestellenmonitor Dresden v2", forHTTPHeaderField: "User-Agent") do { let (content, _) = try await URLSession.shared.data(for: request) diff --git a/images/appstoreImage.svg b/images/appstoreImage.svg new file mode 100644 index 0000000..536f370 --- /dev/null +++ b/images/appstoreImage.svg @@ -0,0 +1,40 @@ + + Download_on_the_App_Store_Badge_DE_RGB_blk_092917 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/example1.PNG b/images/example1.PNG new file mode 100644 index 0000000..c38298b Binary files /dev/null and b/images/example1.PNG differ diff --git a/images/example2.PNG b/images/example2.PNG new file mode 100644 index 0000000..6931c65 Binary files /dev/null and b/images/example2.PNG differ diff --git a/images/example3.PNG b/images/example3.PNG new file mode 100644 index 0000000..6608979 Binary files /dev/null and b/images/example3.PNG differ