diff --git a/Geranium/Assets.xcassets/AccentColor.colorset/Contents.json b/Geranium/Assets.xcassets/AccentColor.colorset/Contents.json index eb87897..4959d94 100644 --- a/Geranium/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Geranium/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,15 @@ { "colors" : [ { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.427", + "green" : "0.348", + "red" : "1.000" + } + }, "idiom" : "universal" } ], diff --git a/Geranium/Assets.xcassets/AppIcon.appiconset/Contents.json b/Geranium/Assets.xcassets/AppIcon.appiconset/Contents.json index 13613e3..4147069 100644 --- a/Geranium/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Geranium/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "geranium.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/Geranium/Assets.xcassets/AppIcon.appiconset/geranium.png b/Geranium/Assets.xcassets/AppIcon.appiconset/geranium.png new file mode 100644 index 0000000..8faf9e4 Binary files /dev/null and b/Geranium/Assets.xcassets/AppIcon.appiconset/geranium.png differ diff --git a/Geranium/Cleaner/CleanerView.swift b/Geranium/Cleaner/CleanerView.swift new file mode 100644 index 0000000..5d505f3 --- /dev/null +++ b/Geranium/Cleaner/CleanerView.swift @@ -0,0 +1,18 @@ +// +// CleanerView.swift +// Geranium +// +// Created by Constantin Clerc on 10/12/2023. +// + +import SwiftUI + +struct CleanerView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + CleanerView() +} diff --git a/Geranium/DaemonMan/DaemonView.swift b/Geranium/DaemonMan/DaemonView.swift new file mode 100644 index 0000000..bfb3948 --- /dev/null +++ b/Geranium/DaemonMan/DaemonView.swift @@ -0,0 +1,18 @@ +// +// DaemonView.swift +// Geranium +// +// Created by Constantin Clerc on 10/12/2023. +// + +import SwiftUI + +struct DaemonView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + DaemonView() +} diff --git a/Geranium/Geranium-Bringing-Header.h b/Geranium/Geranium-Bringing-Header.h new file mode 100644 index 0000000..9e01f81 --- /dev/null +++ b/Geranium/Geranium-Bringing-Header.h @@ -0,0 +1,12 @@ +// +// Geranium-Bringing-Header.h +// Geranium +// +// Created by Constantin Clerc on 10/12/2023. +// + +#ifndef Geranium_Bringing_Header_h +#define Geranium_Bringing_Header_h + + +#endif /* Geranium_Bringing_Header_h */ diff --git a/Geranium/HomeView.swift b/Geranium/HomeView.swift new file mode 100644 index 0000000..3d11c77 --- /dev/null +++ b/Geranium/HomeView.swift @@ -0,0 +1,18 @@ +// +// HomeView.swift +// Geranium +// +// Created by Constantin Clerc on 10/12/2023. +// + +import SwiftUI + +struct HomeView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + HomeView() +} diff --git a/Geranium/Libs/Addon.swift b/Geranium/Libs/Addon.swift new file mode 100644 index 0000000..fa1cc05 --- /dev/null +++ b/Geranium/Libs/Addon.swift @@ -0,0 +1,194 @@ +// +// Addon.swift +// TrollBox +// +// Created by Constantin Clerc on 17/12/2022. +// + +import Foundation +import SwiftUI +import Combine +import UIKit + +struct MaterialView: UIViewRepresentable { + let material: UIBlurEffect.Style + + init(_ material: UIBlurEffect.Style) { + self.material = material + } + + func makeUIView(context: Context) -> UIVisualEffectView { + UIVisualEffectView(effect: UIBlurEffect(style: material)) + } + + func updateUIView(_ uiView: UIVisualEffectView, context: Context) { + uiView.effect = UIBlurEffect(style: material) + } +} + +fileprivate var cancellables = [String : AnyCancellable] () + +public extension Published { + init(wrappedValue defaultValue: Value, key: String) { + let value = UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue + self.init(initialValue: value) + cancellables[key] = projectedValue.sink { val in + UserDefaults.standard.set(val, forKey: key) + } + } +} + +extension StringProtocol { + subscript(offset: Int) -> Character { self[index(startIndex, offsetBy: offset)] } + subscript(range: Range) -> SubSequence { + let startIndex = index(self.startIndex, offsetBy: range.lowerBound) + return self[startIndex..) -> SubSequence { + let startIndex = index(self.startIndex, offsetBy: range.lowerBound) + return self[startIndex..) -> SubSequence { self[index(startIndex, offsetBy: range.lowerBound)...] } + subscript(range: PartialRangeThrough) -> SubSequence { self[...index(startIndex, offsetBy: range.upperBound)] } + subscript(range: PartialRangeUpTo) -> SubSequence { self[.. some View { + let uiColor = UIColor(color) + + // Set appearance for both normal and large sizes. + UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: uiColor ] + UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: uiColor ] + + return self + } +} + +extension UIApplication { + func respring() { + let app = self + // Credit to Amy While for this respring bug + guard let window = app.windows.first else { return } + while true { + window.snapshotView(afterScreenUpdates: false) + } + } +} + + +extension UIColor { + var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + getRed(&red, green: &green, blue: &blue, alpha: &alpha) + + return (red, green, blue, alpha) + } +} + +extension Color { + init(uiColor14: UIColor) { + self.init(red: Double(uiColor14.rgba.red), + green: Double(uiColor14.rgba.green), + blue: Double(uiColor14.rgba.blue), + opacity: Double(uiColor14.rgba.alpha)) + } +} + +var currentUIAlertController: UIAlertController? + +extension UIApplication { + func dismissAlert(animated: Bool) { + DispatchQueue.main.async { + currentUIAlertController?.dismiss(animated: animated) + } + } + func alert(title: String = "Error", body: String, animated: Bool = true, withButton: Bool = true) { + DispatchQueue.main.async { + currentUIAlertController = UIAlertController(title: title, message: body, preferredStyle: .alert) + if withButton { currentUIAlertController?.addAction(.init(title: "OK", style: .cancel)) } + self.present(alert: currentUIAlertController!) + } + } + func confirmAlert(title: String = "Error", body: String, onOK: @escaping () -> (), noCancel: Bool) { + DispatchQueue.main.async { + currentUIAlertController = UIAlertController(title: title, message: body, preferredStyle: .alert) + if !noCancel { + currentUIAlertController?.addAction(.init(title: "Cancel", style: .cancel)) + } + currentUIAlertController?.addAction(.init(title: "Yes", style: noCancel ? .cancel : .default, handler: { _ in + onOK() + })) + self.present(alert: currentUIAlertController!) + } + } + + func TextFieldAlert(title: String, textFieldPlaceHolder: String, completion: @escaping (String?) -> Void) { + let alertController = UIAlertController(title: title, message: nil, preferredStyle: .alert) + alertController.addTextField { (textField) in + textField.placeholder = textFieldPlaceHolder + } + let okAction = UIAlertAction(title: "OK", style: .default) { (action) in + if let text = alertController.textFields?.first?.text { + completion(text) + } else { + completion(nil) + } + } + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(okAction) + alertController.addAction(cancelAction) + present(alert: alertController) + } + + func change(title: String = "Error", body: String) { + DispatchQueue.main.async { + currentUIAlertController?.title = title + currentUIAlertController?.message = body + } + } + + func present(alert: UIAlertController) { + if var topController = self.windows[0].rootViewController { + while let presentedViewController = topController.presentedViewController { + topController = presentedViewController + } + + topController.present(alert, animated: true) + // topController should now be your topmost view controller + } + } +} + +func checkSandbox() -> Bool { + let fileManager = FileManager.default + fileManager.createFile(atPath: "/var/mobile/tboxtemp", contents: nil) + if fileManager.fileExists(atPath: "/var/mobile/tboxtemp") { + do { + try fileManager.removeItem(atPath: "/var/mobile/tboxtemp") + } catch { + print("Failed to remove sandbox check file") + } + return false + } + + return true +} + +func impactVibrate() { + let impact = UIImpactFeedbackGenerator(style: .medium) + impact.impactOccurred() +} + +func miniimpactVibrate() { + let impact = UIImpactFeedbackGenerator(style: .light) + impact.impactOccurred() +} diff --git a/Geranium/Libs/RemoteLog.h b/Geranium/Libs/RemoteLog.h new file mode 100644 index 0000000..4be65bd --- /dev/null +++ b/Geranium/Libs/RemoteLog.h @@ -0,0 +1,61 @@ +#ifndef _REMOTE_LOG_H_ +#define _REMOTE_LOG_H_ + +#import +#import +#import +#import + +// change this to match your destination (server) IP address +#define RLOG_IP_ADDRESS "192.168.0.24" +#define RLOG_PORT 11909 + +__attribute__((unused)) static void RLogv(NSString* format, va_list args) +{ + #if DEBUG + NSString* str = [[NSString alloc] initWithFormat:format arguments:args]; + + int sd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sd <= 0) + { + NSLog(@"[RemoteLog] Error: Could not open socket"); + return; + } + + int broadcastEnable = 1; + int ret = setsockopt(sd, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable)); + if (ret) + { + NSLog(@"[RemoteLog] Error: Could not open set socket to broadcast mode"); + close(sd); + return; + } + + struct sockaddr_in broadcastAddr; + memset(&broadcastAddr, 0, sizeof broadcastAddr); + broadcastAddr.sin_family = AF_INET; + inet_pton(AF_INET, RLOG_IP_ADDRESS, &broadcastAddr.sin_addr); + broadcastAddr.sin_port = htons(RLOG_PORT); + + char* request = (char*)[str UTF8String]; + ret = sendto(sd, request, strlen(request), 0, (struct sockaddr*)&broadcastAddr, sizeof broadcastAddr); + if (ret < 0) + { + NSLog(@"[RemoteLog] Error: Could not send broadcast"); + close(sd); + return; + } + close(sd); + #endif +} + +__attribute__((unused)) static void RLog(NSString* format, ...) +{ + #if DEBUG + va_list args; + va_start(args, format); + RLogv(format, args); + va_end(args); + #endif +} +#endif diff --git a/Geranium/Libs/TrollStore/CoreServices.h b/Geranium/Libs/TrollStore/CoreServices.h new file mode 100644 index 0000000..6107559 --- /dev/null +++ b/Geranium/Libs/TrollStore/CoreServices.h @@ -0,0 +1,60 @@ +@interface LSBundleProxy +@property (nonatomic,readonly) NSString * bundleIdentifier; +@property (nonatomic) NSURL* dataContainerURL; +-(NSString*)localizedName; +@end + +@interface LSApplicationProxy : LSBundleProxy ++ (instancetype)applicationProxyForIdentifier:(NSString*)identifier; +@property NSURL* bundleURL; +@property NSString* bundleType; +@property NSString* canonicalExecutablePath; +@property (nonatomic,readonly) NSDictionary* groupContainerURLs; +@property (nonatomic,readonly) NSArray* plugInKitPlugins; +@property (getter=isInstalled,nonatomic,readonly) BOOL installed; +@property (getter=isPlaceholder,nonatomic,readonly) BOOL placeholder; +@property (getter=isRestricted,nonatomic,readonly) BOOL restricted; +@property (nonatomic,readonly) NSSet * claimedURLSchemes; +@property (nonatomic,readonly) NSString *applicationIdentifier; +@end + +@interface LSApplicationWorkspace : NSObject ++ (instancetype)defaultWorkspace; +- (BOOL)registerApplicationDictionary:(NSDictionary*)dict; +- (BOOL)unregisterApplication:(id)arg1; +- (BOOL)_LSPrivateRebuildApplicationDatabasesForSystemApps:(BOOL)arg1 internal:(BOOL)arg2 user:(BOOL)arg3; +- (BOOL)uninstallApplication:(NSString*)arg1 withOptions:(id)arg2; +- (BOOL)openApplicationWithBundleID:(NSString *)arg1 ; +- (void)enumerateApplicationsOfType:(NSUInteger)type block:(void (^)(LSApplicationProxy*))block; +- (NSArray *)allApplications; +@end + +@interface LSEnumerator : NSEnumerator +@property (nonatomic,copy) NSPredicate * predicate; ++ (instancetype)enumeratorForApplicationProxiesWithOptions:(NSUInteger)options; +@end + +@interface LSPlugInKitProxy : LSBundleProxy +@property (nonatomic,readonly) NSString* pluginIdentifier; +@property (nonatomic,readonly) NSDictionary * pluginKitDictionary; ++ (instancetype)pluginKitProxyForIdentifier:(NSString*)arg1; +@end + +@interface MCMContainer : NSObject ++ (id)containerWithIdentifier:(id)arg1 createIfNecessary:(BOOL)arg2 existed:(BOOL*)arg3 error:(id*)arg4; +@property (nonatomic,readonly) NSURL * url; +@end + +@interface MCMDataContainer : MCMContainer + +@end + +@interface MCMAppDataContainer : MCMDataContainer + +@end + +@interface MCMAppContainer : MCMContainer +@end + +@interface MCMPluginKitPluginDataContainer : MCMDataContainer +@end diff --git a/Geranium/Libs/TrollStore/TSUtil.h b/Geranium/Libs/TrollStore/TSUtil.h new file mode 100644 index 0000000..1c87357 --- /dev/null +++ b/Geranium/Libs/TrollStore/TSUtil.h @@ -0,0 +1,27 @@ +@import Foundation; +#import "CoreServices.h" + +extern void chineseWifiFixup(void); +extern void loadMCMFramework(void); +extern NSString* safe_getExecutablePath(); +extern NSString* rootHelperPath(void); +extern NSString* getNSStringFromFile(int fd); +extern void printMultilineNSString(NSString* stringToPrint); +extern int spawnRoot(NSString* path, NSArray* args, NSString** stdOut, NSString** stdErr); +extern void killall(NSString* processName); +extern void respring(void); +extern void fetchLatestTrollStoreVersion(void (^completionHandler)(NSString* latestVersion)); + +extern NSArray* trollStoreInstalledAppBundlePaths(); +extern NSArray* trollStoreInstalledAppContainerPaths(); +extern NSString* trollStorePath(); +extern NSString* trollStoreAppPath(); + +typedef enum +{ + PERSISTENCE_HELPER_TYPE_USER = 1 << 0, + PERSISTENCE_HELPER_TYPE_SYSTEM = 1 << 1, + PERSISTENCE_HELPER_TYPE_ALL = PERSISTENCE_HELPER_TYPE_USER | PERSISTENCE_HELPER_TYPE_SYSTEM +} PERSISTENCE_HELPER_TYPE; + +extern LSApplicationProxy* findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE allowedTypes); diff --git a/Geranium/Libs/TrollStore/TSUtil.m b/Geranium/Libs/TrollStore/TSUtil.m new file mode 100644 index 0000000..c44c127 --- /dev/null +++ b/Geranium/Libs/TrollStore/TSUtil.m @@ -0,0 +1,204 @@ +#import "TSUtil.h" + +#import +#import +#import + +@interface PSAppDataUsagePolicyCache : NSObject ++ (instancetype)sharedInstance; +- (void)setUsagePoliciesForBundle:(NSString*)bundleId cellular:(BOOL)cellular wifi:(BOOL)wifi; +@end + +#define POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE 1 +extern int posix_spawnattr_set_persona_np(const posix_spawnattr_t* __restrict, uid_t, uint32_t); +extern int posix_spawnattr_set_persona_uid_np(const posix_spawnattr_t* __restrict, uid_t); +extern int posix_spawnattr_set_persona_gid_np(const posix_spawnattr_t* __restrict, uid_t); + +void chineseWifiFixup(void) +{ + NSBundle *bundle = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/SettingsCellular.framework"]; + [bundle load]; + [[NSClassFromString(@"PSAppDataUsagePolicyCache") sharedInstance] setUsagePoliciesForBundle:NSBundle.mainBundle.bundleIdentifier cellular:true wifi:true]; +} + +void loadMCMFramework(void) +{ + static dispatch_once_t onceToken; + dispatch_once (&onceToken, ^{ + NSBundle* mcmBundle = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/MobileContainerManager.framework"]; + [mcmBundle load]; + }); +} + +extern char*** _NSGetArgv(); +NSString* safe_getExecutablePath() +{ + char* executablePathC = **_NSGetArgv(); + return [NSString stringWithUTF8String:executablePathC]; +} + +NSString* getNSStringFromFile(int fd) +{ + NSMutableString* ms = [NSMutableString new]; + ssize_t num_read; + char c; + while((num_read = read(fd, &c, sizeof(c)))) + { + [ms appendString:[NSString stringWithFormat:@"%c", c]]; + } + return ms.copy; +} + +void printMultilineNSString(NSString* stringToPrint) +{ + NSCharacterSet *separator = [NSCharacterSet newlineCharacterSet]; + NSArray* lines = [stringToPrint componentsSeparatedByCharactersInSet:separator]; + for(NSString* line in lines) + { + NSLog(@"%@", line); + } +} + +int spawnRoot(NSString* path, NSArray* args, NSString** stdOut, NSString** stdErr) +{ + NSMutableArray* argsM = args.mutableCopy ?: [NSMutableArray new]; + [argsM insertObject:path.lastPathComponent atIndex:0]; + + NSUInteger argCount = [argsM count]; + char **argsC = (char **)malloc((argCount + 1) * sizeof(char*)); + + for (NSUInteger i = 0; i < argCount; i++) + { + argsC[i] = strdup([[argsM objectAtIndex:i] UTF8String]); + } + argsC[argCount] = NULL; + + posix_spawnattr_t attr; + posix_spawnattr_init(&attr); + + posix_spawnattr_set_persona_np(&attr, 99, POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE); + posix_spawnattr_set_persona_uid_np(&attr, 0); + posix_spawnattr_set_persona_gid_np(&attr, 0); + + posix_spawn_file_actions_t action; + posix_spawn_file_actions_init(&action); + + int outErr[2]; + if(stdErr) + { + pipe(outErr); + posix_spawn_file_actions_adddup2(&action, outErr[1], STDERR_FILENO); + posix_spawn_file_actions_addclose(&action, outErr[0]); + } + + int out[2]; + if(stdOut) + { + pipe(out); + posix_spawn_file_actions_adddup2(&action, out[1], STDOUT_FILENO); + posix_spawn_file_actions_addclose(&action, out[0]); + } + + pid_t task_pid; + int status = -200; + int spawnError = posix_spawn(&task_pid, [path UTF8String], &action, &attr, (char* const*)argsC, NULL); + posix_spawnattr_destroy(&attr); + for (NSUInteger i = 0; i < argCount; i++) + { + free(argsC[i]); + } + free(argsC); + + if(spawnError != 0) + { + NSLog(@"posix_spawn error %d\n", spawnError); + return spawnError; + } + + do + { + if (waitpid(task_pid, &status, 0) != -1) { + NSLog(@"Child status %d", WEXITSTATUS(status)); + } else + { + perror("waitpid"); + return -222; + } + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + + if(stdOut) + { + close(out[1]); + NSString* output = getNSStringFromFile(out[0]); + *stdOut = output; + } + + if(stdErr) + { + close(outErr[1]); + NSString* errorOutput = getNSStringFromFile(outErr[0]); + *stdErr = errorOutput; + } + + return WEXITSTATUS(status); +} + +void enumerateProcessesUsingBlock(void (^enumerator)(pid_t pid, NSString* executablePath, BOOL* stop)) +{ + static int maxArgumentSize = 0; + if (maxArgumentSize == 0) { + size_t size = sizeof(maxArgumentSize); + if (sysctl((int[]){ CTL_KERN, KERN_ARGMAX }, 2, &maxArgumentSize, &size, NULL, 0) == -1) { + perror("sysctl argument size"); + maxArgumentSize = 4096; // Default + } + } + int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL}; + struct kinfo_proc *info; + size_t length; + int count; + + if (sysctl(mib, 3, NULL, &length, NULL, 0) < 0) + return; + if (!(info = malloc(length))) + return; + if (sysctl(mib, 3, info, &length, NULL, 0) < 0) { + free(info); + return; + } + count = length / sizeof(struct kinfo_proc); + for (int i = 0; i < count; i++) { + @autoreleasepool { + pid_t pid = info[i].kp_proc.p_pid; + if (pid == 0) { + continue; + } + size_t size = maxArgumentSize; + char* buffer = (char *)malloc(length); + if (sysctl((int[]){ CTL_KERN, KERN_PROCARGS2, pid }, 3, buffer, &size, NULL, 0) == 0) { + NSString* executablePath = [NSString stringWithCString:(buffer+sizeof(int)) encoding:NSUTF8StringEncoding]; + + BOOL stop = NO; + enumerator(pid, executablePath, &stop); + if(stop) + { + free(buffer); + break; + } + } + free(buffer); + } + } + free(info); +} + +void killall(NSString* processName) +{ + enumerateProcessesUsingBlock(^(pid_t pid, NSString* executablePath, BOOL* stop) + { + if([executablePath.lastPathComponent isEqualToString:processName]) + { + kill(pid, SIGTERM); + } + }); +} diff --git a/Geranium/LocSim/LocSimManager.swift b/Geranium/LocSim/LocSimManager.swift new file mode 100644 index 0000000..d8f6317 --- /dev/null +++ b/Geranium/LocSim/LocSimManager.swift @@ -0,0 +1,38 @@ +// +// LocSimManager.swift +// TrollTools +// +// Created by exerhythm on 12.11.2022. +// + +import Foundation +import CoreLocation + + +class LocSimManager { + static let simManager = CLSimulationManager() + + /// Updates timezone + static func post_required_timezone_update(){ + CFNotificationCenterPostNotificationWithOptions(CFNotificationCenterGetDarwinNotifyCenter(), .init("AutomaticTimeZoneUpdateNeeded" as CFString), nil, nil, kCFNotificationDeliverImmediately); + } + + /// Starts a location simulation of specified argument "location" + static func startLocSim(location: CLLocation) { + simManager.stopLocationSimulation() + simManager.clearSimulatedLocations() + simManager.appendSimulatedLocation(location) + simManager.flush() + simManager.startLocationSimulation() + post_required_timezone_update(); + } + + /// Stops location simulation + static func stopLocSim(){ + simManager.stopLocationSimulation() + simManager.clearSimulatedLocations() + simManager.flush() + post_required_timezone_update(); + } +} + diff --git a/Geranium/LocSim/LocSimPrivateHeaders.h b/Geranium/LocSim/LocSimPrivateHeaders.h new file mode 100644 index 0000000..738f438 --- /dev/null +++ b/Geranium/LocSim/LocSimPrivateHeaders.h @@ -0,0 +1,37 @@ +// Copyright (c) 2021 udevs +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import +#import + + +@interface CLSimulationManager : NSObject +@property (assign,nonatomic) uint8_t locationDeliveryBehavior; +@property (assign,nonatomic) double locationDistance; +@property (assign,nonatomic) double locationInterval; +@property (assign,nonatomic) double locationSpeed; +@property (assign,nonatomic) uint8_t locationRepeatBehavior; +-(void)clearSimulatedLocations; +-(void)startLocationSimulation; +-(void)stopLocationSimulation; +-(void)appendSimulatedLocation:(id)arg1 ; +-(void)flush; +-(void)loadScenarioFromURL:(id)arg1 ; +-(void)setSimulatedWifiPower:(BOOL)arg1 ; +-(void)startWifiSimulation; +-(void)stopWifiSimulation; +-(void)setSimulatedCell:(id)arg1 ; +-(void)startCellSimulation; +-(void)stopCellSimulation; +@end diff --git a/Geranium/LocSim/LocationSimulationView.swift b/Geranium/LocSim/LocationSimulationView.swift new file mode 100644 index 0000000..7966876 --- /dev/null +++ b/Geranium/LocSim/LocationSimulationView.swift @@ -0,0 +1,210 @@ +// +// LocationSimulationView.swift +// TrollTools +// +// Created by Constantin Clerc on 21.12.2022. +// + +import Map +import MapKit +import SwiftUI + +struct EquatableCoordinate: Equatable { + var coordinate: CLLocationCoordinate2D + + static func ==(lhs: EquatableCoordinate, rhs: EquatableCoordinate) -> Bool { + lhs.coordinate.latitude == rhs.coordinate.latitude && lhs.coordinate.longitude == rhs.coordinate.longitude + } +} + +struct LocationSimulationView: View { + struct Location: Identifiable { + var coordinate: CLLocationCoordinate2D + var id = UUID() + } + @State var locations: [Location] = [] + @State private var long = "" + @State private var lat = "" + @State var showBookSheet = false + @State var directions: MKDirections.Response? = nil + @State private var region = MKCoordinateRegion(.world) + @State private var tappedCoordinate: EquatableCoordinate? = nil + @State private var selectedKey: String? + var body: some View { + NavigationView{ + VStack { + TextField("Enter latitude", text: $lat) + .textFieldStyle(RoundedBorderTextFieldStyle()) + + TextField("Enter longitude", text: $long) + .textFieldStyle(RoundedBorderTextFieldStyle()) +// Button(action: { +// showBookSheet = true +// }) { +// Text("Bookmarks") +// } +// .sheet(isPresented: $showBookSheet) { +// VStack { +// if let array = UserDefaults.standard.object(forKey: "bookmarksList") as? [String] { +// Picker("Select a bookmark", selection: $selectedKey) { +// ForEach(array, id: \.self) { key in +// Text(key) +// } +// } +// .pickerStyle(.menu) +// +// Button("Start LocSim") { +// UIApplication.shared.alert(title:"debug", body:"\(selectedKey) sel = \(selectedKey)") +// if let usrarray = UserDefaults.standard.object(forKey: selectedKey!) as? [String], usrarray.count >= 2 { +// let var1 = usrarray[0] +// let var2 = usrarray[1] +// let latitudeValue = Double(var1) +// let longitudeValue = Double(var2) +// LocSimManager.startLocSim(location: .init(latitude: latitudeValue!, longitude: longitudeValue!)) +// locations = [.init(coordinate: .init(latitude: latitudeValue!, longitude: longitudeValue! )),.init(coordinate: .init(latitude: latitudeValue!, longitude: longitudeValue!)),] +// calculateDirections() +// miniimpactVibrate() +// print("LOCSIM ENABLED for Latitude: \(lat), Longitude: \(long), bookmark : \(selectedKey)") +// } +// if let key = selectedKey { +// } +// } +// Button("Remove Bookmark") { +// if let key = selectedKey { +// UserDefaults.standard.removeObject(forKey: key) +// } +// } +// } else { +// Text("No bookmarks added! Try adding some using the button in the toolbar.") +// } +// +// } +// } + let latitudeValue = Double(lat) + let longitudeValue = Double(long) + CustomMapView(tappedCoordinate: $tappedCoordinate) + .onAppear { + CLLocationManager().requestAlwaysAuthorization() + } + } + .toolbar{ + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: { + let latitudeValue = Double(lat) + let longitudeValue = Double(long) + LocSimManager.startLocSim(location: .init(latitude: latitudeValue!, longitude: longitudeValue!)) + locations = [.init(coordinate: .init(latitude: latitudeValue!, longitude: longitudeValue! )),.init(coordinate: .init(latitude: latitudeValue!, longitude: longitudeValue!)),] + calculateDirections() + miniimpactVibrate() + print("LOCSIM ENABLED for Latitude: \(lat), Longitude: \(long)") + }) { + Image(systemName: "location") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 24, height: 24) + } + } + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: { + + UIApplication.shared.TextFieldAlert(title: "Enter a name for the bookmark", textFieldPlaceHolder: "Name of the bookmark") { (text) in + if let name = text { + let latlong = [lat, long] + UserDefaults.standard.set(latlong, forKey: name) + if let value = UserDefaults.standard.object(forKey: "bookmarksList") { + if var bookmarksLi = UserDefaults.standard.array(forKey: "bookmarksList") as? [String] { + bookmarksLi.append(name) + UserDefaults.standard.set(bookmarksLi, forKey: "bookmarksList") + UIApplication.shared.alert(title:"Success!", body:"The bookmark was saved !") + } + } else { + let bookmarksList = [name] + UserDefaults.standard.set(bookmarksList, forKey: "bookmarksList") + } + } + } + + }) { + Image(systemName: "bookmark") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 24, height: 24) + } + } + ToolbarItem(placement: .navigationBarLeading) { + Button(action: { + LocSimManager.stopLocSim() + impactVibrate() + print("ohno") + }) { + Image(systemName: "location.slash") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 24, height: 24) + } + } + } + } + .onChange(of: tappedCoordinate) { newValue in + if let coordinate = newValue { + lat = String(coordinate.coordinate.latitude) + long = String(coordinate.coordinate.longitude) + } + } + } + func calculateDirections() { + guard locations.count >= 2 else { return } + let request = MKDirections.Request() + request.source = MKMapItem(placemark: .init(coordinate: locations[0].coordinate)) + request.destination = MKMapItem(placemark: .init(coordinate: locations[1].coordinate)) + request.transportType = .automobile + + let directions = MKDirections(request: request) + directions.calculate { response, error in + self.directions = response +// region = .init(response?.routes.first?.polyline.boundingMapRect) + } + } +} + +struct CustomMapView: UIViewRepresentable { + @Binding var tappedCoordinate: EquatableCoordinate? + + func makeUIView(context: Context) -> MKMapView { + let mapView = MKMapView() + mapView.delegate = context.coordinator + let tapRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap(_:))) + mapView.addGestureRecognizer(tapRecognizer) + return mapView + } + + func updateUIView(_ uiView: MKMapView, context: Context) {} + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + class Coordinator: NSObject, MKMapViewDelegate { + var parent: CustomMapView + + init(_ parent: CustomMapView) { + self.parent = parent + } + + @objc func handleTap(_ gesture: UITapGestureRecognizer) { + let mapView = gesture.view as! MKMapView + let touchPoint = gesture.location(in: mapView) + let coordinate = mapView.convert(touchPoint, toCoordinateFrom: mapView) + parent.tappedCoordinate = EquatableCoordinate(coordinate: coordinate) + impactVibrate() + } + } +} + + + +struct LocationSimulationView_Previews: PreviewProvider { + static var previews: some View { + LocationSimulationView() + } +} diff --git a/Geranium/The Supviser/SuperviseView.swift b/Geranium/The Supviser/SuperviseView.swift new file mode 100644 index 0000000..c1bef2d --- /dev/null +++ b/Geranium/The Supviser/SuperviseView.swift @@ -0,0 +1,18 @@ +// +// SuperviseView.swift +// Geranium +// +// Created by Constantin Clerc on 10/12/2023. +// + +import SwiftUI + +struct SuperviseView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + SuperviseView() +}